0x11.Spring MVC框架启动原理简述

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主要做的事情是初始化ContextLoaderListenerAbstractDispatcherServletInitializer则是初始化DispatcherServlet;前面两个是主要的初始化器,而且做得事情和之前使用web.xml或者注解来做的注册组件的事情一样,初始化ContextLoaderListenerDispatcherServlet

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,最基本的就是实现父类的两个抽象方法和AbstractDispatcherServletInitializergetServletMappings() 即可,这些都是需要依据实际的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创建逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容