怎么看?
查看源码的顺序就见仁见智了,比较普遍的做法是从IoC入手,了解容器注入的每一个环节,掌握大致的流程。
使用Spring
使用Spring实现一个IoC
由于使用的是Spring,所以在这里我们引入比较古老的xml配置文件进行bean的配置,首先定义一个bean:
package com.test;
public class HelloworldBean{
public void hello(){
System.out.println("HelloWorld");
}
}
配置描述bean的xml,核心只有一行:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="helloWorld" class="com.test.HelloWorld"> </beans>
这样一来就可以使用BeanFactory这个容器来注入bean并使用了:
Resource resource = new ClassPathResource("application.xml");
BeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader =
new XmlBeanDefinitionReader((BeanDefinitionRegistry)beanFactory);
beanDefinitionReader.loadBeanDefinitions(resource);
HelloworldBean hb = (HelloworldBean)beanFactory.getBean("hellloWorld");
hb.hello();
本来有封装好的XmlBeanFactory,这一类现在已经被弃用了,所以采用了他的父类DefaultListableBeanFactory;当然,也可以使用更加方便和常用的ApplicationContext:
ApplicationContext applicationContext= new ClassPathXmlApplicationContext("application.xml");
HelloworldBean hb = (HelloworldBean) applicationContext.getBean("hellloWorld");
hb.hello();
当然从xml文件读取bean的配置只是其中一种目前用的不多的加载方式,还有基于包扫描等加载bean的方法,此处仅为理解IoC的基本使用。
使用Web容器激活Spring
让我们回忆一下经典的“SSM”架构的配置,绝大多数的Java项目都是web项目,我们想要为程序提供一个入口,使用Web容器诸如
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <listener> <description>springListener</description> <!-- 这个listener会监听到web容器的启动事件 --> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>seckill-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contain spring-dao.xml,spring-service.xml,spring-web.xml --> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </init-param> </servlet> <!-- 以下是SpringMVC的配置 --> <servlet-mapping> <servlet-name>seckill-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
这样一来当我们启动web容器时,ContextLoaderListener就会监听到启动事件并进行Spring的初始化行为。
Spring启动监听器——ContextLoaderListener
类定义
在上面的web.xml文件中,我们除去定义了Spring相关配置文件外,还定义了一个listener,最常用的ContextLoaderListener,我们首先看此类的注释:
/*
* Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
* Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
*
* <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
* application context via the {@link #ContextLoaderListener(WebApplicationContext)}
* constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 17.02.2003
* @see #setContextInitializers
* @see org.springframework.web.WebApplicationInitializer
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
此类实现了ServletContextListener接口,其中 后者由Servlet提供,包含了contextInitialized和contextDestroyed两个监听事件(方法),这意味着在servlet容器(例如
此类也继承了ContextLoader类,这是由Spring实现的上下文加载类,继承是为调用相关方法,ServletContextListener提供的触发方法在其中的实现其实全部交给了其父类ContextLoader中的实现。
ContextLoader的实现-initWebApplicationContext方法
ContextLoader封装了context的初始化逻辑。
我们主要研究容器启动时的操作:即initWebApplicationContext方法,最终生成了WebApplicationContext,是一个很常见的ApplicationContext继承,注意里面的中文注释,对于文中获取this.context后的操作暂时不在此篇讲述:
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!");
}
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 {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
//如果非空 是针对Servlet 3.1+自定义Context的情况
if (this.context == null) {
//创建一个ApplicationContext
this.context = createWebApplicationContext(servletContext);
}
//对于默认的XmlWebApplicationContext 肯定会进入这一分支
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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);
}
//在此处会加载具体的配置项,例如contextConfigLocation
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.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;
}
}
在createWebpplicationContext方法中:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//获取Context类
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);
}
获取context-determineContextClass方法
接下来进入determineContextClass方法:
/**
* Return the WebApplicationContext implementation class to use, either the
* default XmlWebApplicationContext or a custom context class if specified.
* @param servletContext current 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) {
//查找有无自定义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);
}
}
//从defaultStrategies获取webApplicationContext类,defaultStrategies的初始化在静态块中
else {
//上面的注释已经提到 此处会默认初始化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);
}
}
}
这里的WebApplicationContext是一个接口,其通过defaultStrategies默认的实现为XmlWebApplicationContext,即使用web.xml配置加载IoC容器相关的信息,defaultStrategies是一个配置信息类,让我们看看他的定义和初始化:
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
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());
}
}

