spring boot 在Linux下服务启动报错Unable to find Java

系统环境操作系统:centos 7.5java 版本:1.8问题描述将 Spring boot 安装为 Linux 服务启动,后输入 service myapp start 报错 Unable to find Java ,但是使用 java -jar myapp.jar 启动成功。不知道为啥引起的,经过百度找到下面这个解决方法和我的情况一样,终于把问题解决解决方法将java 连接到/sbin 文件夹下ln -s /usr/local/jdk/bin/java /sbin/java

March 13, 2019 · 1 min · jiezi

SpringMVC接收和响应json数据

前后端的数据交互,除了通过form表单进行提交外,也可以通过ajax向后端传递和接收json格式数据(这种方式可以实现请求数据和页面分离)。本文将总结一下在Spring MVC中接收和响应json数据的几种方式。准备步骤:1.导入json相关框架的依赖(比如jackson)。2.spring mvc的controller方法正常写,如果需要响应json,增加@responsebody注解。3.在接受json对应的输入参数前,加上@RequestBody注解。服务端接收json数据还原为java对象,称为反序列化,反之,将java对象作为响应转换为json数据发回给客户端,称为序列化。注意:因为要使用ajax,所有一定要引入jQuery,切记!jackson maven依赖: <!– jackson依赖 –> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.7.0</version> </dependency>一、以实体类接收背景:当ajax传递的参数较多时,采用参数名匹配的方法不太方便。如果后台有对应的实体类,这时可以选择在客户端将数据封装为json格式传递给后台,后台用对应的实体类进行接收。客户端:<button onclick=“clickMe()">点我</button><script> function clickMe() { $.ajax({ type : ‘POST’, url : “acceptJsonByEntity”, contentType : “application/json;charset=utf-8”, // 如果想以json格式把数据提交到后台的话,JSON.stringify()必须有,否则只会当做表单提交 data : JSON.stringify({ “bookId” : 1, “author” : “Jack” }), // 期待返回的数据类型 dataType : “json”, success : function(data) { var bookId = data.bookId; var author = data.author; alert(“success:” + bookId+’,’+author); }, error : function(data) { alert(“error” + data); } });</script>@responseBody注解是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML。@RequestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容。一般情况下来说常用其来处理application/json类型。Controller:@Controllerpublic class PassJsonParam { @RequestMapping(value=“acceptJsonByEntity”,method = RequestMethod.POST) @ResponseBody public Book acceptJsonByEntity(@RequestBody Book book, HttpServletRequest request){ System.out.println(“当前http请求方式为:"+request.getMethod()); System.out.println(“bookId="+book.getBookId()+”, author="+book.getAuthor()); return book; }}控制台输出:当前http请求方式为:POST bookId=1, author=Jack客户端(弹窗):success:1,Jack如果Controller中的所有方法都需要返回json格式数据,可以使用@RestController注解。@RestController = @Controller + @ResponseBodyController(上面的Controller可以用下面的替换):@RestControllerpublic class PassJsonParam { @RequestMapping(value=“acceptJsonByEntity”,method = RequestMethod.POST) public Book acceptJsonByEntity(@RequestBody Book book, HttpServletRequest request){ System.out.println(“当前http请求方式为:"+request.getMethod()); System.out.println(“bookId="+book.getBookId()+”, author="+book.getAuthor()); return book; }}注意:使用了@RestController注解后,Controller的方法无法再返回jsp页面或者html,配置的视图解析器也不会起作用。二、以map方式接收背景:前台向后台发送ajax请求并且携带很多参数,而后台并没有对应的实体类进行接收又该如何处理呢?最常见的就是表单,这里可以考虑使用map来解决。因为map的数据结构为key-value形式,所以我们可以遍历搜索框表单,将表单的name作为map的key,表单的value作为map的value。客户端:<form id=“bookForm”> <input type=“text” name=“bookName” id=“bookName”> <input type=“text” name=“author” id=“author” > <button onclick=“submitForm(event)">提交</button></form><script> function submitForm(event) { //阻止form默认事件 event.preventDefault(); //得到搜索框数据 var map = new Map(); $("#bookForm input”).each(function () { var value = $(this).val(); //input 值 var name = $(this).attr(’name’); map.set(name,value); }) //Map转为Json的方法 var obj= Object.create(null); for (var [k,v] of map) { obj[k] = v; } $.ajax({ type: ‘POST’, contentType:‘application/json’, url: “acceptJsonByMap”, data: JSON.stringify(obj), dataType: ‘json’, success: function (data) { var bookName = data.bookName; var author = data.author; alert(“bookName ="+bookName+”; author="+author); }, error: function (data) { alert(“失败啦”); } }); }</script>Controller: @RequestMapping(value=“acceptJsonByMap”) @ResponseBody public Map<String,Object> acceptJsonByMap(@RequestBody Map<String,Object> paramsMap, HttpServletRequest request){ System.out.println(“当前http请求方式为:"+request.getMethod()); System.out.println(paramsMap); return paramsMap; }控制台输出:当前http请求方式为:POST {bookName=Love, author=Frank}客户端(弹窗):bookName =Love; author=Frank三、以list方式接收(以json数组形式传递)客户端:<button onclick=“clickHere()">clickHere</button><script> function clickHere() { var params1 = { “bookId”:“123”, “author”:“Rose” }; var params2 = { “bookId”:“321”, “author”:“Jack” }; var list = []; list.push(params1); list.push(params2); $.ajax({ type: ‘POST’, contentType:‘application/json’, url: “acceptJsonByList”, data: JSON.stringify(list), dataType: ‘json’, success: function (data) { for (let i = 0; i < data.length; i++) { var bookId = data[i].bookId; var author = data[i].author; alert(“bookId ="+bookId+”; author="+author); } }, error: function (data) { alert(“失败啦”); } }); }</script>注意:传递到后端时,list应为[ { key1 : value1}{ key2 : value2} ]的json格式数据,否则可能会出现Json parse error错误。Controller: @RequestMapping(value=“acceptJsonByList”) @ResponseBody public List<Book> acceptJsonByList(@RequestBody List<Book> book, HttpServletRequest request){ System.out.println(“当前http请求方式为:"+request.getMethod()); System.out.println(book); return book; }注意:这里需要Book实体类进行接收。控制台输出:当前http请求方式为:POST [entity.Book@1138a75c, entity.Book@22d1cbcf]客户端(弹窗):bookId =123; author=Rose bookId =321; author=Jack ...

March 11, 2019 · 2 min · jiezi

[闹着玩-2]spring-mvc 主要流程

SpringMvc【源码仓库】【本文仓库】三层结构表现层MVC模型业务层service持久层dao工作流程用户->前端控制器:用户发送请求前端控制器-> 后端控制器:根据用户请求查询具体控制器后端控制器–>前端控制器:处理后结果前端控制器–> 视图:视图渲染视图–>前端控制器:返回视图前端控制器–> 用户:响应结果简单案例依赖<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.huifer</groupId> <artifactId>mySpringMvcBook</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring.version>5.1.5.RELEASE</spring.version> <junit.version>4.12</junit.version> </properties><dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!– https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl –> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.3</version> </dependency> <!– https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core –> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.3</version> </dependency> <!– https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations –> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.3</version> </dependency></dependencies> <build> <resources> <!–千千万万别忘记–> <resource> <directory>src/main/java</directory> <includes> <include>**/.xml</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build></project>spring-config<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns=“http://www.springframework.org/schema/beans" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:context=“http://www.springframework.org/schema/context" xmlns:mvc=“http://www.springframework.org/schema/mvc" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package=“com.huifer.springmvc”/> <!– 配置注解的适配器和映射器,同时还注入了很多其他的bean –> <mvc:annotation-driven/></beans>web.xml<?xml version=“1.0” encoding=“UTF-8”?><web-app xmlns=“http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version=“4.0”> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-config.xml</param-value> </init-param> <!–跟随tomcat启动–> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping></web-app>controller@Controller@RequestMapping(“item”)public class ItemController { @ResponseBody @GetMapping("/query”) public ModelAndView query() throws Exception { List<Item> itemList = new ArrayList<>(); itemList.add(new Item(“吃的”, 3.3, new Date())); itemList.add(new Item(“玩的”, 3.3, new Date())); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject(“itemList”, itemList); modelAndView.setViewName("/WEB-INF/jsp/item.jsp”); return modelAndView; }}item.JSP<%– Created by IntelliJ IDEA. User: huifer Date: 2019/3/10 Time: 11:26 To change this template use File | Settings | File Templates.–%><%@ page language=“java” contentType=“text/html; charset=UTF-8” pageEncoding=“UTF-8” isELIgnored=“false” %><!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “http://www.w3.org/TR/html4/loose.dtd"><html><head> <title>ItemList</title></head><body><h1>ITEM</h1><p>${itemList}</p></body></html>web.xml 中的加载顺序context-param->listener->filter->servleturl-parttern 匹配顺序精确匹配 ,如/baidu目录匹配,如/拓展名匹配,如.jpg默认匹配 ,如/tomcat中的url-parttern文件在 ${tomcatHome}/conf/web.xml中发布到tomcat的web应用共享下面两个配置 <!– The mapping for the default servlet –> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!– The mappings for the JSP servlet –> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>.jsp</url-pattern> <url-pattern>.jspx</url-pattern> </servlet-mapping>此处面试题为什么在自己的项目中web.xml配置/ 报错。如图根据tomcat的web.xml配置可以看到 / 和jsp的拦截是分开的 ,而我们直接用一个/*来拦截那么tomcat将不知道用什么来处理spring-mvc大致流程源码翻阅从配置文件中知道前端控制器DispatcherServlet<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-config.xml</param-value> </init-param> <!–跟随tomcat启动–> <load-on-startup>2</load-on-startup></servlet>在org.springframework.web.servlet.DispatcherServlet 看下面两个方法doService 将访问的数据接收到交给doDispatchdoDispatch 具体调度protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; // 加载handler mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } // handler适配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = “GET”.equals(method); if (isGet || “HEAD”.equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 适配器执行操作 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException(“Handler dispatch failed”, var21); } // 操作结果返回 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException(“Handler processing failed”, var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }handler怎么来initHandlerMappings(context)protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context); this.initHandlerMappings(context); this.initHandlerAdapters(context); this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context); } private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = (HandlerMapping)context.getBean(“handlerMapping”, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException var3) { } } if (this.handlerMappings == null) { // 读取默认配置文件 this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class); if (this.logger.isTraceEnabled()) { this.logger.trace(“No HandlerMappings declared for servlet ‘” + this.getServletName() + “’: using default strategies from DispatcherServlet.properties”); } } }默认配置文件handler适配器也在配置文件中 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { Iterator var2 = this.handlerAdapters.iterator(); while(var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); // 是否能够适配 if (adapter.supports(handler)) { return adapter; } } } throw new ServletException(“No adapter for handler [” + handler + “]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler”); }HttpRequestHandlerAdapterpublic class HttpRequestHandlerAdapter implements HandlerAdapter { public HttpRequestHandlerAdapter() { }// 判断是否是当前类支持的适配器 public boolean supports(Object handler) { return handler instanceof HttpRequestHandler; }// 适配器执行操作 @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler)handler).handleRequest(request, response); return null; } public long getLastModified(HttpServletRequest request, Object handler) { return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L; }}视图解析 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { this.logger.debug(“ModelAndViewDefiningException encountered”, exception); mv = ((ModelAndViewDefiningException)exception).getModelAndView(); } else { Object handler = mappedHandler != null ? mappedHandler.getHandler() : null; mv = this.processHandlerException(request, response, handler, exception); errorView = mv != null; } } if (mv != null && !mv.wasCleared()) { // 这个地方在做渲染 this.render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else if (this.logger.isTraceEnabled()) { this.logger.trace(“No view rendering, null ModelAndView returned.”); } if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } }protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale(); response.setLocale(locale); String viewName = mv.getViewName(); View view; if (viewName != null) { // 视图解析器 view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException(“Could not resolve view with name ‘” + mv.getViewName() + “’ in servlet with name ‘” + this.getServletName() + “’”); } } else { view = mv.getView(); if (view == null) { throw new ServletException(“ModelAndView [” + mv + “] neither contains a view name nor a View object in servlet with name ‘” + this.getServletName() + “’”); } } if (this.logger.isTraceEnabled()) { this.logger.trace(“Rendering view [” + view + “] “); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 视图的渲染函数渲染到页面上 view.render(mv.getModelInternal(), request, response); } catch (Exception var8) { if (this.logger.isDebugEnabled()) { this.logger.debug(“Error rendering view [” + view + “]”, var8); } throw var8; } }@Nullableprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { Iterator var5 = this.viewResolvers.iterator(); while(var5.hasNext()) { // 视图解析器 ViewResolver viewResolver = (ViewResolver)var5.next(); View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null;}spring-mvc请求具体流程图用户->前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果:1.用户发送请求 前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果 -> HandlerMapper : 2.根据url进行处理HandlerMapper -> 前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果:3.将handlerMapper处理结果给前端控制器前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果-> HandlerAdapter:4.给适配器确认具体的适配器,比如我们这里给了HttpRequestHandlerAdapterHandlerAdapter ->ModelAndView:5.用来执行业务操作ModelAndView ->HandlerAdapter: 6.执行完成给HandlerAdapter返回HandlerAdapter->前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果:7.返回一个modelAndView前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果-> ViewResolver 视图解析器 :8.将第7步中的view进行解析ViewResolver 视图解析器->前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果:9.解析结果返回前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果 -> View : 9.将model渲染到view中View->前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果: 10.view 结果返回给前端控制器前端控制器\n org.springframework.web.servlet.DispatcherServlet\n 用来接收响应以及返回响应结果 –> 用户:查看到完整的网页 ...

March 10, 2019 · 5 min · jiezi

代码重构知识点记录

本周学习教程并没有遇到什么问题。这周学到了教程中代码重构的部分,原本代码还能勉勉强强的理解,而经过代码重构之后,不但觉得麻烦,而且还很难理解。如果我自己写一部分模块的话,以我现在的水平很难在完成功能之后,再进行教程中那样的重构。在代码重构过程中讲了好几个新的知识点,我就在这里做一下笔记,以便以后还能想起来有这么个功能,尝试进行使用一、指令作用:消除C层中重复的方法,V层中相同的代码创建方式:yo angular:directive name;使用方式:V层使用:1.创建对应的V层文件2.将template设置为对应文件的路径3.将V层中重复的代码放入该文件中,在原代码处调用指令C层使用1.将重复的方法写入命令行创建的指令文件中2.将self设为空对象,并且传入相应的参数3.在return中调用该方法二、$httpProvider作用:将url中相同的部分消除使用方式:在路由定义文件中加入下面的代码$provide.factory(‘myHttpInterceptor’, function($q) { return { // optional method ‘request’: function(config) { // 如果后缀为html则不改写 var suffix = config.url.split(’.’).pop(); if (suffix !== ‘html’) { config.url = ‘http://127.0.0.1:8080’ + config.url; } return config; } };});$httpProvider.interceptors.push(‘myHttpInterceptor’);$urlRouterProvider.otherwise(’/main’);并注入$provide,$httpProvider效果http://127.0.0.1:8080/Teacher/ -> /Teacher/

March 8, 2019 · 1 min · jiezi

删除数据库中与同步数据冗余的数据(多对多)

思路先获取冗余的数据从关联的中间表删除删除出冗余的数据如果不删除中间表的数据,数据库中有外键,不能删除冗余数据 private void deleteSurplusHosts(List<Host> hostList) { List<Host> hosts = hostService.getAll(); List<HostGroup> hostGroupList = hostGroupService.getAllGroups(); List<Host> deletedHostList = new ArrayList<>(); logger.debug(“判断获取的计算机不为空”); if (hosts.isEmpty()) { return; } logger.debug(“获取移除的计算机”); for (Host host : hosts) { if (!hostList.contains(host)) { deletedHostList.add(host); } } logger.debug(“删除关联的中间表”); for (HostGroup hostGroup : hostGroupList) { hostGroup.getHostList().removeIf((host) -> deletedHostList.contains(host)); } hostGroupRepository.saveAll(hostGroupList); logger.debug(“删除计算机”); hostRepository.deleteInBatch(deletedHostList); }思路清晰其实并不难,主要学习了一下Java中的contains和removeIfcontains描述从数据库中查询出满足一系列条件的记录,然后以对象的形式封装到List中去。此时假设有两个条件A和B,满足A的记录集和为ListA,满足B的记录集合为ListB,现在要将ListA和ListB合并为一个List,注意ListA和ListB中可能有重复的记录(因为可能某条记录即满足条件A又满足条件B),要过滤掉重复的记录。俩个对象的属性相等,但俩个对象不应定相等。可能不在一块内存,所以需要重写hashCode()与equals()重写hashCode()与equals() @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Host host = (Host) o; if (context != host.context) return false; return name != null ? name.equals(host.name) : host.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + context; return result; }重写hashCode()与equals()方法,如果name相同,俩个对象就相同;(上述代码`idea`可自动生成)。removeIf描述删除集合中符合条件的成员,empty集合也可以,但是null就炸啦。例如private static void removeIfTest() { List<String> list = Lists.newArrayList(“1”,“12”,“13”,“14”,“15”,“0”); System.out.println(“初始时:"+ list.toString()); list.removeIf(s -> s.contains(“1”)); System.out.println(“过滤完:” + list.toString()); }和过滤filter用法有点相似循环依赖最后在启动项目时,使用构造函数注入@Autowired出现了循环依赖解决:必需俩边都从构造函数中拿出来,单独注入关于循环依赖在组长的思否中有详细介绍Spring Bean 循环依赖总结总结、总结也没什么好说的,就是自己又学到了新知识,又成长了,在此多谢有张喜硕组长给我讲解contains与removeIf ...

March 1, 2019 · 1 min · jiezi

Spring MVC常用客户端参数接收方式

Spring MVC常用客户端参数接收方式在MVC结构中,控制器组件主要的功能就是接收请求、处理请求、生成响应,接收客户端传来的请求参数的往往是控制器要做的第一件事。Book实体类Book.javapublic class Book { private Integer bookId; private String author; //生成Get、Set方法,此处省略}一、直接用参数名匹配请求参数客户端界面(表单):<form action="/queryString" method=“post”> <input type=“text” name=“bookId”> <input type=“text” name=“author”> <input type=“submit” value=“提交”></form>controller层:@Controllerpublic class ParamPassDemo { @RequestMapping(value="/queryString") public String test1(Integer bookId, String author) { System.out.println(“bookId="+bookId+”, author="+author); //此处返回的地址为(/WEB-INF/jsp/index.jsp) return “index”; }}注意:这里@RequestMapping中只有value属性,value可以省略不写。客户端输入:123,Rose控制台输出:bookId=123, author=Rose二、通过@RequestParam注解来指定请求参数的name客户端界面(表单):<form action="/queryStringWithSpecName" method=“post”> <input type=“text” name=“bookId” value=“321”> <input type=“text” name=“author” value=“Jack”> <input type=“submit” value=“提交”></form>如果表单中的字段与方法中的参数名一致,可以不需要@RequestParam,Spring会自动处理。controller层:@Controllerpublic class ParamPassDemo { @RequestMapping("/queryStringWithSpecName") public String test2((value=“bookId”,required=false) Integer id, @RequestParam(“author”) String name) { System.out.println(“bookId="+id+”, author="+name); return “index”; }}注意:这里@RequestMapping中有两个属性,value不能省略。@RequestParam将请求地址中的参数传递给目标方法,在处理方法入参处使用可以把请求参数传递给请求方法。当使用@RequestParam注解时,设置客户端传递的请求参数name=“bookId"和@RequestParam的value值value=“bookId"相匹配后,参数名int id可以和请求参数不匹配。客户端输入:321, Jack控制台输出:bookId=321, author=Jack客户端界面(ajax):<button onclick=“clickMe()">点我</button><script> function clickMe() { $.ajax({ type : ‘POST’, url : “/queryStringWithSpecName”, data : { “bookId” : 1, “author” : “Jack” }, }); }</script>controller层:(不变)客户端: data:{“author” : “Jack”}控制台输出: bookId=null, author=Jack(如果bookId为int类型,控制台会抛出异常)客户端: data:{“bookId” : 1}控制台输出: org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter ‘author’ is not present通过required设置可选参数,required为false时表示可以不带参数,为true时表示必须带参数(默认值为true)。当可选参数不存在时,Spring默认将其赋值为空(null),但由于bookId已定义为基本类型int,所以赋值会失败。解决方法:采用int包装类Integer。三、使用领域对象来接收参数客户端界面(表单):<form action="/queryStringWithDomainObj” method=“post”> <input type=“text” name=“bookId”> <input type=“text” name=“author”> <input type=“submit” value=“提交”></form>controller层:@Controllerpublic class ParamPassDemo { @RequestMapping("/queryStringWithDomainObj”) public String test3(Book book) { System.out.println(“bookId="+book.getBookId()+”, author="+book.getAuthor()); return “index”; } }客户端输入:111, Bob控制台输出:bookId=111, author=Bob四、URL动态参数传递(路径参数)客户端界面(超链接):<a href="/book/1”>testPathVariable</a>controller层:@Controllerpublic class ParamPassDemo { //@PathVariable可以用来映射URL中的占位符到目标方法的参数中 @RequestMapping("/book/{bookId}") public String test4(@PathVariable(“bookId”) Integer bookId) { System.out.println(“bookId:” + bookId); return “index”; } }控制台输出:bookId:1@PathVariable 映射 URL 绑定的占位符通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。五、使用HttpServletRequest获取请求参数客户端界面(表单):<form action="/queryBook" method=“post”> <input type=“text” name=“bookId”> <input type=“text” name=“author”> <input type=“submit” value=“提交”></form>controller层:@Controllerpublic class ParamPassDemo { @RequestMapping("/queryBook") public String test5(HttpServletRequest request) { System.out.println(“bookId:” + request.getParameter(“bookId”)); //此处index.jsp界面在WEB-INF下 return “redirect:/index.jsp”; } }客户端输入:123控制台输出:用户id:123六、跳转到另一个controller方法客户端界面(url地址栏):http://localhost:8080/test6?bookId=321controller层:@Controllerpublic class ParamPassDemo { @RequestMapping("/test6") public String test6(String bookId){ System.out.println(“bookId="+bookId); //使用服务端跳转的方式转向到另一个controller //return “forward:queryBook?bookId="+bookId; return “redirect:queryUser?bookId="+bookId; } }控制台输出:bookId=321 bookId:321 ...

February 25, 2019 · 2 min · jiezi

学习:springMVC注解

引言在项目中,组长说我们的@Autowired注解都是黄的后来,组长说加上@SuppressWarnings来抑制警告信息@SuppressWarnings 注解目标 其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。第一次使用其注解,发现spring的强大,并了解了一下spring的各种注解了解spring注解spring注解分为两大类:spring的bean容器相关的注解,或者说bean工厂相关的注解;springmvc相关的注解。spring的bean容器相关的注解:先后有:@Required, @Autowired,@PostConstruct,@PreDestory,还有Spring3.0开始支持的JSR-330标准javax.inject.中的注解(@Inject,@Named,@Qualifier, @Provider, @Scope, @Singleton).springmvc相关的注解有:@Controller, @RequestMapping, @RequestParam, @ResponseBody等等。springMVC注解@Override我们最熟悉的@Override, 定义/* * Indicates that a method declaration is intended to override a * method declaration in a supertype. If a method is annotated with * this annotation type compilers are required to generate an error * message unless at least one of the following conditions hold: * The method does override or implement a method declared in a * supertype. * The method has a signature that is override-equivalent to that of * any public method declared in Object. * * @author Peter von der Ah&eacute; * @author Joshua Bloch * @jls 9.6.1.4 @Override * @since 1.5 /@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}从注释,我们可以看出,@Override的作用是,提示编译器,使用了@Override注解的方法必须override父类或者java.lang.Object中的一个同名方法。我们看到@Override的定义中使用到了 @Target, @Retention,它们就是所谓的元注解——就是定义注解的注解@Retention@Retention用于提示注解被保留多长时间,有三种取值:public enum RetentionPolicy { /* * Annotations are to be discarded by the compiler. / SOURCE, /* * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. / CLASS, /* * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement / RUNTIME}RetentionPolicy.SOURCE 保留在源码级别,编译时忽略(@Override就是此类); RetentionPolicy.CLASS被编译器保留在编译后的类文件级别,但是在运行丢弃;RetentionPolicy.RUNTIME保留至运行时。@Target @Target:注解的作用目标public enum ElementType { /* Class, interface (including annotation type), or enum declaration / //接口、类、枚举、注解 TYPE, /* Field declaration (includes enum constants) / //字段、枚举的常量 FIELD, /* Method declaration / //方法 METHOD, /* Formal parameter declaration / //方法参数 PARAMETER, /* Constructor declaration / //构造函数 CONSTRUCTOR, /* Local variable declaration / //局部变量 LOCAL_VARIABLE, /* Annotation type declaration / //注解类型 ANNOTATION_TYPE, /* Package declaration / //包 PACKAGE, /* * Type parameter declaration * @since 1.8 / TYPE_PARAMETER, /* * Use of a type * @since 1.8 */ TYPE_USE}举例@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface TestOnly {}只能使用在方法上,保留在源码级别,编译时忽略总结注解是用于建设基础jar包的一部分,项目都有自己的框架,若运用恰当,注解则为其中良好的一部分;spring从任何一个地方都能体现它的强大之处,但是我们呢?? ...

February 23, 2019 · 2 min · jiezi

Spring MVC打印@RequestBody、@Response日志

问题描述:使用JSON接收前端参数时, SpringMVC默认输出日志如下:o.s.web.servlet.DispatcherServlet : POST “/example_project/app/login”, parameters={}parameters={}无法打印出JSON消息内容。如果自己实现参数打印, 则需要从reqeust.getInputStream中获取JSON内容, 但是由于流只能读取一次, 所以会导致后续SpringMVC解析参数异常。网上找到一种比较解决方法: 用HttpRequestWrapper重新封装Reqeust, 使打印日志后SpringMVC能正常解析HttpReqeust。这种方法比较麻烦, 这里不去研究这里主要说说Spring提供的较好的解决方案:可以通过自定义RequestBodyAdvisor、ResponseBodyAdvisor来实现日志输出。RequestBodyAdvisor可以获取到解析后的Controller方法参数对象。ResponseBodyAdvisor可以获取到Controller方法返回值对象。然后将他们注册到requestMappingHandlerAdapter:// 继承WebMvcConfigurationSupport, 重写该方法@Override@Beanpublic RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); adapter.setRequestBodyAdvice(Lists.newArrayList(new CustomerRequestBodyAdvisor())); adapter.setResponseBodyAdvice(Lists.newArrayList(new CustomerResponseBodyAdvisor())); return adapter;}另附CustomerRequestBodyAdvisor、CustomerResponseBodyAdvisor日志输出实现参考:RequestBodyAdvisor实现参考:// CustomerRequestBodyAdvisor.java/*** 打印请求参数日志*/public class CustomerRequestBodyAdvisor extends RequestBodyAdviceAdapter { private static final Logger logger = LoggerFactory.getLogger(CustomerRequestBodyAdvisor.class); @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { // 只处理@RequestBody注解了的参数 return methodParameter.getParameterAnnotation(RequestBody.class) != null; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { Method method = parameter.getMethod(); // 参数对象转JSON字符串 String jsonBody; if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) { jsonBody = body.toString(); } else { jsonBody = JSON.toJSONString(body, SerializerFeature.UseSingleQuotes); } // 自定义日志输出 if (logger.isInfoEnabled()) { logger.info("{}#{}: {}", parameter.getContainingClass().getSimpleName(), method.getName(), jsonBody); // logger.info(“json request<=========method:{}#{}”, parameter.getContainingClass().getSimpleName(), method.getName()); } return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType); }}ResponseBodyAdvisor实现参考:// CustomerResponseBodyAdvisor.java/*** 打印响应值日志*/public class CustomerResponseBodyAdvisor implements ResponseBodyAdvice<Object> { private static final Logger logger = LoggerFactory.getLogger(CustomerResponseBodyAdvisor.class); @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) || returnType.getMethod().isAnnotationPresent(ResponseBody.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 响应值转JSON串输出到日志系统 if (logger.isInfoEnabled()) { logger.info("{}: {}", request.getURI(), JSON.toJSONString(body, SerializerFeature.UseSingleQuotes)); } return body; }} ...

February 1, 2019 · 1 min · jiezi

猫头鹰的深夜翻译:Spring REST服务异常处理

前言这篇教程主要专注于如何优雅的处理WEB中的异常。虽然我们可以手动的设置ResponseStatus ,但是还有更加优雅的方式将这部分逻辑隔离开来。Spring提供了整个应用层面的异常处理的抽象,并且只是要求您添加一些注释 - 它会处理其他所有内容。下面是一些代码的示例如何手动处理异常下面的代码中, DogController将返回一个ResponseEntity实例,该实例中包含返回的数据和HttpStatus属性如果没有抛出任何异常,则下面的代码将会返回List<Dog>数据作为响应体,以及200作为状态码对于DogsNotFoundException,它返回空的响应体和404状态码对于DogServiceException, 它返回500状态码和空的响应体@RestController@RequestMapping("/dogs")public class DogsController { @Autowired private final DogsService service; @GetMapping public ResponseEntity<List<Dog>> getDogs() { List<Dog> dogs; try { dogs = service.getDogs(); } catch (DogsServiceException ex) { return new ResponseEntity<>(null, null, HttpStatus.INTERNAL_SERVER_ERROR); } catch (DogsNotFoundException ex) { return new ResponseEntity<>(null, null, HttpStatus.NOT_FOUND); } return new ResponseEntity<>(dogs, HttpStatus.OK); }}这种处理异常的方式最大的问题就在于代码的重复。catch部分的代码在很多其它地方也会使用到(比如删除,更新等操作)Controller AdviceSpring提供了一种更好的解决方法,也就是Controller Advice。它将处理异常的代码在应用层面上集中管理。现在我们的的DogsController的代码更加简单清晰了:import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;import static org.springframework.http.HttpStatus.NOT_FOUND;@ControllerAdvicepublic class DogsServiceErrorAdvice { @ExceptionHandler({RuntimeException.class}) public ResponseEntity<String> handleRunTimeException(RuntimeException e) { return error(INTERNAL_SERVER_ERROR, e); } @ExceptionHandler({DogsNotFoundException.class}) public ResponseEntity<String> handleNotFoundException(DogsNotFoundException e) { return error(NOT_FOUND, e); } @ExceptionHandler({DogsServiceException.class}) public ResponseEntity<String> handleDogsServiceException(DogsServiceException e){ return error(INTERNAL_SERVER_ERROR, e); } private ResponseEntity<String> error(HttpStatus status, Exception e) { log.error(“Exception : “, e); return ResponseEntity.status(status).body(e.getMessage()); }}handleRunTimeException:这个方法会处理所有的RuntimeException并返回INTERNAL_SERVER_ERROR状态码handleNotFoundException: 这个方法会处理DogsNotFoundException并返回NOT_FOUND状态码。handleDogsServiceException: 这个方法会处理DogServiceException并返回INTERNAL_SERVER_ERROR状态码这种实现的关键就在于在代码中捕获需检查异常并将其作为RuntimeException抛出。还可以用@ResponseStatus将异常映射成状态码@ControllerAdvicepublic class DogsServiceErrorAdvice { @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler({DogsNotFoundException.class}) public void handle(DogsNotFoundException e) {} @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler({DogsServiceException.class, SQLException.class, NullPointerException.class}) public void handle() {} @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler({DogsServiceValidationException.class}) public void handle(DogsServiceValidationException e) {}}在自定义的异常上添加状态码@ResponseStatus(HttpStatus.NOT_FOUND)public class DogsNotFoundException extends RuntimeException { public DogsNotFoundException(String message) { super(message); }} ...

January 27, 2019 · 1 min · jiezi

深入理解Spring异常处理

1.前言相信我们每个人在Spring MVC开发中,都遇到这样的问题:当我们的代码正常运行时,返回的数据是我们预期格式,比如json或xml形式,但是一旦出现了异常(比如:NPE或者数组越界等等),返回的内容确实服务端的异常堆栈信息,从而导致返回的数据不能使客户端正常解析;很显然,这些并不是我们希望的结果。我们知道,一个较为常见的系统,会涉及控制层,服务(业务)层、缓存层、存储层以及接口调用等,其中每一个环节都不可避免的会遇到各种不可预知的异常需要处理。如果每个步骤都单独try..catch会使系统显的很杂乱,可读性差,维护成本高;常见的方式就是,实现统一的异常处理,从而将各类异常从各个模块中解耦出来;2.常见全局异常处理在Spring中常见的全局异常处理,主要有三种:(1)注解ExceptionHandler(2)继承HandlerExceptionResolver接口(3)注解ControllerAdvice在后面的讲解中,主要以HTTP错误码:400(请求无效)和500(内部服务器错误)为例,先看一下测试代码以及没有任何处理的返回结果,如下:图1:测试代码图2:没有异常的错误返回2.1注解ExceptionHandler注解ExceptionHandler作用对象为方法,最简单的使用方法就是放在controller文件中,详细的注解定义不再介绍。如果项目中有多个controller文件,通常可以在baseController中实现ExceptionHandler的异常处理,而各个contoller继承basecontroller从而达到统一异常处理的目的。因为比较常见,简单代码如下:图3:Controller中的ExceptionHandler使用 在返回异常时,添加了所属的类名,便于大家记忆理解。运行看一下结果:图4:添加ExceptionHandler之后的结果优点:ExceptionHandler简单易懂,并且对于异常处理没有限定方法格式;缺点:由于ExceptionHandler仅作用于方法,对于多个controller的情况,仅为了一个方法,所有需要异常处理的controller都继承这个类,明明不相关的东西,强行给他们找个爹,不太好。2.2注解ControllerAdvice这里虽说是ControllerAdvice注解,其实是其与ExceptionHandler的组合使用。在上文中可以看到,单独使用@ExceptionHandler时,其必须在一个Controller中,然而当其与ControllerAdvice组合使用时就完全没有了这个限制。换句话说,二者的组合达到的全局的异常捕获处理。图5:注解ControllerAdvice异常处理代码在运行之前,需将之前Controller中的ExceptionHandler注释掉,测试结果如下:图6:注解ControllerAdvice异常处理结果 通过上面结果可以看到,异常处理确实已经变更为ExceptionHandlerAdvice类。这种方法将所有的异常处理整合到一处,去除了Controller中的继承关系,并且达到了全局捕获的效果,推荐使用此类方式;2.3实现HandlerExceptionResolver接口HandlerExceptionResolver本身SpringMVC内部的接口,其内部只有resolveException一个方法,通过实现该接口我们可以达到全局异常处理的目的。图7:实现HandlerExceptionResolver接口 同样在执行之前,将上述两个方法的异常处理都注释掉,运行结果如下:图8:实现HandlerExceptionResolver接口运行结果 可以看到500的异常处理已经生效了,但是400的异常处理却没有生效,并且根没有异常前的返回结果一样。这是怎么回事呢?不是说可以做到全局异常处理的么?没办法要想知道问题的原因,我们只能刨根问底,往Spring的祖坟上刨,下面我们结合Spring的源码调试,去需要原因。3.Spring中异常处理源码分析大家都知道,在Spring中第一个收到请求的类就是DispatcherServlet,而该类中核心的方法就是doDispatch,我们可以在该类中打断点,进而一步步跟进异常处理。3.1 HandlerExceptionResolver实现类处理流程参照如下的跟进步骤,在processHandlerException中断点,跟踪的结果如下图:图9:processHandlerException断点 可以看到在图中箭头【1】处,在遍历 handlerExceptionResolvers 进而来处理异常,而在箭头【2】处,看到handlerExceptionResolvers 中共有4个元素,其中最后一个就是2.3方法定义的异常处理类。当前的请求query请求,根据上述现象可以推测出,该异常处理应该是在前3个异常处理中被处理了,从而跳过我们自定义的异常;带着这样的猜测,我们F8继续跟进,可以跟踪到该异常是被第三个,即DefaultHandlerExceptionResolver所处理。DefaultHandlerExceptionResolver SpringMVC默认装配了DefaultHandlerExceptionResolver,该类的doResolveException方法中主要对一些特殊的异常进行处理,并将这类异常转换为相应的响应状态码。而query请求触发的异常为MissingServletRequestParameterException,其恰好也是被DefaultHandlerExceptionResolver所针对的异常,故会在该类中被异常捕获。到此真相大白了,可以看到我们的自定义类MyHandlerExceptionResolver确实可以做到全局处理异常,只不过对于query请求的异常,中间被DefaultHandlerExceptionResolver插了一脚,所以就跳过了MyHandlerExceptionResolver类的处理,从而出现400的返回结果。而对于calc请求,中间没有阻拦,所以就达到了预期效果。3.2三类异常的处理顺序到此我们一共介绍了3类全局异常处理,按照上面的分析可以看出,实现HandlerExceptionResolver接口的方式是排在最后处理,那么@ExceptionHandler和@ControllerAdvice这两个的顺序谁先谁后呢?将三类异常处理全部打开(之前注释掉了),运行一下看看效果:图10:异常处理全放开运行结果 通过现象可以看到,Controller中单独@ExceptionHandle异常处理排在了首位,@ControllerAdvice排在了第二位。严谨的童鞋可以写个Controller02,将query和calc复制过去,异常处理就不要了,这样请求c02的方法时,异常捕获的所属类名就都是@ControllerAdvice所在类了。以上都是我们根据现象得到的结论,下面去Spring源码去找“证据”。在图9中,handlerExceptionResolvers中有4类处理器,而@ExceptionHandler和@ControllerAdvice的处理就在第一个ExceptionHandlerExceptionResolver中(之前断点跟进即可获知)。继续跟进直到进入ExceptionHandlerExceptionResolver类的doResolveHandlerMethodException方法,这里的HandlerMethod就是Spring将HTTP请求映射到指定Controller中的方法,而Exception就是需要被捕获的异常;继续跟进,看看使用这两个参数到底干了什么事儿。图11:doResolveHandlerMethodException断点 继续跟进getExceptionHandlerMethod方法,发现有两个变量可能就是问题的关键:exceptionHandlerCache和exceptionHandlerAdviceCache。首先,两者的变量名很值得怀疑;其次,前者在代码中看,明显是通过类作为key,从而得到一个处理器(resolver),这恰好Controller中@ExceptionHandler处理规则相吻合;最后,这两个Cache的处理顺序,也符合之前的得到的结论。正如之前猜测的那样,Spring中确实是优先根据Controller类名去查找对应的ExceptionHandler,没有找到的话,再进行@ControllerAdvice异常处理。图12:两个异常处理Cache **如有兴趣可继续深入挖掘Spring的源码,这里针对ExceptionHandlerExceptionResolver 简单做个总结:**exceptionHandlerCache中包含Controller中的ExceptionHandler异常处理,处理时通过HandlerMethod得到Controller,进而再找到异常处理方法,需要注意的是,其是在异常处理过程中put值的;exceptionHandlerAdviceCache则是在项目启动时初始化的,大概思路是找到带有@ControllerAdvice注解的bean,从而缓存bean中的ExceptionHandler,在异常处理时需要对齐遍历查找处理,进而达到全局处理的目的。3.3咸鱼翻身介绍了这么多,简单画张图总结一下。蓝色的部分是Spring默认添加的3类异常处理器,黄色部分是我们添加的异常处理以及其所被调用的位置和顺序。看看哪里还有不太清楚的,往回翻翻看(ResponseStatusExceptionResolver是针对@ResponseStatus注解,这里不再详述)。图13:异常总结如果有需要将MyHandlerExceptionResolver提前处理,甚至排在ExceptionHandlerExceptionResolver之前,能做到么?答案是肯定的,在Spring中如果想将MyHandlerExceptionResolver异常处理提前,需要再实现一个Ordered接口,实现里面的getOrder方法即可,这里返回-1,将其放在最上面,这次咸鱼终于可以翻身了。图14:实现Ordered接口 运行看一下结果是不是符合预期,提醒一下,我们三个异常处理都是生效的,如下图: 图15:实现Ordered接口运行结果4.总结本文主要通过介绍SpringMVC中三类常见的全局异常处理,在调试中发现了问题,进而引发去Spring源码中去探究原因,最终解决问题,希望大家能有所收获。当然Spring异常处理类不止介绍的这些,有兴趣的童鞋请自行探索!参考链接:[1] http://www.cnblogs.com/fangji...[2] https://blog.csdn.net/mll9998…作者:张远航 宜信技术学院链接描述

January 24, 2019 · 1 min · jiezi

springmvc 数据加密解密(1)------服务端加解密

第一次写文章,不知道怎么写,写的不好或者不对的地方请大家指出,本人肯定会虚心接纳。对于数据加密和为什么需要数据加密,网上的文章一搜就一大片,以博主那三脚猫的知识就不和大家解释啦(????????)。进入正题,博主所在公司开发的项目,无论是app端还是h5网页,和后端都以json数据交互,也就是常说的一套接口给所有前端使用,所以彻底解放了博主写接口的压力,哈哈????????。 既然涉及数据交互,肯定不能避免数据安全的问题,自然而然我们就会想到对数据进行加密和解密。现在博主接触到的数加密和解密算法是对称算法(AES)和非对称算法(RSA),当然还有其他算法,但我们也不需要研究太多,够用就行,开发不就是这样嘛????????。我们传递的数据被非法用户窃取,如果使用AES算法,这个算法的优势就是加解密速度快,由于秘钥也保存在前端(如果是保存app,还需要费劲的破解app;但如果是保存在网页,这个博主不说大家也知道)基本没有安全保障,如果使用RSA算法,那基本安全无忧,但RSA算法对数据加密和解密非常的耗时,当然这个也受限于服务器哈,如果服务器性能很强,直接使用RSA对数据加密解密就行,如果服务器性能有限,可以继续看博主接下来的方式。如果我们的AES秘钥不能或者很难被获取到,是不是就解决安全性的问题啦。那我们如何来保证AES秘钥很难被获取呢?没错,用RSA来保证,首先AES秘钥都是随机的,然后我们使用RSA算法,对AES的秘钥进行加密解密,这个其实就是数字信封技术,博主小小的卖弄一波哈????????。画个图来解释我们这个加密解密过程,帮助大家理解。1:首先客户端存储着RSA公钥,就算非法用户拿到RSA公钥,他也很难根据公钥和加密数据进行解密,这个算法已经保证了,支付宝 也是使用RSA来对数据加密,所以大家可以放心使用RSA算法。2:客户端随机生成AES的秘钥,这个秘钥长度必须是16的倍数,这个是经过项目检验得到验证,然后使用RSA公钥对AES产生的秘钥 进行加密发送给服务端。 3:服务端使用RSA秘钥对已经过RSA公钥加密的AES秘钥进行解密,得到AES的秘钥,保存起来;接下来比较重要的就是产生临时会 话号,为什么需要这个会话号呢?因为我们的场景下可能是不需要用户登录的,为了标识这个用户,我们得产生一个会话号,这个 必须是保证唯一性,因为我们得根据这个会话号去标识当前的请求用户,还有一个作用就是根据这个会话号来对应不同 的AES秘钥。博主是将会话号保存在redis中,当然也有很多方案,这个看项目需求。4:接下来就开始进行客户端和服务端数据交互,当然如果我们在未登录情况下,注意客户端得传递临时会话号,否则服务端找不到 AES秘钥就没办法对数据解密和加密说了那么多理论的东西,是不是有些急不可待看看怎么取实现了呐。废话不多说,上代码。RSAHelper.javaimport java.io.IOException;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.PublicKey;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import org.apache.commons.codec.binary.Base64;import org.apache.commons.io.output.ByteArrayOutputStream;import org.junit.Test;import org.springframework.util.Base64Utils;public class RSAHelper { public static final String PUBLIC_KEY_VALUES = “MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCgl0qiFQP8P/Wv5ZXRe78wxU3h7f/5xkh0ChiJ0f4Md0KZv0cSOzBGptNC41tL6cg1qKNPcEuJH296jet/T0Q+cD2tuofHtnB4ghhhCVHT8gkxhV+DJ61BquUIRrhJwj8jU3pw/klY+gIiiOfJFz2Hpar6Lyp+KCTZTyljg0vKPQIDAQAB”; public static final String PRIVATE_KEY_VALUES = “MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKCXSqIVA/w/9a/lldF7vzDFTeHt//nGSHQKGInR/gx3Qpm/RxI7MEam00LjW0vpyDWoo09wS4kfb3qN639PRD5wPa26h8e2cHiCGGEJUdPyCTGFX4MnrUGq5QhGuEnCPyNTenD+SVj6AiKI58kXPYelqvovKn4oJNlPKWODS8o9AgMBAAECgYEAgmW2vVtxK/9HYPd8Kmhf+5sKPX0Cz+8YX9jeyfIQZlDkbHErpXsYHRZTDsoMFN0Uq7VuPg/B1esHmyzn3y0fDIU1MYcrxBGtKWm1AxwpTcCafsQ3VakkMRkTD8QNZXqcyctB4aRIVWq3p3556zHEIwyxUMCr4UDUKjilJBi/q2ECQQDf1N4Gi1eWnkFuVjD6mJ6bfGJ04l8DajIdt/N/a/J4O1TlPMnaBons5cg/evatsiZvRHYsXeoWygaJhCcoap8JAkEAt6uzQXxfM/oNSZj5voIgZc2xhMWej7bAYYS6Q9/n5KNkybtWSHYe8lbJc4Q4/eYzinDwMYRxVNFR0WuUwafqlQJAPjlkG7ei+tk14WGOriu9dAYpLMs9lKpyEjbwN00gE/KSkEPM7ZKBx1y9xX/+kZ0D+Ey0+XKGQB2boaEebaruWQJAXHeivVtCCsbenajYQuL8MISH1JIxK6UT4YSSyc0Vz/O6sB0SaVSea97peLCeiKS2WgJVynglHlBrYoVI1N4WqQJBAJt92JHPMlvsKcmeVgQS5LmobsYc5EYUQ0sHUpvQJ26/hh2pDBKxYmyhl1TNuEVfsjuvDbdfPzn9MMqDt5RPbcc=”; /** * RSA密钥长度必须是64的倍数,在512~65536之间。默认是1024 / public static final int KEY_SIZE = 1024; /* * 生成公钥、私钥对(keysize=1024) / public static RSAHelper.KeyPairInfo getKeyPair() { return getKeyPair(KEY_SIZE); } /* * 生成公钥、私钥对 * * @param keySize * @return / public static RSAHelper.KeyPairInfo getKeyPair(int keySize) { try { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(“RSA”); keyPairGen.initialize(keySize); // 生成一个密钥对,保存在keyPair中 KeyPair keyPair = keyPairGen.generateKeyPair(); // 得到私钥 RSAPrivateKey oraprivateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到公钥 RSAPublicKey orapublicKey = (RSAPublicKey) keyPair.getPublic(); RSAHelper.KeyPairInfo pairInfo = new RSAHelper.KeyPairInfo(keySize); //公钥 byte[] publicKeybyte = orapublicKey.getEncoded(); String publicKeyString = Base64.encodeBase64String(publicKeybyte);//Base64.encode(publicKeybyte); pairInfo.setPublicKey(publicKeyString); //私钥 byte[] privateKeybyte = oraprivateKey.getEncoded(); String privateKeyString = Base64.encodeBase64String(privateKeybyte);//Base64.encode(privateKeybyte); pairInfo.setPrivateKey(privateKeyString); return pairInfo; } catch (Exception e) { e.printStackTrace(); return null; } } /* * 获取公钥对象 * * @param publicKeyBase64 * @return * @throws InvalidKeySpecException * @throws NoSuchAlgorithmException / public static PublicKey getPublicKey(String publicKeyBase64) throws InvalidKeySpecException, NoSuchAlgorithmException { KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); X509EncodedKeySpec publicpkcs8KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyBase64));//Base64.decode(publicKeyBase64) PublicKey publicKey = keyFactory.generatePublic(publicpkcs8KeySpec); return publicKey; } /* * 获取私钥对象 * * @param privateKeyBase64 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException / public static PrivateKey getPrivateKey(String privateKeyBase64) throws NoSuchAlgorithmException, InvalidKeySpecException { KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PKCS8EncodedKeySpec privatekcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyBase64));//Base64.decode(privateKeyBase64) PrivateKey privateKey = keyFactory.generatePrivate(privatekcs8KeySpec); return privateKey; } /* * 使用公钥加密 * * @param content 待加密内容 * @param publicKeyBase64 公钥 base64 编码 * @return 经过 base64 编码后的字符串 / public static String encipher(String content, String publicKeyBase64) { return encipher(content, publicKeyBase64, KEY_SIZE / 8 - 11); } /* * 使用公司钥加密(分段加密) * * @param content 待加密内容 * @param publicKeyBase64 公钥 base64 编码 * @param segmentSize 分段大小,一般小于 keySize/8(段小于等于0时,将不使用分段加密) * @return 经过 base64 编码后的字符串 / public static String encipher(String content, String publicKeyBase64, int segmentSize) { try { PublicKey publicKey = getPublicKey(publicKeyBase64); return encipher(content, publicKey, segmentSize); } catch (Exception e) { e.printStackTrace(); return null; } } /* * 分段加密 * * @param ciphertext 密文 * @param key 加密秘钥 * @param segmentSize 分段大小,<=0 不分段 * @return / public static String encipher(String ciphertext, java.security.Key key, int segmentSize) { try { // 用公钥加密 byte[] srcBytes = ciphertext.getBytes(); // Cipher负责完成加密或解密工作,基于RSA Cipher cipher = Cipher.getInstance(“RSA”); // 根据公钥,对Cipher对象进行初始化 cipher.init(Cipher.ENCRYPT_MODE, key); byte[] resultBytes = null; if (segmentSize > 0) resultBytes = cipherDoFinal(cipher, srcBytes, segmentSize); //分段加密 else resultBytes = cipher.doFinal(srcBytes); String base64Str = Base64Utils.encodeToString(resultBytes); return base64Str; } catch (Exception e) { e.printStackTrace(); return null; } } /* * 分段大小 * * @param cipher * @param srcBytes * @param segmentSize * @return * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws IOException / public static byte[] cipherDoFinal(Cipher cipher, byte[] srcBytes, int segmentSize) throws IllegalBlockSizeException, BadPaddingException, IOException { if (segmentSize <= 0) throw new RuntimeException(“分段大小必须大于0”); ByteArrayOutputStream out = new ByteArrayOutputStream(); int inputLen = srcBytes.length; int offSet = 0; byte[] cache; int i = 0; // 对数据分段解密 while (inputLen - offSet > 0) { if (inputLen - offSet > segmentSize) { cache = cipher.doFinal(srcBytes, offSet, segmentSize); } else { cache = cipher.doFinal(srcBytes, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * segmentSize; } byte[] data = out.toByteArray(); out.close(); return data; } /* * 使用私钥解密 * * @param contentBase64 待加密内容,base64 编码 * @param privateKeyBase64 私钥 base64 编码 * @return * @segmentSize 分段大小 / public static String decipher(String contentBase64, String privateKeyBase64) { return decipher(contentBase64, privateKeyBase64, KEY_SIZE / 8); } /* * 使用私钥解密(分段解密) * * @param contentBase64 待加密内容,base64 编码 * @param privateKeyBase64 私钥 base64 编码 * @return * @segmentSize 分段大小 / public static String decipher(String contentBase64, String privateKeyBase64, int segmentSize) { try { PrivateKey privateKey = getPrivateKey(privateKeyBase64); return decipher(contentBase64, privateKey, segmentSize); } catch (Exception e) { e.printStackTrace(); return null; } } /* * 分段解密 * * @param contentBase64 密文 * @param key 解密秘钥 * @param segmentSize 分段大小(小于等于0不分段) * @return / public static String decipher(String contentBase64, java.security.Key key, int segmentSize) { try { // 用私钥解密 byte[] srcBytes = Base64Utils.decodeFromString(contentBase64); // Cipher负责完成加密或解密工作,基于RSA Cipher deCipher = Cipher.getInstance(“RSA”); // 根据公钥,对Cipher对象进行初始化 deCipher.init(Cipher.DECRYPT_MODE, key); byte[] decBytes = null;//deCipher.doFinal(srcBytes); if (segmentSize > 0) decBytes = cipherDoFinal(deCipher, srcBytes, segmentSize); //分段加密 else decBytes = deCipher.doFinal(srcBytes); String decrytStr = new String(decBytes); return decrytStr; } catch (Exception e) { e.printStackTrace(); return null; } } /* * 秘钥对 / public static class KeyPairInfo { String privateKey; String publicKey; int keySize = 0; public KeyPairInfo(int keySize) { setKeySize(keySize); } public KeyPairInfo(String publicKey, String privateKey) { setPrivateKey(privateKey); setPublicKey(publicKey); } public String getPrivateKey() { return privateKey; } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public String getPublicKey() { return publicKey; } public void setPublicKey(String publicKey) { this.publicKey = publicKey; } public int getKeySize() { return keySize; } public void setKeySize(int keySize) { this.keySize = keySize; } } public static void main(String[] args) { System.out.println(“第一步:客户端和服务端进行密码握手通信,确保密码的安全性”); System.out.println(" 1.客户端请求RSA公钥"); System.out.println(" 2.服务端开始颁发公钥给客户端"); KeyPairInfo keyPairInfo = RSAHelper.getKeyPair(); System.out.println(" 3.服务端返回给客户端RSA公钥:"+keyPairInfo.getPublicKey()); System.out.println(" 4.服务端自己保存RSA公钥和RSA私钥:公钥:"+keyPairInfo.getPublicKey()+";私钥"+keyPairInfo.getPrivateKey()); String AESPassword = “1234567890123456”; System.out.println(" 5.客户端产生RES的密码:"+AESPassword); String AESPasswordEncipher = RSAHelper.encipher(AESPassword, keyPairInfo.getPublicKey()); System.out.println(" 6.客户端使用RSA的公钥加密第5步中的产生的RES密码:"+AESPasswordEncipher); System.out.println(" 7.客户端发送使用RSA公钥加密的AES密码,服务端接收到密码:"+AESPasswordEncipher); String AESPasswordDecipher = RSAHelper.decipher(AESPasswordEncipher, keyPairInfo.getPrivateKey()); System.out.println(" 8.服务端使用RSA私钥解密密码(已经经过RSA加密的AES的密码):"+AESPasswordDecipher); System.out.println(“第二步:开始数据交互”); String inputStr = “{"token":"ippay_bf0eb479e1b6cfe7cfe609ebf52c5328","info":[{"an":"2017114256712","sqr":["横琴国际知识产权交易中心有限公司"],"ti":"一种视频编码方法及装置","jiaofei":[{"name":"印花税","amount":"5"}]}],"linkMan":{"userName":"毛雨田","userTel":"13718881828","userEmail":"layn@live.com"},"reimburseInfo":{"isReimburse":"2","type":"1","title":"北京航空航天大学","taxNumber":"","mailType":"1","mailUserName":"毛雨田","mailUserTel":"13718881828","mailAddr":"北京","deliveryType":"1"},"device":"1"}”; System.out.println(" 1.未使用AES加密前的数据:"+inputStr); try { String inputStrEncrypt = AESUtil.aesEncrypt(inputStr, AESPassword); System.out.println(" 1.客户端使用第一步中产生的AES密码对数据加密并发送给服务端:"+inputStrEncrypt); System.out.println(" 2.服务端接收到客户端发送过来的加密数据:"+inputStrEncrypt); String inputStrDecrypt = AESUtil.aesDecrypt(inputStrEncrypt, AESPassword); System.out.println(" 3.服务端使用第一步中解密后的密码解密数据:"+inputStrDecrypt); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }}AESUtil.javaimport java.math.BigInteger;import java.util.Map;import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import javax.crypto.spec.SecretKeySpec;import org.apache.commons.codec.binary.Base64;import org.apache.commons.lang3.StringUtils;import org.apache.log4j.Logger;import org.springframework.util.Base64Utils;import com.fasterxml.jackson.databind.ObjectMapper;public class AESUtil { public static String AES_KEY = “MTIzNDU2Nzg5MDk4NzY1NA==”; // 算法 private static final String ALGORITHMSTR = “AES/ECB/PKCS5Padding”; private static ObjectMapper objectMapper = new ObjectMapper(); public static Logger logger = Logger.getLogger(AESUtil.class); /* * aes解密 * * @param encrypt * 内容 * @return * @throws Exception / public static String aesDecrypt(String encrypt) { try { return aesDecrypt(encrypt, AES_KEY); } catch (Exception e) { e.printStackTrace(); return “”; } } /* * aes加密 * * @param content * @return * @throws Exception / public static String aesEncrypt(String content) { try { return aesEncrypt(content, AES_KEY); } catch (Exception e) { e.printStackTrace(); return “”; } } /* * 将byte[]转为各种进制的字符串 * * @param bytes * byte[] * @param radix * 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制 * @return 转换后的字符串 / public static String binary(byte[] bytes, int radix) { return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数 } /* * base 64 encode * * @param bytes * 待编码的byte[] * @return 编码后的base 64 code / public static String base64Encode(byte[] bytes) { return Base64.encodeBase64String(bytes); } /* * base 64 decode * * @param base64Code * 待解码的base 64 code * @return 解码后的byte[] * @throws Exception / public static byte[] base64Decode(String base64Code) throws Exception { return StringUtils.isEmpty(base64Code) ? null : Base64Utils.decodeFromString(base64Code); } /* * AES加密 * * @param content * 待加密的内容 * @param encryptKey * 加密密钥 * @return 加密后的byte[] * @throws Exception / public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance(“AES”); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), “AES”)); return cipher.doFinal(content.getBytes(“utf-8”)); } /* * AES加密为base 64 code * * @param content * 待加密的内容 * @param encryptKey * 加密密钥 * @return 加密后的base 64 code * @throws Exception / public static String aesEncrypt(String content, String encryptKey) throws Exception { return base64Encode(aesEncryptToBytes(content, encryptKey)); } /* * AES解密 * * @param encryptBytes * 待解密的byte[] * @param decryptKey * 解密密钥 * @return 解密后的String * @throws Exception / public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance(“AES”); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), “AES”)); byte[] decryptBytes = cipher.doFinal(encryptBytes); return new String(decryptBytes); } /* * 将base 64 code AES解密 * * @param encryptStr * 待解密的base 64 code * @param decryptKey * 解密密钥 * @return 解密后的string * @throws Exception */ public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception { return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey); } @SuppressWarnings(“unchecked”) public static Map<String, Object> aesDecrypt2Map(String encryptStr, String decryptKey){ if(StringUtils.isBlank(decryptKey) || decryptKey.length() != 16){ return null; } try { String json = aesDecrypt(encryptStr, decryptKey); return objectMapper.readValue(json, Map.class); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { String content = “kkkkkkk”; System.out.println(“加密前:” + content); System.out.println(“加密密钥和解密密钥:” + AES_KEY); String encrypt = aesEncrypt(content, AES_KEY); System.out.println(“加密后:” + encrypt); String decrypt = aesDecrypt(encrypt, AES_KEY); System.out.println(“解密后:” + decrypt); // js加密后的字符串: lkqsgKHH7OkhIa0tISMtuQ== //String jsData = aesDecrypt(“lkqsgKHH7OkhIa0tISMtuQ==”, AES_KEY); //System.out.println(“前端数据解密后的值:” + jsData); }}EncryptController.javaimport java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.smartsport.common.cache.CacheService;import com.smartsport.common.cache.constant.CacheConstant;import com.smartsport.common.model.ReturnFormatModel;import com.smartsport.common.utils.RSAHelper;import com.smartsport.common.utils.RandomStringUtil;@Controller@RequestMapping({ “/encrypt” })public class EncryptController { @Autowired @Qualifier(“RedisCacheServiceImpl”) private CacheService cacheService; @RequestMapping("/disposable") @ResponseBody public Map<String, Object> disposable(@RequestBody Map<String, String> jsonMap){ String aesPasswordEncrypt = jsonMap.get(“disposable”); System.out.println(aesPasswordEncrypt); String aesPasswordDecipher = RSAHelper.decipher(aesPasswordEncrypt, RSAHelper.PRIVATE_KEY_VALUES); String signal = RandomStringUtil.generateString(16) + cacheService.generate(CacheConstant.prefix+“aes_disposable”); System.out.println(“signal:"+signal); cacheService.put(CacheConstant.prefix+“aes_” + signal, aesPasswordDecipher, CacheConstant.AES_DISPOSABLE_TIMEOUT, CacheConstant.AES_DISPOSABLE_UNIT); Map<String, Object> resultMap= new HashMap<String, Object>(); resultMap.put(“signal”, signal); return ReturnFormatModel.retParam(“1”, resultMap); }} ...

January 23, 2019 · 7 min · jiezi

SpingMvc复杂参数传收总结

上一篇文章[javaWeb传收参数方式总结]总结了简单传收参数,这一篇讲如何传收复杂参数,比如Long[] 、User(bean里面包含List)、User[]、List<User><user style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">、List<Map<String,Object>等几种复杂参数</user>。一.简单数组集合类比如Long[],String[],List<User><long style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">等</long>前端:1.重复单个参数//(1)普通http://localhost:8080/ajaxGet?id=1&id=2&id=3//(2)Ajaxget方式 发送请求时等于(1)方式 $.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet?id=1&id=2&id=3” });//(3)Form表单GET方式 发送请求时等于(1)方式<form id=“fromGet” action=“fromGet” method=“GET”> <input type=“text"name=“id” value=“1”> <input type=“text"name=“id” value=“2”> <input type=“text"name=“id” value=“3”></form>//(4)Form表单POST方式 //发送请求参数会被拼接成 id=1&id=2&id=3 存储在请求体中<form id=“fromGet” action=“fromGet” method=“POST”> <input type=“text"name=“id” value=“1”> <input type=“text"name=“id” value=“2”> <input type=“text"name=“id” value=“3”></form>后端SpringMvc://数组public void ajaxGet(Long[] id){}//List集合public void ajaxGet(@RequestParam(“id”) List<Long> id){}2.数组参数前端://(1)普通urlhttp://localhost:8080/ajaxGet?id[]=1&id[]=2&id[]=3//2.Form GET方式(Ajax异步表单提交) 发送请求时等于(1)方式$.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet”, data: {“id”:[1,2,3]}, contentType:‘application/x-www-form-urlencoded’ });//(3)Form POST方式(Ajax异步表单提交)//发送请求参数会被拼接成 id[]=1&id[]=2&id[]=3 存储在请求体中$.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: {“id”:[1,2,3]}, contentType:‘application/x-www-form-urlencoded’ });后端SpringMvc://数组public void ajaxGet(@RequestParam(“id[]”) Long[] id){}//List集合public void ajaxGet(@RequestParam(“id[]”) List<Long> id){}其实以上两种都是一个道理,主要是看发送请求时 参数是id还是id,来决定后端使不使用@RequestParam(“id[]")进行数据绑定二.复杂实体类与集合比如User(bean里面包含List)、User[]、List<User><user style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">、List<Map<String,Object>等,此种类型均使用Json提交</user>1.复杂实体类UserUser实体类//User实体类public class User { private String name; private String pwd; private List<User> customers;//属于用户的客户群 //省略getter/setter } 前端://用户var user = {};user.name = “李刚”;user.pwd = “888”;//客户var customerArray = new Array();customerArray.push({name: “李四”,pwd: “123”});customerArray.push({name: “张三”,pwd: “332”});user. customers = customerArray; $.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: JSON.stringify(user), contentType:‘application/json;charset=utf-8’ });后端SpringMvc: public void ajaxPost(@ResponBody User user){ }前端://用户var userList = new Array(); userList.push({name: “李四”,pwd: “123”}); userList.push({name: “张三”,pwd: “332”}); $.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: JSON.stringify(userList), contentType:‘application/json;charset=utf-8’ });后端SpringMvc: public void ajaxPost(@ResponBody User[] user){ } public void ajaxPost(@ResponBody List<User> user){ } public void ajaxPost(@ResponBody List<Map<String,Object>> userMap){ } THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 1 min · jiezi

Spring跨域配置

Spring跨域配置介绍跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。比如说,域名A(http://domaina.example)的某 Web 应用程序中通过标签引入了域名B(http://domainb.foo)站点的某图片资源(http://domainb.foo/image.jpg),域名A的那 Web 应用就会导致浏览器发起一个跨站 HTTP 请求。在当今的 Web 开发中,使用跨站 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。 出于安全考虑,浏览器会限制脚本中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy)。 具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用 XMLHttpRequest 发起跨站 HTTP 请求。(这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是crsf跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)普通参数跨域在response的头文件添加httpServletResponse.setHeader(“Access-Control-Allow-Origin”,"");httpServletResponse.setHeader(“Access-Control-Allow-Methods”,“POST”);httpServletResponse.setHeader(“Access-Control-Allow-Headers”,“Access-Control”);httpServletResponse.setHeader(“Allow”,“POST”);参数值描述Access-Control-Allow-Origin授权的源控制Access-Control-Allow-Credentialstrue / false是否允许用户发送和处理cookieAccess-Control-Allow-Methods[,]*允许请求的HTTP Method,多个用逗号分隔Access-Control-Allow-Headers[,]控制哪些header能发送真正的请求,多个用逗号分隔Access-Control-Max-Age秒授权的时间,单位为秒。有效期内,不会重复发送预检请求带headr请求跨域这样客户端需要发起OPTIONS请求, 可以说是一个【预请求】,用于探测后续真正需要发起的跨域 POST 请求对于服务器来说是否是安全可接受的,因为跨域提交数据对于服务器来说可能存在很大的安全问题。因为Springmvc模式是关闭OPTIONS请求的,所以需要开启<servlet> <servlet-name>application</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup></servlet>开启CORSSpringMVC从4.2版本开始增加了对CORS的支持。在springMVC 中增加CORS支持非常简单,可以配置全局的规则,也可以使用@CrossOrigin注解进行细粒度的配置。使用@CrossOrigin注解先通过源码看看该注解支持的属性在Controller上使用@CrossOrigin注解// 指定当前的AccountController中所有的方法可以处理所有域上的请求@CrossOrigin(origins = {“http://domain1.com”, “http://domain2.com”}, maxAge = 72000L)@RestController@RequestMapping("/account")public class AccountController { @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // … } @RequestMapping(method = RequestMethod.DELETE, path = “/{id}”) public void remove(@PathVariable Long id) { // … }}在方法上使用@CrossOrigin注解@CrossOrigin(maxAge = 3600L)@RestController@RequestMapping("/account")public class AccountController { @CrossOrigin(origins = {“http://domain1.com”, “http://domain2.com”}) @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // … } @RequestMapping(method = RequestMethod.DELETE, path = “/{id}”) public void remove(@PathVariable Long id) { // … }}CORS全局配置除了细粒度基于注解的配置,可以定义全局CORS的配置。这类似于使用过滤器,但可以在Spring MVC中声明,并结合细粒度@CrossOrigin配置。默认情况下所有的域名和GET、HEAD和POST方法都是允许的。基于XML的配置<mvc:cors> <mvc:mapping path="/api/" allowed-origins=“http://domain1.com, http://domain2.com” allowed-methods=“GET, POST, PUT, HEAD, PATCH, DELETE, OPTIONS, TRACE” allowed-headers=“header1, header2, header3” exposed-headers=“header1, header2” allow-credentials=“false” max-age=“72000” /> <mvc:mapping path="/resources/" allowed-origins=“http://domain3.com” /></mvc:cors>基于代码的配置这个方法同样适用于SpringBoot。@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/") .allowedOrigins(“http://domain1.com”) .allowedOrigins(“http://domain2.com”) .allowedMethods(“GET”, “POST”, “PUT”, “HEAD”, “PATCH”, “DELETE”, “OPTIONS”, “TRACE”); .allowedHeaders(“header1”, “header2”, “header3”) .exposedHeaders(“header1”, “header2”) .allowCredentials(false) .maxAge(72000L); registry.addMapping("/resources/") .allowedOrigins(“http://domain3.com”); }}基于过滤器的配置import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;@Beanpublic FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin(“http://domain1.com”); config.addAllowedOrigin(“http://domain2.com”); config.addAllowedHeader(""); config.addAllowedMethod(“GET, POST, PUT, HEAD, PATCH, DELETE, OPTIONS, TRACE”); config.setAllowCredentials(true); config.setMaxAge(72000L); // CORS 配置对所有接口都有效 source.registerCorsConfiguration("/**", config); FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source)); bean.setOrder(0); return bean;} ...

January 9, 2019 · 2 min · jiezi

Spring Flux中Request与HandlerMapping关系的形成过程

一、前言Spring Flux中的核心DispatcherHandler的处理过程分为三步,其中首步就是通过HandlerMapping接口查找Request所对应的Handler。本文就是通过阅读源码的方式,分析一下HandlerMapping接口的实现者之一——RequestMappingHandlerMapping类,用于处理基于注解的路由策略,把所有用@Controller和@RequestMapping标记的类中的Handler识别出来,以便DispatcherHandler调用的。HandlerMapping接口的另外两种实现类:1、RouterFunctionMapping用于函数式端点的路由;2、SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler配对。<!– more –>文章系列关于非阻塞IO:《从时间碎片角度理解阻塞IO模型及非阻塞模型》关于SpringFlux新手入门:《快速上手Spring Flux框架》为什么Spring要引入SpringFlux框架 尚未完成Spring Flux中Request与HandlerMapping关系的形成过程 本文Spring Flux中执行HandlerMapping的过程 尚未完成Spring Flux中是如何处理HandlerResult的 尚未完成Spring Flux与WEB服务器之Servlet 3.1+ 尚未完成Spring Flux与WEB服务器之Netty 尚未完成二、对基于注解的路由控制器的抽象Spring中基于注解的控制器的使用方法大致如下:@Controllerpublic class MyHandler{ @RequestMapping("/") public String handlerMethod(){ }}在Spring WebFlux中,对上述使用方式进行了三层抽象模型。Mapping用户定义的基于annotation的映射关系该抽象对应的类是:org.springframework.web.reactive.result.method.RequestMappingInfo比如上述例子中的 @RequestMapping("/")所代表的映射关系Handler代表控制器的类该抽象对应的类是:java.lang.Class比如上述例子中的MyHandler类Method具体处理映射的方法该抽象对应的类是:java.lang.reflect.Method比如上述例子中的String handlerMethod()方法基于上述三层抽象模型,进而可以作一些组合。HandlerMethodHandler与Method的结合体,Handler(类)与Method(方法)搭配后就成为一个可执行的单元了Mapping vs HandlerMethod把Mapping与HandlerMethod作为字典存起来,就可以根据请求中的关键信息(路径、头信息等)来匹配到Mapping,再根据Mapping找到HandlerMethod,然后执行HandlerMethod,并传递随请求而来的参数。理解了这个抽象模型后,接下来分析源码来理解Spring WebFlux如何处理请求与Handler之间的Mapping关系时,就非常容易了。HandlerMapping接口及其各实现类负责上述模型的构建与运作。三、HandlerMapping接口实现的设计模式HandlerMapping接口实现,采用了"模版方法"这种设计模式。1层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware ^ | 2层:AbstractHandlerMethodMapping implements InitializingBean ^ | 3层:RequestMappingInfoHandlerMapping ^ | 4层:RequestMappingHandlerMapping implements EmbeddedValueResolverAware 下面对各层的职责作简要说明:第1层主要实现了对外提供模型的接口即重载了HandlerMapping接口的"Mono<Object> getHandler(ServerWebExchange exchange) “方法,并定义了骨架代码。第2层有两个责任 —— 解析用户定义的HandlerMethod + 实现对外提供模型接口实现所需的抽象方法通过实现了InitializingBean接口的"void afterPropertiesSet()“方法,解析用户定义的Handler和Method。实现第1层对外提供模型接口实现所需的抽象方法:“Mono<?> getHandlerInternal(ServerWebExchange exchange)“第3层提供根据请求匹配Mapping模型实例的方法第4层实现一些高层次用到的抽象方法来创建具体的模型实例。小结一下,就是HandlerMapping接口及其实现类,把用户定义的各Controller等,抽象为上述的Mapping、Handler及Method模型,并将Mapping与HandlerMethod作为字典关系存起来,还提供通过匹配请求来获得HandlerMethod的公共方法。接下来的章节,将先分析解析用户定义的模型并缓存模型的过程,然后再分析一下匹配请求来获得HandlerMethod的公共方法的过程。四、解析用户定义的模型并缓存模型的过程4-1、实现InitializingBean接口第2层AbstractHandlerMethodMapping抽象类中的一个重要方法——实现了InitializingBean接口的"void afterPropertiesSet()“方法,为Spring WebFlux带来了解析用户定义的模型并缓存模型的机会 —— Spring容器初初始化完成该类的具体类的Bean后,将会回调这个方法。在该方法中,实现获取用户定义的Handler、Method、Mapping以及缓存Mapping与HandlerMethod映射关系的功能。@Overridepublic void afterPropertiesSet() { initHandlerMethods(); // Total includes detected mappings + explicit registrations via registerMapping.. …}4-2、找到用户定义的HandlerafterPropertiesSet方法中主要是调用了void initHandlerMethods()方法,具体如下:protected void initHandlerMethods() { //获取Spring容器中所有Bean名字 String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class); for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { //获取Bean的类型 beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let’s ignore it. if (logger.isTraceEnabled()) { logger.trace(“Could not resolve type for bean ‘” + beanName + “’”, ex); } } //如果获取到类型,并且类型是Handler,则继续加载Handler方法。 if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } //初始化后收尾工作 handlerMethodsInitialized(getHandlerMethods());}这儿首先获取Spring容器中所有Bean名字,然后循环处理每一个Bean。如果Bean名称不是以SCOPED_TARGET_NAME_PREFIX常量开头,则获取Bean的类型。如果获取到类型,并且类型是Handler,则继续加载Handler方法。isHandler(beanType)调用,检查Bean的类型是否符合handler定义。AbstractHandlerMethodMapping抽象类中定义的抽象方法"boolean isHandler(Class<?> beanType)",是由RequestMappingHandlerMapping类实现的。具体实现代码如下:protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}不难看出,对于RequestMappingHandlerMapping这个实现类来说,只有拥有@Controller或者@RequestMapping注解的类,才是Handler。(言下之意对于其他实现类来说Handler的定义不同)。具体handler的定义,在HandlerMapping各实现类来说是不同的,这也是isHandler抽象方法由具体实现类来实现的原因。4-3、发现Handler的Method接下来我们要重点看一下"detectHandlerMethods(beanName);“这个方法调用。protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { //将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型) final Class<?> userType = ClassUtils.getUserClass(handlerType); //寻找目标类型userType中的Methods,selectMethods方法的第二个参数是lambda表达式,即感兴趣的方法的过滤规则 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, //回调函数metadatalookup将通过controller定义的mapping与手动定义的mapping合并起来 (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType)); if (logger.isTraceEnabled()) { logger.trace(“Mapped " + methods.size() + " handler method(s) for " + userType + “: " + methods); } methods.forEach((key, mapping) -> { //再次核查方法与类型是否匹配 Method invocableMethod = AopUtils.selectInvocableMethod(key, userType); //如果是满足要求的方法,则注册到全局的MappingRegistry实例里 registerHandlerMethod(handler, invocableMethod, mapping); }); }}首先将参数handler(即外部传入的BeanName或者BeanType)转换为Class<?>类型变量handlerType。如果转换成功,再将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)。接下来获取该用户类型里所有的方法(Method)。循环处理每个方法,如果是满足要求的方法,则注册到全局的MappingRegistry实例里。4-4、解析Mapping信息其中,以下代码片段有必要深入探究一下 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));MethodIntrospector.selectMethods方法的调用,将会把用@RequestMapping标记的方法筛选出来,并交给第二个参数所定义的MetadataLookup回调函数将通过controller定义的mapping与手动定义的mapping合并起来。第二个参数是用lambda表达式传入的,表达式中将method、userType传给getMappingForMethod(method, userType)方法。getMappingForMethod方法在高层次中是抽象方法,具体的是现在第4层RequestMappingHandlerMapping类中实现。在具体实现getMappingForMethod时,会调用到RequestMappingHandlerMapping类的下面这个方法。从该方法中,我们可以看到,首先会获得参数element(即用户在Controller中定义的方法)的RequestMapping类型的类实例,然后构造代表Mapping抽象模型的RequestmappingInfo类型实例并返回。private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }构造代表Mapping抽象模型的RequestmappingInfo类型实例,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的信息,包括paths、methods、params、headers、consumers、produces、mappingName,即用户定义@RequestMapping注解时所设定的可能的参数,都被存在这儿了。拥有了这些信息,当请求来到时,RequestMappingInfo就可以测试自身是否是处理该请求的人选之一了。protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) .methods(requestMapping.method()) .params(requestMapping.params()) .headers(requestMapping.headers()) .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()); if (customCondition != null) { builder.customCondition(customCondition); } return builder.options(this.config).build(); }4-5、缓存Mapping与HandlerMethod关系最后,registerHandlerMethod(handler, invocableMethod, mapping)调用将缓存HandlerMethod,其中mapping参数是RequestMappingInfo类型的。。内部调用的是MappingRegistry实例的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo类型。MappingRegistry类维护所有指向Handler Methods的映射,并暴露方法用于查找映射,同时提供并发控制。public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); …… this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }五、匹配请求来获得HandlerMethodAbstractHandlerMethodMapping类的“Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange)”方法,具体实现了根据请求查找HandlerMethod的逻辑。 @Override public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) { //获取读锁 this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod; try { //调用其它方法继续查找HandlerMethod handlerMethod = lookupHandlerMethod(exchange); } catch (Exception ex) { return Mono.error(ex); } if (handlerMethod != null) { handlerMethod = handlerMethod.createWithResolvedBean(); } return Mono.justOrEmpty(handlerMethod); } //释放读锁 finally { this.mappingRegistry.releaseReadLock(); } }handlerMethod = lookupHandlerMethod(exchange)调用,继续查找HandlerMethod。我们继续看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定义。为方便阅读,我把注释也写在了代码里。 protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception { List<Match> matches = new ArrayList<>(); //查找所有满足请求的Mapping,并放入列表mathes addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange); if (!matches.isEmpty()) { //获取比较器comparator Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange)); //使用比较器将列表matches排序 matches.sort(comparator); //将排在第1位的作为最佳匹配项 Match bestMatch = matches.get(0); if (matches.size() > 1) { //将排在第2位的作为次佳匹配项 Match secondBestMatch = matches.get(1); } handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange); } }六、总结理解了Spring WebFlux在获取映射关系方面的抽象设计模型后,就很容易读懂代码,进而更加理解框架的具体处理方式,在使用框架时做到“知己知彼”。原文:http://www.yesdata.net/2018/11/27/spring-flux-request-mapping/ ...

November 30, 2018 · 3 min · jiezi

设计一个可拔插的 IOC 容器

前言磨了许久,借助最近的一次通宵上线 cicada 终于更新了 v2.0.0 版本。之所以大的版本号变为 2,确实是向下不兼容了;主要表现为:修复了几个反馈的 bug。灵活的路由方式。可拔插的 IOC 容器选择。其中重点是后面两个。新的路由方式先来看第一个:路由方式的更新。在之前的版本想要写一个接口必须的实现一个 WorkAction;而且最麻烦的是一个实现类只能做一个接口。因此也有朋友给我提过这个 issue。于是改进后的使用方式如下:是否有点似曾相识的感觉????。如上图所示,不需要实现某个特定的接口;只需要使用不同的注解即可。同时也支持自定义 pojo, cicada 会在调用过程中对参数进行实例化。拿这个 getUser 接口为例,当这样请求时这些参数就会被封装进 DemoReq 中.http://127.0.0.1:5688/cicada-example/routeAction/getUser?id=1234&name=zhangsan同时得到响应:{“message”:“hello =zhangsan”}实现过程也挺简单,大家查看源码便会发现;这里贴一点比较核心的步骤。扫描所有使用 @CicadaAction 注解的类。扫描所有使用 @CicadaRoute 注解的方法。将他们的映射关系存入 Map 中。请求时根据 URL 去 Map 中查找这个关系。反射构建参数及方法调用。扫描类以及写入映射关系请求时查询映射关系反射调用这些方法是否需要 IOC 容器上面那几个步骤其实我都是一把梭写完的,但当我写到执行具体方法时感觉有点意思了。大家都知道反射调用方法有两个重要的参数:obj 方法执行的实例。args.. 自然是方法的参数。我第一次写的时候是这样的:method.invoke(method.getDeclaringClass().newInstance(), object);然后一测试,也没问题。当我写完之后 review 代码时发现不对:这样这里每次都会创建一个新的实例,而且反射调用 newInstance() 效率也不高。这时我不自觉的想到了 Spring 中 IOC 容器,和这里场景也非常的类似。在应用初始化时将所有的接口实例化并保存到 bean 容器中,当需要使用时只需要从容器中获取即可。这样只是会在启动时做很多加载工作,但造福后代啊。可拔插的 IOC 容器于是我打算自己实现一个这样的 bean 容器。但在实现之前又想到一个 feature:不如把实现 bean 容器的方案交给使用者选择,可以选择使用 bean 容器,也可以就用之前的每次都创建新的实例,就像 Spring 中的 prototype 作用域一样。甚至可以自定义容器实现,比如将 bean 存放到数据库、Redis 都行;当然一般人也不会这么干。和 SPI 的机制也有点类似。要实现上述的需求大致需要以下步骤:一个通用的接口,包含了注册容器、从容器中获取实例等方法。BeanManager 类,由它来管理具体使用哪种 IOC 容器。所以首先定义了一个接口;CicadaBeanFactory:包含了注册和获取实例的接口。同时分别有两个不同的容器实现方案。默认实现;CicadaDefaultBean:也就是文中说道的,每次都会创建实例;由于这种方式其实根本就没有 bean 容器,所以也不存在注册了。接下来是真正的 IOC 容器;CicadaIoc:它将所有的实例都存放在一个 Map 中。当然也少不了刚才提到的 CicadaBeanManager,它会在应用启动的时候将所有的实例注册到 bean 容器中。重点是图中标红的部分:需要根据用户的选择实例化 CicadaBeanFactory 接口。将所有的实例注册到 CicadaBeanFactory 接口中。同时也提供了一个获取实例的方法:就是直接调用 CicadaBeanFactory 接口的方法。然后在上文提到的反射调用方法处就变为:从 bean 容器中获取实例了;获取的过程可以是每次都创建一个新的对象,也可以是直接从容器中获取实例。这点对于这里的调用者来说并不关心。所以这也实现了标题所说的:可拔插。为了实现这个目的,我将 CicadaIoc 的实现单独放到一个模块中,以 jar 包的形式提供实现。所以如果你想要使用 IOC 容器的方式获取实例时只需要在你的应用中额外加入这个 jar 包即可。<dependency> <groupId>top.crossoverjie.opensource</groupId> <artifactId>cicada-ioc</artifactId> <version>2.0.0</version></dependency>如果不使用则是默认的 CicadaDefaultBean 实现,也就是每次都会创建对象。这样有个好处:当你自己想实现一个 IOC 容器时;只需要实现 cicada 提供的 CicadaBeanFactory 接口,并在你的应用中只加入你的 jar 包即可。其余所有的代码都不需要改变,便可随意切换不的容器。当然我是推荐大家使用 IOC 容器的(其实就是单例),牺牲一点应用启动时间带来后续性能的提升是值得的。总结cicada 的大坑填的差不多了,后续也会做一些小功能的迭代。还没有关注的朋友赶紧关注一波:https://github.com/TogetherOS/cicadaPS:虽然没有仔细分析 Spring IOC 的实现,但相信看完此篇的朋友应该对 Spring IOC 以及 SpringMVC 会有一些自己的理解。你的点赞与分享是对我最大的支持 ...

November 15, 2018 · 1 min · jiezi

结束了我短暂的秋招,说点自己的感受

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…秋招历程流水账总结笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks.从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。关于面试一些重要的问题总结另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。面试前如何准备运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试:自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!)自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)自己的简历该如何写。另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。关于着装穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。关于自我介绍如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。比如:面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。提前准备面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。面试中面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题:SpringMVC 工作原理说一下自己对 IOC 、AOP 的理解Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解Spring Bean 的作用域和生命周期了解吗Spring 事务中的隔离级别Spring 事务中的事务传播行为手写一个 LRU 算法知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排String 为什么是不可变的?String为啥要设计为不可变的?Arraylist 与 LinkedList 异同HashMap的底层实现HashMap 的长度为什么是2的幂次方ConcurrentHashMap 和 Hashtable 的区别ConcurrentHashMap线程安全的具体实现方式/底层具体实现如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。一些简单的 Linux 命令。为什么要用 消息队列关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。面试后如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦! ...

October 23, 2018 · 1 min · jiezi