Spring原理MVC
Spring原理 MVC
1 WEB
1.1 RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
- 处理 @RequestMapping 映射
- 调用控制器方法、并处理方法参数与方法返回值
演示1 - DispatcherServlet 初始化
public class A20 { private static final Logger log = LoggerFactory.getLogger(A20.class); public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); } }
@Configuration @ComponentScan //没有加包范围,默认扫描当前类所在的包,以及子包 public class WebConfig { //内嵌web容器工厂 @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory(){ return new TomcatServletWebServerFactory(); } //创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化 @Bean public DispatcherServlet dispatcherServlet(){ return new DispatcherServlet(); } //注册DispatcherServlet ,Spring MVC 的入口 @Bean public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){ DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");// "/"表示所有请求 registrationBean.setLoadOnStartup(1); //大于1,Tomcat 启动后就初始化 return registrationBean; } }
把值写在配置文件中
@Configuration @ComponentScan //没有加包范围,默认扫描当前类所在的包,以及子包 @PropertySource("classpath:application.properties") //注入WebMvcProperties,ServerProperties,这两个类.这两个类都是配置文件中的属性值绑定 //WebMvcProperties: web.servlet //ServerProperties: server @EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) public class WebConfig { //内嵌web容器工厂 @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties){ return new TomcatServletWebServerFactory(serverProperties.getPort()); } //创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化 @Bean public DispatcherServlet dispatcherServlet(){ return new DispatcherServlet(); } //注册DispatcherServlet ,Spring MVC 的入口 @Bean public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet ,WebMvcProperties webMvcProperties){ DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");// "/"表示所有请求 //获取配置文件的值 int loadOnStartup = webMvcProperties.getServlet().getLoadOnStartup(); registrationBean.setLoadOnStartup(loadOnStartup); //大于1,Tomcat 启动后就初始化 return registrationBean; } }
DispatcherServlet 初始化做了什么
RequestMappingHandlerMapping 作用
如果使用默认DispatcherServlet.properties中的RequestMappingHandlerMapping,只会作为成员变量,并不会注入容器中,所以需要自己写
@Controller public class Controller1 { private static final Logger log = LoggerFactory.getLogger(Controller1.class); @GetMapping("/test1") public ModelAndView test1() throws Exception { log.debug("test1()"); return null; } @PostMapping("/test2") public ModelAndView test2(@RequestParam("name") String name) { log.debug("test2({})", name); return null; } @PutMapping("/test3") public ModelAndView test3(@Token String token) { log.debug("test3({})", token); return null; } @RequestMapping("/test4") // @ResponseBody @Yml public User test4() { log.debug("test4"); return new User("张三", 18); } public static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } } public static void main(String[] args) { String str = new Yaml().dump(new User("张三", 18)); System.out.println(str); } }
public class A20 { private static final Logger log = LoggerFactory.getLogger(A20.class); public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); //作用 解析 @RequestMappering 以及派生注解, 生成路径与控制器方法的映射关系,在初始化时就生成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); //获取映射信息 Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods(); handlerMethods.forEach((k,v)->{ System.out.println(k + "=" + v); }); /** 打印结果: 请求方式 + 请求路径 = 哪个类控制器以及类中对应的方法信息 * {GET [/test1]}=com.feng.a20webdispatcherservlet.Controller1#test1() * {PUT [/test3]}=com.feng.a20webdispatcherservlet.Controller1#test3(String) * {POST [/test2]}=com.feng.a20webdispatcherservlet.Controller1#test2(String) * { [/test4]}=com.feng.a20webdispatcherservlet.Controller1#test4() */ //请求来了,获取控制器的方法 Mock: 模拟请求 //返回处理器执行链对象 HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/test1")); System.out.println(chain); /** * 结果:HandlerExecutionChain with [com.feng.a20webdispatcherservlet.Controller1#test1()] and 0 interceptors * */ } }
RequestMappingHandlerAdapter 作用
调用了Controller中的方法
查看参数解析器和返回值解析器
public class A20 { private static final Logger log = LoggerFactory.getLogger(A20.class); public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); //作用 解析 @RequestMappering 以及派生注解, 生成路径与控制器方法的映射关系,在初始化时就生成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); //获取映射信息 Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods(); handlerMethods.forEach((k,v)->{ System.out.println(k + "=" + v); }); /** 打印结果: 请求方式 + 请求路径 = 哪个类控制器以及类中对应的方法信息 * {GET [/test1]}=com.feng.a20webdispatcherservlet.Controller1#test1() * {PUT [/test3]}=com.feng.a20webdispatcherservlet.Controller1#test3(String) * {POST [/test2]}=com.feng.a20webdispatcherservlet.Controller1#test2(String) * { [/test4]}=com.feng.a20webdispatcherservlet.Controller1#test4() */ //请求来了,获取控制器的方法 Mock: 模拟请求 //返回处理器执行链对象 MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2"); request.setParameter("name", "张三"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); /** * 结果:HandlerExecutionChain with [com.feng.a20webdispatcherservlet.Controller1#test1()] and 0 interceptors * */ System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>"); // HandlerAdapter作用:调用控制器的方法 MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class); handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler()); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 所有参数解析器"); for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) { System.out.println(resolver); } System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 所有返回值解析器"); for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) { System.out.println(handler); } } }
收获💡
- DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
- 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
- RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
- key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
- value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
- 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
- RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
- HandlerMethodArgumentResolver 解析控制器方法参数
- HandlerMethodReturnValueHandler 处理控制器方法返回值
演示2 - 自定义参数与返回值处理器
自定义参数解析器
Token
// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它 // token=令牌 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Token { }
TokenArgumentResolver
public class TokenArgumentResolver implements HandlerMethodArgumentResolver { @Override //是否支持某个参数 (查看controller中的参数是否有token注解,如果有返回true,走下一个解析方法,反之false,不走解析方法) public boolean supportsParameter(MethodParameter parameter) { Token token = parameter.getParameterAnnotation(Token.class); return token!=null; } @Override //解析参数 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //获取请求头中的token,然后赋值给 public ModelAndView test3(@Token String token) return webRequest.getHeader("token"); } }
WebConfig
@Configuration @ComponentScan //没有加包范围,默认扫描当前类所在的包,以及子包 @PropertySource("classpath:application.properties") //注入WebMvcProperties,ServerProperties,这两个类.这两个类都是配置文件中的属性值绑定 //WebMvcProperties: web.servlet //ServerProperties: server @EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) public class WebConfig { //内嵌web容器工厂 @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties){ return new TomcatServletWebServerFactory(serverProperties.getPort()); } //创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化 @Bean public DispatcherServlet dispatcherServlet(){ return new DispatcherServlet(); } //注册DispatcherServlet ,Spring MVC 的入口 @Bean public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet ,WebMvcProperties webMvcProperties){ DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");// "/"表示所有请求 //获取配置文件的值 int loadOnStartup = webMvcProperties.getServlet().getLoadOnStartup(); registrationBean.setLoadOnStartup(loadOnStartup); //大于1,Tomcat 启动后就初始化 return registrationBean; } // 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰 // ⬅️1. 加入RequestMappingHandlerMapping @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping(){ return new RequestMappingHandlerMapping(); } // ⬅️2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter @Bean public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){ //将自定义的TokenArgumentResolver 加入到RequestMappingHandlerAdapter中 TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter(); handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver)); return handlerAdapter; } }
模拟请求
自定义返回值处理器
Yml
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Yml { }
YmlReturnValueHandler
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { //获取所在方法上的注解 Yml yml = returnType.getMethodAnnotation(Yml.class); return yml != null; } @Override // 返回值 public void handleReturnValue(Object returnValue , MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { //调用第三方方法,将返回直接转成 yaml 字符串 String str = new Yaml().dump(returnValue); //将 yaml 字符串写入响应 HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); response.setContentType("text/plain;charset=utf-8"); response.getWriter().write(str); //设置请求已经处理完毕(让springmvc不用后续再进行类似视图解析相关操作) mavContainer.setRequestHandled(true); } }
WebConfig
// ⬅️2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter @Bean public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){ //将自定义的TokenArgumentResolver 加入到RequestMappingHandlerAdapter中 TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter(); handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver)); handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler)); return handlerAdapter; }
收获💡
- 体会参数解析器的作用
- 体会返回值处理器的作用
1.2 参数解析器
演示 - 常见参数解析器
WebConfig
@Configuration public class WebConfig { }
A21
/* 目标: 解析控制器方法的参数值 常见的参数处理器如下: org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908 org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5 org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808 org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93 org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956 org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14 org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8 org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407 org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202 org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76 org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781 org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116 org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190 org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216 org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31 org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4 org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33 org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9 org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975 org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3 */ public class A21 { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //提供beanFactory,用于RequestParamMethodArgumentResolver 通过 ${} 解析数据 DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); // 准备测试 Request HttpServletRequest request = mockRequest(); // 要点1. 控制器方法被封装为 HandlerMethod HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class)); // 要点2. 准备对象绑定与类型转换 (传递数据过程中,都是字符串,需要转成其他类型) ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); // 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果 ModelAndViewContainer container = new ModelAndViewContainer(); // 要点4. 解析每个参数值 for (MethodParameter parameter : handlerMethod.getMethodParameters()) { // 多个解析器组合 Composite: 组合 HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); composite.addResolvers( //false表示必须有 @RequestParam new RequestParamMethodArgumentResolver(beanFactory, false), new PathVariableMethodArgumentResolver(), //解析Header new RequestHeaderMethodArgumentResolver(beanFactory), new ServletCookieValueMethodArgumentResolver(beanFactory), //解析 @Value new ExpressionValueMethodArgumentResolver(beanFactory), new ServletRequestMethodArgumentResolver(), //false 表示必须有 @ModelAttribute 注解,true表示可以省略注解 new ServletModelAttributeMethodProcessor(false), //解析 @RequestBody //参数是消息转换器,处理json数据 new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())), // 处理省略注解 ,不能和RequestResponseBodyMethodProcessor调整顺序,避免@RequestBody对应参数被 省略注解解析器解析 new ServletModelAttributeMethodProcessor(true), new RequestParamMethodArgumentResolver(beanFactory, true) //省略@RequestParam注解 ); String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining()); String str = annotations.length() > 0 ? " @" + annotations + " " : " "; parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); /** * composite.supportsParameter(parameter) * 会从组合的解析器中 挨个找出能够解析参数的解析器 */ if (composite.supportsParameter(parameter)) { //支持此参数 (v 代表从请求中获取的结果) Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory); //System.out.println(v.getClass()); System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " -> " + v); System.out.println("模型数据为:" + container.getModel()); }else { System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() ); } } /* 学到了什么 a. 每个参数处理器能干啥 1) 看是否支持某种参数 2) 获取参数的值 b. 组合模式在 Spring 中的体现 c. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取 */ } /** * 模拟请求 * @return */ private static HttpServletRequest mockRequest() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("name1", "zhangsan"); request.setParameter("name2", "lisi"); request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8))); Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123"); System.out.println(map); //将map 集合放入到request作用域中 request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map); request.setContentType("application/json"); request.setCookies(new Cookie("token", "123456")); request.setParameter("name", "张三"); request.setParameter("age", "18"); request.setContent(""" { "name":"李四", "age":20 } """.getBytes(StandardCharsets.UTF_8)); return new StandardServletMultipartResolver().resolveMultipart(request); } /** * 控制器方法 */ static class Controller { public void test( @RequestParam("name1") String name1, // name1=张三 String name2, // name2=李四 @RequestParam("age") int age, // age=18 @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据 @RequestParam("file") MultipartFile file, // 上传文件 @PathVariable("id") int id, // /test/124 /test/{id} @RequestHeader("Content-Type") String header, @CookieValue("token") String token, @Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{} HttpServletRequest request, // request, response, session ... @ModelAttribute("abc") User user1, // name=zhang&age=18 User user2, // name=zhang&age=18 @RequestBody User user3 // json ) { } } static class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } }
收获💡
- 初步了解 RequestMappingHandlerAdapter 的调用过程
- 控制器方法被封装为 HandlerMethod
- 准备对象绑定与类型转换
- 准备 ModelAndViewContainer 用来存储中间 Model 结果
- 解析每个参数值
- 解析参数依赖的就是各种参数解析器,它们都有两个重要方法
- supportsParameter 判断是否支持方法参数
- resolveArgument 解析方法参数
- 常见参数的解析
- @RequestParam
- 省略 @RequestParam
- @RequestParam(defaultValue)
- MultipartFile
- @PathVariable
- @RequestHeader
- @CookieValue
- @Value
- HttpServletRequest 等
- @ModelAttribute
- 省略 @ModelAttribute
- @RequestBody
- 组合模式在 Spring 中的体现
- @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
1.3 参数名解析
收获💡
- 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
- 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
- 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
- 接口, 不会包含局部变量表, 无法获得参数名
- 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
1.4 对象绑定与类型转换
底层第一套转换接口与实现
- Printer 把其它类型转为 String
- Parser 把 String 转为其它类型
- Formatter 综合 Printer 与 Parser 功能
- Converter 把类型 S 转为类型 T
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
- FormattingConversionService 利用其它们实现转换
底层第二套转换接口
- PropertyEditor 把 String 与其它类型相互转换
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
高层接口与实现
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能
演示1 - 类型转换与数据绑定
TestSimpleConverter
public class TestSimpleConverter { public static void main(String[] args) { //仅有类型转换的功能 SimpleTypeConverter typeConverter = new SimpleTypeConverter(); Integer number = typeConverter.convertIfNecessary("13", int.class); System.out.println(number); } }
TestBeanWrapper
public class TestBeanWrapper { public static void main(String[] args) { // 利用反射原理,为bean的属性赋值 MyBean target = new MyBean(); BeanWrapperImpl wrapper = new BeanWrapperImpl(target); /** * 在赋值(用的是get/set方法赋值)的过程中,如果发现值和定义的变量类型不一致,会自动进行类型转换 */ wrapper.setPropertyValue("a","10"); wrapper.setPropertyValue("b","hello"); wrapper.setPropertyValue("c","1999/03/04"); System.out.println(target); } static class MyBean{ private int a; private String b; private Date c; public int getA() { return a; } public void setA(int a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public Date getC() { return c; } public void setC(Date c) { this.c = c; } @Override public String toString() { return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}'; } } }
TestFieldAccessor
public class TestFieldAccessor { public static void main(String[] args) { // 利用反射原理,为bean的属性赋值 MyBean target = new MyBean(); DirectFieldAccessor accessor = new DirectFieldAccessor(target); /** * 在赋值的过程中(直接用成员变量方法赋值),如果发现值和定义的变量类型不一致,会自动进行类型转换 */ accessor.setPropertyValue("a","10"); accessor.setPropertyValue("b","hello"); accessor.setPropertyValue("c","1999/03/04"); System.out.println(target); } static class MyBean{ private int a; private String b; private Date c; @Override public String toString() { return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}'; } } }
TestDataBinder
public class TestDataBinder { public static void main(String[] args) { // 执行数据绑定 MyBean target = new MyBean(); DataBinder dataBinder = new DataBinder(target); //默认用的是get/set方法赋值, 加了这句代码,用的是成员变量赋值 dataBinder.initDirectFieldAccess(); MutablePropertyValues pvs = new MutablePropertyValues(); /** * 在赋值(用的是get/set方法赋值)的过程中,如果发现值和定义的变量类型不一致,会自动进行类型转换 */ pvs.add("a","10"); pvs.add("b","hello"); pvs.add("c","1999/03/04"); dataBinder.bind(pvs); System.out.println(target); } static class MyBean{ private int a; private String b; private Date c; /*public int getA() { return a; } public void setA(int a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public Date getC() { return c; } public void setC(Date c) { this.c = c; }*/ @Override public String toString() { return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}'; } } }
web环境
TestServletDataBinder
public class TestServletDataBinder { public static void main(String[] args) { // 执行数据绑定 MyBean target = new MyBean(); ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target); MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("a", "10"); request.setParameter("b", "hello"); request.setParameter("c", "1999/03/04"); dataBinder.bind(new ServletRequestParameterPropertyValues(request)); System.out.println(target); } static class MyBean{ private int a; private String b; private Date c; public int getA() { return a; } public void setA(int a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public Date getC() { return c; } public void setC(Date c) { this.c = c; } @Override public String toString() { return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}'; } } }
TestServletDataBinderFactory
收获💡
基本的类型转换与数据绑定用法
- SimpleTypeConverter
- BeanWrapperImpl
- DirectFieldAccessor
- ServletRequestDataBinder
演示2 - 数据绑定工厂
TestServletDataBinderFactory
public class TestServletDataBinderFactory { public static void main(String[] args) throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("birthday", "1999|01|02");//这种格式不支持,默认绑定失败 request.setParameter("address.name", "成都"); User target = new User(); // "1. 用工厂, 无转换功能" //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); //WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "2. 用 @InitBinder 转换" 本质使用的是PropertyEditorRegistry PropertyEditor(查看源码) // 利用反射,传递一个类对象和一个方法对象,得到一个标注方法信息 InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class)); //传递方法信息给绑定工厂,创建binder时,回调aaa()方法,进行扩展,最后返回的binder对象包含扩展的方法 ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null); WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "3. 用 ConversionService 转换" ConversionService Formatter // "4. 同时加了 @InitBinder 和 ConversionService" // "5. 使用默认 ConversionService 转换" dataBinder.bind(new ServletRequestParameterPropertyValues(request)); System.out.println(target); } static class MyController{ @InitBinder public void aaa(WebDataBinder dataBinder){ //扩展dataBinder的转换器 MyDateFormatter:自定义的转换器,可以进行 1999|01|02 时间格式转换 dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的")); } } public static class User { private Date birthday; private Address address; public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "User{" + "birthday=" + birthday + ", address=" + address + '}'; } } public static class Address { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Address{" + "name='" + name + '\'' + '}'; } } }
自定义的MyDateFormatter
public class MyDateFormatter implements Formatter<Date> { private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class); private final String desc; public MyDateFormatter(String desc) { this.desc = desc; } @Override public String print(Date date, Locale locale) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd"); return sdf.format(date); } @Override public Date parse(String text, Locale locale) throws ParseException { log.debug(">>>>>> 进入了: {}", desc); SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd"); return sdf.parse(text); } }
用 ConversionService 转换" ConversionService Formatter
public class TestServletDataBinderFactory { public static void main(String[] args) throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("birthday", "1999|01|02");//这种格式不支持,默认绑定失败 request.setParameter("address.name", "成都"); User target = new User(); // "1. 用工厂, 无转换功能" //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); //WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "2. 用 @InitBinder 转换" 本质使用的是PropertyEditorRegistry PropertyEditor(查看源码) // 利用反射,传递一个类对象和一个方法对象,得到一个标注方法信息 //InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class)); //传递方法信息给绑定工厂,创建binder时,回调aaa()方法,进行扩展,最后返回的binder对象包含扩展的方法 //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null); // "3. 用 ConversionService 转换" ConversionService Formatter /** * 这是一个Spring提供的服务,用于处理对象与其字符串表示之间的转换。它允许你添加自定义格式化器,以便处理特定类型(如日期、数字等)的转换。 */ FormattingConversionService service = new FormattingConversionService(); /** * 这里向FormattingConversionService中添加了一个自定义格式化器MyDateFormatter。这个格式化器负责将字符串转换为日期对象, * 或将日期对象转换为字符串。在这个例子中,传入的字符串可能用于定义日期格式或描述。 */ service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展的")); /** * 创建一个ConfigurableWebBindingInitializer实例,这个类用于初始化数据绑定的配置。 * 通过setConversionService(service)方法,将之前定义的自定义转换服务与该初始化器关联。 */ ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); initializer.setConversionService(service); /** * 使用ConfigurableWebBindingInitializer创建一个ServletRequestDataBinderFactory。 * 这个工厂负责创建WebDataBinder,用于将请求参数绑定到目标对象。 */ ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer); /** * 这里调用createBinder方法生成一个WebDataBinder实例。参数包括: * * new ServletWebRequest(request): 这是Spring的一个封装,用于处理HTTP请求。 * target: 绑定的目标对象,通常是控制器中的模型对象。 * "user": 绑定的对象属性名称。 */ WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "4. 同时加了 @InitBinder 和 ConversionService" // "5. 使用默认 ConversionService 转换" dataBinder.bind(new ServletRequestParameterPropertyValues(request)); System.out.println(target); }
同时加了 @InitBinder 和 ConversionService"时候,优先使用@InitBinder标注的转换器
使用默认 ConversionService 转换
public class TestServletDataBinderFactory { public static void main(String[] args) throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("birthday", "1999|01|02");//这种格式不支持,默认绑定失败 request.setParameter("address.name", "成都"); User target = new User(); // "1. 用工厂, 无转换功能" //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); //WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "2. 用 @InitBinder 转换" 本质使用的是PropertyEditorRegistry PropertyEditor(查看源码) // 利用反射,传递一个类对象和一个方法对象,得到一个标注方法信息 //InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class)); //传递方法信息给绑定工厂,创建binder时,回调aaa()方法,进行扩展,最后返回的binder对象包含扩展的方法 //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null); // "3. 用 ConversionService 转换" ConversionService Formatter /** * 这是一个Spring提供的服务,用于处理对象与其字符串表示之间的转换。它允许你添加自定义格式化器,以便处理特定类型(如日期、数字等)的转换。 */ //FormattingConversionService service = new FormattingConversionService(); /** * 这里向FormattingConversionService中添加了一个自定义格式化器MyDateFormatter。这个格式化器负责将字符串转换为日期对象, * 或将日期对象转换为字符串。在这个例子中,传入的字符串可能用于定义日期格式或描述。 */ //service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展的")); /** * 创建一个ConfigurableWebBindingInitializer实例,这个类用于初始化数据绑定的配置。 * 通过setConversionService(service)方法,将之前定义的自定义转换服务与该初始化器关联。 */ //ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); //initializer.setConversionService(service); /** * 使用ConfigurableWebBindingInitializer创建一个ServletRequestDataBinderFactory。 * 这个工厂负责创建WebDataBinder,用于将请求参数绑定到目标对象。 */ //ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer); /** * 这里调用createBinder方法生成一个WebDataBinder实例。参数包括: * * new ServletWebRequest(request): 这是Spring的一个封装,用于处理HTTP请求。 * target: 绑定的目标对象,通常是控制器中的模型对象。 * "user": 绑定的对象属性名称。 */ //WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); // "4. 同时加了 @InitBinder 和 ConversionService" // "5. 使用默认 ConversionService 转换" DefaultFormattingConversionService service = new DefaultFormattingConversionService(); ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); initializer.setConversionService(service); ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer); WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user"); dataBinder.bind(new ServletRequestParameterPropertyValues(request)); System.out.println(target); }
收获💡
ServletRequestDataBinderFactory 的用法和扩展点
- 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
- 控制器私有范围
- 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
- 公共范围
- 同时加了 @InitBinder 和 ConversionService 的转换优先级
- 优先采用 @InitBinder 的转换器
- 其次使用 ConversionService 的转换器
- 使用默认转换器
- 特殊处理(例如有参构造)
演示3 - 获取泛型参数
收获💡
- java api 获取泛型参数
- spring api 获取泛型参数
1.5 @ControllerAdvice 之 @InitBinder
演示 - 准备 @InitBinder
准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置
- RequestMappingHandlerAdapter 在图中缩写为 HandlerAdapter
- HandlerMethodArgumentResolverComposite 在图中缩写为 ArgumentResolvers
- HandlerMethodReturnValueHandlerComposite 在图中缩写为 ReturnValueHandlers
WebConfig
@Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice { @InitBinder //@InitBinder加到@ControllerAdvice中作用于所有的控制器中的类型转换 public void binder3(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器")); } } @Controller static class Controller1 { @InitBinder //@InitBinder加到@Controller中作用于当前控制器中的类型转换 public void binder1(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器")); } public void foo() { } } @Controller static class Controller2 { @InitBinder public void binder21(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器")); } @InitBinder public void binder22(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器")); } public void bar() { } } }
public class A24 { private static final Logger log = LoggerFactory.getLogger(A24.class); public static void main(String[] args) throws Exception { /* @InitBinder 的来源有两个 1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录 2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录 */ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //RequestMappingHandlerAdapter:执行控制器方法,执行之前完成对@InitBinder的解析 RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter(); handlerAdapter.setApplicationContext(context); handlerAdapter.afterPropertiesSet(); log.debug("1. 刚开始..."); showBindMethods(handlerAdapter); Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class); getDataBinderFactory.setAccessible(true); log.info("2. 模拟调用 Controller1 的 foo 方法时 ..."); //调用Controller1的foo方法,foo方法上没有@InitBinder,但是Controller1中binder1()有,所以会解析binder1()上的@InitBinder getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo"))); showBindMethods(handlerAdapter); log.info("3. 模拟调用 Controller2 的 bar 方法时 ..."); getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar"))); showBindMethods(handlerAdapter); context.close(); /* 学到了什么 a. Method 对象的获取利用了缓存来进行加速 b. 绑定器工厂的扩展点(advice 之一), 通过 @InitBinder 扩展类型转换器 */ } private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException { Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache"); initBinderAdviceCache.setAccessible(true); Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter); log.info("全局的 @InitBinder 方法 {}", globalMap.values().stream() .flatMap(ms -> ms.stream().map(m -> m.getName())) .collect(Collectors.toList()) ); Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache"); initBinderCache.setAccessible(true); Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter); log.info("控制器的 @InitBinder 方法 {}", controllerMap.entrySet().stream() .flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName())) .collect(Collectors.toList()) ); } }
收获💡
- RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
- RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
- 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
- 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂
1.6 控制器方法执行流程
图1
HandlerMethod 需要
- bean 即是哪个 Controller
- method 即是 Controller 中的哪个方法
ServletInvocableHandlerMethod 需要
- WebDataBinderFactory 负责对象绑定、类型转换
- ParameterNameDiscoverer 负责参数名解析
- HandlerMethodArgumentResolverComposite 负责解析参数
- HandlerMethodReturnValueHandlerComposite 负责处理返回值
图2
图3
WebConfig
@Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice { @ModelAttribute("a") public String aa() { return "aa"; } } @Controller static class Controller1 { @ModelAttribute("b") public String aa() { return "bb"; } @ResponseStatus(HttpStatus.OK) //加了注解,不用把返回值处理器写完整 public ModelAndView foo(@ModelAttribute User user) { System.out.println("foo"); return null; } } static class User { private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } } }
public class A26 { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); MockHttpServletRequest request = new MockHttpServletRequest(); request.addParameter("name", "zhangsan"); /** * 现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起,并完成控制器方法的调用, 如下 * 根据笔记中的图1进行配置: * ServletInvocableHandlerMethod 需要: * 1. WebDataBinderFactory 负责对象绑定、类型转换 * 2. ParameterNameDiscoverer 负责参数名解析 * 3. HandlerMethodArgumentResolverComposite 负责解析参数 * 4. HandlerMethodReturnValueHandlerComposite 负责处理返回值 */ ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class)); ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); handlerMethod.setDataBinderFactory(factory);//WebDataBinderFactory handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); //ParameterNameDiscoverer handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));//常用的参数解析器组合 HandlerMethodArgumentResolverComposite // WebConfig中加了@ResponseStatus(HttpStatus.OK) 不用把返回值处理器写完整 /** * new ServletWebRequest(request): 对原始request进行封装 * ModelAndViewContainer: 过程中的ModelAndView对象放入这个容器中 */ ModelAndViewContainer container = new ModelAndViewContainer(); handlerMethod.invokeAndHandle(new ServletWebRequest(request),container); System.out.println("===容器中模型数据=="); System.out.println(container.getModel()); context.close(); /** * 流程分析: * ServletInvocableHandlerMethod 调用HandlerMethodArgumentResolverComposite参数解析器组合 * 根据WebConfig.Controller1.foo()方法中的参数User,找到适用的ServletModelAttributeMethodProcessor参数解析器, * 借助WebDataBinderFactory把request中设置的参数值,赋给foo(user)方法中的User参数对象, * 并且加入到ModelAndViewContainer容器中(存入名称为模型数据首字母小写:user) */ } /** * 常用的参数解析器组合 * @param context * @return */ public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) { HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); composite.addResolvers( new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false), new PathVariableMethodArgumentResolver(), new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletRequestMethodArgumentResolver(), new ServletModelAttributeMethodProcessor(false), new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())), new ServletModelAttributeMethodProcessor(true), new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true) ); return composite; } }
1.7 @ControllerAdvice 之 @ModelAttribute
演示 - 准备 @ModelAttribute
准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置
public class A26 { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); adapter.setApplicationContext(context); /** * afterPropertiesSet:初始化方法,会找到@ControllerAdvice和@Controller中标注了@ModelAttribute的方法,并记录 */ adapter.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest(); request.addParameter("name", "zhangsan"); /** * 现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起,并完成控制器方法的调用, 如下 * 根据笔记中的图1进行配置: * ServletInvocableHandlerMethod 需要: * 1. WebDataBinderFactory 负责对象绑定、类型转换 * 2. ParameterNameDiscoverer 负责参数名解析 * 3. HandlerMethodArgumentResolverComposite 负责解析参数 * 4. HandlerMethodReturnValueHandlerComposite 负责处理返回值 */ ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class)); ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null); handlerMethod.setDataBinderFactory(factory);//WebDataBinderFactory handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); //ParameterNameDiscoverer handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));//常用的参数解析器组合 HandlerMethodArgumentResolverComposite // WebConfig中加了@ResponseStatus(HttpStatus.OK) 不用把返回值处理器写完整 /** * new ServletWebRequest(request): 对原始request进行封装 * ModelAndViewContainer: 过程中的ModelAndView对象放入这个容器中 */ ModelAndViewContainer container = new ModelAndViewContainer(); //获取模型工厂方法 Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class); getModelFactory.setAccessible(true); ModelFactory invoke = (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory); //初始化模型数据(找到记录由@ModelAttribute注解的方法,反射调用方法,将方法的返回值取名,并放入模型数据中) invoke.initModel(new ServletWebRequest(request),container,handlerMethod); handlerMethod.invokeAndHandle(new ServletWebRequest(request),container); System.out.println("===容器中模型数据=="); System.out.println(container.getModel()); context.close(); /** * 流程分析: * ServletInvocableHandlerMethod 调用HandlerMethodArgumentResolverComposite参数解析器组合 * 根据WebConfig.Controller1.foo()方法中的参数User,找到适用的ServletModelAttributeMethodProcessor参数解析器, * 借助WebDataBinderFactory把request中设置的参数值,赋给foo(user)方法中的User参数对象, * 并且加入到ModelAndViewContainer容器中(存入名称为模型数据首字母小写:user) */ }
WebConfig
@Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice { //指定了名字用指定的,没有指定用返回值的小写作为名字 @ModelAttribute("a") //通过 RequestMappingHandlerAdapter解析,而不是使用参数解析器进行解析 public String aa() { return "aa"; } } @Controller static class Controller1 { @ModelAttribute("b") public String aa() { return "bb"; } @ResponseStatus(HttpStatus.OK) //加了注解,不用把返回值处理器写完整 public ModelAndView foo(@ModelAttribute("u") User user) { System.out.println("foo"); return null; } }
收获💡
- RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
- RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法
- 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析
- 控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂
1.8 返回值处理器
演示 - 常见返回值处理器
ModelAndViewMethodReturnValueHandler结果处理器
WebConfig
@Configuration public class WebConfig { @Bean public FreeMarkerConfigurer freeMarkerConfigurer() { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setDefaultEncoding("utf-8"); configurer.setTemplateLoaderPath("classpath:templates"); //找到resource的模版目录 return configurer; } @Bean // FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration, 这里想办法去掉了 web 环境的约束 public FreeMarkerViewResolver viewResolver(FreeMarkerConfigurer configurer) { FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() { @Override protected AbstractUrlBasedView instantiateView() { FreeMarkerView view = new FreeMarkerView() { @Override protected boolean isContextRequired() { return false; } }; view.setConfiguration(configurer.getConfiguration()); return view; } }; resolver.setContentType("text/html;charset=utf-8"); resolver.setPrefix("/"); resolver.setSuffix(".ftl"); //拼接模版名称:根据上面的/templates目录拼接, /templates/xxx.ftl resolver.setExposeSpringMacroHelpers(false); return resolver; } }
public class A27 { /* 目标: 解析控制器方法的返回值 常见的返回值处理器 org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38 org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bc org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5d org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426c org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673 org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4 org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7 org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41 org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385 org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4 org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396db org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159 org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8 org.springframework.web.method.annotation.MapMethodProcessor@6ff37443 org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@65cc8228 */ private static final Logger log = LoggerFactory.getLogger(A27.class); public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); Method method = Controller.class.getMethod("test1"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); //1.测试返回值类型为 ModelAndView HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse()); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); //调用渲染视图方法 renderView(context,container,webRequest); } } /** * 返回值处理器组合 * @return */ public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() { HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite(); composite.addHandler(new ModelAndViewMethodReturnValueHandler()); composite.addHandler(new ViewNameMethodReturnValueHandler()); composite.addHandler(new ServletModelAttributeMethodProcessor(false)); composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))); composite.addHandler(new HttpHeadersReturnValueHandler()); composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))); composite.addHandler(new ServletModelAttributeMethodProcessor(true)); return composite; } private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container, ServletWebRequest webRequest) throws Exception { log.info(">>>>>> 渲染视图"); FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class); String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest()); log.info("没有获取到视图名, 采用默认视图名: {}", viewName); // 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化 View view = resolver.resolveViewName(viewName, Locale.getDefault()); view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse()); System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8)); } static class Controller { private static final Logger log = LoggerFactory.getLogger(Controller.class); public ModelAndView test1() { log.info("test1()"); ModelAndView mav = new ModelAndView("view1"); mav.addObject("name", "张三"); return mav; } public String test2() { log.info("test2()"); return "view2"; } @ModelAttribute // @RequestMapping("/test3") public User test3() { log.info("test3()"); return new User("李四", 20); } public User test4() { log.info("test4()"); return new User("王五", 30); } public HttpEntity<User> test5() { log.info("test5()"); return new HttpEntity<>(new User("赵六", 40)); } public HttpHeaders test6() { log.info("test6()"); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/html"); return headers; } @ResponseBody //返回的结果会做为响应体 public User test7() { log.info("test7()"); return new User("钱七", 50); } } // 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败 public static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } }
ViewNameMethodReturnValueHandler:把返回值当做视图名解析
private static final Logger log = LoggerFactory.getLogger(A27.class); public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test2(context); } private static void test2(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test2"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse()); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); //调用渲染视图方法 renderView(context,container,webRequest); } }
public String test2() { log.info("test2()"); return "view2"; }
new ServletModelAttributeMethodProcessor(false):带有@ModelAttribute注解
public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test3(context); } private static void test3(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test3"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("/test3"); //模拟@RequestMapping("/test3") //把路径(/test3)存入request域,后续如果没有找到视图名,就把test3当做视图名 UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); ServletWebRequest webRequest = new ServletWebRequest(request,new MockHttpServletResponse()); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); //调用渲染视图方法 renderView(context,container,webRequest); } }
@ModelAttribute //@RequestMapping("/test3") :如果加了这个注解,找不到视图名,就会把路径名当做视图名 public User test3() { log.info("test3()"); return new User("李四", 20); }
new ServletModelAttributeMethodProcessor(true):处理不带@ModelAttribute注解
private static final Logger log = LoggerFactory.getLogger(A27.class); public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test4(context); } private static void test4(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test4"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("/test4"); //模拟@RequestMapping("/test4") //把路径(/test4)存入request域,后续如果没有找到视图名,就把test4当做视图名 UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); ServletWebRequest webRequest = new ServletWebRequest(request,new MockHttpServletResponse()); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); //调用渲染视图方法 renderView(context,container,webRequest); } }
public User test4() { log.info("test4()"); return new User("王五", 30); }
HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))
public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test5(context); } private static void test5(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test5"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ServletWebRequest webRequest = new ServletWebRequest(request, response); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); if(!container.isRequestHandled()){ //调用渲染视图方法 renderView(context,container,webRequest); }else { //通过RequestResponseBodyMethodProcessor(List.of(new // MappingJackson2HttpMessageConverter()) 把返回值对象转换为json字符串,然后写入到response中 System.out.println(new String(response.getContentAsByteArray(), "UTF-8")); } } }
public HttpEntity<User> test5() { log.info("test5()"); return new HttpEntity<>(new User("赵六", 40)); }
new HttpHeadersReturnValueHandler()
public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test6(context); } private static void test6(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test6"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ServletWebRequest webRequest = new ServletWebRequest(request, response); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); if(!container.isRequestHandled()){ //调用渲染视图方法 renderView(context,container,webRequest); }else { for (String name : response.getHeaderNames()) { System.out.println(name + ":" + response.getHeader(name)); } } } }
public HttpHeaders test6() { log.info("test6()"); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/html"); return headers; }
RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()): 返回值放入响应体,并转成json
public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); //1.测试返回值类型为 ModelAndView //2.测试返回值类型为String 时候,把它当做视图名 //3.测试返回值添加了@ModelAttribute注解时,此时需找到默认视图名 //4.测试返回值不加@ModelAttribute注解时且返回非简单类型时,此时需找到默认视图名 //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程 //6.测试返回值类型为 HttpHeaders时,此时不走视图流程 //7.测试返回值添加了 @ResponseBody 注解时,此时不走视图流程 test7(context); } private static void test7(AnnotationConfigApplicationContext context) throws Exception { Method method = Controller.class.getMethod("test7"); Controller controller = new Controller(); Object returnValue = method.invoke(controller);//获取返回值 HandlerMethod handlerMethod = new HandlerMethod(controller, method); ModelAndViewContainer container = new ModelAndViewContainer(); HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler(); //给一个空的请求对象 webRequest MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ServletWebRequest webRequest = new ServletWebRequest(request, response); //检查是否支持此类型的返回值 if (composite.supportsReturnType(handlerMethod.getReturnType())) { //给一个空的请求对象 webRequest composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest); System.out.println(container.getModel()); System.out.println(container.getViewName()); if(!container.isRequestHandled()){ //调用渲染视图方法 renderView(context,container,webRequest); }else { for (String name : response.getHeaderNames()) { System.out.println(name + ":" + response.getHeader(name)); } System.out.println(new String(response.getContentAsByteArray(), "UTF-8")); } } }
@ResponseBody //返回的结果会做为响应体 public User test7() { log.info("test7()"); return new User("钱七", 50); }
收获💡
- 常见的返回值处理器
- ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
- 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
- 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值类型为 ResponseEntity 时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
- 返回值类型为 HttpHeaders 时
- 会设置 ModelAndViewContainer.requestHandled 为 true
- 返回值添加了 @ResponseBody 注解时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
- 组合模式在 Spring 中的体现 + 1
1.9 MessageConverter
演示 - MessageConverter 的作用
public class A28 { public static void main(String[] args) throws Exception { test1(); } public static void test1() throws Exception { MockHttpOutputMessage message = new MockHttpOutputMessage(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {//判断是否支持转换json格式 converter.write(new User("zhangsan", 18), MediaType.APPLICATION_JSON, message);//最后转换的结果放入message中 System.out.println(message.getBodyAsString()); } } public static class User { private String name; private int age; @JsonCreator public User(@JsonProperty("name") String name, @JsonProperty("age") int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } }
public static void main(String[] args) throws Exception { test2(); } public static void test2() throws Exception { MockHttpOutputMessage message = new MockHttpOutputMessage(); MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) { converter.write(new User("lisi", 20), MediaType.APPLICATION_XML, message);//最后转换的结果放入message中 System.out.println(message.getBodyAsString()); } }
public static void main(String[] args) throws Exception { test3(); } private static void test3() throws Exception { MockHttpInputMessage message = new MockHttpInputMessage(""" {"name":"李四","age":20}""".getBytes(StandardCharsets.UTF_8)); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) { Object read = converter.read(User.class, message); System.out.println(read); } }
public static void main(String[] args) throws Exception { test4(); } private static void test4() throws NoSuchMethodException, HttpMediaTypeNotAcceptableException, IOException { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ServletWebRequest webRequest = new ServletWebRequest(request, response); /** * 优先级 * 第1:response.setContentType("application/json"); * 第2:request.addHeader("Accept","application/xml"); * 第3:根据RequestResponseBodyMethodProcessor(List.of 的添加顺序,依次从高到低 */ request.addHeader("Accept","application/xml"); response.setContentType("application/json"); /** * @ResponseBody是由RequestResponseBodyMethodProcessor解析的 */ RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(List.of( new MappingJackson2HttpMessageConverter(), new MappingJackson2XmlHttpMessageConverter() )); processor.handleReturnValue(new User("zhangsan", 18), new MethodParameter(A28.class.getMethod("user"),-1), new ModelAndViewContainer(), webRequest); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); } @ResponseBody //produces 控制响应的内容类型 == response.setContentType("application/json"); @RequestMapping(produces = "application/json") public User user(){ return null; }
收获💡
- MessageConverter 的作用
- @ResponseBody 是返回值处理器解析的
- 但具体转换工作是 MessageConverter 做的
- 如何选择 MediaType
- 首先看 @RequestMapping 上有没有指定
- 其次看 request 的 Accept 头有没有指定
- 最后按 MessageConverter 的顺序, 谁能谁先转换
1.10 @ControllerAdvice 之 ResponseBodyAdvice
演示 - ResponseBodyAdvice 增强
ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置
@JsonInclude(JsonInclude.Include.NON_NULL) public class Result { private int code; private String msg; private Object data; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } @JsonCreator private Result(@JsonProperty("code") int code, @JsonProperty("data") Object data) { this.code = code; this.data = data; } private Result(int code, String msg) { this.code = code; this.msg = msg; } public static Result ok() { return new Result(200, null); } public static Result ok(Object data) { return new Result(200, data); } public static Result error(String msg) { return new Result(500, "服务器内部错误:" + msg); } }
A29
public class A29 { // {"name":"王五","age":18} // {"code":xx,"msg":xx,data: {"name":"王五","age":18}} public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod( context.getBean(WebConfig.MyController.class), WebConfig.MyController.class.getMethod("user") ); handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null)); handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context)); handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context)); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndViewContainer container = new ModelAndViewContainer(); handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), container); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); /* 学到了什么 a. advice 之三, ResponseBodyAdvice 返回响应体前包装 */ } public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) { HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); composite.addResolvers( new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false), new PathVariableMethodArgumentResolver(), new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletRequestMethodArgumentResolver(), new ServletModelAttributeMethodProcessor(false), new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())), new ServletModelAttributeMethodProcessor(true), new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true) ); return composite; } public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) { // 添加 advice List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context); List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType())) .collect(Collectors.toList()); HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite(); composite.addHandler(new ModelAndViewMethodReturnValueHandler()); composite.addHandler(new ViewNameMethodReturnValueHandler()); composite.addHandler(new ServletModelAttributeMethodProcessor(false)); composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))); composite.addHandler(new HttpHeadersReturnValueHandler()); composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()), collect)); composite.addHandler(new ServletModelAttributeMethodProcessor(true)); return composite; } }
WebConfig
@Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice implements ResponseBodyAdvice<Object> { //满足条件才转换 @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { if (returnType.getMethodAnnotation(ResponseBody.class) != null || //这个方法可以找到外层以及向内层查找 AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ){ //returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) {//所在类上是否有注解(只能找到外层) return true; } return false; } // 将 User 或其他类型统一为 Result 类型 /** * Object body: 将返回结果User对象放入 * MethodParameter returnType: 返回值类型,所有方法,方法上的注解信息 */ @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof Result) { return body; } return Result.ok(body); } } /** * @RequestBody + @ResponseBody = @RestController * @RestController 中包含了 @ResponseBody,但是直接使用 * returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) 只能找到外层的,必须使用 * spring提供的方法 */ //@Controller //@ResponseBody 加到类上表示类中所有方法返回值都会加入到响应体中 @RestController public static class MyController { //@ResponseBody public User user() { return new User("王五", 18); } } public static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
收获💡
- ResponseBodyAdvice 返回响应体前包装
1.11 异常解析器
演示 - ExceptionHandlerExceptionResolver
测试json
public class A30 { public static void main(String[] args) throws NoSuchMethodException, UnsupportedEncodingException { ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); resolver.afterPropertiesSet(); //测试json /** * 执行流程: * 根据handlerMethod找到对应的Controller1类,知道异常是在哪个类中出现的。如果进一步找到Controller1类中是否有 * 对应的@ExceptionHandler标注的方法,如果找到把方法对应的参数类型ArithmeticException和实际的异常类型对比,如果能够对应上 * 则表示这个方法可以处理此异常。接着resolver.resolveException会反射调用Map<String, Object> handle(ArithmeticException e)方法 * 调用的时候把传入的参数用参数解析器解析,把返回的结果根据例如@ResponseBody进行解析,得到最后的结果(这个地方用的是@ResponseBody注解, * 同时resolver设置了MappingJackson2HttpMessageConverter,所以最后的结果是json) */ MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo")); Exception exception = new ArithmeticException("被零除"); resolver.resolveException(request, response, handlerMethod, exception); //验证结果 System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); } static class Controller1 { public void foo() { } @ExceptionHandler @ResponseBody public Map<String, Object> handle(ArithmeticException e) { return Map.of("error", e.getMessage()); } }
测试mav
public static void main(String[] args) throws NoSuchMethodException, UnsupportedEncodingException { ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); resolver.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); //测试 mav HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo")); Exception exception = new ArithmeticException("被零除"); ModelAndView mav = resolver.resolveException(request, response, handlerMethod, exception); System.out.println(mav.getModel()); System.out.println(mav.getViewName()); } static class Controller2 { public void foo() { } @ExceptionHandler public ModelAndView handle(ArithmeticException e) { return new ModelAndView("test2", Map.of("error", e.getMessage())); } }
测试嵌套异常
//3.测试嵌套异常 HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo")); Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3"))); resolver.resolveException(request, response, handlerMethod, e); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); static class Controller3 { public void foo() { } @ExceptionHandler @ResponseBody //把所有嵌套的异常依次放到集合中,然后进行匹配 public Map<String, Object> handle(IOException e3) { return Map.of("error", e3.getMessage()); } }
测试异常处理方法参数解析
//4.测试异常处理方法参数解析 HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo")); Exception e = new Exception("e1"); resolver.resolveException(request, response, handlerMethod, e); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); /** * 学到了什么 * a. ExceptionHandlerExceptionResolver 能够重用参数解析器、返回值处理器,实现组件重用 * b. 能够支持嵌套异常 */ static class Controller4 { public void foo() {} @ExceptionHandler @ResponseBody public Map<String, Object> handler(Exception e, HttpServletRequest request) { System.out.println(request); return Map.of("error", e.getMessage()); } }
收获💡
- 它能够重用参数解析器、返回值处理器,实现组件重用
- 它能够支持嵌套异常
1.12 @ControllerAdvice 之 @ExceptionHandler
演示 - 准备 @ExceptionHandler
WebConfig
@Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice { @ExceptionHandler @ResponseBody public Map<String, Object> handle(Exception e) { return Map.of("error", e.getMessage()); } } @Bean public ExceptionHandlerExceptionResolver resolver() { ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); return resolver; } }
public class A31 { public static void main(String[] args) throws NoSuchMethodException { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); /*ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); resolver.afterPropertiesSet();*/ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class); /** * 执行流程: * 优先从Controller5中找到@ExceptionHandler标注的方法,如果没有,再从所有的@ControllerAdvice标注的类中 * 去找,@ExceptionHandler标注的方法 * ExceptionHandlerExceptionResolver寻找@ControllerAdvice标注@ExceptionHandler标注的方法时机:截图 */ HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo")); Exception e = new Exception("e1"); resolver.resolveException(request, response, handlerMethod, e); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); } static class Controller5 { public void foo() { } } }
收获💡
- ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法
- ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法
- 以上两种 @ExceptionHandler 的解析结果都会缓存来避免重复解析
1.13 Tomcat 异常处理
-
我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?
-
在 Spring Boot 中,是这么实现的:
- 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
- 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为
/error
也可以通过${server.error.path}
进行配置 - 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至
/error
这个地址- 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
- Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为
/error
,所以处理异常的职责就又回到了 Spring - 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
- 具体异常信息会由 DefaultErrorAttributes 封装好
- BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
- 如果要的不是 text/html,走 MessageConverter 流程
- 如果需要 text/html,走 mvc 流程,此时又分两种情况
- 配置了 ErrorViewResolver,根据状态码去找 View
- 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView
评价
- 一个错误处理搞得这么复杂,就问恶心不?
如果没有全局异常处理,默认tomcat处理web异常
WebConfig
@Configuration public class WebConfig { @Bean public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); } @Bean public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/"); registrationBean.setLoadOnStartup(1); return registrationBean; } @Bean // @RequestMapping public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new RequestMappingHandlerMapping(); } @Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器 public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter(); handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); return handlerAdapter; } @Controller public static class MyController { @RequestMapping("test") public ModelAndView test() { int i = 1 / 0; return null; } } }
public class A32 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) ->{ System.out.println("映射路径: "+ k + " 方法: "+ v); }); } }
演示1 - 错误页处理
WebConfig
@Configuration public class WebConfig { @Bean public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); } @Bean public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/"); registrationBean.setLoadOnStartup(1); return registrationBean; } @Bean // @RequestMapping public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new RequestMappingHandlerMapping(); } @Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器 public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter(); handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter())); return handlerAdapter; } @Bean //改动tomcat 默认错误地址 public ErrorPageRegistrar errorPageRegistrar(){ return new ErrorPageRegistrar() { @Override public void registerErrorPages(ErrorPageRegistry webServerFactory) { //当tomcat出现500错误时,跳转到/error页面或者是error路径 //出现错误,会使用请求转发 forward 跳转到 error 地址 webServerFactory.addErrorPages(new ErrorPage("/error")); } }; } //因为errorPageRegistrar不会主动增加错误页面 //方法作用:再创建好WebServerFactory对象之后,初始化之前,执行该方法, // 到容器中找到所有的ErrorPageRegistrar,给webServerFactory对象添加所有的ErrorPage @Bean public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor(){ return new ErrorPageRegistrarBeanPostProcessor(); } @Controller public static class MyController { @RequestMapping("test") public ModelAndView test() { int i = 1 / 0; return null; } @RequestMapping("/error") @ResponseBody //以json格式返回 public Map<String,Object> error(HttpServletRequest request) { //tomcat发生异常,会把异常信息放入request作用域中 Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); return Map.of("error", e.getMessage()); } } }
收获💡
- Tomcat 的错误页处理手段
演示2 - BasicErrorController
WebConfig
@Controller public static class MyController { @RequestMapping("test") public ModelAndView test() { int i = 1 / 0; return null; } /*@RequestMapping("/error") @ResponseBody //以json格式返回 public Map<String,Object> error(HttpServletRequest request) { //tomcat发生异常,会把异常信息放入request作用域中 Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); return Map.of("error", e.getMessage()); }*/ } @Bean public BasicErrorController basicErrorController(){ return new BasicErrorController(new DefaultErrorAttributes(),new ErrorProperties()); } @Bean public View error(){ return new View() { @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println(model); response.setContentType("text/html;charset=utf-8"); response.getWriter().print(""" <h1>服务器内部错误</h1> """); } }; } //需要按照error名称找到对应Bean的View对象,需要一个View的解析器 @Bean public ViewResolver viewResolver(){ //只要你的类型是View类型,根据bean的名字error,对应到我们需要的视图名上,没有这个解析器,找不到error对应的view //这个ViewResolver将来会交给DispatcherServlet,DispatcherServlet会调用解析器,根据视图名找到视图对象,然后渲染 return new BeanNameViewResolver(); } }
如果是用浏览器访问test,需要额外处理
收获💡
- Spring Boot 中 BasicErrorController 如何工作
1.14 BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
演示 - 本组映射器和适配器
WebConfig
@Configuration public class WebConfig { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } // /c1(请求) 找到--> /c1(Bean的名字带 /) // /c2 --> /c2 @Bean //做路径的请求 public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() { return new BeanNameUrlHandlerMapping(); } @Bean //调用请求(调用handleRequest方法,返回结果) public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { return new SimpleControllerHandlerAdapter(); } @Component("/c1") public static class Controller1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().print("this is c1"); return null; } } @Component("/c2") public static class Controller2 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().print("this is c2"); return null; } } @Bean("/c3") public Controller controller3() { return (request, response) -> { response.getWriter().print("this is c3"); return null; }; } }
public class A33 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); } }
自定义HandlerMapping和HandleAdapter
WebConfig
@Configuration public class WebConfig { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } // /c1(请求) 找到--> /c1(Bean的名字带 /) // /c2 --> /c2 @Component static class MyHandlerMapping implements HandlerMapping{ @Override public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { String key = request.getRequestURI(); //只返回路径部分(URI) Controller controller = collect.get(key); if (controller == null) { return null; //返回null, DispatcherServlet会抛出404 } return new HandlerExecutionChain(controller); } @Autowired //依赖注入在前 private ApplicationContext context; private Map<String ,Controller> collect; @PostConstruct //初始化在后 public void init(){ //都实现了Controller接口 Map<String, Controller> map = context.getBeansOfType(Controller.class); //⬇️过滤出Bean名字带 / 的 collect = map.entrySet().stream() .filter(entry -> entry.getKey().startsWith("/")) .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); } } @Component static class MyHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return handler instanceof Controller; //只能处理实现了Controller接口的对象 } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //handler 是 Controller接口的实现类,直接转换成 controller if (handler instanceof Controller controller) { controller.handleRequest(request, response); } return null; //返回null, 不会走ModelAndView视图渲染流程 } @Override public long getLastModified(HttpServletRequest request, Object handler) { return -1; } } @Component("/c1") public static class Controller1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().print("this is c1"); return null; } } @Component("/c2") public static class Controller2 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().print("this is c2"); return null; } } @Bean("/c3") public Controller controller3() { return (request, response) -> { response.getWriter().print("this is c3"); return null; }; } }
public class A33 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); } }
收获💡
- BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径
- 这些 bean 本身当作 handler,要求实现 Controller 接口
- SimpleControllerHandlerAdapter,调用 handler
- 模拟实现这组映射器和适配器
1.15 RouterFunctionMapping 与 HandlerFunctionAdapter
演示 - 本组映射器和适配器
public class A34 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); /* 学到了什么 函数式控制器 a. RouterFunctionMapping, 通过 RequestPredicate 映射 b. handler 要实现 HandlerFunction 接口 c. HandlerFunctionAdapter, 调用 handler 对比 a. RequestMappingHandlerMapping, 以 @RequestMapping 作为映射路径 b. 控制器的具体方法会被当作 handler c. RequestMappingHandlerAdapter, 调用 handler */ } }
WebConfig
@Configuration public class WebConfig { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } @Bean public RouterFunctionMapping routerFunctionMapping() { /**RouterFunctionMapping作用: * 初始化时候,在容器中找到所有的RouterFunction,然后记录对应的信息 * route(GET("/r1"), request -> ok().body("this is r1")); route(GET("/r2"), request -> ok().body("this is r2")); * 浏览器发起请求GET("/r1"),根据记录的信息,找到处理器对象request -> ok().body("this is r1")),把处理器对象 * 交给HandlerFunctionAdapter进行调用。HandlerFunctionAdapter把请求对象request准备好,执行ok().body("this is r1")), * 得到一个响应,最后HandlerFunctionAdapter把这个响应返回给浏览器 * */ return new RouterFunctionMapping(); } @Bean public HandlerFunctionAdapter handlerFunctionAdapter() { return new HandlerFunctionAdapter(); } @Bean public RouterFunction<ServerResponse> r1() { //r1(请求)的GET请求 --> 执行后面的的方法 ---> 返回ServerResponse(状态是:ok 200,响应体是"this is r1") return route(GET("/r1"), request -> ok().body("this is r1")); } @Bean public RouterFunction<ServerResponse> r2() { return route(GET("/r2"), request -> ok().body("this is r2")); } }
收获💡
- RouterFunctionMapping, 通过 RequestPredicate 条件映射
- handler 要实现 HandlerFunction 接口
- HandlerFunctionAdapter, 调用 handler
1.16 SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
演示1 - 本组映射器和适配器
public class A35 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); } }
WebConfig
@Configuration public class WebConfig { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } @Bean public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) { SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); /** * SimpleUrlHandlerMapping没有收集映射(offAndSet),需要手动处理 */ //map -> k: bean的名字/**,/img/** v -> 对应的处理器方法 Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class); handlerMapping.setUrlMap(map); System.out.println(map); //将来请求来了,就到map集合中去找映射关系 例如请求 /index.html --> /** /img/1.jpg --> /img/** //然后根据处理器方法找到类路径下的静态资源 return handlerMapping; } @Bean public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { //根据找到的映射路径找到对应的handler1,HttpRequestHandlerAdapter调用这个handler1,找到静态资源,然后返回给浏览器 return new HttpRequestHandlerAdapter(); } /** * /index.html * /r1.html ---> /** * /r2.html */ @Bean("/**") public ResourceHttpRequestHandler handler1(){ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); handler.setLocations(List.of(new ClassPathResource("static/"))); return handler; } /** * /img/1.jpg * /img/2.jpg ---> /img/** * /img/3.jpg */ @Bean("/img/**") public ResourceHttpRequestHandler handler2(){ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); handler.setLocations(List.of(new ClassPathResource("images/"))); return handler; } }
收获💡
- SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集
- SimpleUrlHandlerMapping 映射路径
- ResourceHttpRequestHandler 作为静态资源 handler
- HttpRequestHandlerAdapter, 调用此 handler
演示2 - 静态资源解析优化
收获💡
- 责任链模式体现
- 压缩文件需要手动生成
演示3 - 欢迎页
A35
public class A35 { public static void main(String[] args) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); } }
WebConfig
@Configuration public class WebConfig { @Bean // ⬅️内嵌 web 容器工厂 public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(8080); } @Bean // ⬅️创建 DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口 public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } @Bean public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) { SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); /** * SimpleUrlHandlerMapping没有收集映射(offAndSet),需要手动处理 */ //map -> k: bean的名字/**,/img/** v -> 对应的处理器方法 Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class); handlerMapping.setUrlMap(map); System.out.println(map); //将来请求来了,就到map集合中去找映射关系 例如请求 /index.html --> /** /img/1.jpg --> /img/** //然后根据处理器方法找到类路径下的静态资源 return handlerMapping; } @Bean public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { //根据找到的映射路径找到对应的handler1,HttpRequestHandlerAdapter调用这个handler1,找到静态资源,然后返回给浏览器 return new HttpRequestHandlerAdapter(); } /** * /index.html * /r1.html ---> /** * /r2.html */ @Bean("/**") public ResourceHttpRequestHandler handler1(){ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); handler.setLocations(List.of(new ClassPathResource("static/"))); handler.setResourceResolvers(List.of( //缓存资源,如果没有通过PathResourceResolver找到资源,就会缓存起来,下次从缓存中读取 new CachingResourceResolver(new ConcurrentMapCache("cache1")), new EncodedResourceResolver(),//读压缩资源 new PathResourceResolver() //基本的资源解析器,到磁盘上去读资源 )); return handler; } /** * /img/1.jpg * /img/2.jpg ---> /img/** * /img/3.jpg */ @Bean("/img/**") public ResourceHttpRequestHandler handler2(){ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); handler.setLocations(List.of(new ClassPathResource("images/"))); return handler; } @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) { Resource resource = context.getResource("classpath:static/index.html"); /** * 参数1:和动态欢迎页相关,不需要 */ return new WelcomePageHandlerMapping(null, context, resource, "/**"); // Controller 接口 } /** * 适配器和其他的映射器配置使用 * @return */ @Bean public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){ return new SimpleControllerHandlerAdapter(); } }
收获💡
- 欢迎页支持静态欢迎页与动态欢迎页
- WelcomePageHandlerMapping 映射欢迎页(即只映射 '/')
- 它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图
- 视图名固定为 forward:index.html
- SimpleControllerHandlerAdapter, 调用 handler
- 转发至 /index.html
- 处理 /index.html 又会走上面的静态资源处理流程
映射器与适配器小结
- HandlerMapping 负责建立请求与控制器之间的映射关系
- RequestMappingHandlerMapping (与 @RequestMapping 匹配)
- WelcomePageHandlerMapping (/)
- BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
- RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
- SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)
- 之间也会有顺序问题, boot 中默认顺序如上
- HandlerAdapter 负责实现对各种各样的 handler 的适配调用
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
- 参数解析器、返回值处理器体现了组合模式
- SimpleControllerHandlerAdapter 处理:Controller 接口
- HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
- HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)
- 这也是典型适配器模式体现
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
1.17 mvc 处理流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
-
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器- jsp 不会匹配到 DispatcherServlet
- 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
- HandlerMapping,初始化时记录映射关系
- HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolver
- 路径:默认映射路径为
-
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
-
例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法
-
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
-
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
-
-
DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice 全局增强点1️⃣:补充模型数据
- @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
- 根据 ModelAndViewContainer 获取 ModelAndView
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 调用拦截器的 postHandle 方法
- 处理异常或视图渲染
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
- 正常,走视图解析及渲染流程
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法
本文作者:千夜ん
本文链接:https://www.cnblogs.com/fengpeng123/p/18486944
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步