Based on Spring Framework 4.3.8 RELEASE
[TOC]
Spring MVC的入口
由servlet规范可知,servlet容器对一个java web应用解析的入口有以下几种:
- WEB-INFO/web.xml
- servlet 3.0提供的注解
- web.xml与jar中web-fragment.xml
- jar中的ServletContainerInitializer接口实现类
servlet容器通过解析以上的一个或多个配置来获取servlet等组件的信息,并将这些组件注册到servletcontext中,以支撑web工作。
Spring MVC作为web的框架,提供由servlet容器解析的配置,有两种方案,一种是使用ServletContextListener的事件监听机制来完成框架的初始化,另一种方式是实现ServletContainerInitializer接口。第一种可以借助web.xml注册listener或者使用注解来注册listener,第二种与web.xml无关,是经由Jar service API来发现的。两种机制是不同的,后者的执行时机是在所有的Listener触发之前。这些都是servlet规范的实现,具体的逻辑都在内部的接口方法实现中。
ServletContextListener
- 接口定义
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
ServletContextListener是容器生命周期的监听器,启动会触发此监听器的contextInitialized()
方法,容器销毁时会触发contextDestroyed()
方法。Spring提供了此接口的实现:ContextLoaderListener
:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
使用ContextLoaderListener方式,只需将此listener注册到ServletContext上即可,即在web.xml中配置此listener。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置Spring根容器的配置文件,不配置的话会查找默认的/WEB-INF/applicationContext.xml -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
如果想使用注解来摒弃web.xml配置文件,可以使用如下方式(因为spring的jar包不好直接修改代码加上注解,但是可以通过继承来使用):
@WebListener
public class MyContextListener extends ContextLoaderListener {
@Override
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 这里指定配置文件的路径
servletContext.setInitParameter("contextConfigLocation", "classpath:spring.xml");
return super.initWebApplicationContext(servletContext);
}
}
在此方式下,ContextLoaderListener#contextInitialized()
是Spring MVC的启动入口。
contextInitialized方法内部调用的是父类ContextLoader的initWebApplicationContext()方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 检查重复注册
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// 这里会创建WebApplicationContext对象,是Spring mvc中的ROOT IOC容器
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 把spring上下文放到ServletContext属性中,这样可以作为全局可访问的变量
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//...
return this.context;
}
// ...
}
这一步主要逻辑是创建root IOC容器并通过读取配置的spring.xml配置文件初始化,这一步是Spring IOC 容器的启动和初始化,实际上和MVC的关系不大。在实际使用的时候也可以不使用这个root WebApplicationContext,而仅仅使用后面的DispatcherServlet中创建的子容器。之所以使用父子容器,一方面是将不同的bean放到不同的容器中,将如DataSource等通用Bean放在父容器,web专用的bean放在子容器中,一方面结构清晰,也便于更换web框架,另外由于父子容器的访问机制,可以隔离不同的层次。
创建完成后将此WebApplicationContext作为ServletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
属性置于全局的环境。
由笔记《Servlet和Spring MVC》可知,Listener注册之后是filter的注册,最后是最为核心的Servlet:DispatcherServlet的注册了,web.xml方式下的配置如下:
<!-- DispatcherServlet -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 内部初始化Spring IOC容器(上面root容器的子容器)的配置来源,默认WEB-INFO下的<servlet-na>-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置映射规则,实际上就是配置哪些请求由mvc框架处理,这里实际上本质就是一个可用处理通配请求的servlet -->
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
和前面使用注解来取代此方式的想法一样,我们自定义一个类继承DispatcherServlet:
@WebServlet(value = "*.do", loadOnStartup = 1, initParams = {
@WebInitParam(name = "contextConfigLocation", value = "classpath:spring.xml") })
public class CustomDispatcherServlet extends DispatcherServlet {
private static final long serialVersionUID = 1L;
}
实际上也可以在前面的listener里面使用ServletContext接口的addServlet()方法来注册servlet。
既然DispatcherServlet也是Servlet,那么必然遵循Servlet规范,在容器启动后会调用其init方法初始化,将其注册到ServletContext上。请求到达时触发service方法来响应请求(通过HttpServlet,一般实现是doGet等方法)。
那么DispatcherServlet的入口也是init方法,先看下它的类结构层次:
public class DispatcherServlet extends FrameworkServlet ;
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware;
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware;
从类层次来看,DispatcherServlet间接实现了ApplicationContextAware、EnvironmentCapable、EnvironmentAware接口,说明它能够获取这些aware对象,中间设计的抽象类FrameworkServlet、HttpServletBean填充了具体的通用逻辑,看下init方法(在HttpServletBean中):
// final方法,不可重写
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
// 初始化Servlet
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
可以看出逻辑就是读取配置的init-param,然后调用模板方法initServletBean()
来实现具体的初始化逻辑:
子类FrameWorkServlet
的实现:
protected final void initServletBean() throws ServletException {
// ...
try {
// 核心逻辑入口
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
// ..
}
protected WebApplicationContext initWebApplicationContext() {
// 这里是获取在ContextLoaderListener中创建的ROOT context
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// ...
if (wac == null) {
// 创建一个子context
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
// 把DispatcherServlet的IOC容器也注册成ServletContext的属性
// 属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT.<DispatcherServlet的全限定类名,
// 这里是com.config.CustomDispatcherServlet>
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// 创建WebApplicationContext
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 获取配置的ApplicationContext的class,默认是
// org.springframework.web.context.support.XmlWebApplicationContext
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
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");
}
// 反射创建context对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器为ContextLoaderListener中创建的root容器
wac.setParent(parent);
// 设置配置location(即在DispatcherServlet上配置的configConfiguration参数的值)
wac.setConfigLocation(getContextConfigLocation());
// refresh,就是初始化IOC容器的逻辑了
configureAndRefreshWebApplicationContext(wac);
return wac;
}
从DispatcherServlet的初始化过程看,这一步的主要逻辑除了注册servlet外,还有的就是和Spring IOC的融合,创建子容器的逻辑。从Spring MVC的整个流程看,就是在普通的servlet等组件的注册上,结合了Spring IOC的特性,IOC是Spring MVC的基石,很多其他功能都是基于IOC来完成的。
DispatcherServlet的url-pattern匹配一系列的请求url,在内部处理请求的时候会再次分发给各个对应的controller的方法,这部分具体逻辑后面再描述。
ServletContainerInitializer
此方式是基于jar service API的方法,需要在jar文件内包含/META-INF/services/javax.servlet.ServletContainerInitializer,且该文件内容为ServletContainerInitializer的实现类的全限定名。
在spring-web-<Version>-RELEASE.jar内包含该文件,文件内容为org.springframework.web.SpringServletContainerInitializer
。
此接口的onStartup方法的触发是在ServletContextListener触发之前的,且与web.xml互相独立。也就是初始化流程是:
- 执行ServletContainerInitializer的onStartup()方法
- 执行初始化所有的listener
- 执行ServletContextListener的contextInitialized()方法
- 实例化filter并执行init方法
- 按照定义Servlet是设置的load-on-startup的数字从小到大顺序实例化Servlet,调用init方法
ServletContainerInitializer接口定义
javadoc中注明了使用规范:
/**
* Interface which allows a library/runtime to be notified of a web
* application's startup phase and perform any required programmatic
* registration of servlets, filters, and listeners in response to it.
*
* <p>Implementations of this interface may be annotated with
* {@link javax.servlet.annotation.HandlesTypes HandlesTypes}, in order to
* receive (at their {@link #onStartup} method) the Set of application
* classes that implement, extend, or have been annotated with the class
* types specified by the annotation.
*
* <p>If an implementation of this interface does not use <tt>HandlesTypes</tt>
* annotation, or none of the application classes match the ones specified
* by the annotation, the container must pass a <tt>null</tt> Set of classes
* to {@link #onStartup}.
*
* <p>When examining the classes of an application to see if they match
* any of the criteria specified by the <tt>HandlesTypes</tt> annontation
* of a <tt>ServletContainerInitializer</tt>, the container may run into
* classloading problems if any of the application's optional JAR
* files are missing. Because the container is not in a position to decide
* whether these types of classloading failures will prevent
* the application from working correctly, it must ignore them,
* while at the same time providing a configuration option that would
* log them.
*
* <p>Implementations of this interface must be declared by a JAR file
* resource located inside the <tt>META-INF/services</tt> directory and
* named for the fully qualified class name of this interface, and will be
* discovered using the runtime's service provider lookup mechanism
* or a container specific mechanism that is semantically equivalent to
* it. In either case, <tt>ServletContainerInitializer</tt> services from web
* fragment JAR files excluded from an absolute ordering must be ignored,
* and the order in which these services are discovered must follow the
* application's classloading delegation model.
*
* @see javax.servlet.annotation.HandlesTypes
*
* @since Servlet 3.0
*/
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
/**
* This annotation is used to declare the class types that a
* {@link javax.servlet.ServletContainerInitializer
* ServletContainerInitializer} can handle.
* @since Servlet 3.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
Class<?>[] value();
}
SpringServletContainerInitializer接口
// 此注解声明了类感兴趣的类,会在运行时将该类的实现类注入到onStartup方法的Set<Class> classes参数中。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
// 找到所有实现WebApplicationInitializer的类,筛去接口和抽象类后把剩下的放入list中
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
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);
// 调用web应用中的初始化器来执行初始化逻辑,执行初始化器的onStartup()方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
onStartup()方法的内部逻辑很简单,就是找到web中定义的所有WebApplicationInitializer
实现类,执行这些初始化类的onStartup()方法来执行初始化逻辑。
WebApplicationInitializer接口
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
接口定义和ServletContainerInitializer相似,Spring中类层次:
从上往下有名字可以确定,Spring提供的三个initializer都是抽象类,所以是没法直接使用的,如果使用此方式来配置使用Spring MVC,需要我们实现(间接)WebApplicationInitializer来完成ServletContainerInitializer中的初始化逻辑。这种方式可以完全摒弃xml配置文件,包括web.xml和spring的xml,可以完全使用JavabBased方式配置。
另外从三个抽象类的名字可以看出,AbstractContextLoaderInitializer
主要做的事情是初始化ContextLoaderListener
, AbstractDispatcherServletInitializer
则是初始化DispatcherServlet
;前面两个是主要的初始化器,而且做得事情和之前使用web.xml或者注解来做的注册组件的事情一样,初始化ContextLoaderListener
和DispatcherServlet
。
AbstractAnnotationConfigDispatcherServletInitializer
依然是初始化DispatcherServlet
,是给前面两个抽象类的抽象方法的实现,是和@Configuration配置类协作的初始化类,它是使用Java-Based方式使用configClass和注解来配置spring容器的抽象实现(结合AnnotationConfigWebApplicationContext
上下文实现)。
简要了解各个实现类的方法:
AbstractContextLoaderInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
// 核心逻辑
protected void registerContextLoaderListener(ServletContext servletContext) {
// 创建根ApplicationContext
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
// ContextLoaderListener是ServletContextListener的实现类
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
// Spring 4.2中新增的方法,提供context初始化器来介入Context的初始化
listener.setContextInitializers(getRootApplicationContextInitializers());
// 注册ServletContextListener监听器到ServletContext上
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
留给子类实现的抽象方法(模板模式的使用):
protected abstract WebApplicationContext createRootApplicationContext();
/**
* Specify application context initializers to be applied to the root application
* context that the {@code ContextLoaderListener} is being created with.
* @since 4.2
* @see #createRootApplicationContext()
* @see ContextLoaderListener#setContextInitializers
*/
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
可见此类的主要逻辑是创建根Spring容器,然后注册ServletContextListener(ContextLoaderListener)到ServletContext上,和使用ContextLoaderListener作为入口的方式相似,只是创建IOC容器的逻辑在listener的外面。
AbstractDispatcherServletInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 执行父类逻辑
super.onStartup(servletContext);
// 注册DispatcherServlet
registerDispatcherServlet(servletContext);
}
抽象方法:
// 创建WebApplicationContext
protected abstract WebApplicationContext createServletApplicationContext();
// 获取映射规则
protected abstract String[] getServletMappings();
其实类中的其他protected方法都是留给子类重写来自定义逻辑的,是模板方法的大量应用。
如:
// 自定义注册逻辑
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
// 返回Filter,实际上是返回到方法中注册的filter
protected Filter[] getServletFilters() {
return null;
}
/**
* Specify application context initializers to be applied to the servlet-specific
* application context that the {@code DispatcherServlet} is being created with.
* @since 4.2
* @see #createServletApplicationContext()
* @see DispatcherServlet#setContextInitializers
* @see #getRootApplicationContextInitializers()
*/
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
此类的主要功能是在父类注册ContextLoaderListener(实际上就是创建RootApplicationContext和设置Listener)的基础上,注册DispaerServlet,以及提供配置映射规则和配置filter和其他自定义注册逻辑,且在这一步创建了DispatcherServlet的IOC容器,和前面web.xml中在DispatcherServlet的init逻辑中创建IOC容器的逻辑类似。
AbstractAnnotationConfigDispatcherServletInitializer
它实现了父类中的两个抽象方法:createRootApplicationContext()
,createServletApplicationContext()
,也就是Spring MVC中的两个ApplicationContext,可以发现创建的内部逻辑借助了getRootConfigClasses()
和getServletConfigClasses()
来获取配置class,然后通过AnnotationConfigWebApplicationContext
类来创建Context,这些配置类肯定是需要开发者自定义的,自然得交给子类来填充的,且必须填充,很自然的是抽象方法。
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
/**
* {@inheritDoc}
* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
* providing it the annotated classes returned by {@link #getRootConfigClasses()}.
* Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}.
*/
@Override
protected WebApplicationContext createRootApplicationContext() {
// 获取根ApplicationContext的配置类
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建根IOC容器
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
// 注册配置类
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
/**
* {@inheritDoc}
* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
* providing it the annotated classes returned by {@link #getServletConfigClasses()}.
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
// 创建DispatcherServlet类的IOC容器,也就是子容器
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
// 交给子类通过配置类的信息
protected abstract Class<?>[] getRootConfigClasses();
protected abstract Class<?>[] getServletConfigClasses();
}
自定义Initializer实现类
自定义实现类一般是实现第三层的抽象类AbstractAnnotationConfigDispatcherServletInitializer
,最基本的就是实现父类的两个抽象方法和AbstractDispatcherServletInitializer
的getServletMappings()
即可,这些都是需要依据实际的web应用来设置的,如下示例:
public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 也可以不使用父IOC容器直接return null即可。
// 是ContextLoaderListener初始化的模板方法实现
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
// 是AbstractAnnotationConfigDispatcherServletInitializer的模板方法的实现
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
// AbstractDispatcherServletInitializer的模板方法实现
@Override
protected String[] getServletMappings() {
return new String[] { "*.do" };
}
}
一般除此之外可能要添加过滤器或者listener,只需要重写上面的抽象类的某些方法就可以了。除上面必须实现的三个方法之外,还可以重写的方法有:
这些方法的作用都从名字可以明显看出来。
另外如果不想使用AnnotationConfigWebApplicationContext
类作为IOC容器的实现类,可以继承AbstractDispatcherServletInitializer
实现createServletApplicationContext()
和createRootApplicationContext()
,在内部实现具体的ApplicationContext创建逻辑。