1. 程式人生 > >springmvc啟動過程

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