22、web场景-web开发简介 Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
Support for serving static resources, including support for WebJars (covered later in this document )).
Automatic registration of Converter
, GenericConverter
, and Formatter
beans.
自动注册 Converter,GenericConverter,Formatter
Support for HttpMessageConverters
(covered later in this document ).
支持 HttpMessageConverters
(后来我们配合内容协商理解原理) Automatic registration of MessageCodesResolver
(covered later in this document ).
自动注册 MessageCodesResolver
(国际化用) Static index.html
support.
Custom Favicon
support (covered later in this document ).
Automatic use of a ConfigurableWebBindingInitializer
bean (covered later in this document ).
自动使用 ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上) If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer
but without @EnableWebMvc
.
不用@EnableWebMvc注解。使用 @Configuration
+ WebMvcConfigurer
自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
, or ExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations
and use it to provide custom instances of those components.
声明 WebMvcRegistrations
改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
, or alternatively add your own @Configuration
-annotated DelegatingWebMvcConfiguration
as described in the Javadoc of @EnableWebMvc
.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
23、web场景-静态资源规则与定制化 静态资源目录 只要静态资源放在类路径下: called /static
(or /public
or /resources
or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。
也可以改变默认的静态资源路径,/static
,/public
,/resources
, /META-INF/resources
失效
1 2 resources: static-locations: [classpath:/haha/ ]
静态资源访问前缀 1 2 3 spring: mvc: static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
webjar 可用jar方式添加css,js等资源文件,
https://www.webjars.org/
例如,添加jquery
1 2 3 4 5 <dependency > <groupId > org.webjars</groupId > <artifactId > jquery</artifactId > <version > 3.5.1</version > </dependency >
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径。
24、web场景-welcome与favicon功能 官方文档
欢迎页支持 Spring Boot supports both static and templated welcome pages. It first looks for an index.html
file in the configured static content locations. If one is not found, it then looks for an index
template. If either is found, it is automatically used as the welcome page of the application.
SpringBoot 同时支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件 。如果没有找到,那么它将查找索引模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。
静态资源路径下 index.html。
可以配置静态资源路径 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问 1 2 3 4 5 spring: resources: static-locations: [classpath:/haha/ ]
自定义Favicon 指网页标签上的小图标。
favicon.ico 放在静态资源目录下即可。
25、web场景-【源码分析】-静态资源原理 SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类) SpringMVC功能的自动配置类WebMvcAutoConfiguration
,生效 1 2 3 4 5 6 7 8 9 10 @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ... }
给容器中配置的内容:配置文件的相关属性的绑定:WebMvcProperties(spring.mvc )、ResourceProperties(spring.resources ) 1 2 3 4 5 6 7 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { ... }
配置类只有一个有参构造器(如WebMvcAutoConfigurationAdapter) 配置类只有一个有参构造器,那么有参构造器所有参数的值都会从容器中确定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public WebMvcAutoConfigurationAdapter (WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this .mvcProperties = mvcProperties; this .beanFactory = beanFactory; this .messageConvertersProvider = messageConvertersProvider; this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this .dispatcherServletPath = dispatcherServletPath; this .servletRegistrations = servletRegistrations; this .mvcProperties.checkConfiguration(); }
ResourceProperties resourceProperties
:获取和spring.resources绑定的所有的值的对象WebMvcProperties mvcProperties
:获取和spring.mvc绑定的所有的值的对象ListableBeanFactory beanFactory
:Spring的beanFactoryHttpMessageConverters
:找到所有的HttpMessageConvertersResourceHandlerRegistrationCustomizer
:找到 资源处理器的自定义器。DispatcherServletPath
:ServletRegistrationBean
:给应用注册Servlet、Filter....资源处理的默认规则addResourceHandlers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ...public class WebMvcAutoConfiguration { ... public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { ... @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { super .addResourceHandlers(registry); if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); return ; } ServletContext servletContext = getServletContext(); addResourceHandler(registry, "/webjars/**" , "classpath:/META-INF/resources/webjars/" ); addResourceHandler(registry, this .mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this .resourceProperties.getStaticLocations()); if (servletContext != null ) { registration.addResourceLocations(new ServletContextResource (servletContext, SERVLET_LOCATION)); } }); } ... } ... }
根据上述代码,我们可以通过配置禁止所有静态资源规则。
1 2 3 spring: resources: add-mappings: false
静态资源规则:CLASSPATH_RESOURCE_LOCATIONS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) public class ResourceProperties { private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/" , "classpath:/resources/" , "classpath:/static/" , "classpath:/public/" }; private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; ... }
欢迎页的处理规则welcomePageHandlerMapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...public class WebMvcAutoConfiguration { ... public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { ... @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping ( new TemplateAvailabilityProviders (applicationContext), applicationContext, getWelcomePage(), this .mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); return welcomePageHandlerMapping; }
WelcomePageHandlerMapping
的构造方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) { if (welcomePage != null && "/**" .equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage); setRootViewName("forward:index.html" ); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index" ); setRootViewName("index" ); } }
这构造方法内的代码也解释了web场景-welcome与favicon功能中配置static-path-pattern
了,welcome页面和小图标失效的问题。
26、请求处理-【源码分析】-Rest映射及源码解析 请求映射 表单方式(为什么put和delete请求没有解析到?)
1 2 3 4 5 spring: mvc: hiddenmethod: filter: enabled: true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <form action ="/user" method ="get" > <input value ="REST-GET提交" type ="submit" /> </form > <form action ="/user" method ="post" > <input value ="REST-POST提交" type ="submit" /> </form > <form action ="/user" method ="post" > <input name ="_method" type ="hidden" value ="DELETE" /> <input value ="REST-DELETE 提交" type ="submit" /> </form > <form action ="/user" method ="post" > <input name ="_method" type ="hidden" value ="PUT" /> <input value ="REST-PUT提交" type ="submit" /> <form >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @GetMapping("/user") public String getUser () { return "GET-张三" ; }@PostMapping("/user") public String saveUser () { return "POST-张三" ; }@PutMapping("/user") public String putUser () { return "PUT-张三" ; }@DeleteMapping("/user") public String deleteUser () { return "DELETE-张三" ; }
Rest原理(表单提交要使用REST的时候) 表单提交会带上\_method=PUT
请求过来被 HiddenHttpMethodFilter
拦截请求是否正常,并且是POST获取到\_method
的值。 兼容以下请求:PUT 、DELETE 、PATCH 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。 Rest使用客户端工具。如PostMan可直接发送put、delete等方式请求。 配置文件开启页面表单的Rest功能:
1 2 3 4 5 spring: mvc: hiddenmethod: filter: enabled: true
源码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class HiddenHttpMethodFilter extends OncePerRequestFilter { private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); public static final String DEFAULT_METHOD_PARAM = "_method" ; private String methodParam = DEFAULT_METHOD_PARAM; public void setMethodParam (String methodParam) { Assert.hasText(methodParam, "'methodParam' must not be empty" ); this .methodParam = methodParam; } @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST" .equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null ) { String paramValue = request.getParameter(this .methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HttpMethodRequestWrapper (request, method); } } } filterChain.doFilter(requestToUse, response); } private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper (HttpServletRequest request, String method) { super (request); this .method = method; } @Override public String getMethod () { return this .method; } } }
27、请求处理-【源码分析】-怎么改变默认的_method 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ... @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter () { return new OrderedHiddenHttpMethodFilter (); } ... }
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
意味着在没有HiddenHttpMethodFilter
时,才执行hiddenHttpMethodFilter()
。因此,我们可以自定义filter,改变默认的\_method
。 例如:
1 2 3 4 5 6 7 8 9 10 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter (); methodFilter.setMethodParam("_m" ); return methodFilter; } }
将\_method
改成_m
。
1 2 3 4 <form action ="/user" method ="post" > <input name ="_m" type ="hidden" value ="DELETE" /> <input value ="REST-DELETE 提交" type ="submit" /> </form >
28、请求处理-【源码分析】-请求映射原理 SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet
-> doDispatch()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); ... }
getHandler()
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 @Nullable protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { if (this .handlerMappings != null ) { for (HandlerMapping mapping : this .handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null ) { return handler; } } } return null ; }
this.handlerMappings
在Debug模式下展现的内容:
其中,保存了所有@RequestMapping
和handler
的映射规则。
所有的请求映射都在HandlerMapping中:
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
如果有就找到这个请求对应的handler 如果没有就是下一个 HandlerMapping 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 。自定义 HandlerMapping
IDEA快捷键:
Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。 Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。 Ctrl + H : 以树形方式展现类层次结构图。 29、请求处理-常用参数注解使用 注解:
@PathVariable
路径变量@RequestHeader
获取请求头@RequestParam
获取请求参数(指问号后的参数,url?a=1&b=2)@CookieValue
获取Cookie值@RequestAttribute
获取request域属性@RequestBody
获取请求体[POST]@MatrixVariable
矩阵变量@ModelAttribute
使用用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @RestController public class ParameterTestController { @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String name, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam("age") Integer age, @RequestParam("inters") List<String> inters, @RequestParam Map<String,String> params, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie) { Map<String,Object> map = new HashMap <>(); map.put("age" ,age); map.put("inters" ,inters); map.put("params" ,params); map.put("_ga" ,_ga); System.out.println(cookie.getName()+"===>" +cookie.getValue()); return map; } @PostMapping("/save") public Map postMethod (@RequestBody String content) { Map<String,Object> map = new HashMap <>(); map.put("content" ,content); return map; } }
30、请求处理-@RequestAttribute 用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Controller public class RequestController { @GetMapping("/goto") public String goToPage (HttpServletRequest request) { request.setAttribute("msg" ,"成功了..." ); request.setAttribute("code" ,200 ); return "forward:/success" ; } @GetMapping("/params") public String testParam (Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response) { map.put("hello" ,"world666" ); model.addAttribute("world" ,"hello666" ); request.setAttribute("message" ,"HelloWorld" ); Cookie cookie = new Cookie ("c1" ,"v1" ); response.addCookie(cookie); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public Map success (@RequestAttribute(value = "msg",required = false) String msg, @RequestAttribute(value = "code",required = false) Integer code, HttpServletRequest request) { Object msg1 = request.getAttribute("msg" ); Map<String,Object> map = new HashMap <>(); Object hello = request.getAttribute("hello" ); Object world = request.getAttribute("world" ); Object message = request.getAttribute("message" ); map.put("reqMethod_msg" ,msg1); map.put("annotation_msg" ,msg); map.put("hello" ,hello); map.put("world" ,world); map.put("message" ,message); return map; } }
31、请求处理-@MatrixVariable与UrlPathHelper 语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
SpringBoot默认是禁用了矩阵变量的功能
手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。 矩阵变量必须 有url路径变量才能被解析
手动开启矩阵变量 :
1 2 3 4 5 6 7 8 9 10 11 @Configuration(proxyBeanMethods = false) public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } }
创建返回WebMvcConfigurer
Bean: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } } } }
@MatrixVariable
的用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @RestController public class ParameterTestController { @GetMapping("/cars/{path}") public Map carsSell (@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand, @PathVariable("path") String path) { Map<String,Object> map = new HashMap <>(); map.put("low" ,low); map.put("brand" ,brand); map.put("path" ,path); return map; } @GetMapping("/boss/{bossId}/{empId}") public Map boss (@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge) { Map<String,Object> map = new HashMap <>(); map.put("bossAge" ,bossAge); map.put("empAge" ,empAge); return map; } }
32、请求处理-【源码分析】-各种类型参数解析原理 这要从DispatcherServlet
开始说起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class DispatcherServlet extends FrameworkServlet { protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ...
HandlerMapping
中找到能处理请求的Handler
(Controller.method())。为当前Handler 找一个适配器 HandlerAdapter
,用的最多的是RequestMappingHandlerAdapter 。 适配器执行目标方法并确定方法参数的每一个值。 HandlerAdapter 默认会加载所有HandlerAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DispatcherServlet extends FrameworkServlet { private boolean detectAllHandlerAdapters = true ; ... private void initHandlerAdapters (ApplicationContext context) { this .handlerAdapters = null ; if (this .detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true , false ); if (!matchingBeans.isEmpty()) { this .handlerAdapters = new ArrayList <>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this .handlerAdapters); } } ...
有这些HandlerAdapter
:
支持方法上标注@RequestMapping
支持函数式编程的
...
...
执行目标方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DispatcherServlet extends FrameworkServlet { protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = null ; ... mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ... mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
HandlerAdapter
接口实现类RequestMappingHandlerAdapter
(主要用来处理@RequestMapping
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... public final ModelAndView handle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } @Override protected ModelAndView handleInternal (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; mav = invokeHandlerMethod(request, response, handlerMethod); return mav; } }
参数解析器argumentResolvers 确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器argumentResolvers 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Nullable protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } ...
this.argumentResolvers
在afterPropertiesSet()
方法内初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @Override public void afterPropertiesSet () { ... if (this .argumentResolvers == null ) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this .argumentResolvers = new HandlerMethodArgumentResolverComposite ().addResolvers(resolvers); } ... } private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers () { List<HandlerMethodArgumentResolver> resolvers = new ArrayList <>(30 ); resolvers.add(new RequestParamMethodArgumentResolver (getBeanFactory(), false )); resolvers.add(new RequestParamMapMethodArgumentResolver ()); resolvers.add(new PathVariableMethodArgumentResolver ()); resolvers.add(new PathVariableMapMethodArgumentResolver ()); resolvers.add(new MatrixVariableMethodArgumentResolver ()); resolvers.add(new MatrixVariableMapMethodArgumentResolver ()); resolvers.add(new ServletModelAttributeMethodProcessor (false )); resolvers.add(new RequestResponseBodyMethodProcessor (getMessageConverters(), this .requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver (getMessageConverters(), this .requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver (getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver ()); resolvers.add(new ServletCookieValueMethodArgumentResolver (getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver (getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver ()); resolvers.add(new RequestAttributeMethodArgumentResolver ()); resolvers.add(new ServletRequestMethodArgumentResolver ()); resolvers.add(new ServletResponseMethodArgumentResolver ()); resolvers.add(new HttpEntityMethodProcessor (getMessageConverters(), this .requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver ()); resolvers.add(new ModelMethodProcessor ()); resolvers.add(new MapMethodProcessor ()); resolvers.add(new ErrorsMethodArgumentResolver ()); resolvers.add(new SessionStatusMethodArgumentResolver ()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver ()); if (KotlinDetector.isKotlinPresent()) { resolvers.add(new ContinuationHandlerMethodArgumentResolver ()); } if (getCustomArgumentResolvers() != null ) { resolvers.addAll(getCustomArgumentResolvers()); } resolvers.add(new PrincipalMethodArgumentResolver ()); resolvers.add(new RequestParamMethodArgumentResolver (getBeanFactory(), true )); resolvers.add(new ServletModelAttributeMethodProcessor (true )); return resolvers; } }
HandlerMethodArgumentResolverComposite
类如下:(众多参数解析器argumentResolvers 的包装类)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList <>(); ... public HandlerMethodArgumentResolverComposite addResolvers ( @Nullable HandlerMethodArgumentResolver... resolvers) { if (resolvers != null ) { Collections.addAll(this .argumentResolvers, resolvers); } return this ; } ... }
我们看看HandlerMethodArgumentResolver
的源码:
1 2 3 4 5 6 7 8 9 10 11 public interface HandlerMethodArgumentResolver { boolean supportsParameter (MethodParameter parameter) ; @Nullable Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
返回值处理器ValueHandler ValueHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Nullable protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } ...
this.returnValueHandlers
在afterPropertiesSet()
方法内初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; @Override public void afterPropertiesSet () { ... if (this .returnValueHandlers == null ) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this .returnValueHandlers = new HandlerMethodReturnValueHandlerComposite ().addHandlers(handlers); } } private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers () { List<HandlerMethodReturnValueHandler> handlers = new ArrayList <>(20 ); handlers.add(new ModelAndViewMethodReturnValueHandler ()); handlers.add(new ModelMethodProcessor ()); handlers.add(new ViewMethodReturnValueHandler ()); handlers.add(new ResponseBodyEmitterReturnValueHandler (getMessageConverters(), this .reactiveAdapterRegistry, this .taskExecutor, this .contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler ()); handlers.add(new HttpEntityMethodProcessor (getMessageConverters(), this .contentNegotiationManager, this .requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler ()); handlers.add(new CallableMethodReturnValueHandler ()); handlers.add(new DeferredResultMethodReturnValueHandler ()); handlers.add(new AsyncTaskMethodReturnValueHandler (this .beanFactory)); handlers.add(new ServletModelAttributeMethodProcessor (false )); handlers.add(new RequestResponseBodyMethodProcessor (getMessageConverters(), this .contentNegotiationManager, this .requestResponseBodyAdvice)); handlers.add(new ViewNameMethodReturnValueHandler ()); handlers.add(new MapMethodProcessor ()); if (getCustomReturnValueHandlers() != null ) { handlers.addAll(getCustomReturnValueHandlers()); } if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler (getModelAndViewResolvers())); } else { handlers.add(new ServletModelAttributeMethodProcessor (true )); } return handlers; } }
HandlerMethodReturnValueHandlerComposite
类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler { private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList <>(); ... public HandlerMethodReturnValueHandlerComposite addHandlers ( @Nullable List<? extends HandlerMethodReturnValueHandler> handlers) { if (handlers != null ) { this .returnValueHandlers.addAll(handlers); } return this ; } }
HandlerMethodReturnValueHandler
接口:
1 2 3 4 5 6 7 8 public interface HandlerMethodReturnValueHandler { boolean supportsReturnType (MethodParameter returnType) ; void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
回顾执行目标方法 1 2 3 4 5 6 public class DispatcherServlet extends FrameworkServlet { ... protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = null ; ... mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
RequestMappingHandlerAdapter
的handle()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... public final ModelAndView handle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } @Override protected ModelAndView handleInternal (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; mav = invokeHandlerMethod(request, response, handlerMethod); return mav; } }
RequestMappingHandlerAdapter
的invokeHandlerMethod()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { ... ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } ... invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null ; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
invokeAndHandle()
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... try { this .returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { ... } } @Nullable public Object invokeForRequest (NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); ... return doInvoke(args); } @Nullable protected Object doInvoke (Object... args) throws Exception { Method method = getBridgedMethod(); ReflectionUtils.makeAccessible(method); try { if (KotlinDetector.isSuspendingFunction(method)) { return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } return method.invoke(getBean(), args); } catch (IllegalArgumentException ex) { ... } catch (InvocationTargetException ex) { ... } } }
如何确定目标方法每一个参数的值 重点分析ServletInvocableHandlerMethod
的getMethodArgumentValues
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { ... @Nullable public Object invokeForRequest (NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); ... return doInvoke(args); } protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object [parameters.length]; for (int i = 0 ; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this .parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null ) { continue ; } if (!this .resolvers.supportsParameter(parameter)) { throw new IllegalStateException (formatArgumentError(parameter, "No suitable resolver" )); } try { args[i] = this .resolvers.resolveArgument(parameter, mavContainer, request, this .dataBinderFactory); } catch (Exception ex) { .... } } return args; } }
this.resolvers
的类型为HandlerMethodArgumentResolverComposite
(在参数解析器 章节提及)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter (MethodParameter parameter) { return getArgumentResolver(parameter) != null ; } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null ) { throw new IllegalArgumentException ("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first." ); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } @Nullable 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; } }
小结 本节描述,一个请求发送到DispatcherServlet后的具体处理流程,也就是SpringMVC的主要原理。
本节内容较多且硬核,对日后编程很有帮助,需耐心对待。
可以运行一个示例,打断点,在Debug模式下,查看程序流程。
33、请求处理-【源码分析】-Servlet API参数解析原理 WebRequest ServletRequest MultipartRequest HttpSession javax.servlet.http.PushBuilder Principal InputStream Reader HttpMethod Locale TimeZone ZoneId ServletRequestMethodArgumentResolver 用来处理以上的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable private static Class<?> pushBuilder; static { try { pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder" , ServletRequestMethodArgumentResolver.class.getClassLoader()); } catch (ClassNotFoundException ex) { pushBuilder = null ; } } @Override public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); } @Override public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException ( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { return resolveNativeRequest(webRequest, paramType); } return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); } private <T> T resolveNativeRequest (NativeWebRequest webRequest, Class<T> requiredType) { T nativeRequest = webRequest.getNativeRequest(requiredType); if (nativeRequest == null ) { throw new IllegalStateException ( "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); } return nativeRequest; } @Nullable private Object resolveArgument (Class<?> paramType, HttpServletRequest request) throws IOException { if (HttpSession.class.isAssignableFrom(paramType)) { HttpSession session = request.getSession(); if (session != null && !paramType.isInstance(session)) { throw new IllegalStateException ( "Current session is not of type [" + paramType.getName() + "]: " + session); } return session; } else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) { return PushBuilderDelegate.resolvePushBuilder(request, paramType); } else if (InputStream.class.isAssignableFrom(paramType)) { InputStream inputStream = request.getInputStream(); if (inputStream != null && !paramType.isInstance(inputStream)) { throw new IllegalStateException ( "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream); } return inputStream; } else if (Reader.class.isAssignableFrom(paramType)) { Reader reader = request.getReader(); if (reader != null && !paramType.isInstance(reader)) { throw new IllegalStateException ( "Request body reader is not of type [" + paramType.getName() + "]: " + reader); } return reader; } else if (Principal.class.isAssignableFrom(paramType)) { Principal userPrincipal = request.getUserPrincipal(); if (userPrincipal != null && !paramType.isInstance(userPrincipal)) { throw new IllegalStateException ( "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal); } return userPrincipal; } else if (HttpMethod.class == paramType) { return HttpMethod.resolve(request.getMethod()); } else if (Locale.class == paramType) { return RequestContextUtils.getLocale(request); } else if (TimeZone.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone : TimeZone.getDefault()); } else if (ZoneId.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); } throw new UnsupportedOperationException ("Unknown parameter type: " + paramType.getName()); } private static class PushBuilderDelegate { @Nullable public static Object resolvePushBuilder (HttpServletRequest request, Class<?> paramType) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null && !paramType.isInstance(pushBuilder)) { throw new IllegalStateException ( "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder); } return pushBuilder; } } }
用例:
1 2 3 4 5 6 7 8 9 10 11 @Controller public class RequestController { @GetMapping("/goto") public String goToPage (HttpServletRequest request) { request.setAttribute("msg" ,"成功了..." ); request.setAttribute("code" ,200 ); return "forward:/success" ; } }
34、请求处理-【源码分析】-Model、Map原理 复杂参数:
Map
Model(map、model里面的数据会被放在request的请求域 request.setAttribute)
Errors/BindingResult
RedirectAttributes( 重定向携带数据)
ServletResponse(response)
SessionStatus
UriComponentsBuilder
ServletUriComponentsBuilder
用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @GetMapping("/params") public String testParam (Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response) { map.put("hello" ,"world666" ); model.addAttribute("world" ,"hello666" ); request.setAttribute("message" ,"HelloWorld" ); Cookie cookie = new Cookie ("c1" ,"v1" ); response.addCookie(cookie); return "forward:/success" ; }@ResponseBody @GetMapping("/success") public Map success (@RequestAttribute(value = "msg",required = false) String msg, @RequestAttribute(value = "code",required = false) Integer code, HttpServletRequest request) { Object msg1 = request.getAttribute("msg" ); Map<String,Object> map = new HashMap <>(); Object hello = request.getAttribute("hello" ); Object world = request.getAttribute("world" ); Object message = request.getAttribute("message" ); map.put("reqMethod_msg" ,msg1); map.put("annotation_msg" ,msg); map.put("hello" ,hello); map.put("world" ,world); map.put("message" ,message); return map; }
上面三位都是可以给request域中放数据,用request.getAttribute()
获取
接下来我们看看,Map<String,Object> map
与Model model
用什么参数处理器。
Map<String,Object> map
参数用MapMethodProcessor
处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MapMethodProcessor implements HandlerMethodArgumentResolver , HandlerMethodReturnValueHandler { @Override public boolean supportsParameter (MethodParameter parameter) { return (Map.class.isAssignableFrom(parameter.getParameterType()) && parameter.getParameterAnnotations().length == 0 ); } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null , "ModelAndViewContainer is required for model exposure" ); return mavContainer.getModel(); } ... }
mavContainer.getModel()
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ModelAndViewContainer { ... private final ModelMap defaultModel = new BindingAwareModelMap (); @Nullable private ModelMap redirectModel; ... public ModelMap getModel () { if (useDefaultModel()) { return this .defaultModel; } else { if (this .redirectModel == null ) { this .redirectModel = new ModelMap (); } return this .redirectModel; } } private boolean useDefaultModel () { return (!this .redirectModelScenario || (this .redirectModel == null && !this .ignoreDefaultModelOnRedirect)); } ... }
Model model
用ModelMethodProcessor
处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ModelMethodProcessor implements HandlerMethodArgumentResolver , HandlerMethodReturnValueHandler { @Override public boolean supportsParameter (MethodParameter parameter) { return Model.class.isAssignableFrom(parameter.getParameterType()); } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null , "ModelAndViewContainer is required for model exposure" ); return mavContainer.getModel(); } ... }
return mavContainer.getModel();
这跟MapMethodProcessor
的一致
Model
也是另一种意义的Map
。
接下来看看 Map<String,Object> map
与Model model
值是如何做到用request.getAttribute()
获取的。
众所周知,所有的数据都放在 ModelAndView 包含要去的页面地址View,还包含Model数据。
先看ModelAndView 接下来是如何处理的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class DispatcherServlet extends FrameworkServlet { ... protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { ... try { ModelAndView mv = null ; ... mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } ... } private void processDispatchResult (HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { ... if (mv != null && !mv.wasCleared()) { render(mv, request, response); ... } ... } protected void render (ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { ... View view; String viewName = mv.getViewName(); if (viewName != null ) { view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null ) { throw new ServletException ("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + 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 '" + getServletName() + "'" ); } } view.render(mv.getModelInternal(), request, response); ... } }
在Debug模式下,view
属为InternalResourceView
类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class InternalResourceView extends AbstractUrlBasedView { @Override public void render (@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ... Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } @Override protected void renderMergedOutputModel ( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { exposeModelAsRequestAttributes(model, request); exposeHelpers(request); String dispatcherPath = prepareForRendering(request, response); RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ... } protected void exposeModelAsRequestAttributes (Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null ) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); } }
exposeModelAsRequestAttributes
方法看出,Map<String,Object> map
,Model model
这两种类型数据可以给request域中放数据,用request.getAttribute()
获取。
35、请求处理-【源码分析】-自定义参数绑定原理 1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class ParameterTestController { @PostMapping("/saveuser") public Person saveuser (Person person) { return person; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; }@Data public class Pet { private String name; private String age; }
封装过程用到ServletModelAttributeMethodProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor { @Override public boolean supportsParameter (MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this .annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } @Override @Nullable public final Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ... String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null ) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null ; BindingResult bindingResult = null ; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { ... } } if (bindingResult == null ) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null ) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException (binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; } }
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
36、请求处理-【源码分析】-自定义Converter原理 未来我们可以给WebDataBinder里面放自己的Converter;
下面演示将字符串“啊猫,3”
转换成Pet
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Pet>() { @Override public Pet convert (String source) { if (!StringUtils.isEmpty(source)){ Pet pet = new Pet (); String[] split = source.split("," ); pet.setName(split[0 ]); pet.setAge(Integer.parseInt(split[1 ])); return pet; } return null ; } }); } }; }
37、响应处理-【源码分析】-ReturnValueHandler原理
假设给前端自动返回json数据,需要引入相关的依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-json</artifactId > <version > 2.3.4.RELEASE</version > <scope > compile</scope > </dependency >
控制层代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Controller public class ResponseTestController { @ResponseBody @GetMapping(value = "/test/person") public Person getPerson () { Person person = new Person (); person.setAge(28 ); person.setBirth(new Date ()); person.setUserName("zhangsan" ); return person; } }
32、请求处理-【源码分析】-各种类型参数解析原理 - 返回值处理器 有讨论ReturnValueHandler 。现在直接看看重点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... @Nullable protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { ... ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } ... invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null ; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... try { this .returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { ... } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler { ... @Override public void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null ) { throw new IllegalArgumentException ("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } @Nullable private HandlerMethodReturnValueHandler selectHandler (@Nullable Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this .returnValueHandlers) { if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue ; } if (handler.supportsReturnType(returnType)) { return handler; } } return null ; }
@ResponseBody
注解,即RequestResponseBodyMethodProcessor
,它实现HandlerMethodReturnValueHandler
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { ... @Override public void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true ); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } }
38、响应处理-【源码分析】-HTTPMessageConverter原理 返回值处理器ReturnValueHandler
原理:
返回值处理器判断是否支持这种类型返回值 supportsReturnType
返回值处理器调用 handleReturnValue
进行处理 RequestResponseBodyMethodProcessor
可以处理返回值标了@ResponseBody
注解的。利用 MessageConverters
进行处理 将数据写为json内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型) 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据, SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter
,看谁能处理?得到MappingJackson2HttpMessageConverter
可以将对象写为json 利用MappingJackson2HttpMessageConverter
将对象转为json再写出去。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { ... protected <T> void writeWithMessageConverters (@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object body; Class<?> valueType; Type targetType; if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; targetType = String.class; } else { body = value; valueType = getReturnValueType(body, returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass()); } ... MediaType selectedMediaType = null ; MediaType contentType = outputMessage.getHeaders().getContentType(); boolean isContentTypePreset = contentType != null && contentType.isConcrete(); if (isContentTypePreset) { if (logger.isDebugEnabled()) { logger.debug("Found 'Content-Type:" + contentType + "' in response" ); } selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleTypes.isEmpty()) { throw new HttpMessageNotWritableException ( "No converter found for return value of type: " + valueType); } List<MediaType> mediaTypesToUse = new ArrayList <>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (mediaTypesToUse.isEmpty()) { if (body != null ) { throw new HttpMediaTypeNotAcceptableException (producibleTypes); } if (logger.isDebugEnabled()) { logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes); } return ; } MediaType.sortBySpecificityAndQuality(mediaTypesToUse); for (MediaType mediaType : mediaTypesToUse) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break ; } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break ; } } if (logger.isDebugEnabled()) { logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes); } } if (selectedMediaType != null ) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> converter : this .messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null ); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter <?>>) converter.getClass(), inputMessage, outputMessage); if (body != null ) { Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]" ); addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null ) { genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body" ); } } return ; } } } ... }
HTTPMessageConverter
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public interface HttpMessageConverter <T> { boolean canRead (Class<?> clazz, @Nullable MediaType mediaType) ; boolean canWrite (Class<?> clazz, @Nullable MediaType mediaType) ; List<MediaType> getSupportedMediaTypes () ; T read (Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write (T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
HttpMessageConverter
: 看是否支持将 此 Class
类型的对象,转为MediaType
类型的数据。
例子:Person
对象转为JSON,或者 JSON转为Person
,这将用到MappingJackson2HttpMessageConverter
1 2 3 public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { ... }
关于MappingJackson2HttpMessageConverter
的实例化请看下节。
关于HttpMessageConverters的初始化 DispatcherServlet
的初始化时会调用initHandlerAdapters(ApplicationContext context)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DispatcherServlet extends FrameworkServlet { ... private void initHandlerAdapters (ApplicationContext context) { this .handlerAdapters = null ; if (this .detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true , false ); if (!matchingBeans.isEmpty()) { this .handlerAdapters = new ArrayList <>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this .handlerAdapters); } } ...
上述代码会加载ApplicationContext
的所有HandlerAdapter
,用来处理@RequestMapping
的RequestMappingHandlerAdapter
实现HandlerAdapter
接口,RequestMappingHandlerAdapter
也被实例化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... private List<HttpMessageConverter<?>> messageConverters; ... public RequestMappingHandlerAdapter () { this .messageConverters = new ArrayList <>(4 ); this .messageConverters.add(new ByteArrayHttpMessageConverter ()); this .messageConverters.add(new StringHttpMessageConverter ()); if (!shouldIgnoreXml) { try { this .messageConverters.add(new SourceHttpMessageConverter <>()); } catch (Error err) { } } this .messageConverters.add(new AllEncompassingFormHttpMessageConverter ()); }
在构造器中看到一堆 HttpMessageConverter
。接着,重点查看AllEncompassingFormHttpMessageConverter
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter { private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore" ); private static final boolean jaxb2Present; private static final boolean jackson2Present; private static final boolean jackson2XmlPresent; private static final boolean jackson2SmilePresent; private static final boolean gsonPresent; private static final boolean jsonbPresent; private static final boolean kotlinSerializationJsonPresent; static { ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader(); jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder" , classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper" , classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator" , classLoader); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper" , classLoader); jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory" , classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson" , classLoader); jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb" , classLoader); kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json" , classLoader); } public AllEncompassingFormHttpMessageConverter () { if (!shouldIgnoreXml) { try { addPartConverter(new SourceHttpMessageConverter <>()); } catch (Error err) { } if (jaxb2Present && !jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter ()); } } if (jackson2Present) { addPartConverter(new MappingJackson2HttpMessageConverter ()); } else if (gsonPresent) { addPartConverter(new GsonHttpMessageConverter ()); } else if (jsonbPresent) { addPartConverter(new JsonbHttpMessageConverter ()); } else if (kotlinSerializationJsonPresent) { addPartConverter(new KotlinSerializationJsonHttpMessageConverter ()); } if (jackson2XmlPresent && !shouldIgnoreXml) { addPartConverter(new MappingJackson2XmlHttpMessageConverter ()); } if (jackson2SmilePresent) { addPartConverter(new MappingJackson2SmileHttpMessageConverter ()); } } }public class FormHttpMessageConverter implements HttpMessageConverter <MultiValueMap<String, ?>> { ... private List<HttpMessageConverter<?>> partConverters = new ArrayList <>(); ... public void addPartConverter (HttpMessageConverter<?> partConverter) { Assert.notNull(partConverter, "'partConverter' must not be null" ); this .partConverters.add(partConverter); } ... }
在AllEncompassingFormHttpMessageConverter
类构造器看到MappingJackson2HttpMessageConverter
类的实例化,AllEncompassingFormHttpMessageConverter
包含 MappingJackson2HttpMessageConverter
。
ReturnValueHandler
是怎么与MappingJackson2HttpMessageConverter
关联起来?请看下节。
ReturnValueHandler与MappingJackson2HttpMessageConverter关联 再次回顾RequestMappingHandlerAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware , InitializingBean { ... @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; @Override @Nullable public final ModelAndView handle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } @Override protected ModelAndView handleInternal (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; ... mav = invokeHandlerMethod(request, response, handlerMethod); ... return mav; } @Nullable protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } ... invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null ; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } } @Override public void afterPropertiesSet () { ... if (this .returnValueHandlers == null ) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this .returnValueHandlers = new HandlerMethodReturnValueHandlerComposite ().addHandlers(handlers); } } private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers () { List<HandlerMethodReturnValueHandler> handlers = new ArrayList <>(20 ); ... handlers.add(new RequestResponseBodyMethodProcessor (getMessageConverters(), this .contentNegotiationManager, this .requestResponseBodyAdvice)); ... return handlers; } public List<HttpMessageConverter<?>> getMessageConverters() { return this .messageConverters; } public RequestMappingHandlerAdapter () { this .messageConverters = new ArrayList <>(4 ); this .messageConverters.add(new ByteArrayHttpMessageConverter ()); this .messageConverters.add(new StringHttpMessageConverter ()); if (!shouldIgnoreXml) { try { this .messageConverters.add(new SourceHttpMessageConverter <>()); } catch (Error err) { } } this .messageConverters.add(new AllEncompassingFormHttpMessageConverter ()); } ... }
应用中WebMvcAutoConfiguration
(底层是WebMvcConfigurationSupport
实现)传入更多messageConverters
,其中就包含MappingJackson2HttpMessageConverter
。
39、响应处理-【源码分析】-内容协商原理 根据客户端接收能力不同,返回不同媒体类型的数据。
引入XML依赖:
1 2 3 4 <dependency > <groupId > com.fasterxml.jackson.dataformat</groupId > <artifactId > jackson-dataformat-xml</artifactId > </dependency >
可用Postman软件分别测试返回json和xml:只需要改变请求头中Accept字段(application/json、application/xml)。
Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型。
内容协商原理 :
判断当前响应头中是否已经有确定的媒体类型MediaType
。 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)contentNegotiationManager
内容协商管理器 默认使用基于请求头的策略HeaderContentNegotiationStrategy
确定客户端可以接收的内容类型 遍历循环所有当前系统的 MessageConverter
,看谁支持操作这个对象(Person) 找到支持操作Person的converter,把converter支持的媒体类型统计出来。 客户端需要application/xml,服务端有10种MediaType。 进行内容协商的最佳匹配媒体类型 用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { ... protected <T> void writeWithMessageConverters (@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object body; Class<?> valueType; Type targetType; if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; targetType = String.class; } else { body = value; valueType = getReturnValueType(body, returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass()); } ... MediaType selectedMediaType = null ; MediaType contentType = outputMessage.getHeaders().getContentType(); boolean isContentTypePreset = contentType != null && contentType.isConcrete(); if (isContentTypePreset) { if (logger.isDebugEnabled()) { logger.debug("Found 'Content-Type:" + contentType + "' in response" ); } selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleTypes.isEmpty()) { throw new HttpMessageNotWritableException ( "No converter found for return value of type: " + valueType); } List<MediaType> mediaTypesToUse = new ArrayList <>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (mediaTypesToUse.isEmpty()) { if (body != null ) { throw new HttpMediaTypeNotAcceptableException (producibleTypes); } if (logger.isDebugEnabled()) { logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes); } return ; } MediaType.sortBySpecificityAndQuality(mediaTypesToUse); for (MediaType mediaType : mediaTypesToUse) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break ; } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break ; } } if (logger.isDebugEnabled()) { logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes); } } if (selectedMediaType != null ) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> converter : this .messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null ); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter <?>>) converter.getClass(), inputMessage, outputMessage); if (body != null ) { Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]" ); addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null ) { genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body" ); } } return ; } } } ... }
40、响应处理-【源码分析】-基于请求参数的内容协商原理 上一节内容协商原理的第二步:
获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)
contentNegotiationManager
内容协商管理器 默认使用基于请求头的策略HeaderContentNegotiationStrategy
确定客户端可以接收的内容类型1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { ... protected <T> void writeWithMessageConverters (@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object body; Class<?> valueType; Type targetType; ... MediaType selectedMediaType = null ; MediaType contentType = outputMessage.getHeaders().getContentType(); boolean isContentTypePreset = contentType != null && contentType.isConcrete(); if (isContentTypePreset) { if (logger.isDebugEnabled()) { logger.debug("Found 'Content-Type:" + contentType + "' in response" ); } selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); ... } private List<MediaType> getAcceptableMediaTypes (HttpServletRequest request) throws HttpMediaTypeNotAcceptableException { return this .contentNegotiationManager.resolveMediaTypes(new ServletWebRequest (request)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ContentNegotiationManager implements ContentNegotiationStrategy , MediaTypeFileExtensionResolver { ... public ContentNegotiationManager () { this (new HeaderContentNegotiationStrategy ()); } @Override public List<MediaType> resolveMediaTypes (NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { for (ContentNegotiationStrategy strategy : this .strategies) { List<MediaType> mediaTypes = strategy.resolveMediaTypes(request); if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) { continue ; } return mediaTypes; } return MEDIA_TYPE_ALL_LIST; } ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy { @Override public List<MediaType> resolveMediaTypes (NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT); if (headerValueArray == null ) { return MEDIA_TYPE_ALL_LIST; } List<String> headerValues = Arrays.asList(headerValueArray); try { List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues); MediaType.sortBySpecificityAndQuality(mediaTypes); return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST; } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotAcceptableException ( "Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage()); } } }
开启浏览器参数方式内容协商功能 为了方便内容协商,开启基于请求参数的内容协商功能。
1 2 3 4 spring: mvc: contentnegotiation: favor-parameter: true
内容协商管理器,就会多了一个ParameterContentNegotiationStrategy
(由Spring容器注入)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { private String parameterName = "format" ; public ParameterContentNegotiationStrategy (Map<String, MediaType> mediaTypes) { super (mediaTypes); } public void setParameterName (String parameterName) { Assert.notNull(parameterName, "'parameterName' is required" ); this .parameterName = parameterName; } public String getParameterName () { return this .parameterName; } @Override @Nullable protected String getMediaTypeKey (NativeWebRequest request) { return request.getParameter(getParameterName()); } @Override public List<MediaType> resolveMediaTypes (NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)); } public List<MediaType> resolveMediaTypeKey (NativeWebRequest webRequest, @Nullable String key) throws HttpMediaTypeNotAcceptableException { if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); if (mediaType != null ) { handleMatch(key, mediaType); return Collections.singletonList(mediaType); } mediaType = handleNoMatch(webRequest, key); if (mediaType != null ) { addMapping(key, mediaType); return Collections.singletonList(mediaType); } } return MEDIA_TYPE_ALL_LIST; } }
然后,浏览器地址输入带format参数的URL:
1 2 3 http://localhost:8080/test/person?format=json 或 http://localhost:8080/test/person?format=xml
这样,后端会根据参数format的值,返回对应json或xml格式的数据。
41、响应处理-【源码分析】-自定义MessageConverter 实现多协议数据兼容。json、xml、x-guigu (这个是自创的)
@ResponseBody
响应数据出去 调用 RequestResponseBodyMethodProcessor
处理
Processor 处理方法返回值。通过 MessageConverter
处理
所有 MessageConverter
合起来可以支持各种媒体类型数据的操作(读、写)
内容协商找到最终的 messageConverter
SpringMVC的什么功能,一个入口给容器中添加一个 WebMvcConfigurer
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new GuiguMessageConverter ()); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class GuiguMessageConverter implements HttpMessageConverter <Person> { @Override public boolean canRead (Class<?> clazz, MediaType mediaType) { return false ; } @Override public boolean canWrite (Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); } @Override public List<MediaType> getSupportedMediaTypes () { return MediaType.parseMediaTypes("application/x-guigu" ); } @Override public Person read (Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null ; } @Override public void write (Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String data = person.getUserName()+";" +person.getAge()+";" +person.getBirth(); OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.util.Date;@Controller public class ResponseTestController { @ResponseBody @GetMapping(value = "/test/person") public Person getPerson () { Person person = new Person (); person.setAge(28 ); person.setBirth(new Date ()); person.setUserName("zhangsan" ); return person; } }
用Postman发送/test/person
(请求头Accept:application/x-guigu
),将返回自定义协议数据的写出。
42、响应处理-【源码分析】-浏览器与PostMan内容协商完全适配 假设你想基于自定义请求参数的自定义内容协商功能。
换句话,在地址栏输入http://localhost:8080/test/person?format=gg
返回数据,跟http://localhost:8080/test/person
且请求头参数Accept:application/x-guigu
的返回自定义协议数据的一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configureContentNegotiation (ContentNegotiationConfigurer configurer) { Map<String, MediaType> mediaTypes = new HashMap <>(); mediaTypes.put("json" ,MediaType.APPLICATION_JSON); mediaTypes.put("xml" ,MediaType.APPLICATION_XML); mediaTypes.put("gg" ,MediaType.parseMediaType("application/x-guigu" )); ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy (mediaTypes); HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy (); configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy)); } } } ... }
日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。