DispatcerServlet 初始化时机

环境准备:

AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
                
public class WebConfig {
		// ⬅️内嵌 web 容器工厂
		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
		    return new TomcatServletWebServerFactory(serverProperties.getPort());
		}
		
		// ⬅️创建 DispatcherServlet
		@Bean
		public DispatcherServlet dispatcherServlet() {
		    return new DispatcherServlet();
		}
		
		// ⬅️注册 DispatcherServlet, Spring MVC 的入口
		@Bean
		public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
		        DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
		    DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}

通过浏览器访问,观察日志,可以得出 DispatcherServlet 的初始化时机:

默认是第一次访问初始化,也可以通过配置 registrationBean.setLoadOnStartup() 来指定初始化顺序

修改后的 WebConfig 代码:

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
    // ⬅️内嵌 web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactory(serverProperties.getPort());
    }

    // ⬅️创建 DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // ⬅️注册 DispatcherServlet, Spring MVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

DispatcherServlet 初始化时执行的操作

通过调用栈最终可以分析出,DispatcherServlet 初始化的入口方法:

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	// 每个初始化方法都会初始化一类组件
	// 解析文件上传相关的解析器
	initMultipartResolver(context);
	// 本地化、国际化相关的解析器
	initLocaleResolver(context);
	initThemeResolver(context);
	**// 用来路径映射
	initHandlerMappings(context);
	// 适配不同形式的控制器方法
	initHandlerAdapters(context);
	// 处理异常**
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

这些方法都会在容器中找各自对应的实现,如果找不到就使用默认的实现(存储在 DispatcherServlet.properties 中)

RequestMappingHandlerMapping 基本用途

解析 @RequestMapping 注解和它的派生注解,生成路径和控制器方法的映射关系,在初始化的时候就生成

获取映射结果:

// 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);

// 获取映射结果
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) -> {
    System.out.println(k + "=" + v);
});

打印结果:

{POST [/test2]}=com.itheima.a20.Controller1#test2(String)
{PUT [/test3]}=com.itheima.a20.Controller1#test3(String)
{ [/test4]}=com.itheima.a20.Controller1#test4()
{GET [/test1]}=com.itheima.a20.Controller1#test1()

那么如何根据请求获取控制器方法?

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);