现在在Java web 的世界中,很多都用spring的,因为它的高度解耦,使得开发人员能够快速构建出高可用和高扩展的应用。我们看下spring在web项目中的启动过程和上下文的使用情况。web 应用启动过程可以参考上一篇文章。https://www.jianshu.com/p/a9babadb5f4b
我一般都用xml或者注解的方式使用spring,先说下xml的
1、xml based configuration
在XML based configuration的项目中,web.xml大致如下:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
ContextLoaderListener 如下:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
在应用启动的时候,会调用contextInitialized,里面调用ContextLoader 的initWebApplicationContext 方法(servlet context 已经由web容器创建好了),方法中会创建 WebApplicationContext 并把它注册到servlet context中:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
WebApplicationContext 熟悉吧?xml用的XmlWebApplicationContext、groovy用的GroovyWebApplicationContext、基于注解的AnnotationConfigWebApplicationContext 都是它的实现类。它就是作为IOC容器的上下文,也是root context。
看下initWebApplicationContext方法的两个关键点:
this.context = this.createWebApplicationContext(servletContext);//创建WebApplicationContext对象
...
if(this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if(!cwac.isActive()) {
if(cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);//将web application context 的父容器设置为servletContext
}
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
root context初始化完了以后,按照定义的顺序开始加载初始化servlet。这里是DispatcherServlet。DispatcherServlet在初始化的时候,会创建自己的IOC context(servlet application context),并且从servlet context中取出属性名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 的context,也就是root context,并根据创建出来的context创建DispatcherServlet,注册过滤器,最后将DispatcherServlet加入到servlet context(root context)中。servletApplicationContext 就作为WebApplicationContext的子容器了。这个不太直观,我们看下基于注解的配置的代码就很清楚了。
2、annotation based configuration
web application context:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
web mvc context:
class MyWebConfig extends WebMvcConfigurationSupport {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
...
}
}
我们看看AbstractAnnotationConfigDispatcherServletInitializer:
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
public AbstractAnnotationConfigDispatcherServletInitializer() {
}
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = this.getRootConfigClasses();
if(!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
} else {
return null;
}
}
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = this.getServletConfigClasses();
if(!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
主要就是创建rootApplicationContext 和 servletApplicationContext,也就是两个父子容器了。
再来看看AbstractDispatcherServletInitializer,主要代码如下:
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
this.registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = this.getServletName();
WebApplicationContext servletAppContext = this.createServletApplicationContext();
FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(this.getServletMappings());
registration.setAsyncSupported(this.isAsyncSupported());
Filter[] filters = this.getServletFilters();
if(!ObjectUtils.isEmpty(filters)) {
Filter[] var7 = filters;
int var8 = filters.length;
for(int var9 = 0; var9 < var8; ++var9) {
Filter filter = var7[var9];
this.registerServletFilter(servletContext, filter);
}
}
this.customizeRegistration(registration);
}
protected String getServletName() {
return "dispatcher";
}
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
看到代码了,就是那么实现的。哈!
父子容器(父:WebApplicationContext,子:ServletApplicationContext),在子容器中可以访问父容器中的bean,但是,父容器中却不能够访问子容器中的bean,这个是很自然的事情。。父容器中的bean主要是 DAO、service、repository,也就是业务逻辑层和数据持久化层;子容器中主要就是controller,也就是ACTION相关的东西。这个是很自然的事情,mvc嘛,子容器访问父容器中的bean,不正是mvc中的c调用m?
一个不标准的时序和调用图如下:
这种采用父子容器的方式是很正规,中规中矩的方式,其实也可以直接都在同一个context中,不过不建议这样做。