springmvc启动过程

一年前写过springMvc基于javaConfig的实现,这篇文章主要介绍通过ServletContainerInitializer来实现可插拔性.进而实现基于javaConfig的springMvc.粗略介绍启动过程.此文稍为深入一点,详细一点介绍这个启动过程.
从springMvc基于javaConfig的实现可以知道,org.springframework.web.SpringServletContainerInitializer#onStartup可以看作一个入口,当调用org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup

1.先注册ContextLoaderListener,RootApplicationContext下面的Config就会装在ContextLoaderInitializer.context
org.springframework.web.context.AbstractContextLoaderInitializer#registerContextLoaderListener

2.再注册DispatcherServlet,ServletApplicationContext下面的Config就装在DispatcherServlet.webApplicationContext
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet

3.在注册DispatcherServlet方法内包含注册一系列的Filter
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerServletFilter

4.再往下运行会触发ContextLoader的初始化,这个过程还包含RootApplicationContext下面的Config的Bean的初始实例化,并将此上下文存在key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的servletContext
org.springframework.web.context.ContextLoader#initWebApplicationContext{
configureAndRefreshWebApplicationContext(cwac, servletContext);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}

5.在org.eclipse.jetty.servlet.ServletHandler#initialize完成Filter的初始化,再到Servlet的初始化

public void initialize() throws Exception {
    MultiException mx = new MultiException();
    //start filter holders now
    if (_filters != null) {
        for (FilterHolder f : _filters) {
            try {
                f.start();
                f.initialize();//Filter初始化
            } catch (Exception e) {
                mx.add(e);
            }
        }
    }
    // Sort and Initialize servlets
    if (_servlets != null) {
        ServletHolder[] servlets = _servlets.clone();
        Arrays.sort(servlets);
        for (ServletHolder servlet : servlets) {
            try {
                servlet.start();
                servlet.initialize();//Servlet的初始化
            } catch (Throwable e) {
                LOG.debug(Log.EXCEPTION, e);
                mx.add(e);
            }
        }
    }
    //any other beans
    for (Holder<?> h : getBeans(Holder.class)) {
        try {
            if (!h.isStarted()) {
                h.start();
                h.initialize();
            }
        } catch (Exception e) {
            mx.add(e);
        }
    }
    mx.ifExceptionThrow();
}

6.初始化DispatcherServlet时会触发org.springframework.web.servlet.HttpServletBean#init,再触发org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext(这两个方法的的实例都是同一个DispatcherServlet)

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());//获取RootApplicationContext
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;//获取ServletApplicationContext
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            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 -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);//将rootApplicationContext设为servletApplicationContext的parent
                }
                configureAndRefreshWebApplicationContext(cwac);//完成servletApplicationContext的Bean的初始实例化
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }
    return wac;
}

这篇文章的主要目的是了解这个过程的先后顺序,spring security的DelegatingFilterProxy依赖springSecurityFilterChain Bean应放在RootApplicationContext下面
理由如下:
1.假设将Filter依赖Bean的定义放在ServletApplicationContext.
2.从上面可知ServletApplicationContext的Bean的初始实例化是在DispatcherServlet初始化期间,而Filter的初始化要比DispatcherServlet初始化靠前
3.也就是说在初始化这个Filter的时候,是无法得到这个依赖Bean实例的,进而初始化这个Filter失败.
所以放在RootApplicationContext是一种解决方法,具体做法:这个依赖Bean是在org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain下面定义的.那么使用@EnableWebMvcSecurity或@EnableWebSecurity来定义springSecurityFilterChain Bean的Configuration类应放在RootApplicationContext即可.
另一种解决方法是初始化这个Filter的时候就不要去获取这个依赖Bean实例,具体做法,将DelegatingFilterProxy设置ContextAttribute,指定去那找这个Bean,当这个ApplicationContext都找不到时,它就不会再去获取这个依赖Bean.如下:

@Order(100)
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    @Override
    protected String getDispatcherWebApplicationContextSuffix() {
        return AbstractDispatcherServletInitializer.DEFAULT_SERVLET_NAME;
    }
}

对于使用spring boot内置嵌入式容器的tomcat和jetty:
查看ServletContextInitializer:
org.springframework.boot.context.embedded.jetty.ServletContextInitializerConfiguration.Initializer#callInitializers
org.springframework.boot.context.embedded.tomcat.TomcatStarter#onStartup
可以查看注册的Filter,Servlet,Listener(可使用FilterRegistrationBean,ServletRegistrationBean,ServletListenerRegistrationBean注册成一个Bean即可,甚至在application.properties配置文件,但基于约束的这种就不够灵活控制):
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#selfInitialize

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值