网站建设与实践心得体会,一般网站模块,网站优化名词解释,无锡网站定制SpringMVC的作用毋庸置疑#xff0c;虽然我们现在都是用SpringBoot#xff0c;但是SpringBoot中仍然是在使用SpringMVC来处理请求。
我们在使用SpringMVC时#xff0c;传统的方式是通过定义web.xml#xff0c;比如#xff1a;
web-appservletservle…SpringMVC的作用毋庸置疑虽然我们现在都是用SpringBoot但是SpringBoot中仍然是在使用SpringMVC来处理请求。
我们在使用SpringMVC时传统的方式是通过定义web.xml比如
web-appservletservlet-nameapp/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-value/WEB-INF/spring.xml/param-value/init-paramload-on-startup1/load-on-startup/servletservlet-mappingservlet-nameapp/servlet-nameurl-pattern/app/*/url-pattern/servlet-mapping/web-app
我们只要定义这样的一个web.xml然后启动Tomcat那么我们就能正常使用SpringMVC了。
SpringMVC中最为核心的就是DispatcherServlet在启动Tomcat的过程中
Tomcat会先创建DispatcherServlet对象然后调用DispatcherServlet对象的init()
而在init()方法中会创建一个Spring容器并且添加一个ContextRefreshListener监听器该监听器会监听ContextRefreshedEvent事件Spring容器启动完成后就会发布这个事件也就是说Spring容器启动完成后就会执行ContextRefreshListener中的onApplicationEvent()方法从而最终会执行DispatcherServlet中的initStrategies()这个方法中会初始化更多内容
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
}
其中最为核心的就是HandlerMapping和HandlerAdapter。
什么是Handler
Handler表示请求处理器在SpringMVC中有四种Handler
实现了Controller接口的Bean对象实现了HttpRequestHandler接口的Bean对象添加了RequestMapping注解的方法一个HandlerFunction对象
比如实现了Controller接口的Bean对象
Component(/test2)
public class ControllerController implements Controller {Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {ModelAndView mav new ModelAndView(new MappingJackson2JsonView());mav.addObject(result, {});return mav;}
}
实现了HttpRequestHandler接口的Bean对象
Component(/test3)
public class HttpRequestHandlerController implements HttpRequestHandler {Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {JSONObject jsonObject new JSONObject();jsonObject.put(result, {});response.getWriter().write(jsonObject.toJSONString());}
}
添加了RequestMapping注解的方法
public class Controller {RequestMapping(method RequestMethod.GET, path /test)ResponseBodypublic Object controller() {MapObject, String result new HashMap(1);result.put(result, {});return result;}}一个HandlerFunction对象以下代码中有两个
public class AppConfig {Beanpublic RouterFunctionServerResponse person() {JSONObject jsonObject new JSONObject();jsonObject.put(result, {});return route().GET(test4, request - ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString())).POST(test4, request - ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString())).build();}}
什么是HandlerMapping?
HandlerMapping负责去寻找Handler并且保存路径和Handler之间的映射关系。
因为有不同类型的Handler所以在SpringMVC中会由不同的HandlerMapping来负责寻找Handler比如
BeanNameUrlHandlerMapping负责Controller接口和HttpRequestHandler接口RequestMappingHandlerMapping负责RequestMapping的方法RouterFunctionMapping负责RouterFunction以及其中的HandlerFunction
BeanNameUrlHandlerMapping的寻找流程 找出Spring容器中所有的beanName判断beanName是不是以“/”开头如果是则把它当作一个Handler并把beanName作为keybean对象作为value存入handlerMap中handlerMap就是一个Map
RequestMappingHandlerMapping的寻找流程 找出Spring容器中所有beanType判断beanType是不是有Controller注解或者是不是有RequestMapping注解判断成功则继续找beanType中加了RequestMapping的Method并解析RequestMapping中的内容比如method、path封装为一个RequestMappingInfo对象最后把RequestMappingInfo对象做为keyMethod对象封装为HandlerMethod对象后作为value存入registry中registry就是一个Map
RouterFunctionMapping的寻找流程会有些区别但是大体是差不多的相当于是一个path对应一个HandlerFunction。 各个HandlerMapping除开负责寻找Handler并记录映射关系之外自然还需要根据请求路径找到对应的Handler在源码中这三个HandlerMapping有一个共同的父类AbstractHandlerMapping AbstractHandlerMapping实现了HandlerMapping接口并实现了getHandler(HttpServletRequest request)方法。 AbstractHandlerMapping会负责调用子类的getHandlerInternal(HttpServletRequest request)方法从而找到请求对应的Handler然后AbstractHandlerMapping负责将Handler和应用中所配置的HandlerInterceptor整合成为一个HandlerExecutionChain对象。 所以寻找Handler的源码实现在各个HandlerMapping子类中的getHandlerInternal()中根据请求路径找到Handler的过程并不复杂因为路径和Handler的映射关系已经存在Map中了。 比较困难的点在于当DispatcherServlet接收到一个请求时该利用哪个HandlerMapping来寻找Handler呢看源码 很简单就是遍历找到就返回默认顺序为 所以BeanNameUrlHandlerMapping的优先级最高比如
如果请求路径都是/test但是最终是Controller接口的会生效。
什么是HandlerAdapter
找到了Handler之后接下来就该去执行了比如执行下面这个test()
但是由于有不同种类的Handler所以执行方式是不一样的再来总结一下Handler的类型
实现了Controller接口的Bean对象执行的是Bean对象中的handleRequest()实现了HttpRequestHandler接口的Bean对象执行的是Bean对象中的handleRequest()添加了RequestMapping注解的方法具体为一个HandlerMethod执行的就是当前加了注解的方法一个HandlerFunction对象执行的是HandlerFunction对象中的handle()
所以按逻辑来说找到Handler之后我们得判断它的类型比如代码可能是这样的
Object handler mappedHandler.getHandler();
if (handler instanceof Controller) {((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {((HandlerFunction)handler).handle(...);
}
但是SpringMVC并不是这么写的还是采用的适配模式把不同种类的Handler适配成一个HandlerAdapter后续再执行HandlerAdapter的handle()方法就能执行不同种类Hanlder对应的方法。 针对不同的Handler会有不同的适配器 HttpRequestHandlerAdapterSimpleControllerHandlerAdapterRequestMappingHandlerAdapterHandlerFunctionAdapter
适配逻辑为
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters ! null) {for (HandlerAdapter adapter : this.handlerAdapters) {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);
}
传入handler遍历上面四个Adapter谁支持就返回谁比如判断的代码依次为
public boolean supports(Object handler) {return (handler instanceof HttpRequestHandler);
}public boolean supports(Object handler) {return (handler instanceof Controller);
}public final boolean supports(Object handler) {return (handler instanceof HandlerMethod supportsInternal((HandlerMethod) handler));
}public boolean supports(Object handler) {return handler instanceof HandlerFunction;
}
根据Handler适配出了对应的HandlerAdapter后就执行具体HandlerAdapter对象的handle()方法了比如 HttpRequestHandlerAdapter的handle()
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);return null;
}
SimpleControllerHandlerAdapter的handle()
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);
}
HandlerFunctionAdapter的handle()
HandlerFunction? handlerFunction (HandlerFunction?) handler;
serverResponse handlerFunction.handle(serverRequest);
因为这三个接收的直接就是Requeset对象不用SpringMVC做额外的解析所以比较简单比较复杂的是RequestMappingHandlerAdapter它执行的是加了RequestMapping的方法而这种方法的写法可以是多种多样SpringMVC需要根据方法的定义去解析Request对象从请求中获取出对应的数据然后传递给方法并执行。
RequestMapping方法参数解析 当SpringMVC接收到请求并找到了对应的Method之后就要执行该方法了不过在执行之前需要根据方法定义的参数信息从请求中获取出对应的数据然后将数据传给方法并执行。 一个HttpServletRequest通常有
request parameterrequest attributerequest sessionreqeust headerreqeust body
比如如下几个方法
public String test(String username) {return test;
}
表示要从request parameter中获取key为username的value
public String test(RequestParam(uname) String username) {return test;
}
表示要从request attribute中获取key为username的value
public String test(RequestAttribute String username) {return test;
}
表示要从request session中获取key为username的value
public String test(SessionAttribute String username) {return test;
}
表示要从request header中获取key为username的value
public String test(RequestHeader String username) {return test;
}
表示获取整个请求体
public String test(RequestBody String username) {return test;
}
所以我们发现SpringMVC要去解析方法参数看该参数到底是要获取请求中的哪些信息。 而这个过程源码中是通过HandlerMethodArgumentResolver来实现的比如 RequestParamMethodArgumentResolver负责处理RequestParamRequestHeaderMethodArgumentResolver负责处理RequestHeaderSessionAttributeMethodArgumentResolver负责处理SessionAttributeRequestAttributeMethodArgumentResolver负责处理RequestAttributeRequestResponseBodyMethodProcessor负责处理RequestBody还有很多其他的...
而在判断某个参数该由哪个HandlerMethodArgumentResolver处理时也是很粗暴
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result this.argumentResolverCache.get(parameter);if (result null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}
就是遍历所有的HandlerMethodArgumentResolver哪个能支持处理当前这个参数就由哪个处理。 比如
RequestMapping(method RequestMethod.GET, path /test)
ResponseBody
public String test(RequestParam SessionAttribute String username) {System.out.println(username);return test;
}
以上代码的username将对应RequestParam中的username而不是session中的因为在源码中RequestParamMethodArgumentResolver更靠前。 当然HandlerMethodArgumentResolver也会负责从request中获取对应的数据对应的是resolveArgument()方法。 比如RequestParamMethodArgumentResolver
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest request.getNativeRequest(HttpServletRequest.class);if (servletRequest ! null) {Object mpArg MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg ! MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;}}Object arg null;MultipartRequest multipartRequest request.getNativeRequest(MultipartRequest.class);if (multipartRequest ! null) {ListMultipartFile files multipartRequest.getFiles(name);if (!files.isEmpty()) {arg (files.size() 1 ? files.get(0) : files);}}if (arg null) {String[] paramValues request.getParameterValues(name);if (paramValues ! null) {arg (paramValues.length 1 ? paramValues[0] : paramValues);}}return arg;
}
核心是
if (arg null) {String[] paramValues request.getParameterValues(name);if (paramValues ! null) {arg (paramValues.length 1 ? paramValues[0] : paramValues);}
}
按同样的思路可以找到方法中每个参数所要求的值从而执行方法得到方法的返回值。 RequestMapping方法返回值解析 而方法返回值也会分为不同的情况。比如有没有加ResponseBody注解如果方法返回一个String:
加了ResponseBody注解表示直接将这个String返回给浏览器没有加ResponseBody注解表示应该根据这个String找到对应的页面把页面返回给浏览器
在SpringMVC中会利用HandlerMethodReturnValueHandler来处理返回值
RequestResponseBodyMethodProcessor处理加了ResponseBody注解的情况ViewNameMethodReturnValueHandler处理没有加ResponseBody注解并且返回值类型为String的情况ModelMethodProcessor处理返回值是Model类型的情况还有很多其他的...
我们这里只讲RequestResponseBodyMethodProcessor因为它会处理加了ResponseBody注解的情况也是目前我们用得最多的情况。 RequestResponseBodyMethodProcessor相当于会把方法返回的对象直接响应给浏览器如果返回的是一个字符串那么好说直接把字符串响应给浏览器那如果返回的是一个Map呢是一个User对象呢该怎么把这些复杂对象响应给浏览器呢 处理这块SpringMVC会利用HttpMessageConverter来处理比如默认情况下SpringMVC会有4个HttpMessageConverter
ByteArrayHttpMessageConverter处理返回值为字节数组的情况把字节数组返回给浏览器StringHttpMessageConverter处理返回值为字符串的情况把字符串按指定的编码序列号后返回给浏览器SourceHttpMessageConverter处理返回值为XML对象的情况比如把DOMSource对象返回给浏览器AllEncompassingFormHttpMessageConverter处理返回值为MultiValueMap对象的情况
StringHttpMessageConverter的源码也比较简单
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {HttpHeaders headers outputMessage.getHeaders();if (this.writeAcceptCharset headers.get(HttpHeaders.ACCEPT_CHARSET) null) {headers.setAcceptCharset(getAcceptedCharsets());}Charset charset getContentTypeCharset(headers.getContentType());StreamUtils.copy(str, charset, outputMessage.getBody());
}
先看有没有设置Content-Type如果没有设置则取默认的默认为ISO-8859-1所以默认情况下返回中文会乱码可以通过以下来中方式来解决
RequestMapping(method RequestMethod.GET, path /test, produces {application/json;charsetUTF-8})
ResponseBody
public String test() {return test;
}
ComponentScan
Configuration
EnableWebMvc
public class AppConfig implements WebMvcConfigurer {Overridepublic void configureMessageConverters(ListHttpMessageConverter? converters) {StringHttpMessageConverter messageConverter new StringHttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}
不过以上四个Converter是不能处理Map对象或User对象的所以如果返回的是Map或User对象那么得单独配置一个Converter比如MappingJackson2HttpMessageConverter这个Converter比较强大能把String、Map、User对象等等都能转化成JSON格式。
ComponentScan
Configuration
EnableWebMvc
public class AppConfig implements WebMvcConfigurer {Overridepublic void configureMessageConverters(ListHttpMessageConverter? converters) {MappingJackson2HttpMessageConverter messageConverter new MappingJackson2HttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}
具体转化的逻辑就是Jackson2的转化逻辑。