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容器工厂@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(){return new TomcatServletWebServerFactory();}//创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}//注册DispatcherServlet ,Spring MVC 的入口@Beanpublic 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容器工厂@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties){return new TomcatServletWebServerFactory(serverProperties.getPort());}//创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}//注册DispatcherServlet ,Spring MVC 的入口@Beanpublic 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@Ymlpublic 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容器工厂@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties){return new TomcatServletWebServerFactory(serverProperties.getPort());}//创建 DispatcherServlet 通过Spring容器创建,通过tomcat初始化@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}//注册DispatcherServlet ,Spring MVC 的入口@Beanpublic 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@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping(){return new RequestMappingHandlerMapping();}// ⬅️2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter@Beanpublic 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 {@Overridepublic 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@abbc908org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523corg.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacbaorg.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060forg.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77aorg.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21borg.springframework.web.method.annotation.MapMethodProcessor@16c3ca31org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988forg.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975org.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();// 准备测试 RequestHttpServletRequest request = mockRequest();// 要点1. 控制器方法被封装为 HandlerMethodHandlerMethod 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表示必须有 @RequestParamnew RequestParamMethodArgumentResolver(beanFactory, false),new PathVariableMethodArgumentResolver(),//解析Headernew RequestHeaderMethodArgumentResolver(beanFactory),new ServletCookieValueMethodArgumentResolver(beanFactory),//解析 @Valuenew 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=18User 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;}@Overridepublic 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;}@Overridepublic 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;@Overridepublic 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;}*/@Overridepublic 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;}@Overridepublic 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{@InitBinderpublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic String print(Date date, Locale locale) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");return sdf.format(date);}@Overridepublic 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 {@ControllerAdvicestatic class MyControllerAdvice {@InitBinder //@InitBinder加到@ControllerAdvice中作用于所有的控制器中的类型转换public void binder3(WebDataBinder webDataBinder) {webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));}}@Controllerstatic class Controller1 {@InitBinder //@InitBinder加到@Controller中作用于当前控制器中的类型转换public void binder1(WebDataBinder webDataBinder) {webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));}public void foo() {}}@Controllerstatic class Controller2 {@InitBinderpublic void binder21(WebDataBinder webDataBinder) {webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器"));}@InitBinderpublic 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()上的@InitBindergetDataBinderFactory.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 {@ControllerAdvicestatic class MyControllerAdvice {@ModelAttribute("a")public String aa() {return "aa";}}@Controllerstatic 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;}@Overridepublic 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);//WebDataBinderFactoryhandlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); //ParameterNameDiscovererhandlerMethod.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);//WebDataBinderFactoryhandlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); //ParameterNameDiscovererhandlerMethod.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 {@ControllerAdvicestatic class MyControllerAdvice {//指定了名字用指定的,没有指定用返回值的小写作为名字@ModelAttribute("a") //通过 RequestMappingHandlerAdapter解析,而不是使用参数解析器进行解析public String aa() {return "aa";}}@Controllerstatic 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 {@Beanpublic 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() {@Overrideprotected AbstractUrlBasedView instantiateView() {FreeMarkerView view = new FreeMarkerView() {@Overrideprotected 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.ftlresolver.setExposeSpringMacroHelpers(false);return resolver;}
}
public class A27 {/*目标: 解析控制器方法的返回值常见的返回值处理器org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bcorg.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5dorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426corg.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396dborg.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8org.springframework.web.method.annotation.MapMethodProcessor@6ff37443org.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.测试返回值类型为 ModelAndViewHandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();//给一个空的请求对象 webRequestServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse());//检查是否支持此类型的返回值if (composite.supportsReturnType(handlerMethod.getReturnType())) {//给一个空的请求对象 webRequestcomposite.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;}@Overridepublic 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();//给一个空的请求对象 webRequestServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse());//检查是否支持此类型的返回值if (composite.supportsReturnType(handlerMethod.getReturnType())) {//给一个空的请求对象 webRequestcomposite.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();//给一个空的请求对象 webRequestMockHttpServletRequest 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())) {//给一个空的请求对象 webRequestcomposite.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();//给一个空的请求对象 webRequestMockHttpServletRequest 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())) {//给一个空的请求对象 webRequestcomposite.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();//给一个空的请求对象 webRequestMockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);//检查是否支持此类型的返回值if (composite.supportsReturnType(handlerMethod.getReturnType())) {//给一个空的请求对象 webRequestcomposite.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();//给一个空的请求对象 webRequestMockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);//检查是否支持此类型的返回值if (composite.supportsReturnType(handlerMethod.getReturnType())) {//给一个空的请求对象 webRequestcomposite.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();//给一个空的请求对象 webRequestMockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);//检查是否支持此类型的返回值if (composite.supportsReturnType(handlerMethod.getReturnType())) {//给一个空的请求对象 webRequestcomposite.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;@JsonCreatorpublic 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;}@Overridepublic 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;}@JsonCreatorprivate 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) {// 添加 adviceList<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 {@ControllerAdvicestatic class MyControllerAdvice implements ResponseBodyAdvice<Object> {//满足条件才转换@Overridepublic 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: 返回值类型,所有方法,方法上的注解信息*/@Overridepublic 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 加到类上表示类中所有方法返回值都会加入到响应体中@RestControllerpublic static class MyController {//@ResponseBodypublic 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@ResponseBodypublic 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();//测试 mavHandlerMethod 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() {}@ExceptionHandlerpublic 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@ResponseBodypublic 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 {@ControllerAdvicestatic class MyControllerAdvice {@ExceptionHandler@ResponseBodypublic Map<String, Object> handle(Exception e) {return Map.of("error", e.getMessage());}}@Beanpublic 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 {@Beanpublic TomcatServletWebServerFactory servletWebServerFactory() {return new TomcatServletWebServerFactory();}@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Beanpublic DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");registrationBean.setLoadOnStartup(1);return registrationBean;}@Bean // @RequestMappingpublic RequestMappingHandlerMapping requestMappingHandlerMapping() {return new RequestMappingHandlerMapping();}@Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));return handlerAdapter;}@Controllerpublic 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 {@Beanpublic TomcatServletWebServerFactory servletWebServerFactory() {return new TomcatServletWebServerFactory();}@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Beanpublic DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");registrationBean.setLoadOnStartup(1);return registrationBean;}@Bean // @RequestMappingpublic 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() {@Overridepublic void registerErrorPages(ErrorPageRegistry webServerFactory) {//当tomcat出现500错误时,跳转到/error页面或者是error路径//出现错误,会使用请求转发 forward 跳转到 error 地址webServerFactory.addErrorPages(new ErrorPage("/error"));}};}//因为errorPageRegistrar不会主动增加错误页面//方法作用:再创建好WebServerFactory对象之后,初始化之前,执行该方法,// 到容器中找到所有的ErrorPageRegistrar,给webServerFactory对象添加所有的ErrorPage@Beanpublic ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor(){return new ErrorPageRegistrarBeanPostProcessor();}@Controllerpublic 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
@Controllerpublic 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());}*/}@Beanpublic BasicErrorController basicErrorController(){return new BasicErrorController(new DefaultErrorAttributes(),new ErrorProperties());}@Beanpublic View error(){return new View() {@Overridepublic 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的解析器@Beanpublic 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 // ⬅️创建 DispatcherServletpublic 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 {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().print("this is c1");return null;}}@Component("/c2")public static class Controller2 implements Controller {@Overridepublic 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 // ⬅️创建 DispatcherServletpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}// /c1(请求) 找到--> /c1(Bean的名字带 /)// /c2 --> /c2@Componentstatic class MyHandlerMapping implements HandlerMapping{@Overridepublic 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()));}}@Componentstatic class MyHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return handler instanceof Controller; //只能处理实现了Controller接口的对象}@Overridepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//handler 是 Controller接口的实现类,直接转换成 controllerif (handler instanceof Controller controller) {controller.handleRequest(request, response);}return null; //返回null, 不会走ModelAndView视图渲染流程}@Overridepublic long getLastModified(HttpServletRequest request, Object handler) {return -1;}}@Component("/c1")public static class Controller1 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().print("this is c1");return null;}}@Component("/c2")public static class Controller2 implements Controller {@Overridepublic 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. 控制器的具体方法会被当作 handlerc. RequestMappingHandlerAdapter, 调用 handler*/}
}
WebConfig
@Configuration
public class WebConfig {@Bean // ⬅️内嵌 web 容器工厂public TomcatServletWebServerFactory servletWebServerFactory() {return new TomcatServletWebServerFactory(8080);}@Bean // ⬅️创建 DispatcherServletpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}@Beanpublic 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();}@Beanpublic HandlerFunctionAdapter handlerFunctionAdapter() {return new HandlerFunctionAdapter();}@Beanpublic RouterFunction<ServerResponse> r1() {//r1(请求)的GET请求 --> 执行后面的的方法 ---> 返回ServerResponse(状态是:ok 200,响应体是"this is r1")return route(GET("/r1"), request -> ok().body("this is r1"));}@Beanpublic 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 // ⬅️创建 DispatcherServletpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}@Beanpublic 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;}@Beanpublic 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 // ⬅️创建 DispatcherServletpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}@Beanpublic 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;}@Beanpublic 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;}@Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {Resource resource = context.getResource("classpath:static/index.html");/*** 参数1:和动态欢迎页相关,不需要*/return new WelcomePageHandlerMapping(null, context, resource, "/**");// Controller 接口}/*** 适配器和其他的映射器配置使用* @return*/@Beanpublic 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 方法