ApplicationContext是spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了(IOC容器)。
容器又是个很抽象的概念,在 Java 中形如 ArrayList、HashMap 等都是容器。 那么 Spring 如何保存这么多 Bean 呢?
随便在一个 getBean 方法上下断点:
@Test public void test1() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean(UserMapper.class);//断了个点 }进入了AbstractApplicationContext#getBean(java.lang.Class<T>),AbstractApplicationContext 实现了 BeanFactory 接口并实现了其中的getBean方法。
getBeanFactory 是一个抽象方法。
@Override public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;有如下两个实现: Spring 使用的是第一个。
@Override public final ConfigurableListableBeanFactory getBeanFactory() { synchronized (this.beanFactoryMonitor) { if (this.beanFactory == null) { throw new IllegalStateException("BeanFactory not initialized or already closed - " + "call 'refresh' before accessing beans via the ApplicationContext"); } return this.beanFactory; } }beanFactory 的定义:
private DefaultListableBeanFactory beanFactory;最终调用的是 DefaultListableBeanFactory 里的 getBean 方法。
@Override public <T> T getBean(Class<T> requiredType) throws BeansException { return getBean(requiredType, (Object[]) null); } @Override public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException { NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args); if (namedBean != null) { return namedBean.getBeanInstance(); } BeanFactory parent = getParentBeanFactory(); if (parent != null) { return (args != null ? parent.getBean(requiredType, args) : parent.getBean(requiredType)); } throw new NoSuchBeanDefinitionException(requiredType); } /** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);就是 Spring 中保存 Bean 的容器。
在Web应用中,我们会用到WebApplicationContext,WebApplicationContext继承自ApplicationContext。 WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须拥有Web容器的前提下才能完成启动的工作。 有过Web开发经验的读者都知道可以在web.xml中配置Web容器监听器ServletContextListener,借助于该监听器我们就可以启动Spring Web应用上下文的工作。
spring为我们提供了用于启动WebApplicationContext的Web容器监听器:ContextLoaderListener,用来在web应用启动的时候来初始化WebApplicationContext。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }当Servlet 容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由 ServletContextListener 来处理。 在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。 - contextInitialized(ServletContextEvent sce) :当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。 - contextDestroyed(ServletContextEvent sce) :当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { ... this.context = createWebApplicationContext(servletContext); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ... }我们发现,原来ContextLoader是把WebApplicationContext放在了ServletContext中,ServletContext也是一个“容器”,也是一个类似Map的结构,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们如果要使用WebApplicationContext则需要从ServletContext取出,Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。 WebApplicationContextUtils是一个抽象类,其提供了一个很便利的方法来获取spring应用的上下文即WebApplicationContext。
其中的静态方法getWebApplicationContext(ServletContext sc),提供一个ServletContext 类型参数即可。 其原理十分简单,在spring容器初始化的方法ContextLoader.initWebApplicationContext(ServletContext)中通过servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);已经将WebApplicationContext的实例放入ServletContext 中了。 然后在工具类的org.springframework.web.context.support.WebApplicationContextUtils的 getWebApplicationContext(ServletContext)方法中就可以通过传入的ServletContext参数获取到WebApplicationContext实例了。
public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { Assert.notNull(sc, "ServletContext must not be null"); Object attr = sc.getAttribute(attrName); ... }