环境准备:
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 初始化的入口方法:
/**
* 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 中)
解析 @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);