DispatcherServlet 初始化
在一个简单的Spring MVC项目中,需要web.xml配置DispatcherServlet
<!--配置DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!--默认匹配所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
contextConfigLocation
指定spring配置文件位置,除此之外无其他加载Spring ApplicationContex配置的代码。那么Spring ApplicationContext的加载肯定是随着 DispatcherServlet 初始化而加载,并且使用的是此contextConfigLocation
指定的配置文件位置。
看看DispatcherServlet继承结构
在Servlet
中有一个init
方法,执行servlet初始化操作,在子类 HttpServletBean
中进行重写此方法,并调用initServletBean()
方法完成环境的加载,initServletBean
在子类中通过重写,完成bean加载任务。
HttpServletBean.init()
:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 将web.xml配置的属性设置到dispatcherServlet的属性中,除了contextConfigLocation属性,
// 在DispatcherServlet的构造函数注释中还说明有contextClass、contextInitializerClasses
...
// Let subclasses do whatever initialization they like.
// 空方法,子类重写,完成bean加载任务
initServletBean();
}
在子类FrameworkServlet
中重写initServletBean()
方法,完成创建WebApplicationContext
。
FrameworkServlet.initServletBean()
:
@Override
protected final void initServletBean() throws ServletException {
//log
...
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
//log
...
}
initFrameworkServlet()
是个空方法,子类也并未重写,这里主要是initWebApplicationContext()
,初始化WebApplicationContext
。
FrameworkServlet.initWebApplicationContext()
:
// 初始化并发布此servlet的WebApplicationContext。
// 真正创建context的工作委派给createWebApplicationContext方法。
protected WebApplicationContext initWebApplicationContext() {
// 查找rootContext,并设置当前servlet的parent
...
if (wac == null) {
//查找是否手动设置过context
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建context
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 刷新applicationContext,和ConfigurableApplicationContext.refresh() 方法类似,
// 完成servlet 的 handlerMapping、viewResolver等获取
onRefresh(wac);
}
if (this.publishContext) {
// 将context作为servlet上下文属性发布。
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
重点在于创建createWebApplicationContext()
和onRefresh()
,先看createWebApplicationContext
方法
FrameworkServlet.createWebApplicationContext(@Nullable ApplicationContext parent)
:
// 实例化此 servlet 的 WebApplicationContext,之前有提到,在web.xml配置文件中可以配置 contextClass,
// 如果未配置则使用默认的 XmlWebApplicationContext,此处,自定义的额contexClass需要实现
// ConfigurableWebApplicationContext 接口
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// web.xml 配置,默认 XmlWebApplicationContext
Class<?> contextClass = getContextClass();
...
// 实例化 ApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// web.xml 配置,spring 配置位置
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 加载并刷新,这里完成 IOC 容器的装载
configureAndRefreshWebApplicationContext(wac);
return wac;
}
FrameworkServlet.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)
:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 设置listener使得context刷新,servlet也跟着刷新
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 为 post-processing or initialization 初始化配置属性
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
// 这里会取之前提到的,在web.xml中配置的 contextInitializerClasses 初始化context
// contextInitializerClasses 需实现 ApplicationContextInitializer 接口
applyInitializers(wac);
// context refresh 初始化容器、事件监听、source等
wac.refresh();
}
至此容器初始化完成,但是容器与 servlet 之间并未关联起来,现在回到FrameworkServlet.initWebApplicationContext()
方法,
protected WebApplicationContext initWebApplicationContext() {
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// context不是ConfigurableApplicationContext或者已经刷新过,则手动刷新,将容器中相关的handlerMapping、viewResolver关联到servlet
onRefresh(wac);
// 之前 configureAndRefreshWebApplicationContext 方法中配置listener可以监听context的刷新事件,并自动调用servlet 的 onRefresh 方法
}
return wac;
}
DispatcherServlet.onRefresh(ApplicationContext context)
:
@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);
// handlerMapping
initHandlerMappings(context);
// handlerAdapter
initHandlerAdapters(context);
// 异常
initHandlerExceptionResolvers(context);
// 请求 -> 视图
initRequestToViewNameTranslator(context);
// 视图解析
initViewResolvers(context);
// flashMapManager
initFlashMapManager(context);
}
这几个方法都是从容器中将所需要的相应的bean放到 DispatcherServlet中,以下属性中:
private MultipartResolver multipartResolver;
private LocaleResolver localeResolver;
private ThemeResolver themeResolver;
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private RequestToViewNameTranslator viewNameTranslator;
private FlashMapManager flashMapManager;
private List<ViewResolver> viewResolvers;
这样容器中的bean就可以为 DispatcherServlet 所用了。这里以initHandlerMappings(ApplicationContext context)
为例
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//将ApplicationContext中HandlerMappings找出来并放在handlerMappings中
...
// 确保至少有一个HandlerMapping, 否则配置一个默认的HandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
}
在未找到 HandlerMapping 时,会在配置文件中读取默认配置,默认的配置在DispatcherServlet.properties
中,可以看到默认的HandlerMapping
有两个
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
同理我们的DispatcherServlet
也拿到了其他的 bean,至此,servlet环境是初始化完成了,接下来看DispatcherServlet
怎么处理请求的。
DispatcherServlet 处理请求
在处理请求之前先看一下Spring MVC处理请求的流程:
DispatcherServlet
接收到请求后,将Request交由 HandlerMapping 查找相应的 Controller ,然后由HandlerAdapter去执行具体的 Controller 的方法,处理返回 ModelAndView ,然后根据视图名称查找页面,此后就是页面渲染,返回页面了。
DispatcherServlet
中处理Http请求的方法,doGet、doPost等,都是由父类 FrameworkServlet 实现 ,最终调用到 DispatcherServlet.doDispatch()
方法,这个是真正处理请求的方法。
DispatcherServlet.doDispatch()
:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// 确定当前请求的handler。
mappedHandler = getHandler(processedRequest);
// 确定当前请求的handler adapter。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// PreHandle (interceptor.preHandle),预拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 实际调用处理程序。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 确定视图名称
applyDefaultViewName(processedRequest, mv);
// 后拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
}
这里看重点看一下getHandler
方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
这里遍历handlerMappings
直到能根据request匹配到handler,getHandler 方法的实现在AbstractHandlerMapping
类中
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取handler
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// 实现 Controller 接口的controller
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 根据 handler 生成 ExecutionChain ,主要是添加拦截器
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
// Cors
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
关于获取handler,这里以 RequestMappingHandlerMapping
的实现讲解,因为RequestMappingHandlerMapping
是匹配 @Controller
注解的controller,日常使用较多。RequestMappingHandlerMapping
使用的父类AbstractHandlerMethodMapping
的 getHandlerInternal
方法。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
调用lookupHandlerMethod()
方法获取 HandlerMethod,这个 HandlerMethod 就是具体需要执行的某个 controller 中的某个方法,只要反射调用其中的 method 就能获得调用结果。
lookupHandlerMethod()
是怎么获得 HandlerMethod 的呢?该方法主要是根据AbstractHandlerMethodMapping
内部的变量 MappingRegistry mappingRegistry 获取的,在 MappingRegistry 内部,我们可看到如下变量
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
也就是说 HandlerMapping 中已经有了 请求路径 -> handler 的映射的集合,只要根据相应的方法获取即可。那么问题来了,如果我们写一个@Controller
注解的 controller,那这个 controller 是如何处理被映射到 HandlerMapping 中的呢?
HandlerMapping 注册请求映射
RequestMappingHandlerMapping
实现的接口中,其中一个是InitializingBean
,在 Bean 实例化的过程中有一步便是检测是否有InitializingBean
接口,若有,则执行其afterPropertiesSet
方法。(单一Bean的实例化主要在AbstractAutowireCapableBeanFactory#doCreateBean方法)。那么回到RequestMappingHandlerMapping
的afterPropertiesSet()
方法,该方法主要是调用AbstractHandlerMethodMapping
的initHandlerMethods()
方法完成请求映射。
// AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// 遍历所有的bean name
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
}
// 判断是否为Handler,RequestMappingHandlerMapping是根据是否有Controller或RequestMapping注解
if (beanType != null && isHandler(beanType)) {
// 根据beanName,装载handlerMethod
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
// RequestMappingHandlerMapping#isHandler,根据是否有Controller或RequestMapping注解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
// AbstractHandlerMethodMapping#detectHandlerMethods
protected void detectHandlerMethods(final Object handler) {
// handlerType,可能为proxy
Class<?> handlerType = ...
// 实际类型
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 找到 Medhod -> RequestMappingInfo的映射,RequestMappingHandlerMapping会根据是否有RequestMapping注解判断
Map<Method, T> methods = ...
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
// 注册到 mappingRegistry 中
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
那么至此,整个 spring mvc 初始化、处理请求的流程也就结束了。
结语
DispatcherServlet
的初始化会伴随Spring 容器的初始化,并获取容器中的bean完成servlet处理请求所需要的配置装载。最后以一张流程图总结