一年前写过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