从Servlet和Spring-web整合理解父子容器
这篇文章主要分为两个部分,
- spring和servlet的融合。怎么Servlet容器启动起来,我就可以用Spring了。
- 从ServletContext和RootWebContext的关系理解Spring中的父子容器。
现在Springboot作为Java开发的主力军,开箱即用的特性,并且大多数的人在pom文件中写的都是直接写spring-boot-starter-web
,然后在主启动类上在写@SpringbootApplication
,在写一个Controller,一个简单的springboot 的web程序就开始了。这样好是好,但是就是因为太好了,导致底层实现不太清楚。Spring-web是怎么和Servlet容器结合起来的呢?
这让我想到了一开始学习Spring-web的时候,web.xml配置文件。得从它开始。在说一句,我觉得框架的发展不是一步到位的,也是需要慢慢进化的,这点在Spring身上体现的淋漓尽致,一开始是xml配置,之后是注解,现在是Springboot。
一个经典的基于web.xml的配置方式。
<!-- 这配置的是Root Application Context-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springConfig.xml</param-value>
</context-param>
<!-- 配置DispatchServlet -->
<servlet>
<servlet-name>springmvcservlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 给Servlet配置applicationContext的xml配置文件路径 -->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc.xml</param-value>
</init-param>
<!--其他参数-->
<init-param>
<param-name>appName</param-name>
<param-value>authplatform</param-value>
</init-param>
<!-- 下面值小一点比较合适,会优先加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--- 给servlet配置需要映射的请求路径 -->
<servlet-mapping>
<servlet-name>springmvcservlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- 配置请求过滤器,编码格式设为UTF-8,避免中文乱码 -->
<filter>
<filter-name>charsetfilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charsetfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该类作为spring的listener使用,它会在创建时自动查找web.xml配置的applicationContext.xml文件 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
那么基于这个例子,开始今天的分析
一. Servlet和Spring-web整合分析
首先要说明,在Servlet中存在父子容器的概念,抛开Spring不谈,在一个经典的Web程序里是可以有多个Servlet的,这点是毋庸置疑的,否则上面xml里 <servlet-mapping>
就不会存在了,
所以,应该是这个样子,每一个Servlet都可以处理一类的请求,这都是随便指定的。那么Spring-web干的事情就是,利用一个Servlet拦截指定的请求,在处理请求的时候,添加了一套Spring自己的东西(之后博客说),比如拦截器,异常处理,Multipart,Locale,Theme,等等。
对比上图,一个web程序对应的就是一个Context(图中蓝色),在这个Context里面可以有多个servlet(图中白色)。Context和Servlet在Spring-web里面各有一个ApplicationContext,Context对应的就是Root(父),Servlet对应的就是子ApplicationContext。
Spring-web里面也是可以添加多个dispatchServlet的。
那么现在回想一下在Servlet容器里面的几个特殊的Listener,还有servlet是怎么写的?
Servlet容器里几个特殊的Listener,和Servlet接口
几个特殊的Listener
-
ervletContextAttributeListener :Context里面属性变动
-
ServletRequestListener ; request 初始化和销毁的回调
-
ServletRequestAttributeListener: request 属性变动
-
HttpSessionAttributeListener:session 属性变动
-
HttpSessionIdListener: session id变动
6, HttpSessionListener:session 创建和销毁
- ServletContextListener: Context初始化和销毁
Servlet创建和Servlet的生命周期
servlet怎么写?实现Servlet
接口,一般来说都是直接继承于HttpServlet
.这就说到Servlet的生命周期了。
servlet生命周期
- init 方法,在第一个请求来的时候初始化这个Servlet。之后来的请求就不会调用这个方法了
- service 方法,处理真正的请求的地方。
- destroy方法,servlet关闭的时候,也就是servlet容器关闭的时候
分析完这些,下面就可以看两者是怎么结合的。 从上面知道,context有一个rootApplicationContext,Servlet也有一个子的ApplicationContext。加上web.xml配置文件上的配置。两者的结合肯定是在servlet的Context初始化的时候加载根配置的xml文件,创建ApplicationContext,在HttpServlet#init(Servlet初始化)创建Servlet的ApplicationContext。
一切的开始(源码分析)
从web.xml可以看到。必须要配置一个org.springframework.web.context.ContextLoaderListener
,用于ServletContext初始化的时候的监听。同时,还得在 <context-param>
里面指定一个<param-name>>
为contextConfigLocation的值,value是该ServletContext对应的ApplicationContext的配置文件的路径。
此外,还得在配置<servlet>
的时候在<param-name>
里面配置key为contextConfigLocation的值,value是该Servlet对应ApplicationContext的配置文件的路径。
下面就从源码来分析分析
ContextLoaderListener
ContextLoaderListener,在ServletContext,初始化和销毁时候的监听。创建和销毁rootApplicationContext。将真正的实现委托给ContextLoader来实现。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
将真正的实现委托给ContextLoader#initWebApplicationContext
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
将真正的实现委托给ContextLoader#closeWebApplicationContext
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ContextLoader#initWebApplicationContext(初始化root web application)
// servletContext这是javax.servlet里面的
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 是一个key,value存放的初始化好的root web application
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
// 说明在web.xml中只能配置一个ContextLoader的listener
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
// 创建root web application
this.context = createWebApplicationContext(servletContext);
}
// 这就说明如果要启动配置root web application,就必须是ConfigurableWebApplicationContext的子类
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 如果这个还没有初始化过
if (!cwac.isActive()) {
//如果没有设置父context
if (cwac.getParent() == null) {
// 加载父context,这个方法默认的实现是空方法,也就是说,可以重写这个方法,为root web application 设置父容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 指定配置文件地址,并且调用context的refresh方法,启动容器。
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 设置context到servletContext中,
// 之后就可以通过属性名获得root web application
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
// 记录耗费时间
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
// 如果报错,属性存放的就是异常。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
ContextLoader#createWebApplicationContext(通过contextClass判断ApplicationContext类型,并且创建WebApplicationContext)
创建的webApplication必须是ConfigurableWebApplicationContext的子类,并且调用determineContextClass
方法,在determineContextClass
里面获取<context-param>
里面param-name
为contextClass。值是具体的WebApplicationContext实现类的全限定类名。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
- ContextLoader#determineContextClass(通过contextClass判断WebApplicationContext具体类型)
先是加载<context-param>
里面param-name
为contextClass的值,如果没有,就去ContextLoader.properties
里面加载默认的实现。总之,这个方法是为了判断WebApplicationContext的具体类型。
问题
- 默认的文件(ContextLoader.properties)长什么样?
protected Class<?> determineContextClass(ServletContext servletContext) {
// public static final String CONTEXT_CLASS_PARAM = "contextClass";
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 如果没有就采用默认策略,默认加载ContextLoader.properties文件里面写的内容(默认实现是XmlWebApplicationContext)
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
ContextLoader#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 从配置文件中获取contextId参数,如果没有,就采用默认值。
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 设置Servletcontext给webApplicationContext
wac.setServletContext(sc);
// 拿到 contextConfigLocation参数,这个就是web.xml中设置的参数,
//
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 设置配置文件路径,
wac.setConfigLocation(configLocationParam);
}
// 过早初始化,确保安全。初始化环境。
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//调用ApplicationContextInitiaial
customizeContext(sc, wac);
// 调用refresh。启动容器。
wac.refresh();
}
ContextLoader#customizeContext(自定义WebApplicationContext)
自定义WebApplicationContext,通过ApplicationContextInitializer#initialize
方法。
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
// 拿到ApplicationContextInitializer
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(sc);
// 循环遍历,验证,判断
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
throw new ApplicationContextException(String.format(
"Could not apply context initializer [%s] since its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
wac.getClass().getName()));
}
// 实例化
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
// 居然也是可以使用order来做排序的。
AnnotationAwareOrderComparator.sort(this.contextInitializers);
// 循环调用
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
-
ContextLoader#determineContextInitializerClasses
加载web.xml中配置的ApplicationContextInitializer,(分为全局和局部,全局是
globalInitializerClasses
,局部是contextInitializerClasses
)。
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<>();
String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
classes.add(loadInitializerClass(className));
}
}
String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
if (localClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
classes.add(loadInitializerClass(className));
}
}
return classes;
}
在ContextLoaderListener主要干了下面的几件事情:
- 从web.xml中获取需要创建的WebApplicationContext的类型(默认是XmlWebApplicationContext)
- 从web.xml中获取contextId,设置给WebApplicationContext。
- 从web.xml中获取配置文件的路径,设置给WebApplicationContext。
- 初始化环境。
- 调用ApplicationContextInitializer来自定义。
- 调用refresh方法,启动容器。
- 并且会将已经创建好的WebApplicationContext设置为ServletContext的一个属性。
到此,ServletContext关联的rootwebApplication已经启动起来了。并且已经放在ServletContext里面了。到这里rootWebApplicationContext启动已经搞定了,在看看关闭时候的操作(上面忘了写了,写在这里把)
可以看到,这里调用了两个方法,在这个方法里面会关闭调用rootWebApplicationContext。并且移除之前缓存在Servlet中的context的引用的属性,此外,他还会遍历servletContext里面所有的属性,判断这个对象是否是DisposableBean,如果是调用他的destroy方法。
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
// 可以看到,这里调用了两个方法,在这个方法里面会关闭调用rootWebApplicationContext。并且移除之前缓存在Servlet的属性
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
}
static void cleanupAttributes(ServletContext servletContext) {
Enumeration<String> attrNames = servletContext.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
// 是否是Spring的
if (attrName.startsWith("org.springframework.")) {
Object attrValue = servletContext.getAttribute(attrName);
// 是否是DisposableBean
if (attrValue instanceof DisposableBean) {
try {
((DisposableBean) attrValue).destroy();
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Invocation of destroy method failed on ServletContext " +
"attribute with name '" + attrName + "'", ex);
}
}
}
}
}
}
DispatcherServlet
上面已经介绍了,它继承于HttpServlet,再想想Servlet的生命周期,所以,先看init方法,再看destroy方法,在此之前,先看看DispatchServlet实现的几个特殊的父类。
- HttpServletBean
这个bean提供了将servlet的配置变为属性的功能。
-
FrameworkServlet
一个基础的实现,在HttpServletBean的基础上,让Servlet和ApplicationContext结合,主要有两个功能
- 为每个servlet管理一个WebApplicationContext实例。
- 在处理请求的时候发布事件,无论处理是否成功。
好了,说完这些,就开始看init方法和destroy方法,至于service方法后面专门分析一波。
HttpServletBean#init
将servletConfig配置的参数变为这个bean的属性。
问题
具体是怎么做的?BeanWrapper的功能是什么?怎么做映射的?
是通过BeanWrapper来做的,先拿到ServletConfig所有的属性,封装为PropertyValues,通过bw做映射。后面专门说说这个
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 将init param设置为bean的属性
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 这就是加载资源的
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 初始化Servlet
initServletBean();
}
FrameworkServlet#initServletBean
给Servlet创建WebApplicationContext,并且初始化Servlet。
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
// 这个地方和是WebApplicationInitializer大体是一样的,但是有各别是不一样的, 具体在方法里面看看
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
FrameworkServlet#initWebApplicationContext
初始化WebApplicationContext,并且会将之前在ContextLoaderListener初始化好的rootWebApplicationContext,作为当前ApplicationContext的父容器,并且对创建好的容器设置ServletContext,配置,namespace,还有一个特殊的Listener,给子类留了一个postProcessWebApplicationContext
,初始化环境。之后调用onRefresh方法回调。
如果webApplicationContext没有初始化,就会先从ServletContext中获取有没有对应的ApplicationContext,如果没有,才会去创建它。
创建它的时候会调用两个方法
- getContextClass (拿到这个context的类)
- getContextConfigLocation(拿到配置文件的地址)
那么有问题来了。这俩的属性的值什么?在哪设置的?
首先说,FrameworkServlet是继承于HttpServletBean,在HttpServletBean#init方法里面会将servletConfig(web.xml文件)里面的servlet的属性映射起来。这样就能将属性和配置文件对应起来了。再回头看看属性的字段名字(contextClass,contextConfigLocation)和web.xml文件中的属性名对应起来了。
问题
这个Listener是什么?有什么作用
在FrameworkServlet#configureAndRefreshWebApplicationContext方法里面。
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
可以看到,在SourceFilteringListener里面包含了一个ContextRefreshListener,
ContextRefreshListener利用了代理模式,代理了ContextRefreshListener。具体看看ContextRefreshListener里面
onApplicationEvent
干了什么事情
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { FrameworkServlet.this.onApplicationEvent(event); } } public void onApplicationEvent(ContextRefreshedEvent event) { this.refreshEventReceived = true; synchronized (this.onRefreshMonitor) { onRefresh(event.getApplicationContext()); } }
从上面的代码可以看到,这里就是最终还是调用到onRefresh方法。
protected WebApplicationContext initWebApplicationContext() {
// 从ServletContext中获取,这个获取是获取之前在ContextLoaderListener里面创建的webApplicationContext。
// 从ServletContext属性里面获取。属性中获取。因为在创建DispatchServlet的时候就已经把ServletContext设置进来了。
// 这里直接从属性中获取值就好了。
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// webApplicationContext就这个servlet初始化的属性。
if (this.webApplicationContext != null) {
//如果之前已经有了,就直接将root web ApplicationContext设置为父容器
// 并且调用configureAndRefreshWebApplicationContext方法
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 这里会将rootWebApplication设置为父类
cwac.setParent(rootContext);
}
// 和rootwebApplicationContext基本一样的操作。
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 从servletContext中通过属性名字来获取webApplicationContext,
// 子类是可以重写这个方法,提供不同的WebApplicationContext检索的策略。
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果依然还是没有,就会通过getContextClass方法和getContextConfigLocation方法获取对应的参数,和root web ApplicationContext一样
// 参数的赋值是通过BeanWrapper来做的,
// 并且将rootWebApplicationContext作为他的父类。
wac = createWebApplicationContext(rootContext);
}
// refreshEventReceived判断onRefresh是否调用过。
// 一开始是false,在ContextRefreshListener会修改。
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 留给子类的拓展方法,在具体看dispatchServlet里面
onRefresh(wac);
}
}
// publishContext表示,是否要发布Context,也就是将这个Servlet的Context添加到属性里面去。
if (this.publishContext) {
// 属性名字是 SERVLET_CONTEXT_PREFIX + servlet name
String attrName = getServletContextAttributeName();
// 将这个servlet的webApplicatin 放在servletContext里面
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
DispatcherServlet#onRefresh
ApplicationContext完全初始化好之后的回调Servlet方法,在这个方法里面主要是初始化了一些在处理Request的时候用到的一些bean,因为这个时候ApplicationContext已经初始化好了,这里就是简单的从ApplicationContext获取值,设置就好。
// 创建好每个servlet相关的webApplicationContext之后的回调方法
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 初始化这个servlet使用到的东西。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这里初始化的方法都大同小异,这里用initMultipartResolver
方法来举例子。
initMultipartResolver
初始化MultipartResolver。方法很简单,就是从ApplicationContext中获取bean,因为这个时候spring的容器已经启动了。所以,就按照最经典的bean的获取方式来,直接通过bean的名字和类型类获取就好。
private void initMultipartResolver(ApplicationContext context) {
try {
// 从ApplicationContext中获取MultipartResolver的bean,bean的名字叫做MULTIPART_RESOLVER_BEAN_NAME。
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
到这里从源码分析了Servlet和Spring-web的整合操作。下面总结一下
总结:
ContextLoaderListener,是Servlet容器的监听,在Servlet容器初始化的时候,通过监听,就可以为Servlet容器创建一个 ApplicationContext,我叫它为RootApplicationContext,在Servlet容器销毁的时候,在回调里面关闭ApplicationContext,并且调用实现disposeBean接口的bean。
DispatchServlet,是Servlet监听,想想Servlet的生命周期,在Servlet初始化的时候,会为这个Servlet创建一个ApplicationContext,并且在ApplicationContext初始化好之后,会为DispatchServlet初始化一些处理request的时候需要的bean。
创建ApplicationContext的时候,配置文件和ApplicationContext的类型都是在web.xml中指定的,如果不指定ApplicationContext的类型,默认的是XmlWebApplicationContext。所以,web.xml文件中是配置参数名字必须是这个。
注意,ContextLoaderListener带参数的构造方法FrameworkServlet#initWebApplicationContext里面对webApplicationContext的非空判断是有别的用处的,在分析Springboot是怎么做的时候再说
二. Spring中Application的父子容器
结合上面的Servlet容器和Servlet的关系,理解Spring的父子容器就好理解了。
关系如下:
- 父容器是不能访问子容器。子容器是可以访问父容器
- 父容器的环境(Environment)和子容器的环境(Environment)是一样的,因为他有一个合并的操作。
例子:
父容器是不能访问子容器。子容器是可以访问父容器
父容器配置类例子:
@Configuration(proxyBeanMethods = false)
public class ParentConfig {
@Bean
public B1 b1(){
B1 parent = new B1("parent", 12);
return parent;
}
static public class B1 {
private String name;
private Integer age;
public B1(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "B1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
子容器配置类
@Configuration(proxyBeanMethods = false)
public class SimpleConfiguration {
@Bean
public Student student(Environment environment) {
System.out.println(environment);
Student student = new Student();
student.setName("a");
student.setAge(1);
return student;
}
static public class Student {
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
主测试类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SimpleConfiguration.class);
context.refresh();
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(ParentConfig.class);
context.setParent(parent);
System.out.println("================");
// 从子类中获取父类中定义的bean。
B1 bean = context.getBean(B1.class);
System.out.println(bean);
context.close();
答案
是可以获取的到的,下面从源码来看看
直接看getBean,最后就会找到这样一段代码。DefaultListableBeanFactory#resolveBean
@Nullable
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
// 先从自己找
NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
if (namedBean != null) {
return namedBean.getBeanInstance();
}
// 从父类找
BeanFactory parent = getParentBeanFactory();
if (parent instanceof DefaultListableBeanFactory) {
return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull);
}
else if (parent != null) {
ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
if (args != null) {
return parentProvider.getObject(args);
}
else {
return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable());
}
}
return null;
}
父容器的环境(Environment)和子容器的环境(Environment)是一样的,因为他有一个合并的操作。
- 父容器Environment中添加属性,子容器能获取到。
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SimpleConfiguration.class);
context.refresh();
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(ParentConfig.class);
ConfigurableEnvironment environment = parent.getEnvironment();
// 父类中添加属性
environment.getPropertySources()
.addLast(new MockPropertySource().withProperty("a","b"));
context.setParent(parent);
System.out.println("================");
B1 bean = context.getBean(B1.class);
System.out.println(bean);
System.out.println(context.getEnvironment().getProperty("a", String.class));
context.close();
}catch (Throwable e){
e.printStackTrace();
}
- 父容器Environment中添加属性,子容器能获取到。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SimpleConfiguration.class);
context.refresh();
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(ParentConfig.class);
ConfigurableEnvironment environment = parent.getEnvironment();
environment.getPropertySources()
.addLast(new MockPropertySource().withProperty("a","b"));
context.setParent(parent);
System.out.println("================");
B1 bean = context.getBean(B1.class);
System.out.println(bean);
System.out.println(context.getEnvironment().getProperty("a", String.class));
context.close();
源码分析
从 context.setParent(parent);
进去,能找到下面的代码,AbstractApplicationContext#setParent
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
// 合并操作
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
这样设置是为了什么?
我觉得抽取底层,就可以将一些基本的功能,放在父容器中,每个子容器都是自己的东西,这也符合父子容器的概念。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢