kphx
23. WebMvc:自动装配回顾与DispatcherServlet组件

23. WebMvc:自动装配回顾与DispatcherServlet组件

 
我们把应用重置回刚初始化的状态,也或者新创建一个干净的工程,还是只导入 spring-boot-starter-web 依赖,接下来咱开始分析WebMvc的一些原理。
之前分析自动装配时,咱们拿WebMvc来解析实例了。咱简单回顾一下WebMvc的自动装配都干了什么。

1. WebMvc自动配置装配的核心组件

1.1 WebMvcAutoConfiguration

配置 Converter
@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) { this.messageConvertersProvider .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters())); }
ViewResolver
// 最常用的视图解析器 @Beanpublic InternalResourceViewResolver defaultViewResolver() {} @Beanpublic BeanNameViewResolver beanNameViewResolver() {} @Beanpublic ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {} // 国际化组件 @Beanpublic LocaleResolver localeResolver() {}
静态资源映射,webjars 映射:
public void addResourceHandlers(ResourceHandlerRegistry registry) { // ...... // 映射webjars if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 映射静态资源路径 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
设置 index.html
private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); }
应用图标:
@Beanpublic SimpleUrlHandlerMapping faviconHandlerMapping() { // ...... mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; }

1.2 DispatcherServletAutoConfiguration

DispatcherServlet:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); // ...... return dispatcherServlet; }

1.3 ServletWebServerFactoryAutoConfiguration

TomcatServletWebServerFactory:
@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); }
WebServerFactoryCustomizerBeanPostProcessor + ErrorPageRegistrarBeanPostProcessor:
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } // 编程式注入组件 registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); }

1.4 官方文档的说明

notion image
notion image
官方文档列举的组件如下:
  • 视图解析器
  • webjars的资源映射
  • 自动配置的转换器、格式化器(Converter、Formatter)
  • Http请求转换器(HttpMessageConverter)
  • 响应代码解析器
  • 静态主页映射
  • 网站图标映射
  • 可配置的Web初始化绑定器
基本上面列举的部分都在官方文档中有描述了。
咱们都知道,SpringWebMvc的核心是 DispatcherServlet ,那对于WebMvc部分咱就着重来看启动、配置,以及与 DispatcherServlet 相关的部分。

2. 启动应用相关原理

在了解启动原理之前,先来了解一下Servlet3.0的一些规范,这对后续了解 SpringWebMvc 和 SpringBootWebMvc 有很大帮助。

2.1 Servlet3.0规范中引导应用启动的说明

在Servlet3.0的规范文档(小伙伴可点击链接下载:download.oracle.com/otn-pub/jcp…
An instance of the ServletContainerInitializer is looked up via the jar services API by the container at container / application startup time. The framework providing an implementation of the ServletContainerInitializer MUST bundle in the META-INF/services directory of the jar file a file called javax.servlet.ServletContainerInitializer, as per the jar services API, that points to the implementation class of the ServletContainerInitializer.
咱也不贴正儿八经的翻译了,咱用自己的语言描述一下。
在Servlet容器(Tomcat、Jetty等)启动应用时,会扫描应用jar包中 ServletContainerInitializer 的实现类。框架必须在jar包的 META-INF/services 的文件夹中提供一个名为 javax.servlet.ServletContainerInitializer 的文件,文件内容要写明 ServletContainerInitializer 的实现类的全限定名。
而这个 ServletContainerInitializer 是一个接口,实现它的类必须实现一个方法:onStartUp 。
public interface ServletContainerInitializer { void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; }
那不难猜出,要这个接口肯定是为了要执行这个 onStartUp 方法。
另外,还可以在这个 ServletContainerInitializer 的实现类上标注 @HandlesTypes 注解,在应用启动的时候自行加载一些附加的类,这些类会以字节码的集合形式传入 onStartup 方法的第一个参数中。
了解了这部分Servlet3.0规范后,咱来回顾之前遇到的一个陌生的类:SpringBootServletInitializer 。

2.2 SpringBootServletInitializer的作用和原理

回顾 SpringBoot 应用打包启动的两种方式:
  • 打jar包启动时,先创建IOC容器,在创建过程中创建了嵌入式Web容器。(详细的jar包启动会在 JarLauncher 篇解析)
  • 打war包启动时,要先启动外部的Web服务器,Web服务器再去启动 SpringBoot 应用,然后才是创建IOC容器。
那么在打war包启动时,里面最核心的步骤:Web服务器启动SpringBoot应用 。
而这个步骤,就需要依靠 SpringBootServletInitializer 。下面咱来看看外置Web容器是如何成功引导 SpringBoot 应用启动的:
  1. 外部Web容器(Tomcat、Jetty、Undertow等)启动,开始加载 SpringBoot 的war 包并解压。
  1. 去 SpringBoot 应用中的每一个被依赖的jar中寻找 META-INF/services/javax.servlet.SpringBootServletInitializer 的文件。
  1. 根据文件中标注的全限定类名,去找这个类(就是 SpringServletContainerInitializer)。
  1. 这个类的 onStartup 方法中会将 @HandlesTypes 中标注的类型的所有普通实现类(也就是非抽象子类)都实例化出来,之后分别调他们自己的 onStartup 方法。
    1. @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { // SpringServletContainerInitializer会加载所有的WebApplicationInitializer类型的普通实现类 List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // 如果不是接口,不是抽象类 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { // 创建该类的实例 initializers.add((WebApplicationInitializer) 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; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); // 调用各自的onStartup方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
      这个 onStartup 方法的文档注释原文翻译:
      Delegate the ServletContext to any WebApplicationInitializer implementations present on the application classpath. Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring's WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method. If no WebApplicationInitializer implementations are found on the classpath, this method is effectively a no-op. An INFO-level log message will be issued notifying the user that the ServletContainerInitializer has indeed been invoked but that no WebApplicationInitializer implementations were found. Assuming that one or more WebApplicationInitializer types are detected, they will be instantiated (and sorted if the @@Order annotation is present or the Ordered interface has been implemented). Then the WebApplicationInitializer.onStartup(ServletContext) method will be invoked on each instance, delegating the ServletContext such that each instance may register and configure servlets such as Spring's DispatcherServlet, listeners such as Spring's ContextLoaderListener, or any other Servlet API componentry such as filters.将 ServletContext 委托给应用程序类路径上存在的任何 WebApplicationInitializer 实现。 因为此类声明了 @HandlesTypes(WebApplicationInitializer.class),所以 Servlet 3.0+ 容器将自动扫描类路径以查找 Spring 的 WebApplicationInitializer 接口的实现,并将所有此类的类型的集合提供给此方法的 webAppInitializerClasses 参数。 如果在类路径上没有找到 WebApplicationInitializer 实现,则此方法实际上是无操作的。将发出info级别的日志消息,通知用户确实已调用 ServletContainerInitializer,但是未找到 WebApplicationInitializer 实现。 假设检测到一个或多个 WebApplicationInitializer 类型,将对其进行实例化(如果存在 @Order 注解或已实现 Ordered 接口,则将对其进行排序)。然后将在每个实例上调用 WebApplicationInitializer.onStartup(ServletContext) 方法,委派 ServletContext,以便每个实例可以注册和配置 Servlet(例如 Spring 的 DispatcherServlet),监听器(例如 Spring 的 ContextLoaderListener)或任何其他 Servlet API组件(例如Filter)。
  1. 因为打war包的 SpringBoot 工程会在启动类的同包下创建 ServletInitializer ,并且必须继承 SpringBootServletInitializer,所以会被服务器创建对象。
  1. SpringBootServletInitializer 没有重写 onStartup 方法,去父类 SpringServletContainerInitializer 中寻找
      • 父类 SpringServletContainerInitializer 中的 onStartup 方法中有一句核心源码:
      • WebApplicationContextrootAppContext rootAppContext = createRootApplicationContext(servletContext);
      @Overridepublic void onStartup(ServletContext servletContext) throws ServletException { // Logger initialization is deferred in case an ordered // LogServletContextInitializer is being used this.logger = LogFactory.getLog(getClass()); // 创建 父IOC容器 WebApplicationContext rootAppContext = createRootApplicationContext(servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { @Overridepublic void contextInitialized(ServletContextEvent event) { // no-op because the application context is already initialized } }); } else { this.logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not " + "return an application context"); } } protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { // 使用Builder机制,前面也介绍过 SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } // 设置Initializer builder.initializers(new ServletContextApplicationContextInitializer(servletContext)); // 在这里设置了容器启动类:AnnotationConfigServletWebServerApplicationContext builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); // 【引导】多态进入子类(自己定义)的方法中 builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); // builder.build(),创建SpringApplication SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } // 启动SpringBoot应用 return run(application); }
  1. 在这个方法中:
    1. 先创建 SpringApplicationBuilder 应用构建器;
    2. 再创建一些环境配置;
    3. 下面中间部分有一句: builder = configure(builder);
    4. 这句源码由于多态,执行了子类(SpringBoot 工程中必须写的那个启动类的同包下的 ServletInitializer)重写的方法;
    5. 又因为重写的格式固定,是传入了 SpringBoot 的目标运行主程序;
      1. return builder.sources(DemoApplication.class);
    6. 所以下一步才能启动 SpringBoot 工程。
  1. 之后就跟启动运行主程序 SpringBootApplication 没什么区别了。
以上就是 SpringBootServletInitializer 的作用和原理。

3. @Controller标注的Bean装配MVC原理

做过Controller开发的小伙伴都知道,自己写的Controller类只需要打上 @Controller 或 @RestController 注解,即可加载到WebMvc中,被 DispatcherServlet 找到,这一章节咱来看WebMvc是如何将这些Bean注册到WebMvc中的。

3.0 回顾IOC和AOP原理

先回想一下IOC和AOP的几个原理:
  • @Autowired 是什么时机被解析的:AutowiredAnnotationBeanPostProcessor 在 postProcessMergedBeanDefinition 中触发。
  • 代理对象是什么时机创建的:Bean的初始化之后,AnnotationAwareAspectJAutoProxyCreator 负责创建代理对象 。
那由此可以猜测,解析 @Controller 中 @RequestMapping 的时机可能也在这两种情况之内,暂且保存这个猜想。
下面根据IOC容器的启动过程,来实际探究 @RequestMapping 的解析时机。

3.1 初始化 RequestMapping 的入口

这个入口讲真一开始我找的时候找了好久,抓后置处理器死活抓不到关键的解析部分,后来我换了一个思路(小伙伴们可以一起来跟我体会一下这个寻找的思路):解析 @Controller 中的所有映射的方法,就是解析被 @RequestMapping 标注的方法。之前看 WebMvcAutoConfiguration 时又知道注册了一个 RequestMappingHandlerMapping 的组件,那估计可以从这个组价中找到一些端倪。

3.2 来到RequestMappingHandlerMapping

打开这个类,借助IDEA列举所有方法时,第一个方法吸引了我:
notion image
它实现了 InitializingBean ,可是它为什么要这么干呢?咱来进到实现中:
public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet(); }
这里面都是一些设置,不稀奇啊,继续进到父类的 afterPropertiesSet 中:
public void afterPropertiesSet() { initHandlerMethods(); }
它只是调了 initHandlerMethods 方法,但这个方法的字面意思貌似就有些问题:初始化 HandlerMethod ?难不成这个方法有关键的含义吗?

3.2 initHandlerMethods

private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; protected void initHandlerMethods() { for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); }
可以发现它把IOC容器中所有Bean的名称前缀不是 "scopedTarget." 的都拿出来,执行一个 processCandidateBean 方法。

3.3 processCandidateBean

protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }
上面的步骤是根据Bean的名称来获取Bean的类型,下面有一个判断:isHandler
protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
很明显它要看当前Bean是否有 @Controller 或 @RequestMapping 标注。
至此发现了重大关键点:它真的在解析 @Controller 和 @RequestMapping 了!证明咱的寻找思路是正确的。
那判断成功后,if中的结构体就一定是解析类中标注了 @RequestMapping 的方法了。

3.4 detectHandlerMethods

protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); // 3.5 解析筛选方法 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isTraceEnabled()) { logger.trace(formatMappings(userType, methods)); } // 3.6 注册方法映射 methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
上面的一开始还是拿到这个Bean的类型,下面会使用一个 MethodInterceptor 来筛选一些方法。

3.4.0 MethodIntrospector.selectMethods

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { final Map<Method, T> methodMap = new LinkedHashMap<>(); Set<Class<?>> handlerTypes = new LinkedHashSet<>(); Class<?> specificHandlerType = null; if (!Proxy.isProxyClass(targetType)) { specificHandlerType = ClassUtils.getUserClass(targetType); handlerTypes.add(specificHandlerType); } handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType)); for (Class<?> currentHandlerType : handlerTypes) { final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); ReflectionUtils.doWithMethods(currentHandlerType, method -> { Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); T result = metadataLookup.inspect(specificMethod); if (result != null) { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { methodMap.put(specificMethod, result); } } }, ReflectionUtils.USER_DECLARED_METHODS); } return methodMap; }
核心是中间的 for 循环:它会循环类中所有的方法,并且根据一个 MetadataLookup 类型来确定是否可以符合匹配条件。
注意 MetadataLookup 是一个函数式接口:
@FunctionalInterfacepublic interface MetadataLookup<T> { T inspect(Method method); }

回到上面的方法中,筛选方法中传入的 Lambda 表达式如下:
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { // 3.5 return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } });
它最终是调 getMappingForMethod 方法:

3.5 getMappingForMethod

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { // 创建方法级别的RequestMappingInfo RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { // 创建类级别的RequestMappingInfo RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } // 拼接路径前缀 String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).build().combine(info); } } return info; }
这里面分为几个步骤:创建方法级别的 RequestMappingInfo ,创建类级别的 RequestMappingInfo ,拼接路径前缀。一步一步来看:

3.5.1 createRequestMappingInfo(method)

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }
这部分很明显就是在解析 @RequestMapping 注解了!最终会把 @RequestMapping 及相关的属性封装到一个 RequestMappingInfo 对象中,逻辑比较简单。

3.5.2 createRequestMappingInfo(handlerType)

这部分也是一样的道理,不过这里面有个关键的部分:如果类上声明了 @RequestMapping 注解,会把这段注解跟方法上的 @RequestMapping 做一个拼接。
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); // 拼接 }

3.5.3 getPathPrefix(handlerType)

private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>(); String getPathPrefix(Class<?> handlerType) { for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) { if (entry.getValue().test(handlerType)) { String prefix = entry.getKey(); if (this.embeddedValueResolver != null) { prefix = this.embeddedValueResolver.resolveStringValue(prefix); } return prefix; } } return null; }
这里面提到了一个陌生的属性:pathPrefixes 。
这个属性,借助IDEA发现有对应的set方法,而set方法只有一个位置有调用它:WebMvcAutoConfiguration ,初始化时调用过(不过它直接回调了父类 WebMvcConfigurationSupport 的方法)。

3.5.3.1 WebMvcConfigurationSupport#requestMappingHandlerMapping

public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); // ...... PathMatchConfigurer configurer = getPathMatchConfigurer(); // ...... Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes(); if (pathPrefixes != null) { mapping.setPathPrefixes(pathPrefixes); } return mapping; }
可以发现调用的set方法来自于上面的 configurer.getPathPrefixes ,而 configurer 又来源于 getPathMatchConfigurer 。

3.5.3.2 getPathMatchConfigurer

protected PathMatchConfigurer getPathMatchConfigurer() { if (this.pathMatchConfigurer == null) { this.pathMatchConfigurer = new PathMatchConfigurer(); configurePathMatch(this.pathMatchConfigurer); } return this.pathMatchConfigurer; }
这里面它专门有一个 configurePathMatch 方法用来配置 PathMatch 。

3.5.2.3 configurePathMatch

protected void configurePathMatch(PathMatchConfigurer configurer) { this.configurers.configurePathMatch(configurer); } public void configurePathMatch(PathMatchConfigurer configurer) { for (WebMvcConfigurer delegate : this.delegates) { delegate.configurePathMatch(configurer); } }
可以发现这一部分是将所有IOC容器中的 WebMvcConfigurer 都拿出来回调 configurePathMatch 方法。(这也启发我们可以自定义一些配置类,实现 WebMvcConfigurer 接口来重写 configurePathMatch 方法,添加自定义规则)

到这里位置,方法的前缀路径就拼好了,下面到了最后一步:注册方法映射

3.6 registerHandlerMethod:注册方法映射

methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); });
这部分会根据已经筛选好的方法,来注册 HandlerMethod 。
private final MappingRegistry mappingRegistry = new MappingRegistry(); protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); }
进到 register 中:(关键部分注释已标注在源码中)
public void register(T mapping, Object handler, Method method) { // 读写锁加锁 this.readWriteLock.writeLock().lock(); try { // 将Controller的类型和Controller中的方法包装为一个HandlerMethod对象 HandlerMethod handlerMethod = createHandlerMethod(handler, method); assertUniqueMethodMapping(handlerMethod, mapping); // 将RequestMappingInfo和Controller的目标方法存入Map中 this.mappingLookup.put(mapping, handlerMethod); // 将注解中的映射url和RequestMappingInfo存入Map List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } // 将Controller目标方法和跨域配置存入Map CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } // uri 映射 HandlerMethod封装的MappingRegistration对象,存入Map中 this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
上面的源码逻辑也算比较清晰的了,外层它会保证线程安全,中间的try块会封装 Controller 和它的方法,变成一个 HandlerMethod 对象,之后分别保存三组Map映射(源码中已标注注释),完成注册。
至此,@Controller 中的 @RequestMapping 信息已经被装载进 RequestMappingHandlerMapping 中。

小结

  1. Servlet3.0规范中取消了 web.xml,改用 ServletContainerInitializer 来接管应用启动。
  1. SpringBootServletInitializer 实现了 WebApplicationInitializer ,用于被 ServletContainerInitializer 引导 SpringBoot 应用启动。
  1. Controller 中的 @RequestMapping 标注的方法装载时机是 RequestMappingHandlerMapping 的初始化阶段。
【本篇我们主要了解了 SpringBoot 打war包运行的原理,以及 Controller 中的映射方法的加载原理,下一篇咱来真正走一遍 DispatcherServlet 的工作原理,体会 WebMvc 中的设计】
badge