Spring原理MVC

news/2024/10/20 9:36:36

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;}
}

image-20240924223957272

把值写在配置文件中

@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;}
}

image-20240924225242732

image-20240924225315519

DispatcherServlet 初始化做了什么

image-20240926215918205

image-20240926220249837

image-20240926221049947

image-20240926220352741

RequestMappingHandlerMapping 作用

如果使用默认DispatcherServlet.properties中的RequestMappingHandlerMapping,只会作为成员变量,并不会注入容器中,所以需要自己写

image-20240926224822893

@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* */}
}

image-20240926225138788

RequestMappingHandlerAdapter 作用

image-20240926231631822

image-20240926231656417

image-20240926231718272

image-20240926231758207

调用了Controller中的方法

image-20240926232451678

image-20240926232228898

image-20240926232326135

查看参数解析器和返回值解析器

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);}}
}

image-20241007104116084

image-20241007104128778

收获💡

  1. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
  2. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
  3. RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
  4. RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
    • HandlerMethodArgumentResolver 解析控制器方法参数
    • HandlerMethodReturnValueHandler 处理控制器方法返回值

演示2 - 自定义参数与返回值处理器

自定义参数解析器

Token

// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
// token=令牌
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}

image-20241007110059915

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;}
}

image-20241007110232673

image-20241007110258609

模拟请求

image-20241007110345171

image-20241007110402565

自定义返回值处理器

Yml

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}

image-20241007112708624

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;
}

image-20241007113457269

image-20241007113559046

image-20241007113719099

image-20241007113745488

收获💡

  1. 体会参数解析器的作用
  2. 体会返回值处理器的作用

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 +'}';}}
}

image-20241007175400626

image-20241007175424771

收获💡

  1. 初步了解 RequestMappingHandlerAdapter 的调用过程
    1. 控制器方法被封装为 HandlerMethod
    2. 准备对象绑定与类型转换
    3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
    4. 解析每个参数值
  2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法
    • supportsParameter 判断是否支持方法参数
    • resolveArgument 解析方法参数
  3. 常见参数的解析
    • @RequestParam
    • 省略 @RequestParam
    • @RequestParam(defaultValue)
    • MultipartFile
    • @PathVariable
    • @RequestHeader
    • @CookieValue
    • @Value
    • HttpServletRequest 等
    • @ModelAttribute
    • 省略 @ModelAttribute
    • @RequestBody
  4. 组合模式在 Spring 中的体现
  5. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取

1.3 参数名解析

image-20241008213427708

收获💡

  1. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
  2. 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
    • 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
    • 接口, 不会包含局部变量表, 无法获得参数名
      • 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名

1.4 对象绑定与类型转换

底层第一套转换接口与实现

classDiagramFormatter --|> Printer Formatter --|> Parserclass Converters {Set~GenericConverter~ } class Converterclass ConversionService class FormattingConversionServiceConversionService <|-- FormattingConversionService FormattingConversionService o-- ConvertersPrinter --> Adapter1 Adapter1 --> Converters Parser --> Adapter2 Adapter2 --> Converters Converter --> Adapter3 Adapter3 --> Converters<<interface>> Formatter <<interface>> Printer <<interface>> Parser <<interface>> Converter <<interface>> ConversionService
  • Printer 把其它类型转为 String
  • Parser 把 String 转为其它类型
  • Formatter 综合 Printer 与 Parser 功能
  • Converter 把类型 S 转为类型 T
  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
  • FormattingConversionService 利用其它们实现转换

底层第二套转换接口

classDiagramPropertyEditorRegistry o-- "多" PropertyEditor<<interface>> PropertyEditorRegistry <<interface>> PropertyEditor
  • PropertyEditor 把 String 与其它类型相互转换
  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

高层接口与实现

classDiagram TypeConverter <|-- SimpleTypeConverter TypeConverter <|-- BeanWrapperImpl TypeConverter <|-- DirectFieldAccessor TypeConverter <|-- ServletRequestDataBinderSimpleTypeConverter --> TypeConverterDelegate BeanWrapperImpl --> TypeConverterDelegate DirectFieldAccessor --> TypeConverterDelegate ServletRequestDataBinder --> TypeConverterDelegateTypeConverterDelegate --> ConversionService TypeConverterDelegate --> PropertyEditorRegistry<<interface>> TypeConverter <<interface>> ConversionService <<interface>> PropertyEditorRegistry
  • 它们都实现了 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);}
}

image-20241008221002061

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 +'}';}}
}

image-20241008221057596

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 +'}';}}
}

image-20241008221152835

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 +'}';}}}

image-20241008221319516

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 +'}';}}}

image-20241008222618213

TestServletDataBinderFactory

image-20241008223940638

收获💡

基本的类型转换与数据绑定用法

  • 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);}}

image-20241008230921111

用 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);}

image-20241009203645820

image-20241009203520928

同时加了 @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);}

image-20241009204701682

收获💡

ServletRequestDataBinderFactory 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
    • 控制器私有范围
  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
    • 公共范围
  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级
    1. 优先采用 @InitBinder 的转换器
    2. 其次使用 ConversionService 的转换器
    3. 使用默认转换器
    4. 特殊处理(例如有参构造)

演示3 - 获取泛型参数

image-20241009205312433

收获💡

  1. java api 获取泛型参数
  2. spring api 获取泛型参数

1.5 @ControllerAdvice 之 @InitBinder

演示 - 准备 @InitBinder

准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置

sequenceDiagram participant adapter as HandlerAdapter participant bf as WebDataBinderFactory participant mf as ModelFactory participant ihm as ServletInvocableHandlerMethod participant ar as ArgumentResolvers participant rh as ReturnValueHandlers participant container as ModelAndViewContainer rect rgb(200, 150, 255) adapter ->> +bf: 准备 @InitBinder bf -->> -adapter: end adapter ->> +mf: 准备 @ModelAttribute mf ->> +container: 添加Model数据 container -->> -mf: mf -->> -adapter: adapter ->> +ihm: invokeAndHandle ihm ->> +ar: 获取 args ar ->> ar: 有的解析器涉及 RequestBodyAdvice ar ->> container: 有的解析器涉及数据绑定生成Model数据 ar -->> -ihm: args ihm ->> ihm: method.invoke(bean,args) 得到 returnValue ihm ->> +rh: 处理 returnValue rh ->> rh: 有的处理器涉及 ResponseBodyAdvice rh ->> +container: 添加Model数据,处理视图名,是否渲染等 container -->> -rh: rh -->> -ihm: ihm -->> -adapter: adapter ->> +container: 获取 ModelAndView container -->> -adapter:
  • 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()));}}

image-20241009221913966

收获💡

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
  3. 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
  4. 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂

1.6 控制器方法执行流程

图1

classDiagram class ServletInvocableHandlerMethod {+invokeAndHandle(ServletWebRequest,ModelAndViewContainer) } HandlerMethod <|-- ServletInvocableHandlerMethod HandlerMethod o-- bean HandlerMethod o-- method ServletInvocableHandlerMethod o-- WebDataBinderFactory ServletInvocableHandlerMethod o-- ParameterNameDiscoverer ServletInvocableHandlerMethod o-- HandlerMethodArgumentResolverComposite ServletInvocableHandlerMethod o-- HandlerMethodReturnValueHandlerComposite

HandlerMethod 需要

  • bean 即是哪个 Controller
  • method 即是 Controller 中的哪个方法

ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory 负责对象绑定、类型转换
  • ParameterNameDiscoverer 负责参数名解析
  • HandlerMethodArgumentResolverComposite 负责解析参数
  • HandlerMethodReturnValueHandlerComposite 负责处理返回值

图2

sequenceDiagram participant adapter as RequestMappingHandlerAdapter participant bf as WebDataBinderFactory participant mf as ModelFactory participant container as ModelAndViewContainer adapter ->> +bf: 准备 @InitBinder bf -->> -adapter: adapter ->> +mf: 准备 @ModelAttribute mf ->> +container: 添加Model数据 container -->> -mf: mf -->> -adapter:

图3

sequenceDiagram participant adapter as RequestMappingHandlerAdapter participant ihm as ServletInvocableHandlerMethod participant ar as ArgumentResolvers participant rh as ReturnValueHandlers participant container as ModelAndViewContaineradapter ->> +ihm: invokeAndHandle ihm ->> +ar: 获取 args ar ->> ar: 有的解析器涉及 RequestBodyAdvice ar ->> container: 有的解析器涉及数据绑定生成模型数据 container -->> ar: ar -->> -ihm: args ihm ->> ihm: method.invoke(bean,args) 得到 returnValue ihm ->> +rh: 处理 returnValue rh ->> rh: 有的处理器涉及 ResponseBodyAdvice rh ->> +container: 添加Model数据,处理视图名,是否渲染等 container -->> -rh: rh -->> -ihm: ihm -->> -adapter: adapter ->> +container: 获取 ModelAndView container -->> -adapter:

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;}
}

image-20241009230459182

image-20241009230539240

1.7 @ControllerAdvice 之 @ModelAttribute

演示 - 准备 @ModelAttribute

准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置

sequenceDiagram participant adapter as HandlerAdapter participant bf as WebDataBinderFactory participant mf as ModelFactory participant ihm as ServletInvocableHandlerMethod participant ar as ArgumentResolvers participant rh as ReturnValueHandlers participant container as ModelAndViewContaineradapter ->> +bf: 准备 @InitBinder bf -->> -adapter: rect rgb(200, 150, 255) adapter ->> +mf: 准备 @ModelAttribute mf ->> +container: 添加Model数据 container -->> -mf: mf -->> -adapter: end adapter ->> +ihm: invokeAndHandle ihm ->> +ar: 获取 args ar ->> ar: 有的解析器涉及 RequestBodyAdvice ar ->> container: 有的解析器涉及数据绑定生成Model数据 ar -->> -ihm: args ihm ->> ihm: method.invoke(bean,args) 得到 returnValue ihm ->> +rh: 处理 returnValue rh ->> rh: 有的处理器涉及 ResponseBodyAdvice rh ->> +container: 添加Model数据,处理视图名,是否渲染等 container -->> -rh: rh -->> -ihm: ihm -->> -adapter: adapter ->> +container: 获取 ModelAndView container -->> -adapter:
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;}}

image-20241010211703271

image-20241010211825834

image-20241010211927037

收获💡

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法
  3. 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析
  4. 控制器方法调用时,会综合利用本类的 @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;}
}

image-20241010224835161

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 +'}';}}
}

image-20241010225116547

image-20241010225209975

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";
}

image-20241011211549865

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);
}

image-20241011213137663

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);
}

image-20241011214303461

HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))

image-20241011214827095

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));
}

image-20241011220119488

new HttpHeadersReturnValueHandler()

image-20241011220343005

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;
}

image-20241011220815518

RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()): 返回值放入响应体,并转成json

image-20241011221159992

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);
}

image-20241011221346823

收获💡

  1. 常见的返回值处理器
    • ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
    • 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
    • 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
      • 此时需找到默认视图名
    • 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
      • 此时需找到默认视图名
    • 返回值类型为 ResponseEntity 时
      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
    • 返回值类型为 HttpHeaders 时
      • 会设置 ModelAndViewContainer.requestHandled 为 true
    • 返回值添加了 @ResponseBody 注解时
      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
  2. 组合模式在 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 +'}';}}
}

image-20241011223547989

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());}
}

image-20241011224122315

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);}
}

image-20241011224822261

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;
}

image-20241011230744517

收获💡

  1. MessageConverter 的作用
    • @ResponseBody 是返回值处理器解析的
    • 但具体转换工作是 MessageConverter 做的
  2. 如何选择 MediaType
    • 首先看 @RequestMapping 上有没有指定
    • 其次看 request 的 Accept 头有没有指定
    • 最后按 MessageConverter 的顺序, 谁能谁先转换

1.10 @ControllerAdvice 之 ResponseBodyAdvice

演示 - ResponseBodyAdvice 增强

ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置

sequenceDiagram participant adapter as HandlerAdapter participant bf as WebDataBinderFactory participant mf as ModelFactory participant ihm as ServletInvocableHandlerMethod participant ar as ArgumentResolvers participant rh as ReturnValueHandlers participant container as ModelAndViewContaineradapter ->> +bf: 准备 @InitBinder bf -->> -adapter: adapter ->> +mf: 准备 @ModelAttribute mf ->> +container: 添加Model数据 container -->> -mf: mf -->> -adapter: adapter ->> +ihm: invokeAndHandle ihm ->> +ar: 获取 args ar ->> ar: 有的解析器涉及 RequestBodyAdvice ar ->> container: 有的解析器涉及数据绑定生成Model数据 ar -->> -ihm: args ihm ->> ihm: method.invoke(bean,args) 得到 returnValue ihm ->> +rh: 处理 returnValue rect rgb(200, 150, 255) rh ->> rh: 有的处理器涉及 ResponseBodyAdvice end rh ->> +container: 添加Model数据,处理视图名,是否渲染等 container -->> -rh: rh -->> -ihm: ihm -->> -adapter: adapter ->> +container: 获取 ModelAndView container -->> -adapter:
@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;}}
}

image-20241013143904957

收获💡

  1. 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());}}

image-20241013150716367

测试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()));}
}

image-20241013151416174

测试嵌套异常

//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());}
}

image-20241013152305584

image-20241013152546002

测试异常处理方法参数解析

//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());}
}

image-20241013153843594

收获💡

  1. 它能够重用参数解析器、返回值处理器,实现组件重用
  2. 它能够支持嵌套异常

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() {}}
}

image-20241013161017707

image-20241013160307470

image-20241013160653176

收获💡

  1. ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法
  2. ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法
  3. 以上两种 @ExceptionHandler 的解析结果都会缓存来避免重复解析

1.13 Tomcat 异常处理

  • 我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?

  • 在 Spring Boot 中,是这么实现的:

    1. 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
    2. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置
    3. 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至 /error 这个地址
      • 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
    4. Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为 /error,所以处理异常的职责就又回到了 Spring
    5. 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
    6. 具体异常信息会由 DefaultErrorAttributes 封装好
    7. 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);});}
}

image-20241013181002097

image-20241013181029097

演示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());}}
}

image-20241013183032447

image-20241013183120842

image-20241013183139697

收获💡

  1. 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();}
}

image-20241013185907449

image-20241013190012466

image-20241013190136141

如果是用浏览器访问test,需要额外处理

image-20241013190318893

image-20241013190225700

image-20241013190345226

收获💡

  1. 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);}
}

image-20241013223411812

image-20241013223439271

image-20241013223458170

自定义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);}
}

image-20241013230553469

image-20241013230615863

image-20241013230639222

image-20241013230719595

收获💡

  1. BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径
  2. 这些 bean 本身当作 handler,要求实现 Controller 接口
  3. SimpleControllerHandlerAdapter,调用 handler
  4. 模拟实现这组映射器和适配器

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"));}}

image-20241014214454747

收获💡

  1. RouterFunctionMapping, 通过 RequestPredicate 条件映射
  2. handler 要实现 HandlerFunction 接口
  3. 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;}}

image-20241014222803394

image-20241014222830505

image-20241014222953298

image-20241014223007863

收获💡

  1. SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集
  2. SimpleUrlHandlerMapping 映射路径
  3. ResourceHttpRequestHandler 作为静态资源 handler
  4. HttpRequestHandlerAdapter, 调用此 handler

演示2 - 静态资源解析优化

image-20241014224751394

image-20241014224850995

image-20241014224938359

image-20241014225015448

image-20241014225041698

收获💡

  1. 责任链模式体现
  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();}}

image-20241015221942396

image-20241015222007049

image-20241015222024939

收获💡

  1. 欢迎页支持静态欢迎页与动态欢迎页
  2. WelcomePageHandlerMapping 映射欢迎页(即只映射 '/')
    • 它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图
    • 视图名固定为 forward:index.html
  3. SimpleControllerHandlerAdapter, 调用 handler
    • 转发至 /index.html
    • 处理 /index.html 又会走上面的静态资源处理流程

映射器与适配器小结

  1. HandlerMapping 负责建立请求与控制器之间的映射关系
    • RequestMappingHandlerMapping (与 @RequestMapping 匹配)
    • WelcomePageHandlerMapping (/)
    • BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
    • RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
    • SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)
    • 之间也会有顺序问题, boot 中默认顺序如上
  2. HandlerAdapter 负责实现对各种各样的 handler 的适配调用
    • RequestMappingHandlerAdapter 处理:@RequestMapping 方法
      • 参数解析器、返回值处理器体现了组合模式
    • SimpleControllerHandlerAdapter 处理:Controller 接口
    • HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
    • HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)
    • 这也是典型适配器模式体现

1.17 mvc 处理流程

当浏览器发送一个请求 http://localhost:8080/hello 后,请求到达服务器,其处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术

    • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
      • jsp 不会匹配到 DispatcherServlet
      • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
    • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
    • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
      • HandlerMapping,初始化时记录映射关系
      • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
      • ViewResolver
  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

    • 例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法

    • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet

    • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象

  3. DispatcherServlet 接下来会:

    1. 调用拦截器的 preHandle 方法
    2. 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 步走视图解析及渲染流程
    3. 调用拦截器的 postHandle 方法
    4. 处理异常或视图渲染
      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
      • 正常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/73727.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

树状数组——原理详解

前言 这两天在网上学树状数组,但是发现网上关于树状数组的解释大都对初学者不太友善,原理讲解部分并不是很容易理解,所以写了一篇树状数组,顺便帮自己巩固一下。一、什么是树状数组 1.概念: 简单来说,这是一种数据结构。顾名思义,它通过树的结构来对数组进行高效操作,一…

机器学习中空间和时间自相关的分析:从理论基础到实践应用

空间和时间自相关是数据分析中的两个基本概念,它们揭示了现象在空间和时间维度上的相互依赖关系。这些概念在各个领域都有广泛应用,从环境科学到城市规划,从流行病学到经济学。本文将探讨这些概念的理论基础,并通过一个实际的野火风险预测案例来展示它们的应用。图1: 空间自相关…

manim边做边学--直角平面

直角平面NumberPlane是Manim库中用于创建二维坐标平面的对象,它可以帮助用户在场景中可视化坐标轴以及网格线。 通过坐标轴、网格线以及刻度,它能够动态地展示函数曲线、几何图形以及它们的变换过程,使得复杂的数学概念变得直观易懂。 NumberPlane提供了x轴和y轴,通常是中心…

一文彻底弄懂MySQL的MVCC多版本控制器

InnoDB 的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是 MySQL 实现高并发事务处理的一种机制。通过 MVCC,InnoDB 可以在高并发环境下支持 事务隔离,并提供 非阻塞的读操作,从而避免锁定所有读操作带来的性能瓶颈。MVCC 允许事务在不加锁的情况下读取数据…

sicp每日一题[2.50]

Exercise 2.50Define the transformation flip-horiz, which flips painters horizontally, and transformations that rotate painters counterclockwise by 180 degrees and 270 degrees.这道题挺有意思的,搞明白这道题就明白了 frame 的3个点的位置。如上图所示,为了更好区…

stiReport中的DataBand的DataSource要设置,不然打印时哪怕数据有两行也只显示一行

stiReport中的DataBand的DataSource要设置,不然打印时哪怕数据有两行也只显示一行。哪怕report的数据源是通过程序动态设置的,这个属性也要在设计时有值。

读数据工程之道:设计和构建健壮的数据系统14源系统

源系统1. 源系统中的数据生成 1.1. 数据工程师的工作是从源系统获取数据,对其进行处理,使其有助于为下游用例提供服务 1.2. 数据工程师的角色将在很大程度上转向理解数据源和目的地之间的相互作用 1.3. 数据工程的最基本的数据管道任务——将数据从A移动到B 2. 数据源 2.1. 数…

Gartner 魔力象限:企业备份和恢复解决方案 2024

Gartner Magic Quadrant for Enterprise Backup and Recovery Solutions 2024Gartner Magic Quadrant for Enterprise Backup and Recovery Solutions 2024 Gartner 魔力象限:企业备份和恢复解决方案 2024 请访问原文链接:https://sysin.org/blog/gartner-magic-quadrant-ent…