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