SpringMVC源码解析(一):web容器启动流程

SpringMVC源码系列文章

SpringMVC源码解析(一):web容器启动流程


目录

  • 一、SpringMVC全注解配置
    • 1、pom文件
    • 2、web容器初始化类(代替web.xml)
    • 3、SpringMVC配置类(代替springmvc.xml)
    • 4、测试Controller
  • 二、SpringServletContainerInitializer
    • 1、web容器初始化入口
    • 2、SpringServletContainerInitializer的作用解析
  • 三、自定义配置类的加载
    • 1、AbstractDispatcherServletInitializer注册前端控制器
      • 1.1、创建web注解容器
      • 1.2、创建DispatcherServlet对象
      • 1.3、注册过滤器
    • 2、DispatcherServlet初始化
  • 四、@EnableWebMvc解析
    • 1、WebMvcConfigurer接口
    • 2、DelegatingWebMvcConfiguration
    • 3、WebMvcConfigurationSupport(🔥重点)
      • 3.1、处理器映射器和处理器适配器
      • 3.2、RequestMappingHandlerMapping映射器
        • 3.2.1、实例化
          • 3.2.1.1、获取所有拦截器
          • 3.2.1.2、获取所有跨域配置
        • 3.2.2、初始化
          • 3.2.2.1、获取候选bean
          • 3.2.2.2、获取HandlerMethod并统计数量
      • 3.3、RequestMappingHandlerAdapter适配器
        • 3.3.1、实例化
          • 3.3.1.1、获取消息转换器
        • 3.3.2、初始化
          • 3.3.2.1、初始化@ControllerAdvice
          • 3.3.2.2、HandlerMethodArgumentResolver方法参数解析器
          • 3.3.2.3、HandlerMethodReturnValueHandler返回值处理器
      • 3.4、HandlerExceptionResolver异常处理器
        • 3.4.1、加载自定义异常处理器
  • 总结

一、SpringMVC全注解配置

1、pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.xc.mvc</groupId><artifactId>springmvc</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><dependencies><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.27</version></dependency><!-- ServletAPI --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- Jackson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><!--配置Maven中Java的编译版本 --><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>
</project>

2、web容器初始化类(代替web.xml)

/*** web工程的初始化类,用来代替web.xml* 以下配置的都是以前在web.xml中配置的*/
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** Spring的配置,目前不涉及Spring,这里设置为空*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class<?>[0];}/*** SpringMVC的配置*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class<?>[]{SpringMVCConfig.class};}/*** 用于配置DispatcherServlet的映射路径*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}/*** 注册过滤器*/@Overrideprotected Filter[] getServletFilters() {// 字符编码过滤器CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();characterEncodingFilter.setEncoding("UTF-8");characterEncodingFilter.setForceEncoding(true);// HiddenHttpMethodFilter 用于支持 PUT、DELETE 等 HTTP 请求HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};}
}

3、SpringMVC配置类(代替springmvc.xml)

// 将当前类标识为一个配置类
@Configuration
// 仅仅扫描@Controller、@RestController
@ComponentScan(value = "com.xc",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, RestController.class})},// 默认扫描 @Component @Repository, @Service, or @ControlleruseDefaultFilters = false 
)
// mvc注解驱动
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {// 拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {MyInterceptor myInterceptor = new MyInterceptor();registry.addInterceptor(myInterceptor).addPathPatterns("/**");}
}

拦截器

public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("处理器方法前调用");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("处理器方法后调用");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("渲染完成后调用");}
}

4、测试Controller

// 接受User对象修改并返回
@PostMapping("/test")
@ResponseBody
public User test(@RequestBody User user) {// 修改名字为李四然后返回给前台user.setName("李四");System.out.println(user);return user;
}

启动tomcat发送请求结果

在这里插入图片描述

二、SpringServletContainerInitializer

1、web容器初始化入口

  • 在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等
    • servlet规范中通过ServletContainerInitializer接口实现此功能
  • 需要在对应的jar包的META-INF/services目录创建一个名为javax.servlet.ServletContainerInitializer的文件
    • 文件内容指定具体的ServletContainerInitializer实现类

在这里插入图片描述

ps:JDK会自动加载META-INF/services目录下的类(深入理解SPI机制)
容器在启动应用的时候,会扫描当前应用每一个jar包里面META-INF/services指定的实现类(tomcat默认读取)

2、SpringServletContainerInitializer的作用解析

  • @HandlesTypes注解作用
    • 获取到所有的实现了WebApplicationInitializer接口的类,然后赋值给onStartup方法的webAppInitializerClasses参数
    • 官方话术为,获取当前类(SpringServletContainerInitializer)感兴趣的类(WebApplicationInitializer)信息
  • 获取到WebApplicationInitializer实现类的Class集合,反射创建对象,遍历调用对象的onStartup方法
// SpringServletContainerInitializer类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();// 如果找不到WebApplicationInitializer的实现类,webAppInitializerClasses就为nullif (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// 判断实现类不是接口抽象类,即正常的接口实现类if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {// 反射创建对象,并添加到集合中,后面统一遍历调用onStartup方法initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());}catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}// 集合为空,证明没找到实现类,直接返回if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}// 排序,证明WebApplicationInitializer的实现类有先后顺序AnnotationAwareOrderComparator.sort(initializers);for (WebApplicationInitializer initializer : initializers) {// 调用WebApplicationInitializer接口实现类的onStartup方法initializer.onStartup(servletContext);}}
}// WebApplicationInitializer接口
public interface WebApplicationInitializer {/*** 初始化此Web应用程序所需的任何Servlet、过滤器、侦听器上下文参数和属性配置给定的ServletContext*/void onStartup(ServletContext servletContext) throws ServletException;
}

WebApplicationInitializer接口与自定义配置类WebAppInitalizer(代替web.xml)关系

  • WebApplicationInitializer初始化核心接口,onStartup方法初始化Servelt、过滤器等
  • WebAppInitalizer即为WebApplicationInitializer的实现类,也就是SpringServletContainerInitializer要找的感兴趣的类,获取到多个放到集合initializer中,然后排序,最后遍历调用onStartup方法

在这里插入图片描述

  总结SpringServletContainerInitializer作用:加载自定义的WebApplicationInitializer初始化核心接口的实现类WebAppInitializer,调用onStartup方法来实现web容器初始化。

在这里插入图片描述

三、自定义配置类的加载

自定义配置类WebAppInitializer(代替web.xml)的类图如下:

在这里插入图片描述
  由上一节可知,web容器初始化工作会调用自定义配置类的onStartup方法,那就是根据类图从下往上找onStartup方法调用,WebAppInitializer和AbstractAnnotationConfigDispatcherServletInitializer中都没有onStartup方法,那么首先进入AbstractDispatcherServletInitializer重写的onStartup方法,核心内容注册前端控制器

在这里插入图片描述

1、AbstractDispatcherServletInitializer注册前端控制器

  • getServletMappings():调用自定义配置类配置DispatcherServlet的映射路径的方法
  • getServletFilters():调用自定义配置类注册过滤器的方法
// AbstractDispatcherServletInitializer类的方法
protected void registerDispatcherServlet(ServletContext servletContext) {// 获取servlet名称,常量“dispatcher”String servletName = getServletName();// 创建一个web应用程序子容器WebApplicationContext servletAppContext = createServletApplicationContext();// 创建DispatcherServlet对象,将上下文设置到dispatcherServlet中FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);// 设置servlet容器初始化参数(这里不设置一般默认为null)dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());// 把servlet添加到Tomcat容器中ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);if (registration == null) {throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +"Check if there is another servlet registered under the same name.");}// 将前端控制器初始化提前到服务器启动时,否则调用时才会初始化registration.setLoadOnStartup(1);// 添加servlet映射,拦截请求// 调用自定义配置类重写的getServletMappings方法registration.addMapping(getServletMappings());// 设置是否支持异步,默认trueregistration.setAsyncSupported(isAsyncSupported());// 获取所有的过滤器getServletFilters方法// 调用自定义配置类重写的getServletMappings方法Filter[] filters = getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {registerServletFilter(servletContext, filter);}}// 空方法,可以再对dispatcherServlet进行处理customizeRegistration(registration);
}

1.1、创建web注解容器

  • getServletConfigClasses():调用自定义配置类配置SpringMVC配置的方法
// AbstractAnnotationConfigDispatcherServletInitializer类方法
protected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();// 调用自定义配置类的设置子容器配置文件的方法Class<?>[] configClasses = getServletConfigClasses();if (!ObjectUtils.isEmpty(configClasses)) {context.register(configClasses);}return context;
}

1.2、创建DispatcherServlet对象

  • 创建DispatcherServlet对象,传入web容器
// AbstractDispatcherServletInitializer类方法
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {return new DispatcherServlet(servletAppContext);
}

1.3、注册过滤器

  • 将过滤器添加到Tomcat容器的过滤器集合中
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {String filterName = Conventions.getVariableName(filter);Dynamic registration = servletContext.addFilter(filterName, filter);...registration.setAsyncSupported(isAsyncSupported());registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());return registration;
}
  • 对于注册过滤器是Tomcat的内容,之前文章Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper中有介绍

在这里插入图片描述

小结一下

  • 目前为止代替web.xml的配置类中内容加载完成
    • 创建web注解容器,此时只是创建出来,还没有初始化
    • 创建DispatcherServlet并设置映射路径
    • 注册过滤器
  • 接下来的入口在DispatcherServlet这里,因为其本质是Servlet,那么就会涉及到Tomcat初始化Servelt

2、DispatcherServlet初始化

DispatcherServlet类图如下:

在这里插入图片描述

  Tomcat启动容器加载Servelt(这里是DispatcherServlet)并初始化,就会调用到这里。之前文章Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper中有介绍。

在这里插入图片描述

  • initWebApplicationContext方法内核心内容就是调用configureAndRefreshWebApplicationContext这个方法
  • 看这方法名字中文直译:配置和刷新web容器
  • 核心内容就是最后一句,web容器的刷新

在这里插入图片描述

  • 容器刷新抽象类AbstractApplicationContextrefresh方法,看过spring源码的应该很熟悉
  • web容器spring容器都间接继承了AbstractApplicationContext,容器刷新都调用如下方法
  • 关于spring的源码Spring源码解析(三):bean容器的刷新之前介绍

在这里插入图片描述

  容器初始化时候有个很重要的bean工厂后置处理器ConfigurationClassPostProcessor,作用就是解析@Configuration,@Import,@ComponentScan,@Bean等注解给bean容器添加bean定义,之前文章Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor有介绍。

  接下来的入口就在自定义g配置类SpringMVCConfi这里,因为它的配置类注解@Configuration(也是@Component),@ComponentScan(扫描@Controller注解)@EnableWebMvc(导入DelegatingWebMvcConfiguration.class)注解都会被扫描解析到。

在这里插入图片描述

四、@EnableWebMvc解析

@EnableWebMvc

在这里插入图片描述

1、WebMvcConfigurer接口

  在讲DelegatingWebMvcConfiguration之前先说下WebMvcConfigurer接口,因为下面内容都是围绕着WebMvcConfigurer接口展开的。

  WebMvcConfigurer是一个接口,它提供了一种扩展SpringMVC配置的方式。通过实现WebMvcConfigurer接口,可以定制化SpringMVC的配置例如添加拦截器、跨域设置、方法参数解析器、返回值处理、消息转换器、异常处理器

public interface WebMvcConfigurer {...// 配置拦截器default void addInterceptors(InterceptorRegistry registry) {}// 配置跨域default void addCorsMappings(CorsRegistry registry) {}// 配置视图解析器default void configureViewResolvers(ViewResolverRegistry registry) {}// 配置方法参数解析器(解析@RequestBody就是通过HandlerMethodArgumentResolver接口实现的)default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}// 配置方法返回值(解析@ResponseBody就是通过HandlerMethodReturnValueHandler接口实现的)default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}// 配置消息转换器(此时可能还没有转换器)default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}// 扩展消息转换器(至少已存在默认转换器)default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}// 配置异常处理器default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}...
}

2、DelegatingWebMvcConfiguration

DelegatingWebMvcConfiguration的类图如下:
在这里插入图片描述

  • 如果开发者或者第三方想要配置拦截器、消息转换器的等配置,只要实现WebMvcConfigurer接口重写对应方法即可
  • 通过setConfigurers方法讲所有WebMvcConfigurer接口实现类注入进来,放入configurers的List<WebMvcConfigurer> delegates属性中
  • 下面会讲到什么时候触发调用DelegatingWebMvcConfiguration中重写的MVC配置方法
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {// WebMvcConfigurerComposite实现WebMvcConfigurer,内部有个WebMvcConfigurer集合private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();// 注入一组WebMvcConfigurer,这些WebMvcConfigurer由开发人员提供,或者框架其他部分提供@Autowired(required = false)public void setConfigurers(List<WebMvcConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers);}}...// 如下方法都是重写父类WebMvcConfigurationSupport// 与WebMvcConfigurer接口中的方法一样,配置拦截器、跨域配置等@Overrideprotected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {this.configurers.configureDefaultServletHandling(configurer);}@Overrideprotected void addInterceptors(InterceptorRegistry registry) {this.configurers.addInterceptors(registry);}@Overrideprotected void addCorsMappings(CorsRegistry registry) {this.configurers.addCorsMappings(registry);}@Overrideprotected void configureViewResolvers(ViewResolverRegistry registry) {this.configurers.configureViewResolvers(registry);}@Overrideprotected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {this.configurers.configureMessageConverters(converters);}@Overrideprotected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);}...
}

  调用DelegatingWebMvcConfiguration重写的MVC配置方法实际就是对应的配置添加到对应的注册器中。如所有的拦截器都会被添加到InterceptorRegistry(拦截器注册器)、所有跨域配置则会被添加到CorsRegistry(跨域注册器),不用说对应的注册器中肯定维护着对应的配置集合。

// WebMvcConfigurerComposite类
class WebMvcConfigurerComposite implements WebMvcConfigurer {private final List<WebMvcConfigurer> delegates = new ArrayList<>();@Overridepublic void addInterceptors(InterceptorRegistry registry) {for (WebMvcConfigurer delegate : this.delegates) {delegate.addInterceptors(registry);}}@Overridepublic void addCorsMappings(CorsRegistry registry) {for (WebMvcConfigurer delegate : this.delegates) {delegate.addCorsMappings(registry);}}@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {for (WebMvcConfigurer delegate : this.delegates) {delegate.configureViewResolvers(registry);}}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {for (WebMvcConfigurer delegate : this.delegates) {delegate.configureMessageConverters(converters);}}
}

3、WebMvcConfigurationSupport(🔥重点)

  在上面说到WebMvcConfigurationSupport类中定义了与WebMvcConfigurer接口一样的配置方法,都是空实现,由子类DelegatingWebMvcConfiguration重写,遍历所有WebMvcConfigurer的实现类,将对应配置添加到对应注册器中。

  另外一方面在WebMvcConfigurationSupport类中有很多@Bean方法,即bean定义,返回值即为创建的bean对象。其中有两个很重要,映射器RequestMappingHandlerMapping适配器RequestMappingHandlerAdapter

3.1、处理器映射器和处理器适配器

映射器HandlerMapping

public interface HandlerMapping {/*** 获取执行器链(包括Handler和拦截器)*/@NullableHandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
  • HandlerMapping映射器作用:主要是根据request请求匹配/映射上能够处理当前request的Handler
  • 定义Handler的方式有很多
    • 最常用的@Controller,结合@RequestMapping("/test")
    • 实现Controller接口,实现handleRequest方法,结合@Component(“/test”)
    • 实现HttpRequestHandler接口,实现handleRequest方法,结合@Component(“/test”)
  • 以上三种方式都需要生成请求和Handler的映射
    • 所以抽象出一个接口:HandlerMapping,有很多实现类
    • @RequestMapping注解的Handler使用RequestMappingHandlerMapping处理
    • 其他方式会使用BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping

适配器HandlerAdapter

public interface HandlerAdapter {/*** 因为有多个HandlerMapping和HandlerAdapter* 对于HandlerAdapter是否支持对应的HandlerMapping,通过此方法判断*/boolean supports(Object handler);/*** 具体调用Hangder的方法*/@NullableModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
  • 上一步中不同映射器通过请求映射到的Handler不确定类型
    • @RequestMapping注解方式获取到的是具体的Method
    • 其他实现接口方式获取到的是具体的Class
  • 此时拿到Handler去执行具体的方法时候,方式是不统一
  • 那么适配器就出现了
  • RequestMappingHandlerAdapter就是处理@RequestMapping注解的Handler

3.2、RequestMappingHandlerMapping映射器

  • 一句话解释它:解析@RequestMapping注解
3.2.1、实例化
// WebMvcConfigurationSupport类方法
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {// 创建RequestMappingHandlerMapping对象RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();// 设置排序为0,以后会遍历所有HandlerMapping,排序0的话,则会先遍历// 简单说@RequestMaping注解使用概率大,先用RequestMappingHandlerMapping处理mapping.setOrder(0);// 设置所有的拦截器,并排序mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));mapping.setContentNegotiationManager(contentNegotiationManager);// 设置所有的跨域配置mapping.setCorsConfigurations(getCorsConfigurations());// 省略各种匹配url的属性,如url正则匹配等等,我们这次只考虑url正常匹配...return mapping;
}
3.2.1.1、获取所有拦截器

  这里先创建拦截器注册器InterceptorRegistry,然后调用DelegatingWebMvcConfiguration重写WebMvcConfigurationSupport的添加拦截器方法addInterceptors,这样所有拦截器就都会被添加到InterceptorRegistry registry中。最后将我们自定义的拦截器组成MappedInterceptor

// WebMvcConfigurationSupport
protected final Object[] getInterceptors(FormattingConversionService mvcConversionService,ResourceUrlProvider mvcResourceUrlProvider) {if (this.interceptors == null) {InterceptorRegistry registry = new InterceptorRegistry();// 这里就是调用DelegatingWebMvcConfiguration重写的方法addInterceptors(registry);// 这是两个设置setAttribute值的拦截器,不用深入研究registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));// 这里会将上面我们自己设置的拦截器包装一层返回this.interceptors = registry.getInterceptors();}return this.interceptors.toArray();
}

  这里将我们所有自定义拦截器都这样包装一层,因为是以后为了统一管理,这个MappedInterceptor拦截器里面有个很重要方法matches(此次请求是否匹配当前拦截器),就是筛选拦截器链使用的。

在这里插入图片描述

3.2.1.2、获取所有跨域配置

  也是先创建跨域注册器CorsRegistry,然后调用然后调用DelegatingWebMvcConfiguration重写WebMvcConfigurationSupport的添加跨域方法addCorsMappings,也是添加到CorsRegistry registry中。

// WebMvcConfigurationSupport
protected final Map<String, CorsConfiguration> getCorsConfigurations() {if (this.corsConfigurations == null) {CorsRegistry registry = new CorsRegistry();addCorsMappings(registry);this.corsConfigurations = registry.getCorsConfigurations();}return this.corsConfigurations;
}
3.2.2、初始化

RequestMappingHandlerMapping的复杂类图看一下(有删减)

  @RequestMapping注解肯定是在容器启动时候解析的,那么这个工作就放在RequestMappingHandlerMapping这个bean对象的初始化阶段来完成。之前文章Spring源码解析(四):单例bean的创建流程有介绍过,bean对象创建后会调用各种初始化方法,其实就包括调用InitializingBean接口afterPropertiesSet方法来实现初始化

在这里插入图片描述

  • RequestMappingHandlerMapping实现了afterPropertiesSet方法如下
// RequestMappingHandlerMapping类方法
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {// 创建RequestMappingInfo对象this.config = new RequestMappingInfo.BuilderConfiguration();this.config.setTrailingSlashMatch(useTrailingSlashMatch());this.config.setContentNegotiationManager(getContentNegotiationManager());if (getPatternParser() != null) {this.config.setPatternParser(getPatternParser());Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,"Suffix pattern matching not supported with PathPatternParser.");}else {this.config.setSuffixPatternMatch(useSuffixPatternMatch());this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());this.config.setPathMatcher(getPathMatcher());}// 调用父类实现的afterPropertiesSet方法super.afterPropertiesSet();
}
  • 父类AbstractHandlerMethodMapping实现了afterPropertiesSet方法如下
// AbstractHandlerMethodMapping类方法/*** 在初始化时检测处理程序方法*/
@Override
public void afterPropertiesSet() {initHandlerMethods();
}/*** 扫描 ApplicationContext 中的 Bean,检测并注册处理程序方法*/
protected void initHandlerMethods() {// 获取所有的bean对象并遍历for (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// 筛选候选的beanprocessCandidateBean(beanName);}}// getHandlerMethods()获取请求路径与具体Controller方法的映射关系handlerMethodsInitialized(getHandlerMethods());
}
3.2.2.1、获取候选bean
// AbstractHandlerMethodMapping类方法
protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {// 获取bean对象的Class类型beanType = obtainApplicationContext().getType(beanName);}catch (Throwable ex) {...}// 判断是否处理程序if (beanType != null && isHandler(beanType)) {// 查找处理程序的方法detectHandlerMethods(beanName);}
}
  • 根据类上@Controller@RequestMapping注解判断是否为处理程序
// RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
  • 查询处理程序的方法
    1. 此时获取到的handler已经确定有@Controller或者@RequestMapping
    2. 遍历handler类下所有的Method
      • 判断Method方法上是否有@RequestMapping注解
      • 有注解则将注解内的属性包装成一个类:RequestMappingInfo
    3. 返回一个map集合methods
      • key为有@RequestMapping注解的Method对象
      • value为RequestMappingInfo对象
// AbstractHandlerMethodMapping类方法
protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {// 类如果被代理,获取真正的类型Class<?> userType = ClassUtils.getUserClass(handlerType);Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {...}});...		// 注册Handler方法(将请求路径、RequestMappingInfo、Controller#Method统一放一起)methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);registerHandlerMethod(handler, invocableMethod, mapping);});}
}
// RequestMappingHandlerMapping类方法
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);...return info;
}
  • getMappingForMethod方法的实现就交给具体子类RequestMappingHandlerMapping了
  • getMappingForMethod方法核心内容:筛选Controller类@RequestMapping注解方法

在这里插入图片描述

registerHandlerMethod:注册Handler方法(将请求路径、RequestMappingInfo、Controller#Method统一放到MappingRegistry对象中)

  • 核心方法为AbstractHandlerMethodMapping抽象类的内部类MappingRegistry类的register方法
// AbstractHandlerMethodMapping的内部类MappingRegistry类方法
class MappingRegistry {private final Map<T, MappingRegistration<T>> registry = new HashMap<>();private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {// handler(Controller对象)与有@RequestMapping的Method组成对象HandlerMethodHandlerMethod handlerMethod = createHandlerMethod(handler, method);// 校验@RequestMapping注解内容是否注册过validateMethodMapping(handlerMethod, mapping);// 获取@RequestMapping注解映射的路径,因为可以设置多个,所以这里是集合Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);for (String path : directPaths) {this.pathLookup.add(path, mapping);}...// 获取@CrossOrigin获取CorsConfiguration,并初始化跨域的默认值,最后跨域配置添加到corsLookupCorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {corsConfig.validateAllowCredentials();this.corsLookup.put(handlerMethod, corsConfig);}// 各类属性配置组成对象MappingRegistration,然后添加到registry中this.registry.put(mapping,new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));}finally {this.readWriteLock.writeLock().unlock();}}
}
  • MappingRegistry(映射注册器),以后的mapping映射什么都放这里
    • MultiValueMap<String, T> pathLookup
      • key为@RequestMapping注解的映射路径
      • value为RequestMappingInfo对象(@RequestMapping注解属性组成的对象)
    • Map<HandlerMethod, CorsConfiguration> corsLookup
      • key为HandlerMethod
      • value为解析@CrossOrigin注解的跨域配置
    • Map<T, MappingRegistration> registry
      • key为RequestMappingInfo对象
      • value为MappingRegistration对象(RequestMappingInfo、HandlerMethod、CorsConfiguration组成的对象)
  • 校验RequestMappingInfo(@RequestMapping属性对象)是否存在
    • 存在的话这里就会抛出异常There is already 'xxx' bean method(这个大家应该很熟悉)
  • 注意:以上步骤都是在readWriteLock锁内完成的,以防多个线程注册映射对象重复
3.2.2.2、获取HandlerMethod并统计数量
  • 从mappingRegistry中获取map集合即为上面所说MappingRegistry对象的registry属性
// AbstractHandlerMethodMapping类方法
public Map<T, HandlerMethod> getHandlerMethods() {this.mappingRegistry.acquireReadLock();try {return Collections.unmodifiableMap(this.mappingRegistry.getRegistrations().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod)));}finally {this.mappingRegistry.releaseReadLock();}
}
  • 获取到上面拿到的handlerMethods,打印数量
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {// 获取到上面拿到的值,打印数量int total = handlerMethods.size();if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {logger.debug(total + " mappings in " + formatMappingName());}
}

3.3、RequestMappingHandlerAdapter适配器

  • 一句话解释它:拿到RequestMappingHandlerMapping解析出的HandlerMethod去调用具体的Method
3.3.1、实例化
// WebMvcConfigurationSupport类方法private static final boolean jackson2Present;
// 加载对应的类,能加载成功方true,不能加载成功,表示没有这个类,没有导入包,返回false
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {// 创建RequestMappingHandlerAdapter对象RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();adapter.setContentNegotiationManager(contentNegotiationManager);// 设置消息转换器adapter.setMessageConverters(getMessageConverters());adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));adapter.setCustomArgumentResolvers(getArgumentResolvers());adapter.setCustomReturnValueHandlers(getReturnValueHandlers());// 如果导入Jackson包,则添加if (jackson2Present) {// 处理jackson中的@JsonView的字段,过滤输出的字段,具体实现以后单独讲adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}// 省略异步配置,以后再研究...return adapter;
}
3.3.1.1、获取消息转换器

  先调用DelegatingWebMvcConfiguration重写的方法,也就是遍历所有WebMvcConfigurer实现类,调用他们的configureMessageConverters方法,新增的消息转换器都会添加到messageConverters集合中。如果开发者和第三方都没有添加,那么设置默认的消息转换器,设置完以后,再调用扩展方法,也就是遍历所有WebMvcConfigurer实现类,调用他们的extendMessageConverters方法,对消息转换器做最后修改

// WebMvcConfigurationSupport
protected final List<HttpMessageConverter<?>> getMessageConverters() {if (this.messageConverters == null) {this.messageConverters = new ArrayList<>();// 这里就是调用DelegatingWebMvcConfiguration重写的方法,配置消息转换器configureMessageConverters(this.messageConverters);if (this.messageConverters.isEmpty()) {// 如果上面没有添加消息转换器,这里添加默认的消息转换器addDefaultHttpMessageConverters(this.messageConverters);}// 这里就是调用DelegatingWebMvcConfiguration重写的方法,扩展消息转化器extendMessageConverters(this.messageConverters);}return this.messageConverters;
}

  加载jackson里的类,能加载成功jackson2Present返回true,不能加载成功,表示没有这个类,没有导入包,返回jackson2Present返回false,然后去判断是否导入Google、JDK自带的处理JSON类。一般我们会导入Jackson,那么这里会添加MappingJackson2HttpMessageConverter消息转换器。

// WebMvcConfigurationSupport类方法
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {// 默认消息转换器messageConverters.add(new ByteArrayHttpMessageConverter());messageConverters.add(new StringHttpMessageConverter());messageConverters.add(new ResourceHttpMessageConverter());messageConverters.add(new ResourceRegionHttpMessageConverter());...// jacksonif (jackson2Present) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));}// Google Gson 库中的一个核心类,Java对象与JSON 格式字符串进行相互转换else if (gsonPresent) {messageConverters.add(new GsonHttpMessageConverter());}// JDK 类库JSON类else if (jsonbPresent) {messageConverters.add(new JsonbHttpMessageConverter());}...
}
3.3.2、初始化

RequestMappingHandlerAdapter的类图如下(有删减)

  与RequestMappingHandlerMapping一样,RequestMappingHandlerAdapter对象初始化就会调用InitializingBean接口afterPropertiesSet方法来实现初始化

在这里插入图片描述

  • RequestMappingHandlerAdapter实现了afterPropertiesSet方法如下
// RequestMappingHandlerAdapter类方法
@Override
public void afterPropertiesSet() {// 初始化@ControllerAdviceinitControllerAdviceCache();// 设置默认的方法参数解析器if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// 设置默认的返回值处理器if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}
3.3.2.1、初始化@ControllerAdvice
  • 遍历所有的bean对象,获取有@ControllerAdvice注解的bean
  • 通过@ControllerAdvice内容和bean名称创建对象ControllerAdviceBean,添加到集合中
  • 再从集合中筛选出bean对象实现接口RequestBodyAdviceResponseBodyAdvice
    • RequestBodyAdvice请求增强器:允许请求体被读取并转换为对象
    • ResponseBodyAdvice响应增强器:允许在执行@ResponseBody后自定义返回数据
// RequestMappingHandlerAdapter类方法
private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}// 获取所有@ControllerAdvice注解的bean,创建成ControllerAdviceBean对象的集合List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}...		// 筛选实现RequestBodyAdvice或ResponseBodyAdvice的adviceBeanif (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}}if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}...
}public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {ListableBeanFactory beanFactory = context;if (context instanceof ConfigurableApplicationContext) {beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();}List<ControllerAdviceBean> adviceBeans = new ArrayList<>();// 遍历所有的bean对象for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {if (!ScopedProxyUtils.isScopedTarget(name)) {// 寻找有@ControllerAdvice注解ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class);if (controllerAdvice != null) {// 创建ControllerAdviceBean对象添加到集合中adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice));}}}OrderComparator.sort(adviceBeans);return adviceBeans;
}
3.3.2.2、HandlerMethodArgumentResolver方法参数解析器
public interface HandlerMethodArgumentResolver {/*** 此解析器是否支持给定的方法参数*/boolean supportsParameter(MethodParameter parameter);/*** 将方法参数解析为给定请求的参数值*/@NullableObject resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}
  • 方法参数解析器有很多,差不多一个注解就对应一个参数解析器

在这里插入图片描述

3.3.2.3、HandlerMethodReturnValueHandler返回值处理器
public interface HandlerMethodReturnValueHandler {/*** 此处理程序是否支持给定的方法返回类型*/boolean supportsReturnType(MethodParameter returnType);/*** 通过向模型添加属性并设置视图或设置*/void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;}
  • 返回值处理器也有很多,我们只研究熟悉常用的就可以

在这里插入图片描述

  • RequestResponseBodyMethodProcessor类既是@RequestBody解析器又是@ResponseBody解析器

在这里插入图片描述

  对应方法参数解析器和返回值处理器这里就不展开说了,只要知道项目启动会加载这些东西即可,下一篇文章请求的执行流程再进入源码研究。

3.4、HandlerExceptionResolver异常处理器

  • 一句话解释它:Hander异常抛错后调用的方法

  与消息转换器很像,先遍历所有WebMvcConfigurer实现类configureHandlerExceptionResolvers中新增的异常处理器,如果没有开发者或者第三方新增,那么添加默认的的异常处理器,再调用扩展方法,也就是遍历所有WebMvcConfigurer实现类,调用他们的extendHandlerExceptionResolvers方法,对异常处理器做最后修改

// WebMvcConfigurationSupport类方法
@Bean
public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {// 创建空集合List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();// 这里就是调用DelegatingWebMvcConfiguration重写的方法// 其实就是寻找WebMvcConfigurer实现类是否添加异常处理器configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}// 这里就是调用DelegatingWebMvcConfiguration重写的方法,扩展异常处理器extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;
}
  • ExceptionHandlerExceptionResolver:这个异常处理器内包含所有的自定义异常处理器
    • 因为这个对象不是bean对象,这里手动调用初始化接口的方法afterPropertiesSet
  • 最后添加默认异常处理器DefaultHandlerExceptionResolver
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,ContentNegotiationManager mvcContentNegotiationManager) {// 这个异常处理器内包含所有的自定义异常处理器ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}// 手动调用初始化接口的初始化方法exceptionHandlerResolver.afterPropertiesSet();exceptionResolvers.add(exceptionHandlerResolver);// ResponseStatus异常处理器ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);// 默认异常处理器exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
3.4.1、加载自定义异常处理器
// ExceptionHandlerExceptionResolver类方法
@Override
public void afterPropertiesSet() {// 初始化异常处理增强类initExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}
  • 筛选异常处理器类:@ControllerAdvice类且方法上存在@ExceptionHandler
// ExceptionHandlerExceptionResolver类方法
private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}// 获取所有@ControllerAdvice注解的bean,创建成ControllerAdviceBean,添加到集合中List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 筛选方法上@ExceptionHandler注解ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}
}// ExceptionHandlerMethodResolver类属性和方法
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);public ExceptionHandlerMethodResolver(Class<?> handlerType) {for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {addExceptionMapping(exceptionType, method);}}
}

  至此,SpringMVC启动重要的组件都介绍完毕。

总结

  • 加载继承AbstractAnnotationConfigDispatcherServletInitializer的MVC配置类(开发者创建,代替web.xml)
  • 既然代替web.xml那么这个配置类可以设置DispatcherServlet的映射路径注册过滤器
  • 父类AbstractAnnotationConfigDispatcherServletInitializer里面会创建web注解容器创建DispatcherServlet对象添加过滤器到Tomcat容器的过滤器集合中
    • DispatcherServlet初始化触发了web容器的刷新,加载所有@Controller注解的bean
  • 如果开发者或者第三方想要配置拦截器消息转换器的等配置,只要实现WebMvcConfigurer接口重写对应方法即可
  • 解析@RequestMapping注解(根据注解属性创建对象RequestMappingInfo)
    • 遍历所有的bean,获取类上是否有@Controller@RequestMapping注解的bean
    • 再遍历所有的方法,筛选方法上是否有@RequestMapping注解
    • 最后注册成两个map存起来,以后映射方法从这里获取
      • MappingRegistry#MultiValueMap<String, T> pathLookup
        • key为@RequestMapping注解的映射路径
        • value为RequestMappingInfo对象
      • MappingRegistry#Map<T, MappingRegistration> registry
        • key为RequestMappingInfo对象
        • value为MappingRegistration对象(包含Controller#Method)
  • 解析@ControllerAdvice注解
    • 遍历所有的bean,筛选类上@ControllerAdvice注解的bean
      • 如果bean实现接口RequestBodyAdviceResponseBodyAdvice,那就是请求响应数据增强器
      • 如果bean有方法存在@ExceptionHandler,那就是异常处理器

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从本地到全局:基于图的RAG方法进行查询聚焦原理摘要

摘要 使用检索增强生成&#xff08;RAG&#xff09;从外部知识源检索相关信息&#xff0c;使大型语言模型&#xff08;LLMs&#xff09;能够回答有关私有和/或以前未见过的文档集合的问题。然而&#xff0c;当针对整个文本文档库提出全局问题时&#xff0c;例如“数据集中的主…

音视频入门基础:H.264专题(13)——FFmpeg源码中通过SPS属性获取视频色彩格式的实现

一、引言 通过FFmpeg命令可以获取到H.264裸流文件的色彩格式&#xff08;又译作色度采样结构、像素格式&#xff09;&#xff1a; 在vlc中也可以获取到色彩格式&#xff08;vlc底层也使用了FFmpeg进行解码&#xff09;&#xff1a; 这个色彩格式就是之前的文章《音视频入门基础…

【精品资料】模块化数据中心解决方案(33页PPT)

引言&#xff1a;模块化数据中心解决方案是一种创新的数据中心设计和部署策略&#xff0c;旨在提高数据中心的灵活性、可扩展性和效率。这种方案通过将数据中心的基础设施、计算、存储和网络资源封装到标准化的模块中&#xff0c;实现了快速部署、易于管理和高效运维的目标 方案…

2024最新Cloudways主机使用教程(含最新Cloudways折扣码)

Cloudways是一家提供云托管服务的公司&#xff0c;可以帮助你轻松管理和运行你的网站。本教程是Cloudways主机注册和使用教程。Cloudways界面简洁&#xff0c;使用方便&#xff0c;不需要复杂的设置&#xff0c;就能快速搭建一个WordPress网站。它的主机功能包括高级缓存和Bree…

DepthAnything(2): 基于ONNXRuntime在ARM(aarch64)平台部署DepthAnything

DepthAnything(1): 先跑一跑Depth Anything_depth anything离线怎么跑-CSDN博客 目录 1. 写在前面 2. 安装推理组件 3. 生成ONNX 4. 准备ONNXRuntime库 5. API介绍 6. 例程 1. 写在前面 DepthAnything是一种能在任何情况下处理任何图像的简单却又强大的深度估计模型。 …

KingbaseES数据库逻辑备份还原

数据库版本&#xff1a;KingbaseES V008R006C008B0014 简介 介绍2个KingbaseES用于备份还原的工具&#xff1a; sys_dump&#xff1a;逻辑备份sys_restore&#xff1a;逻辑还原 sys_dump 是 KingbaseES 用于逻辑备份的工具&#xff0c;可以将数据备份为不同类型的文件。支持数据…

ARM功耗管理标准接口之SCMI

安全之安全(security)博客目录导读 思考&#xff1a;功耗管理有哪些标准接口&#xff1f;ACPI&PSCI&SCMI&#xff1f; Advanced Configuration and Power Interface Power State Coordination Interface System Control and Management Interface 下图示例说明了实现…

docker部署canal 并监听mysql

1.部署mysql 需要开启mysql的binlong&#xff0c;和创建好用户等 可以参考这个 Docker部署Mysql数据库详解-CSDN博客 2.部署canal 参考这一篇&#xff1a; docker安装Canal&#xff0c;开启MySQL binlog &#xff0c;连接Java&#xff0c;监控MySQL变化_docker canal-CSD…

内网信息收集——MSF信息收集浏览器记录配置文件敏感信息

文章目录 一、配置文件敏感信息收集二、浏览器密码&记录三、MSF信息收集 域控&#xff1a;windows server 2008 域内机器&#xff1a;win7 攻击机&#xff1a;kali 就是红日靶场&#xff08;一&#xff09;的虚拟机。 一、配置文件敏感信息收集 使用searchall64.exe&#…

【错题集-编程题】四个选项(DFS + 剪枝 + 哈希表)

牛客对应题目链接&#xff1a;四个选项 (nowcoder.com) 一、分析题目 用递归枚举出所有的情况&#xff0c;注意剪枝&#xff1a; 填写某个数时&#xff0c;要看看还有没有剩余次数。填写某个数时&#xff0c;要看看符不符合若干题的选项必须相同。 二、代码 // 值得学习的代码…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(六)-人工智能控制的自主无人机用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

SparkStreaming--scala

文章目录 第1关&#xff1a;QueueStream代码 第2关&#xff1a;File Streams代码 第1关&#xff1a;QueueStream 任务描述 本关任务&#xff1a;编写一个清洗QueueStream数据的SparkStreaming程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.如何使用S…

OrangePi AI Pro 实测:感受 AI 应用的独特魅力与强大性能

OrangePi AiPro介绍和初始化配置 小寒有话说一、OrangePi AiPro介绍1. 主板详情2. 开发配置3. 镜像烧录4. 设备连接5. WiFi连接6. NVMe SSD的安装和挂载7. 更新下载源并下载必要的软件8. 扩展内存 二、Jupyter Lab AI测评应用案例1. 获取Jupyter Lab 网址链接2. 图像提取文字3.…

帕金森病患者应该如何进行日常锻炼以提高生活质量?

帕金森病患者的日常锻炼建议 帕金森病患者进行日常锻炼对于改善症状、维持肌肉功能和延缓疾病进展至关重要。以下是一些具体的锻炼建议&#xff1a; 选择适合的运动类型&#xff1a;帕金森病患者应选择低冲击、有氧的活动&#xff0c;如散步、骑自行车、游泳和太极拳等。这些运…

【qt】考试系统项目

话不多说,先一睹芳颜 咱们的账号,题库和答案都是通过文件获取的. 话不多说,直接开干 目录 一.登录窗口1.界面设计2.邮箱验证3.登录验证 二.题库窗口1.考试计时2.布局管理器3.题库显示4.按钮布局5.计算分数 三.窗口交互四.完整代码五.结语 一.登录窗口 1.界面设计 这里添加背…

从信息化、数字化、智能化到企业大模型应用

新时代背景下&#xff0c;数字经济发展速度之快、辐射范围之广、影响程度之深前所未有&#xff0c;5G、大数据、云计算、人工智能、区块链等技术加速创新&#xff0c;全域融入经济社会、民生服务全过程&#xff0c;成为资源要素重组、经济结构重塑、竞争格局重构的关键力量。千…

Visual Studio 安装程序无法执行修复或更新

一.问题场景 出现问题的场景&#xff1a;当你的VS已经安装但是无法在工具中下载新组件或者卸载了当时一直无法安装。 二.问题原因 如果计算机上的 Visual Studio 实例已损坏&#xff0c;则可能会出现此问题。 三.解决方法 如果之前尝试修复或更新 Visual Studio 失败&…

浅谈RLHF---人类反馈强化学习

浅谈RLHF&#xff08;人类反馈强化学习&#xff09; RLHF&#xff08;Reinforcement Learning fromHuman Feedback&#xff09;人类反馈强化学习 RLHF是[Reinforcement Learning from Human Feedback的缩写&#xff0c;即从人类反馈中进行强化学习。这是一种结合了机器学习中…

51单片机6(P0P1P2P3结构框架图)

一、GPIO结构框架图与工作原理 1、接下来我们介绍一下这个GPIO结构框图和工作原理&#xff0c;我们使用51单片机的GPIO分为了P0&#xff0c;P1&#xff0c;P2&#xff0c;P3这四组端口&#xff0c;下面我们就分别来介绍这四组端口它的一个内部结构&#xff0c;只有了解了内部的…

[PM]原型与交互设计

原型分类 1.草图原型 手绘图稿, 规划的早期,整理思路会使用 2.低保真原型 简单交互, 无需配色, 黑白灰为主, 产品规划和评审阶段使用 标准化的低保真原型是高保真原型的基础 3.高保真原型 复杂交互, 一般用于公开演示, 产品先产出低保真原型, 设计师根据原型产出设计稿 低保…