一篇文章搞懂Spring mvc底层原理,DispatcherServlet如何运作的?

3. WEB

3.1 DispatcherServlet 的初始化时机

DispatcherServlet 是被 spring 容器进行管理的,但是 DispatcherServlet 的初始化等是被 TomcatServlet 进行生命周期管理的

DispatcherServlet 初始化时机可以是,TomcatServlet 第一次被访问时,也可以是 TomcatServlet启动时开始初始化操作

  1. 默认的 DispatcherServlet 是在第一次被 TomcatServlet 访问时执行初始化。 也可以通过配置修改为 Tomcat 启动后就初始化(registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup())、spring.mvc.servlet.load-on-startup=1)

  2. DispatcherServlet 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context); 	// 处理文件上传
        this.initLocaleResolver(context);		// 处理国际化设置
        this.initThemeResolver(context);		// 处理主题切换
        this.initHandlerMappings(context);		// 根据请求映射找到处理器方法
        this.initHandlerAdapters(context);		// 执行处理器方法
        this.initHandlerExceptionResolvers(context);	// 处理异常
        this.initRequestToViewNameTranslator(context);	// 将请求转换为视图名称
        this.initViewResolvers(context);		// 根据视图名称返回视图
        this.initFlashMapManager(context);		// 处理重定向后的临时数据
    }
    

3.2 映射器和调用器

RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter:

  1. RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对

    • RequestMappingHandlerMapping 处理 @RequestMapping 映射
    • RequestMappingHandlerAdapter 调用控制器方法、并处理方法参数与方法返回值
  2. RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,获取了所有的 HandleMethod 方法,其中

    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
  3. RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:

    • HandlerMethodArgumentResolver 解析控制器方法参数

    • HandlerMethodReturnValueHandler 处理控制器方法返回值

    • HandlerAdapter(RequestMappingHandlerAdapter)作用就是调用控制器方法(HandlerMethod),HandlerMethod 可以通过 getHandlerMethods 获得一个 set 集合,也可以通过 getHandlerMethod 并传递 request 获取指定的 HandlerMethod

3.2.1 自定义参数解析器

总结:

  1. 自定义参数解析器

    1. 参数解析器需要实现 HandlerMethodArgumentResolver 接口,完成 supportsParameter(检查是否支持某个参数),resolveArgument(获取参数的值)两个方法
    2. 将实现的类加入到 HandlerAdapter 中( HandlerAdapter提供setCustomArgumentResolvers)
  2. 参数解析器的作用:

    1. 看是否支持某种参数(supportsParameter 方法)
    2. 获取参数的值(resolveArgument 方法)

3.2.2 自定义返回值处理器

总结:

  1. 自定义返回值处理器
    1. 参数解析器需要实现 HandlerMethodReturnValueHandler 接口,完成 supportsParameter(检查是否支持某个参数),resolveArgument(获取参数的值)两个方法
    2. 将实现的类加入到 HandlerAdapter 中(HandlerAdapter提供setCustomReturnValueHandlers)
  2. 自定义返回值处理器的作用:
    1. 看是否支持某种参数(supportsReturnType 方法)
    2. 返回值处理逻辑(handleReturnValue 方法)

3.3  参数解析器(组合器设计模式) \textcolor{green}{3.3\ 参数解析器(组合器设计模式)} 3.3 参数解析器(组合器设计模式)

总结:

  1. 初步了解 RequestMappingHandlerAdapter 的调用过程

    1. 控制器方法被封装为 HandlerMethod
    2. 准备对象绑定与类型转换
    3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
    4. 利用 HandlerMethodArgumentResolverComposite 解析每个参数值
  2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法

    • supportsParameter 判断是否支持方法参数
    • resolveArgument 解析方法参数
  3. 常见参数的解析

    @RequestParam("name1") String name1, // 参数绑定
    String name2,                        // 省略 @RequestParam
    @RequestParam("age") int age,        // 类型转换
    @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // 设置默认值
    @RequestParam("file") MultipartFile file, // 文件上传
    @PathVariable("id") int id,               // 路径参数
    @RequestHeader("Content-Type") String header, // Header信息
    @CookieValue("token") String token,  //
    @Value("${JAVA_HOME}") String home2, // spring 获取数据,插值表达式
    HttpServletRequest request,          // request, response, session ...
    @ModelAttribute("abc") User user1,   // 对象绑定,用对象接受参数
    User user2,                          // 省略 @ModelAttribute
    @RequestBody User user3              // 从请求体中获取数据
    
  4. 常见的参数解析器

    // false 表示必须有 @RequestParam
    new RequestParamMethodArgumentResolver(beanFactory, false),
    new PathVariableMethodArgumentResolver(),
    new RequestHeaderMethodArgumentResolver(beanFactory),
    new ServletCookieValueMethodArgumentResolver(beanFactory),
    new ExpressionValueMethodArgumentResolver(beanFactory),
    new ServletRequestMethodArgumentResolver(),
    new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
    new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())), // 位置必须放在这两个之间,不然@RequestBody会被当作是省略了 @ModelAttribute 注解的情况
    new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
    new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
    
  5. **组合器设计模式(composite设计模式)**在 Spring 中的体现(HandlerMethodArgumentResolverComposite 多个解析器组合)

  6. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取

  7. PathVariableMethodArgumentResolver 解析器需要将路径与参数对应关系放到 request 作用域,这这一步 HandlerMapping 会帮我们处理好

    Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
    System.out.println(map);
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
    

3.4 参数名解析(两种方法)

总结:

  1. 只使用 javac 进行编译不会保留变量名
  2. (javap -c -v 字节码) ==> 反编译查看类的信息
  3. 如果编译时添加了 -parameters( javac -parameters 类) 可以生成参数表(会将参数放在 MethodParameters 中), 反射时就可以拿到参数名m
  4. 如果编译时添加了 -g ( javac -g 类) 可以生成调试信息, 但分为两种情况
    • 普通类, 会包含局部变量表(LocalVariableTable), 用 ASM 可以拿到参数名,例如通过ASM库 LocalVariableTableParameterNameDiscoverer
    • 接口, 不会包含局部变量表, 无法获得参数名**(本地变量表的局限性)**
      • 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
// 1. 反射获取参数名
Method foo = itheima.a22.Bean2.class.getMethod("foo", String.class, int.class);
for (Parameter parameter : foo.getParameters()) {
    System.out.println(parameter.getName());
}
// 2. 基于 LocalVariableTable 本地变量表
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));

3.5 对象绑定与类型转换

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

«interface»
Formatter
«interface»
Printer
«interface»
Parser
Converters
Set<GenericConverter>
«interface»
Converter
«interface»
ConversionService
FormattingConversionService
Adapter1
Adapter2
Adapter3
  • Printer 把其它类型转为 String
  • Parser 把 String 转为其它类型
  • Formatter 综合 Printer 与 Parser 功能(spring提供)
  • Converter 把类型 S 转为类型 T
  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
  • FormattingConversionService 利用其它们实现转换

3.5.2 底层第二套转换接口

«interface»
PropertyEditorRegistry
«interface»
PropertyEditor
  • PropertyEditor 把 String 与其它类型相互转换(jdk提供,类似于Formatter)
  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

3.5.3 高层接口与实现

«interface»
TypeConverter
SimpleTypeConverter
BeanWrapperImpl
DirectFieldAccessor
ServletRequestDataBinder
TypeConverterDelegate
«interface»
ConversionService
«interface»
PropertyEditorRegistry
  • 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverterDelegate 委派 ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)

    • 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
    • 再看有没有 ConversionService 转换
    • 再利用默认的 PropertyEditor 转换(DefaultFormattingConversionService)
    • 最后有一些特殊处理
  • SimpleTypeConverter 仅做类型转换

  • BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property(属性的set方法)

  • DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field

  • ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能

3.5.4 类型转换与数据绑定

3.5.4.1 SimpleTypeConverter
// 仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(number);
System.out.println(date);
3.5.4.2 BeanWrapperImpl
public static void main(String[] args) {
    // 利用反射原理, 为 bean 的属性赋值
    MyBean target = new MyBean(); // MyBean需要提供set方法
    BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
    wrapper.setPropertyValue("a", "10");
    wrapper.setPropertyValue("b", "hello");
    wrapper.setPropertyValue("c", "1999/03/04");
    System.out.println(target);
}
3.5.4.3 DirectFieldAccessor
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);
}
3.5.4.4 ServletRequestDataBinder
public static void main(String[] args) {
    // web 环境下数据绑定,ServletRequestDataBinder 的核心职责就是把 HTTP 请求里的参数(ServletRequest.getParameterMap())映射到目标对象(Bean)的属性上
    MyBean target = new MyBean(); // Bean休要提供set方法
    ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);	
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setParameter("a", "10");
    request.setParameter("b", "hello");
    request.setParameter("c", "1999/03/04");
	// 从请求中进行 Bean的绑定的属性绑定和类型转换(request的参数全是string类型)
    dataBinder.bind(new ServletRequestParameterPropertyValues(request));
    System.out.println(target);
}

3.5.5 数据绑定工厂

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 无转换功能"
    /*ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);*/

    // "2. 用 @InitBinder 转换"          PropertyEditorRegistry PropertyEditor
    /*InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
    ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);*/

    // "3. 用 ConversionService 转换"    ConversionService Formatter
    /*FormattingConversionService service = new FormattingConversionService();
    service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
    ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
    initializer.setConversionService(service);
    ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);*/

    // "4. 同时加了 @InitBinder 和 ConversionService"
    /*InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
    FormattingConversionService service = new FormattingConversionService();
    service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
    ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
    initializer.setConversionService(service);
    ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);*/

    // "5. 使用默认 ConversionService 转换"
    DefaultFormattingConversionService service = new DefaultFormattingConversionService();  // 默认的ConversionService,需要搭配注解使用,如 @DateTimeFormat(Spring)
	//	ApplicationConversionService service = new ApplicationConversionService();  // 默认的ConversionService,需要搭配注解使用,如 @DateTimeFormat(springboot )
    ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
    initializer.setConversionService(service);
    ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

    WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
    dataBinder.bind(new ServletRequestParameterPropertyValues(request));
    System.out.println(target);
}

ServletRequestDataBinderFactory 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
    • 控制器私有范围
  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
    • 公共范围
  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级
    1. 优先采用 @InitBinder 的转换器
    2. 其次使用 ConversionService 的转换器
    3. 使用默认转换器
    4. 特殊处理(例如有参构造)
  4. @DateTimeFormat 注解解析
    1. spring 通过 DefaultFormattingConversionService 识别该注解,并针对该注解进行类型的转换
    2. springboot 通过 ApplicationConversionService 识别该注解…
    3. 两个类都是继承 FormattingConversionService (第一套底层实现)

3.5.6 获取泛型参数(两种方法)

3.5.6.1 java api 获取泛型参数
// 示例类
class TeacherDao extends BaseDao<Teacher> {
}
  1. 反射调用 getGenericSuperclass 才能获得父类的泛型信息,getSuperclass 并不会获得泛型信息。Type 是 Java 中所有类型的通用表示
  2. 要通过 instanceof 进行类型转化的判断,将 Type 转为他的子类 ParameterizedType (专门用于表示带有参数的泛型类型)
  3. parameterizedType.getActualTypeArguments()[0]
// 获取继承的父类泛型信息
Type type = TeacherDao.class.getGenericSuperclass();
System.out.println(type); // com.itheima.a23.sub.BaseDao<com.itheima.a23.sub.Teacher>
// 获取参数的泛型
if (type instanceof ParameterizedType parameterizedType) {
    System.out.println(parameterizedType.getActualTypeArguments()[0]);	// class com.itheima.a23.sub.Teacher
}
3.5.6.2 spring api 获取泛型参数
  1. GenericTypeResolver.resolveTypeArgument(子类型class,父类型class)
  2. GenericTypeResolver.resolveTypeArguments(子类型class,父类型class)
Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
System.out.println(t);	// class com.itheima.a23.sub.Teacher

3.6 @ControllerAdvice 之 @InitBinder

ControllerAdvice 和 AOP 没有关系,这里取名Advice只是《功能增强》的形容,当 RequestMappingHandlerAdapter 进行 afterPropertiesSet 初始化时,会去扫描所有的 ControllerAdviceBean ,进行功能增强(例如:@InitBinder,@ModelAttribute,@ResponseBodyAdvice, @ExceptionHandler)

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

HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer 准备 @InitBinder 准备 @ModelAttribute 添加Model数据 invokeAndHandle 获取 args 有的解析器涉及 RequestBodyAdvice 有的解析器涉及数据绑定生成Model数据 args method.invoke(bean,args) 得到 returnValue 处理 returnValue 有的处理器涉及 ResponseBodyAdvice 添加Model数据,处理视图名,是否渲染等 获取 ModelAndView HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer
  • RequestMappingHandlerAdapter 在图中缩写为 HandlerAdapter
  • HandlerMethodArgumentResolverComposite 在图中缩写为 ArgumentResolvers
  • HandlerMethodReturnValueHandlerComposite 在图中缩写为 ReturnValueHandlers

总结:

  1. RequestMappingHandlerAdapter 不是直接调用 method的,他会先进行一些初始化,完成功能的扩展
  2. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法,存放在 initBinderAdviceCache 中
  3. RequestMappingHandlerAdapter 会以类为单位,在该** 类首次使用时解析**此类的 @InitBinder 方法,存放在 initBinderCache 中
  4. 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析,存放在各自的缓存 Map 中
  5. 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂

3.7 控制器方法执行流程

3.7.1 ServletInvocableHandlerMethod
ServletInvocableHandlerMethod
+invokeAndHandle(ServletWebRequest,ModelAndViewContainer)
HandlerMethod
bean
method
WebDataBinderFactory
ParameterNameDiscoverer
HandlerMethodArgumentResolverComposite
HandlerMethodReturnValueHandlerComposite

1.HandlerMethod 需要

  • bean 就是对应的 Controller 对象
  • method 是对应的方法对象

2.ServletInvocableHandlerMethod 继承 HandlerMethod, ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory 负责对象绑定、类型转换
  • ParameterNameDiscoverer 负责参数名解析
  • HandlerMethodArgumentResolverComposite 负责参数解析
  • HandlerMethodReturnValueHandlerComposite 负责处理返回值
3.7.2 初始化@ControllerAdvice
RequestMappingHandlerAdapter WebDataBinderFactory ModelFactory ModelAndViewContainer 初始化(Advice @InitBinder 补充数据绑定) 初始化(Advice @ModelAttribute 补充模型数据) 添加Model数据 RequestMappingHandlerAdapter WebDataBinderFactory ModelFactory ModelAndViewContainer
3.7.3 HandlerAdapter整体调用流程
RequestMappingHandlerAdapter ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer invokeAndHandle(调用) 获取 args 有的解析器涉及 RequestBodyAdvice 有的解析器涉及数据绑定生成模型数据 args method.invoke(bean,args) 得到 returnValue 处理 returnValue 有的处理器涉及 ResponseBodyAdvice 添加Model数据,处理视图名,是否渲染等 获取 ModelAndView RequestMappingHandlerAdapter ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer

3.8 @ControllerAdvice 之 @ModelAttribute

参数中的 @ModelAttribute 注解是进行对象的创建,然后根据数据绑定,给空对象的参数进行绑定,最终得到的结作为模型数据放入到 ModelAndView 中,名字可以根据 @ModelAttribute指定,不指定的话就以对象的类型首字母小写。是通过参数解析器完成的

方法中的 @ModelAttribute 注解是进行 Model 的创建,然后添加到 ModelAndViewContainer 中。名字也是可以根据注解指定,不指定就采用方法名。是通过 RequestMappingHandlerAdapter 初始化的时候根据 modelFactory 进行解析的

紫色区域为 RequestMappingHandlerAdapter 初始化时,进行的部分功能增强(对象绑定,模型数据),然后再进行的方法调用

HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer 准备 @InitBinder 准备 @ModelAttribute(Adapter的模型工厂进行解析) 添加Model数据 invokeAndHandle 获取 args 有的解析器涉及 RequestBodyAdvice 有的解析器涉及数据绑定生成Model数据 args method.invoke(bean,args) 得到 returnValue 处理 returnValue 有的处理器涉及 ResponseBodyAdvice 添加Model数据,处理视图名,是否渲染等 获取 ModelAndView HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer

总结:

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
  2. RequestMappingHandlerAdapter 会以类为单位,在该**类首次使用时解析**此类的 @ModelAttribute 方法
  3. 以上两种 @ModelAttribute 的解析结果都会**缓存来避免重复解析**(modelAttributeCache / modelAttributeAdviceCache)
  4. 控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂
  5. @InitBinder 和 @ModelAttribute 都是扩展方法,加在@ControllerAdvice和@Controller中是不一样的结果

3.9  返回值处理器(组合器设计模式) \textcolor{green}{3.9\ 返回值处理器(组合器设计模式)} 3.9 返回值处理器(组合器设计模式)

总结:

  1. 常见的返回值处理器
    • ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
    • 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
    • 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
      • 此时需找到默认视图名
    • 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
      • 此时需找到默认视图名
    • 返回值类型为 ResponseEntity
      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true,表示请求已经处理完毕,springmvc不需要在进行视图解析
    • 返回值类型为 HttpHeaders
      • 会设置 ModelAndViewContainer.requestHandled 为 true
    • 返回值添加了 @ResponseBody 注解时
      • 此时走 MessageConverter, 并设置 ModelAndViewContainer.requestHandled 为 true,表示请求已经处理完毕,springmvc不需要再进行视图解析
  2. **组合器设计模式(composite设计模式)**在 Spring 中的体现 + 1,在 Spring 中的体现(HandlerMethodReturnValueHandlerComposite 多个解析器组合)
  3. spring 常用的 java对象 和 json 数据的转换器:MappingJackson2HttpMessageConverter

3.10 MessageConverter(消息转换器)

总结:

  1. MessageConverter 的作用
    • @ResponseBody 是返回值处理器解析的,但具体转换工作是 MessageConverter 做的
    • MessageConverter 将输入或输出的对象转换为我们指定的 MediaType 类型
  2. 如何选择 MediaType
    1. 首先看 @RequestMapping 上有没有指定,服务端指定了返回类型(@RequestMapping(produces = “application/json”))
    2. 其次看 request 的 Accept 头有没有指定(客服端需要什么类型)
    3. 最后按返回值处理器中 MessageConverter 的添加顺序, 谁能谁先转换(都没有指定则按converter的顺序执行)
  3. 常见的消息转换器
    1. MappingJackson2HttpMessageConverter
    2. MappingJackson2XmlHttpMessageConverter
    3. FormHttpMessageConverter(HTML表单数据与 Java 对象之间进行转换)

3.11 @ControllerAdvice 之 @ResponseBodyAdvice

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

HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer 准备 @InitBinder 准备 @ModelAttribute 添加Model数据 invokeAndHandle 获取 args 有的解析器涉及 RequestBodyAdvice 有的解析器涉及数据绑定生成Model数据 args method.invoke(bean,args) 得到 returnValue 处理 returnValue 有的处理器涉及 ResponseBodyAdvice 添加Model数据,处理视图名,是否渲染等 获取 ModelAndView HandlerAdapter WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer

总结:

  1. 实现 ResponseBodyAdvice 接口,并且实现方法 supports 和 beforeBodyWrite
  2. supports 方法中不仅要查询该方法是否包含 @Responsebody 注解,还要去类上查找。利用 spring 提供的 AnnotationUtils 的 findAnnotation 可以查看注解及其包含的注解(@RestController = @ResponseBody + @Controller)
  3. ResponseBodyAdvice 返回响应体前包装(功能增强,统一包装返回数据的类型 ==> 统一响应结果Result)

3.12 异常解析器

ExceptionHandlerExceptionResolver总结:

  1. 它能够重用参数解析器、返回值处理器,实现组件重用
  2. 它能够支持嵌套异常
  3. @ExceptionHandler + @Controller 局部生效
  4. @ExceptionHandler + @ControllerAdvice 全局生效

3.13 @ControllerAdvice 之 @ExceptionHandler

总结:

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

3.14 Tomcat 异常处理

  • 我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?
  • 在 Spring Boot 中,是这么实现的:
    1. 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
    2. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置
      • 也可以通过 ErrorPageRegistrar 修改 Tomcat 服务器默认错误地址
    3. 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至 /error 这个地址
      • 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
    4. Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为 /error,所以处理异常的职责就又回到了 Spring
      • new BasicErrorController(new DefaultErrorAttributes(), new ErrorProperties());
      • 具体异常信息会由 DefaultErrorAttributes 封装好,其中 DefaultErrorAttributes 默认返回四个有关于异常的信息。如果需要更多的信息,可以通过设置 ErrorProperties 读取配置文件,返回想要的异常信息
    5. 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
    6. BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
      • 如果要的不是 text/html,走 MessageConverter 流程
      • 如果需要 text/html,走 mvc 流程,此时又分两种情况
        • 配置了 ErrorViewResolver,根据状态码去找 View
        • 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView

3.14.1 ErrorPageRegistrarBeanPostProcessor

@Bean // ⬅️修改了 Tomcat 服务器默认错误地址
public ErrorPageRegistrar errorPageRegistrar() { // 出现错误,会使用请求转发 forward 跳转到 error 地址
    return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
@Bean // ⬅️TomcatServletWebServerFactory 初始化前用它增强, 注册所有 ErrorPageRegistrar
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
    return new ErrorPageRegistrarBeanPostProcessor();
}

总结:

  1. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置
  2. 也可以通过 ErrorPageRegistrar 修改 Tomcat 服务器默认错误地址

3.14.2 BasicErrorController

@Bean // ⬅️ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
public BasicErrorController basicErrorController() {
    ErrorProperties errorProperties = new ErrorProperties();
    // 可以通过配置进行设置(返回的错误属性包含异常内容)。DefaultErrorAttributes,里面默认返回四个异常属性信息
    errorProperties.setIncludeException(true);
    return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
@Bean // ⬅️名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
public View error() {
    return new View() {
        @Override
        public void render(
            Map<String, ?> model, 
            HttpServletRequest request, 
            HttpServletResponse response
        ) throws Exception {
            System.out.println(model);
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().print("""
                    <h3>服务器内部错误</h3>
                    """);
        }
    };
}
@Bean // ⬅️收集容器中所有 View 对象, bean 的名字作为视图名
public ViewResolver viewResolver() {
    return new BeanNameViewResolver();
}

总结:

  1. BasicErrorController就是采用 Tomcat 的请求转发 forward 跳转到 error 地址

  2. Spring Boot 中 BasicErrorController 首先进行路径映射:KaTeX parse error: Expected '}', got 'EOF' at end of input: …ver.error.path:{error.path:/error}}

  3. 根据 MediaType 类型进行返回

    • ① 是 text/html 形式(可以通过 errorProperties 添加属性展示内容)

    • ② 是 json 形式返回(必须要添加一个名为 view 的视图。添加视图又必须添加一个视图名字解析器(收集 View 对象,bean 的名字作为视图名)

3.15 映射器和适配器(以前的实现方式)

3.15.1 BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
    return new BeanNameUrlHandlerMapping();
}

@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
    return new SimpleControllerHandlerAdapter();
}

@Bean("/c3")
public Controller controller3() {
    return (request, response) -> {
        response.getWriter().print("this is c3");
        return null;
    };
}

总结:

  1. BeanNameUrlHandlerMapping,以 ‘/’ 开头的 bean 的名字会被当作映射路径
    • 这些 bean 本身当作 handler,要求实现 Controller 接口
  2. SimpleControllerHandlerAdapter,调用 handler

3.15.2 RouterFunctionMapping 与 HandlerFunctionAdapter

@Bean
public RouterFunctionMapping routerFunctionMapping() {
    return new RouterFunctionMapping();
}

@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
    return new HandlerFunctionAdapter();
}

@Bean
public RouterFunction<ServerResponse> r1() {
//           							 ⬇️映射条件   					⬇️handler(需要实现HandlerFunction接口)
     return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("this is r1");
            }
        });
}

总结:

  1. RouterFunctionMapping, 通过 RequestPredicate 条件映射
    • handler 要实现 HandlerFunction 接口
  2. HandlerFunctionAdapter, 调用 handler

函数式控制器
a. RouterFunctionMapping, 通过 RequestPredicate 映射
b. handler 要实现 HandlerFunction 接口
c. HandlerFunctionAdapter, 调用 handler
对比
a. RequestMappingHandlerMapping, 以 @RequestMapping 作为映射路径
b. 控制器的具体方法会被当作 handler
c. RequestMappingHandlerAdapter, 调用 handler

3.15.3 SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
    SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
    // SimpleUrlHandlerMapping 没有 afterProperties 初始化方法,所以需要手动建立映射关系 映射路径(String)和ResourceHttpRequestHandler的关系
    Map<String, ResourceHttpRequestHandler> map 
        = context.getBeansOfType(ResourceHttpRequestHandler.class);
    handlerMapping.setUrlMap(map);
    return handlerMapping;
}

@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
    return new HttpRequestHandlerAdapter();
}

@Bean("/**")
public ResourceHttpRequestHandler handler1() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(List.of(new ClassPathResource("static/")));
    return handler;
}

@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(List.of(new ClassPathResource("images/")));
    return handler;
}

总结:

  1. SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集,没有 afterProperties 初始化方法,所以需要手动建立映射关系
    • 映射路径(String ) 和 ResourceHttpRequestHandler(控制器方法) 的关系
    • ResourceHttpRequestHandler 作为静态资源 handler
  2. HttpRequestHandlerAdapter, 调用此 handler
3.15.3.1  静态资源解析优化(责任链模式) \textcolor{green}{3.15.3.1\ 静态资源解析优化(责任链模式)} 3.15.3.1 静态资源解析优化(责任链模式)
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(List.of(new ClassPathResource("static/")));
    handler.setResourceResolvers(List.of(
        	// ⬇️缓存优化
            new CachingResourceResolver(new ConcurrentMapCache("cache1")),
        	// ⬇️压缩优化
            new EncodedResourceResolver(),
        	// ⬇️磁盘路径资源解析
            new PathResourceResolver()
    ));
    return handler;
}

总结:

  1. ResourceHttpRequestHandler 资源解析器默认只有 PathResourceResolver (磁盘路径解析),我们常用的资源解析器还有 CachingResoutceResolver(缓存文件)EncodedRescourceResolver(压缩文件,压缩文件需要自己生成压缩文件)
  2. 静态资源解析就是责任链设计模式的一个体现

3.15.4 WelcomePageHandlerMapping(欢迎页)

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
    Resource resource = context.getResource("classpath:static/index.html");
    return new WelcomePageHandlerMapping(null, context, resource, "/**");
    // WelcomePageHandlerMapping 会生成 ==> ParameterizableViewController ==> 这个处理器会固定跳转到视图 forward:index.html
}

@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
    return new SimpleControllerHandlerAdapter();	// SimpleControllerHandlerAdapter 识别Controller
}

总结:

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

3.16  映射器与适配器小结(五大映射器,四大适配器) \textcolor{red}{3.16\ 映射器与适配器小结(五大映射器,四大适配器)} 3.16 映射器与适配器小结(五大映射器,四大适配器)

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

3.17   M V C  处理流程 \textcolor{BLUE}{3.17\ MVC\ 处理流程} 3.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 方法(true就是被拦截,false表示放行)
    2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
      • @ControllerAdvice 全局增强点1️⃣:补充模型数据(@ModelAttribute)
      • @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器(@InitBinder)
      • 使用 HandlerMethodArgumentResolver 准备参数
        • @ControllerAdvice 全局增强点3️⃣:RequestBody 增强(@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 方法
      在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值