**注:为了代码清晰易读,分析代码的过程将会删去日志实现以及异常处理(这些代码也很重要,具体可以看源码)。
1.ContextLoaderListener
初始接口java.util.EventListener,是个空接口,标志着这是个事件监听器。
实现了接口javax.servlet.ServletContextListener,能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。Servlet容器启动或者终止Web应用的时候,就会触发ServletContextEvent事件,该事件就会由ServletContextListener来处理,其中ServletContextListener中定义了两个方法:
public void contextInitialized(ServletContextEvent sce);当Servlet容器启动Web应用时调用该方法。在调用完该方法之后,容器再对Filter初始化,并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。
public void contextDestroyed(ServletContextEvent sce);当Servlet容器终止Web应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet和Filter过滤器。
继承了org.springframework.web.context.ContextLoader:
①defaultStrategies静态属性
按照默认路径加载配置Properties对象
private static final String DEFAULT_STRATEGIES_PATH = “ContextLoader.properties”;
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
throw new IllegalStateException("Could not load ‘ContextLoader.properties’: " + ex.getMessage());
}
}
其中配置文件就一行:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
说明了如果没有指定那么用的WebApplicationContext类型就是XmlWebApplicationContext类。
②context 属性,Root WebApplicationContext 对象
@Nullable
private WebApplicationContext context;
public ContextLoader() {
}
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
③initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果ServletContext中已经有了RootApplicationContext,就抛出错误。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
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!");
}
//记录开始时间
long startTime = System.currentTimeMillis();
try {
//判断成员变量的WebApplicationContext是不是空的,如果是就创建对象
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//判断是否是ConfigurationWebApplicationContext的子类
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//判断是否已经激活/刷新
if (!cwac.isActive()) {
//如果没有父容器就加载并且设置父容器
if (cwac.getParent() == null) {
//空实现
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//没激活的就重新设置并且激活
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将rootWebApplicationContext存入ServletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//从线程中取出线程上下文类加载器
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
//如果线程上下文类加载器就是当前类的类加载,就把类变量的context指给currentContext
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
//存入map中,键是当前线程的类加载器,值是context
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
④createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//确定Context的类
Class<?> contextClass = determineContextClass(sc);
//如果确定的类型不是ConfigurableWebApplicationContext的子类,抛出错误
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//接着通过反射创建context类的对象,强转成ConfigurableWebApplicationContext
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
⑤determineContextClass
protected Class<?> determineContextClass(ServletContext servletContext) {
//首先从ServletContext中获取设置的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 {
//如果没有设置过,就从defaultStrategies获取类,即 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);
}
}
}
⑥configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//如果使用的是默认id,就重新设置id
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
//如果设置了contextid 就直接使用,用<context-param>在web.xml中设置的
if (idParam != null) {
wac.setId(idParam);
}
else {
//否则自动生成
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//给context设置servletcontext属性
wac.setServletContext(sc);
//从servletcontext中取出储存的configlocation,例如
//<context-param>
// <param-name>contextConfigLocation</param-name>
//<param-value>classpath:config/applicationContext.xml</param-value>
//</context-param>
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
//如果有储存的就赋值给wac
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//不知道环境什么用,这段先忽略
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
//刷新 context ,执行初始化,这是ioc中的内容,简单点描述就是根据applicationContext.xml中配置的内容生成一个容器,称为rootXmlApplicationContext。
wac.refresh();
}
⑦closeWebApplicationContext
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
//关闭context
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
//从currentContext,currentContextPerThread和servletContext移出
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);
}
}
接下来分析ContextLoaderListener。
构造方法:
public ContextLoaderListener() {}
public ContextLoaderListener(WebApplicationContext context) {
super(context);}
第二个构造方法是传入一个WebApplicationContext,这实际上就不再需要再创建一个新的WebApplicationContext对象。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
在容器启动时运行,作用是初始化WebApplicationContext。具体实现在org.springframework.web.context.ContextLoader。上文已经分析。
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
销毁 WebApplicationContext 容器的逻辑,关闭并且清理WebApplicationContext。
2.DispatcherServlet
可以看到左边的关系是关于spring的,右边的是关于servlet的。
HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中。
FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器。
DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。
首先我们知道在创建servlet对象后会调用servlet对象的init方法进行初始化,DispatcherServlet中也有init方法,具体的实现在HttpServletBean类中:
首先看看HttpServletBean的两个成员变量:
@Nullable
private ConfigurableEnvironment environment;
关于environment相关的方法:
@Override // 实现自 EnvironmentAware 接口,自动注入
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
this.environment = (ConfigurableEnvironment) environment;
}
// getting 方法
@Override // 实现自 EnvironmentCapable 接口
public ConfigurableEnvironment getEnvironment() {
// 如果 environment 为空,主动创建
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
//必须要配置的属性的集合
private final Set<String> requiredProperties = new HashSet<>(4);
@Override
//final修饰,是不允许子类重写的
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//目的是解析<init-param>标签,将其内容封装到PropertyValues pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//将当前的Servlet类封装成BeanWrapper对象
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,遇到了Resource属性就会用ResourceLoader进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空实现
initBeanWrapper(bw);
//以spring方式将pvs注入到bw中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
//子类实现
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
下面分析一下上面的几个代码:
1️⃣ServletConfigPropertyValues
这是HttpServletBean的私有静态类,是ServletConfig的PropertyValues封装实现类。
private static class ServletConfigPropertyValues extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
//获取一个缺失属性的集合
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
//遍历ServletConfig的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
//加入MutablePropertyValues的propertyValueList中
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
//如果传入的参数中没有必须要设置的参数就抛出错误
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
2️⃣PropertyAccessorFactory.forBeanPropertyAccess(this)
将目标对象包装成BeanWrapperImpl,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。
3️⃣bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
注册自定义的属性编辑器
4️⃣bw.setPropertyValues(pvs, true);
将pvs注入BeanWrapper对象中,例如可以将配置注入到FrameworkServlet的成员变量中。
5️⃣initServletBean()
这个方法是在子类FrameworkServlet中实现
首先先看看FrameworkServlet中的属性:
//创建的容器类型
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
//配置文件的位置
private String contextConfigLocation;
//context对象
private WebApplicationContext webApplicationContext;
有四种方式来创建:
1️⃣public FrameworkServlet(WebApplicationContext webApplicationContext)构造函数直接传入
2️⃣public void setApplicationContext(ApplicationContext applicationContext)通过spring注入
3️⃣findWebApplicationContext()
4️⃣createWebApplicationContext(WebApplicationContext parent)
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
//记录开始时间
long startTime = System.currentTimeMillis();
try {
//初始化容器
this.webApplicationContext = initWebApplicationContext();
//空实现
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
接下来看看initWebApplicationContext()
protected WebApplicationContext initWebApplicationContext() {
//首先获得根容器,就是注册的rootWebApplication
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果已经通过构造函数传入,那么就可以直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
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);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
if (wac == null) {
wac = findWebApplicationContext();
}
//创建一个context容器
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
//如果未触发刷新事件,则主动触发刷新事件
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//将 context 设置到 ServletContext 中
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;
}
接下来是createWebApplicationContext的实现:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//创建context类的对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置 environment、parent、configLocation 属性
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//初始化wac
configureAndRefreshWebApplicationContext(wac);
return wac;
}
初始化wac的方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//同样的如果是默认编号就重新设置id属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
//设置 wac 的 servletContext、servletConfig、namespace 属性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//添加监听器
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
//空实现,处理完初始化后的逻辑
postProcessWebApplicationContext(wac);
//执行自定义初始化
applyInitializers(wac);
// 刷新 wac ,从而初始化 wac
wac.refresh();
}
真正的在onRefresh中实现了SpringMVC组件的初始化,而该方法的具体实现是在DispatcherServlet中:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
而这个方法将会以两种方式被触发:
1️⃣就是initWebApplicationContext中
if (!this.refreshEventReceived) {
onRefresh(wac);
}
2️⃣在上述的SourceFilteringListener中回调
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
原始类
private final Object source;
代理的监听器
private GenericApplicationListener delegate;
核心方法:
protected void onApplicationEventInternal(ApplicationEvent event) {
if (this.delegate == null) {
throw new IllegalStateException(
"Must specify a delegate object or override the onApplicationEventInternal method");
}
this.delegate.onApplicationEvent(event);
}
将事件转发给delegate监听器。
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));传入的参数中
还有ContextRefreshListener对象,这是FrameworkServlet的内部类:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
OK,完结,最终发现监听后会执行onRefresh方法。