怎么看?
查看源码的顺序就见仁见智了,比较普遍的做法是从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()); } }