五、web开发
1、SpringMVC自动配置概览
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
2、简单功能分析
2.1、静态资源访问
静态资源目录
只要静态资源放在类路径下: called
/static
/public
/resources
/META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
请求进来,先去找Controller看能不能处理。Controller不能处理的所有请求都交给静态资源处理器 。
可以改变默认的静态资源路径,/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 + 静态资源名 = 静态资源文件夹下找
例如要访问一个 test.jpg 的静态资源 ,
原本是:
更改了前缀之后就是:
localhost:8080/res/test.jpg
也可以通过yml配置文件手动设置静态资源目录,
修改之后的目录, 如果想要访问静态资源文件, 那么必须要把文件放在设置的目录下( /haha)
1 2 3 4 5 6 7 spring: mvc: static-path-pattern: /myStatic/** resources: static-locations: classpath: /haha
静态资源路径也可以定义多个
使用数组的形式设置 static-locations即可
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 后面地址要按照依赖里面的包路径。
2.2、欢迎页支持
官方文档
静态资源路径下 index.html。
可以配置静态资源路径
但是不可以配置静态资源的访问前缀 (可能算是springboot的一个bug )。否则导致 index.html不能被默认访问
注意下面的图片的访问路径
1 2 3 4 5 spring: # mvc: # static-path-pattern: /res/** 这个会导致welcome page功能失效 resources: static-locations: [classpath:/haha/]
或者是直接吧静态资源的访问路径设置成默认的/**
失效原因在于源码的设定
WelcomePageHandlerMapping的构造方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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)) { //调用Controller /index logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
2.3、自定义Favicon
指网页标签上的小图标。
favicon.ico 放在静态资源目录下即可。
1 2 3 spring: # mvc: # static-path-pattern: /res/** 这个会导致 Favicon 功能失效
功能失效在源码部分有原因
2.4、静态资源配置原理
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 { ... }
配置类只有一个有参构造器
有参构造器中所有参数的值都会从容器中获取
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的beanFactory
HttpMessageConverters 找到所有的HttpMessageConverters
ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
DispatcherServletPath
ServletRegistrationBean 给应用注册Servlet、Filter…
资源处理的默认规则
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 ... 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 #禁用所有静态资源规则
静态资源规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 @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/" }; /** * Locations of static resources. Defaults to classpath:[/META-INF/resources/, * /resources/, /static/, /public/]. */ private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; ... }
欢迎页的处理规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... 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 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)) { //调用Controller /index logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
这构造方法内的代码也解释了web场景-welcome与favicon功能 中配置static-path-pattern了,welcome页面和小图标失效的问题。
3、请求参数处理
1.请求映射
@xxxMapping;
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
Rest风格 支持(使用HTTP 请求方式动词来表示对资源的操作)
以前:
/getUser 获取用户
/deleteUser 删除用户
/editUser 修改用户
/saveUser保存用户
现在: /user
GET-获取用户
DELETE-删除用户
PUT-修改用户
POST-保存用户
核心Filter;HiddenHttpMethodFilter
用法
开启页面表单的Rest功能
页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
编写请求映射
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 spring: mvc: hiddenmethod: filter: enabled: true #开启页面表单的Rest功能 (选择性开启) <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> @GetMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.GET) public String getUser(){ return "GET-张三"; } @PostMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.POST) public String saveUser(){ return "POST-张三"; } @PutMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser(){ return "PUT-张三"; } @DeleteMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(){ return "DELETE-张三"; }
Rest原理(表单提交要使用REST的时候)
表单提交会带上\_method=PUT
请求过来被
拦截
请求是否正常,并且是POST
获取到\_method的值。
兼容以下请求;PUT . DELETE . PATCH
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
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 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())); /** Default method parameter: {@code _method}. */ public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = DEFAULT_METHOD_PARAM; /** * Set the parameter name to look for HTTP methods. * @see #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); } /** * Simple {@link HttpServletRequest} wrapper that returns the supplied method for * {@link HttpServletRequest#getMethod()}. */ 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; } } }
PostMan
Rest使用客户端工具。
如PostMan 可直接发送put、delete等方式请求。
官网: Postman - Sign In (getpostman.com)
2.请求处理-[源码分析]-怎么改变默认的_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{ //自定义filter @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>
3.[源码分析]-请求映射原理
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); // 找到当前请求使用哪个Handler(Controller的方法)处理 mappedHandler = getHandler(processedRequest); //HandlerMapping:处理器映射。/xxx->>xxxx ... }
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中:
因此会在启动的时候, 判断是否有重复的RequestMapping , 如果有重复的, springboot就会启动失败
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
如果有就找到这个请求对应的handler
如果没有就是下一个 HandlerMapping
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 。自定义 HandlerMapping
IDEA快捷键:
Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
Ctrl + H : 以树形方式展现类层次结构图。
4.常用参数注解使用
注解:
使用用例:
发送请求
1 2 3 4 5 6 7 8 9 10 11 测试基本注解: <ul> <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a> <li>@PathVariable(路径变量)</li> <li>@RequestHeader(获取请求头)</li> <li>@RequestParam(获取请求参数)</li> <li>@CookieValue(获取cookie值)</li> <li>@RequestBody(获取请求体[POST])</li> <li>@RequestAttribute(获取request域属性)</li> <li>@MatrixVariable(矩阵变量)</li> </ul>
controller
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 { // car/2/owner/zhangsan @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("id",id); // map.put("name",name); // map.put("pv",pv); // map.put("userAgent",userAgent); // map.put("headers",header); 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; } }
5.@RequestAttribute
@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"; //转发到 /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"; } ///<-----------------主角@RequestAttribute在这个方法 @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; } }
6.@MatrixVariable与UrlPathHelper
矩阵变量的含义
根据 URI 规范 RFC 3986 中 URL 的定义,路径片段中可以包含键值对。规范中没有对应的术语…在 Spring MVC 它被成为矩阵变量.
同一个变量, 对应好几个值
语法: 请求路径:/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); } }
创建返回WebMvcConfigurerBean:
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 { ///cars/sell;low=34;brand=byd,audi,yd @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; } // /boss/1;age=20/2;age=10 @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; } }
7.各种类型参数解析原理
这要从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); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. 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 { /** Detect all HandlerAdapters or just expect "handlerAdapter" bean?. */ private boolean detectAllHandlerAdapters = true; ... private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. 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; ... // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ... //本节重点 // Actually invoke the handler. 真正执行目标方法 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 { ... //AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter 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; //handleInternal的核心 mav = invokeHandlerMethod(request, response, handlerMethod);//解释看下节 //... return mav; } }
参数解析器
确定将要执行的目标方法的每一个参数的值是什么;
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) {//初始化argumentResolvers List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } ... } //初始化了一堆的实现HandlerMethodArgumentResolver接口的 private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30); // Annotation-based argument resolution 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()); // Type-based argument resolution 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()); } // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all 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 public interface HandlerMethodArgumentResolver { //当前解析器是否支持解析这种参数 boolean supportsParameter(MethodParameter parameter); @Nullable//如果支持,就调用 resolveArgument Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
返回值处理器
ValueHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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); } } //初始化了一堆的实现HandlerMethodReturnValueHandler接口的 private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20); // Single-purpose return value types 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)); // Annotation-based return value types handlers.add(new ServletModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all 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模式下,查看程序流程。
8.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) { // Servlet 4.0 PushBuilder not found - not supported for injection 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(); // WebRequest / NativeWebRequest / ServletWebRequest if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { return resolveNativeRequest(webRequest, paramType); } // HttpServletRequest required for all further argument types 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()); } // Should never happen... throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName()); } /** * Inner class to avoid a hard dependency on Servlet API 4.0 at runtime. */ 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"; //转发到 /success请求 } }
9.⭐️[源码分析]-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){ //下面三位都是可以给request域中放数据 , 可以在页面中访问 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");//得出testParam方法赋予的值 world666 Object world = request.getAttribute("world");//得出testParam方法赋予的值 hello666 Object message = request.getAttribute("message");//得出testParam方法赋予的值 HelloWorld map.put("reqMethod_msg",msg1); map.put("annotation_msg",msg); map.put("hello",hello); map.put("world",world); map.put("message",message); return map; }
Map<String,Object> map
Model model
HttpServletRequest request
上面三位都是可以给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; ... // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. 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 { ... // Did the handler return a view to render? 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) { // We need to resolve the view name. 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 { // No need to lookup: the ModelAndView object contains the actual View object. 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//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView 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 { // Expose the model object as request attributes. // 暴露模型作为请求域属性 exposeModelAsRequestAttributes(model, request);//<---重点 // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ... } //该方法在AbstractView,AbstractUrlBasedView继承了AbstractView 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()获取。
10.[源码分析]-自定义参数绑定原理
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 @RestController public class ParameterTestController { @PostMapping("/saveuser") public Person saveuser (Person person) { return person; } } @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 //1、WebMvcConfigurer定制化SpringMVC的功能 @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) { // 啊猫,3 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; } }); } }; }