基于XML 文件的 Spring Ioc 的启动流程
IOC时序图
spring的启动是建筑在servlet容器之上的,所有web工程的初始位置就是web.xml,它配置了servlet的上下文(context)和监听器(Listener)
web.xml中一般需要配置前端控制器与上下文监听器,如下:
<!--dispatcherServlet的配置,这个servlet主要用于前端控制,这是springMVC的基础-->
<servlet>
<servlet-name>service_dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/services/service_dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--spring资源上下文定义,在指定地址找到spring的xml配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/application_context.xml</param-value>
</context-param>
<!--spring的上下文监听器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
1.1 spring的上下文监听器
上下文监听器在web.xml中的配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/application_context.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
spring的启动其实就是IOC容器的启动过程,通过上述的第一段配置<context-param>
是初始化上下文,然后通过后一段的的来加载配置文件,其中调用的spring包中的ContextLoaderListener
这个上下文监听器,ContextLoaderListener
是一个实现了ServletContextListener
接口的监听器,他的父类是 ContextLoader
,在启动项目时会触发contextInitialized
上下文初始化方法。下面我们来看看这个方法:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
public ContextLoaderListener() {}
public ContextLoaderListener(WebApplicationContext context) super(context);
// 上下文初始化,启动项目的时候触发
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = this.createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
// 核心的初始化方法
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
//上下文销毁方法,关闭项目时触发
public void contextDestroyed(ServletContextEvent event) {
if (this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
}
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
1.1.1 初始化上下文方法解析
ContextLoaderListener的contextInitialized(ServletContextEvent event)
这个上下文初始化方法是调用了父类ContextLoader
的initWebApplicationContext(event.getServletContext());
方法,很显然,这是对ApplicationContext的初始化方法,也就是到这里正是进入了springIOC的初始化。
initWebApplicationContext(event.getServletContext());
主要干了三件事情
- 创建WebApplicationContext。(核心方法是createWebApplicationContext)
- 加载Spring配置文件,Bean相关操作。(核心方法是refresh)
- 将WebApplicationContext放入ServletContext(Java Web的全局变量)中。(servletContext.setAttribute() )
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!");
} else {
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 {
if (this.context == null) {
// 1、创建applicationContext
this.context = this.createWebApplicationContext(servletContext);
}
// 2、如果这个类是ConfigurableWebApplicationContext或者其子类等相关类
//如果是则configureAndRefreshWebApplicationContext方法就是加载对应的Spring配置文件中的Bean。
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);
}
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//3、将WebApplicationContext放入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 var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
}
创建WebApplicationContext
- 首先判断servletContext的具体类型contextClass
- 然后判断该具体类型contextClass是否是ConfigurableWebApplicationContext同类型或者子类或者子接口
- 根据实例化该contextClass并转型为ConfigurableWebApplicationContext返回
// 1、创建applicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// determineContextClass主要是根据类加载器判断sc的具体类型
Class<?> contextClass = this.determineContextClass(sc);
// 这里isAssignableFrom主要是判断contextClass是否可以向上转型为ConfigurableWebApplicationContext
// 也就是说ConfigurableWebApplicationContext是否是contextClass的同类型、或者超类、或者超接口
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
// determineContextClass主要是根据类加载器判断sc的具体类型
protected Class<?> determineContextClass(ServletContext servletContext) {
// 判断是否指定contextClass的名称,则根据加载器去找该类
String contextClassName = servletContext.getInitParameter("contextClass");
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
// 否则,该类名称为WebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
//实例化该class
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");
} else {
try {
return instantiateClass(clazz.getDeclaredConstructor());
} catch (NoSuchMethodException var2) {
throw new BeanInstantiationException(clazz, "No default constructor found", var2);
}
}
}
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 var3) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", var3);
} catch (IllegalAccessException var4) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", var4);
} catch (IllegalArgumentException var5) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", var5);
} catch (InvocationTargetException var6) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", var6.getTargetException());
}
}
加载Spring配置,Bean相关操作
- 设置id值
- 设置配置文件地址
- 若有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作
- 读取SpringMVC配置文件,解析bean、组装bean。wac是ConfigurableApplicationContext的类型,refresh方法由其子抽象类AbstractApplicationContext进行实现
// 2、加载对应的Spring配置文件中的Bean
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String initParameter;
// 判断是否自定义了该wac的id,如果自定义了,就设置
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
initParameter = sc.getInitParameter("contextId");
if (initParameter != null) {
wac.setId(initParameter);
} else if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getServletContextName()));
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//contextConfigLocation是在web.xml文件中指定的配置文件的地址
initParameter = sc.getInitParameter("contextConfigLocation");
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
// 如果有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作。
this.customizeContext(sc, wac);
// 这个方法就是读取SpringMVC配置文件,解析bean、组装bean等等一系列操作了
wac.refresh();
}
//如果有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作。
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = this.determineContextInitializerClasses(servletContext);
if (initializerClasses.size() != 0) {
Class<?> contextClass = applicationContext.getClass();
ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList();
Iterator i$ = initializerClasses.iterator();
while(i$.hasNext()) {
Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass = (Class)i$.next();
Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format("Could not add context initializer [%s] as its generic parameter [%s] is not assignable from the type of application context used by this context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(), contextClass.getName()));
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
ConfigurableEnvironment env = applicationContext.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(servletContext, (ServletConfig)null);
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
Iterator i$ = initializerInstances.iterator();
while(i$.hasNext()) {
ApplicationContextInitializer<ConfigurableApplicationContext> initializer = (ApplicationContextInitializer)i$.next();
initializer.initialize(applicationContext);
}
}
}
//读取SpringMVC配置文件,解析bean、组装bean
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
// 1.调用容器准备刷新的方法,获取当前时间和同步标识
this.prepareRefresh();
// 2.调用子类的refreshBeanFactory()方法,Bean定义资源文件的载入从这里启动,解析Bean
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 3.为beanFactory配置事件处理器、类加载器等
this.prepareBeanFactory(beanFactory);
try {
// 4.为容器的某些子类指定特殊的Post事件处理器
this.postProcessBeanFactory(beanFactory);
// 5.调用所有注册的BeanFactoryPostProcesser的Bean
this.invokeBeanFactoryPostProcessors(beanFactory);
// 6.为BeanFactory注册Post事件处理器
this.registerBeanPostProcessors(beanFactory);
// 7.国际化初始化
this.initMessageSource();
// 8.初始化事件传播器
this.initApplicationEventMulticaster();
// 9.调用子类某些特殊的Bean初始化方法
this.onRefresh();
// 10.为上面的事件传播器注册事件监听器
this.registerListeners();
// 11.初始化所有单例Bean
this.finishBeanFactoryInitialization(beanFactory);
// 12.完成初始化
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
// 销毁已创建的Bean
this.destroyBeans();
// 取消刷新并重置标识
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
1.1.2 Spring 容器对Bean配置资源的载入是从refresh方法开始的
AbstractApplicationContext里面的obtainFreshBeanFactory方法,来进行刷新beanFactory方法
核心方法是loadBeanDefinitions方法,核心过程如下:
- 通过ResourceLoader 来完成资源文件的定位,也就是说Bean定义文件时通过抽象成Resource来被Ioc容器处理
- 容器通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,Bean的定义信息由BeanDefinition来表示
- 注册过程就是维护一个HashMap保存得到的这个BeanDefinition
(1)obtainFreshBeanFactory:加载bean的资源
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
// 刷新beanFactory
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
这个方法是由其子类AbstractRefreshableApplicationContext进行实现,核心的方法是loadBeanDefinitions() 方法
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
// 核心方法
this.loadBeanDefinitions(beanFactory);
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;
loadBeanDefinitions由AbstractRefreshableApplicationContext的子类AbstractXmlApplicationContext进行实现
// 实现父类抽象的载入Bean定义方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建XmlBeanDefinitionReader,创建Bean读取器
// 并通过回调设置到容器中,容器使用该读取器读取Bean 的配置资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);、
// 为Bean读取器设置Spring资源加载器
// AbstractXmlApplicationContext的祖先父类AbstractApplicationContext继承DefaultResourceLoader
// 因此容器本身也是一个资源加载器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
// 为Bean读取器设置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 当Bean读取器读取Bean定义的xml资源文件时,启用xml的校验机制
this.initBeanDefinitionReader(beanDefinitionReader);
// Bean读取器真正实现加载的方法
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
// xml Bean读取器加载Bean配置资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 获取Bean配置资源的定位
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
// xml Bean 读取器调用其父类 XmlBeanDefinitionReader 读取定位的Bean配置资源
reader.loadBeanDefinitions(configResources);
}
// 如果子类中获取的Bean资源定位为空
// 则获取ClassPathXmlApplicationContext 构造方法中setConfigLocations方法设置的资源
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
// xml Bean 读取器调用其父类 XmlBeanDefinitionReader 读取定位的Bean配置资源
reader.loadBeanDefinitions(configLocations);
}
}
1.2 DispatchServlet的加载
查看另外一篇博文:DispatchServlet的原理