7 minutes
Week1034_share
ARTS - Share 补2019.2.27
Spring容器启动过程
根据这个图了解spring的启动过程:
Web.xml
-Start-contextInitialzed(ServletContextEvent event)
ContextLoaderListener
-1. initWebApplicationContext(event.getServletContext)
ContextLoader
-1.1 createWebApplicationContext(servletContext)
-1.1.1 determineContextClass(sc) 获取web应用上下文类的class
-1.1.1.1 BeanUtils.instantiateClass(contextClass)工具实例化bean
-1.1.1.2 T
1.1.2 ClasscontextClass
1.2 WebApplicationContext context
1.3 configureAndRefreshWebApplicationContext(cwac, servletContext)
1.3.1 customizeContext(sc, wac) 查找所有配置的ApplicationContext初始化容器
1.3.2 void
1.3.3 -wac.refresh()
1.3.4 void
1.4 void
web.xml
Tomcat启动会首先找web.xml文件,spring容器的入口自然就是这里注册的ContextLoaderListener
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener
进入这个类
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
/**
* 创建一个新的 ContextLoaderListener ,将会创建一个基于 contextClass 和 ContextConfigLocation context-params 的web应用上下文。看父类的ContextLoader来看默认配置值。
*
* 在web.xml里声明ContextLoaderListener时候一个无参构造是必须的。
* 创建的应用上下文将会注册到ServletContext里, 在属性名为WebApplicationContext 的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
*
*/
public ContextLoaderListener() {
}
/**
* 使用给定的应用context来创建一个新的ContextLoaderListener.这个构造方法在基于实例的Servet3.0+环境中使用javax.servlet.ServletContext#addListener注册listener 也是可以的。
*
* 如果以下情况context可能没被ConfigurableApplicationContext 重新刷新:
* a. 该context实现了ConfigurableWebApplicationContext接口
* b. 没有被推荐方法重新刷新
* 之后会发生以下情况:
* 1.如果给定的context 没有被分配一个ConfigurableApplicationContext#setId的 id,将会分配一个
* 2.ServletContext和ServletConfig 对象将会委托给应用上下文
* 3.customizeContext将会被调用
* 4.任意的ApplicationContextInitializer通过contextInitializerClasses定义的init-param参数将会被应用
* 5.ConfigurableApplicationContext#refresh 将会被调用
*
* 如果context上下文已经被重新刷新或者没有实现ConfigurableWebApplicationContext,以上都不会发生。
* 用户根据自己需要明确定义。
* 看 org.springframework.web.WebApplicationInitializer} 使用例子.
* 无论何种情况,给定的context都会被注册到ServletContext 的属性WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE之下,spring应用上下文将在调用contextDestroyed 方法后关闭。
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* 初始化根web应用上下文
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* 关闭根web应用上下文
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
1.initWebApplicationContext(event.getServletContext)
初始化WebApplicationContext。我们继续看看这里的初始化方法,它调用了父类ContextLoader的initWebApplicationContext方法,
/**
* 初始化给定的servlet上下文 ,或者根据CONTEXT_CLASS_PARAM 上下文类和 CONFIG_LOCATION_PARAM上下文配置的context-params来创建一个新的。
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 储存context到本地实例变量,保证ServletContext关闭时候也存在
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 如果context实现了ConfigurableWebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 如果context没被刷新,就去配置父context, context id 等
// 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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新context
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
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.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
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;
}
}
1.1 createWebApplicationContext(servletContext)
/**
* 为loader 初始化根WebApplicationContext,不是默认的context class 就是一个明确自定义的context class
* 这个实现期望自定义context实现ConfigurableWebApplicationContext接口
* 可以在自类被覆盖
* 另外,customizeContext调用刷新context, 允许子类自定义修改context.
*
* @param sc 当前context
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
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);
}
1.1.1 determineContextClass(sc) 获取web应用上下文类的class
/**
* 返回WebApplicationContext实现的class来使用,不是默认XmlWebApplicationContext就是明确自定义的context 类。
* @param servletContext 当前 servlet context
* @return the WebApplicationContext implementation class to use
* @see #CONTEXT_CLASS_PARAM
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
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 {
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);
}
}
}
1.1.1.1 BeanUtils.instantiateClass(contextClass)工具实例化bean
// createWebApplicationContext方法里determineContextClass 返回加载的Class, 之后实例化这些class
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
/**
* 使用无参构造初始化class
* 注意如果方法非public ,会尝试设置为可操作的
* <p>Note that this method tries to set the constructor accessible
* if given a non-accessible (that is, non-public) constructor.
* @param clazz class to instantiate
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
* @see Constructor#newInstance
*/
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
return instantiateClass(clazz.getDeclaredConstructor());
}
catch (NoSuchMethodException ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
/**
*
* 使用给定的构造器实例化class
* <p>Note that this method tries to set the constructor accessible if given a
* non-accessible (that is, non-public) constructor.
* @param ctor the constructor to instantiate
* @param args the constructor arguments to apply
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
* @see Constructor#newInstance
*/
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(args);
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
}
catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
}
catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}
// 实例化完成,强转为ConfigurableWebApplicationContext 类型返回
1.3 configureAndRefreshWebApplicationContext(cwac, servletContext)
之后就进入了ConfigurableWebApplicationContext逻辑了
// 如果当前context是实现了可配置的WebApplicationContet, 就去刷新一下
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 没被刷新(设置父context, 设置context id等),
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 之后刷新context
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
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
// context id 设置为原始默认值
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
// 生成默认ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 处置自定义的Context
customizeContext(sc, wac);
wac.refresh();
}
1.3.1 customizeContext(sc, wac) 查找所有配置的ApplicationContext初始化容器
/**
* context refresh 之前、config location应用之后,设置自定义参数
*
* <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext)
* determines} what (if any) context initializer classes have been specified through
* {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and
* {@linkplain ApplicationContextInitializer#initialize invokes each} with the
* given web application context.
* <p>Any {@code ApplicationContextInitializers} implementing
* {@link org.springframework.core.Ordered Ordered} or marked with @{@link
* org.springframework.core.annotation.Order Order} will be sorted appropriately.
* @param sc the current servlet context
* @param wac the newly created application context
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
*/
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
// 要初始化的类
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));
}
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
/**
* 返回使用CONTEXT_INITIALIZER_CLASSES_PARAM指定的实现class
* Return the {@link ApplicationContextInitializer} implementation classes to use
* if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}.
* @param servletContext current servlet context
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
*/
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
// 创建集合储存所有的全局参数定义,和自定的
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
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;
}
这之后,刷新context : wac.refresh();
刷新完context, 所有调用结束,返回。