通常在一个 Web 应用中,会通过将 Web 程序部署到类似于 Tomcat/Jetty 这类 Web 容器中去。Tomcat 在整个系统扮演的角色主要有两个,一个是面向 HTTP 请求的 HTTP 服务器,另一个是后面用于处理后端逻辑的 Servlet 容器。SpringMVC 简单的来说就是一个 Servlet。运行在 Servlet 容器中。
这篇文章将简单概述 SpringMVC 的初始化过程。
在 Tomcat 启动应用程序
Tomcat 会扫描 webapps 目录下的所有的应用,并且依次进行加载。 打包的应用程序会被放到 webapps 目录下。随后,Tomcat 会读取应用内部的 WEB-INF 目录下的 web.xml 文件,对该文件进行解析,包括解析 servlet,该 servlet 的 load_on_startup 的值等属性。这里插一句。在 Tomcat 中,一个 web.xml 文件中读取出来的所有的 servlet 会存储在 TreeMap 的数据结构中,key 为 load_on_startup 的值,value 为一个 List,List 中存的是 Wrapper(servlet)。只有 load_on_startup 的值大于等于 0 时才会被放入到 TreeMap 中。并且值越低,启动的优先级越高。servlet 的加载就是通过 load 方法进行加载的。所以,这里的 wrapper.load() 就是 servlet 加载的入口。下面的代码就是 Tomcat 中加载 Wrapper 的入口。
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();$$
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
Servlet 的加载流程
StandardWrapper#load() 方法对 Servlet 进行加载。load 方法的主要逻辑在 loadServlet 方法中。
public synchronized Servlet loadServlet() throws ServletException {
// ...
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
}
// ...
initServlet(servlet);
fireContainerEvent("load", this);
// ...
return servlet;
}
上述方法中删去了部分不重要的代码,仅留下了比较重要的代码。上述代码中可以看到调用了 InstanceManager 的 newInstance 的方法,创建了一个 servlet。虽然编译类型为 Servlet 类型,但是实际上的运行时类型为 DispatcherServlet 类型。
随后调用了 initServlet 方法,对 servlet 进行初始化。
private synchronized void initServlet(Servlet servlet)
throws ServletException {
// ...
//protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
// StandardWrapperFacade 实现了 ServletConfig 接口
servlet.init(facade);
instanceInitialized = true;
// ...
}
初始化方法中调用了 servlet 的 init 方法。最终调到了 HttpServletBean#init() 方法。 init 方法的主要功能就是将属性设置到 Dispatcher 中。比如把 web.xml 中的 ContextConfigLocation 属性 设置给 DispatcherServlet。设置之后在初始化 Spring 容器的时候就能够直接获取容器的配置文件。
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 将 servlet 中配置的参数封装进 PropertyValues
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 将当前对象 servlet 封装成 BeanWrapper
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 创建 ResourceLoader
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 注册自定义的 PropertyEditor
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
// 属性设置进去
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// 初始化 Servlet Bean
// 模板方法
initServletBean();
}
PropertyValues 中保存了 Spring xml 文件路径(ContextConfigLocation)。 属性设置之后,后续就是 Spring 那一套了。随后调用 initServletBean 方法。
protected final void initServletBean() throws ServletException {
//...
// 初始化 webApplicationContext
this.webApplicationContext = initWebApplicationContext();
// WebApplicationContext 已经启动
// 初始化 FrameworkServlet
// 模板方法
initFrameworkServlet();
//...
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 默认创建一个 XMLWebApplicationContext 类型的应用上下文
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
// 将配置的 xml 文件设置进去
// 一般为 [servletName]-Servlet.xml
wac.setConfigLocation(configLocation);
}
// 启动容器
configureAndRefreshWebApplicationContext(wac);
return wac;
}
前面提到还没有关联 IOC 容器,initServletBean 方法中就通过调用 initWebApplicationContext() 方法对 IOC 容器进行了初始化。初始化是通过调用 FrameworkServlet#createWebApplicationContext(ApplicationContext) 方法来进行的。通过创建一个 ConfigurableWebApplicationContext 类型的容器(具体的类型为 XMLWebApplicationContext),设置其配置文件路径,随后就是 Spring 容器的启动了。
容器启动的过程中回去加载 Spring 配置文件中的 BeanDefinition。随后在 AbstractApplicationContext#finishBeanFactoryInitialization 方法中对 Bean 进行初始化。包括一些 Controller 或者一些注解驱动等标签解析后相关的 Bean。
在 Spring 容器启动的最后,会调用到 finishRefresh() 方法,该方法中会发布一个 ContextRefreshedEvent 事件。所有监听了该事件的监听器会收到该事件的通知。因为 FrameworkServlet#configureAndRefreshWebApplicationContext 方法中在启动容器之前注册了一个 FrameworkServlet 内部的 ContextRefreshListener 监听器,可以监听 ContextRefreshedEvent 事件。因此当发布了 ContextRefreshedEvent 事件的时候会回调 onApplicationEvent 方法。
public void onApplicationEvent(ContextRefreshedEvent event) {
// 置为 true,表示已经启动
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
// 调到子类,这里会调到 DispatcherServlet 的 onRefresh 方法
// 将当前的 webApplicationContext 传进去
onRefresh(event.getApplicationContext());
}
}
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
回调方法中最后执行到了 initStrategies 方法,该方法会初始化 DispatcherServlet 中用到的九大组件。
DispatcherServlet 的 initStrategies
initStrategies 中会初始化九大组件。默认需要初始化的组件定义在 DispatcherServlet.properties 文件中。如果已经初始化,已经初始化是指前面在 Spring 容器启动过程中已经注册成为一个 Bean,则不再进行初始化。若自定义了指定名称的 Bean,则不会初始化 DispatcherServlet.properties 文件中定义的 Bean,而是初始化自定义的 Bean。当然不同的组件根据需要也分别会选择进行初始化或者不初始化。下面代码注释中标明了是否需要初始化的情况。
protected void initStrategies(ApplicationContext context) {
// MultipartResolver 可以不需要
initMultipartResolver(context);
// 如果没有指定会创建一个默认的 LocaleResolver AcceptHeaderLocaleResolver
initLocaleResolver(context);
// 如果没有指定会创建一个默认的 ThemeResolver FixedThemeResolver
initThemeResolver(context);
// 如果没有指定会创建默认的 HandlerMapping:
// FixedThemeResolverBeanNameUrlHandlerMapping
// RequestMappingHandlerMapping
// RouterFunctionMapping
initHandlerMappings(context);
// 如果没有指定会创建默认的 HandlerAdapter:
// HttpRequestHandlerAdapter
// SimpleControllerHandlerAdapter
// RequestMappingHandlerAdapter
// HandlerFunctionAdapter
initHandlerAdapters(context);
// 如果没有指定会创建默认的 HandlerExceptionResolver:
// ExceptionHandlerExceptionResolver
// ResponseStatusExceptionResolver
// DefaultHandlerExceptionResolver
initHandlerExceptionResolvers(context);
// 如果没有指定会创建一个默认的 RequestToViewNameTranslator DefaultRequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 如果没有指定会创建默认的 ViewResolvers:
// 可以有多个
// InternalResourceViewResolver
initViewResolvers(context);
// 如果没有指定会创建默认的 FlashMapManager:SessionFlashMapManager
initFlashMapManager(context);
}
初始化之后的实例因为是 prototype 类型,因为 IOC 容器并不会维护这些实例。仅仅通过 Spring 来创建这些对象。这些对象都会被赋给 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;
至此,DispatcherServlet 的加载过程就全部完成了,后续就是等待请求的到来,以及对请求的处理了。