一、背景介绍 Spring 容器是 Spring 框架的核心。容器负责配置对象,注入对象,管理对象,并完成整个生命周期。Spring 容器依赖控制反转(Inversion of Control),也即依赖注入(Dependency Injection)来配置并注入对象。这样的对象被成为 Bean。具体过程可抽象为:Spring 读取程序中的 Bean 配置信息,并据此在容器中生成一份 Bean 配置注册表,然后再根据注册表实例化 Bean ,装配好待用。
Spring 提供了两种容器:BeanFactory 与 ApplicationContext。BeanFactory 为 Spring Ioc 功能提供了底层的实现基础,但为了实现框架化,Spring 新增了ApplicationContext 接口。 ApplicationContext 继承于 BeanFactory,并涵盖其所有功能。官方 对两个容器做出比较:
特征
BeanFactory
ApplicationContext
Bean 实例化 / 连接
是
是
集成生命周期管理
否
是
BeanPostProcessor 自动注册
否
是
BeanFactoryPostProcessor 自动注册
否
是
利用 MessageSource 进行国际化
否
是
嵌入式 ApplicationEvent 发布机制
否
是
本文分析 ApplicationContext 容器。Spring 源码参考版本 5.2.5.RELEASE 。
二、ApplicationContext 容器涉及的类关系图 ApplicationContext 有很多实现类,这里我们以 Java EE web (采用 Spring 框架)应用的启动过程为例。为了更直观地描述初始化过程,我们有必要认识 XmlWebApplicationContext 的关联类图:
三、ApplicationContext 容器初始化 Java EE Web 项目启动,会触发 ContextLoaderListener 监听器,完成 WebApplicationContext -根容器的初始化。具体实现方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 public WebApplicationContext initWebApplicationContext (ServletContext servletContext) { if (servletContext .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null ) { throw new IllegalStateException("..." ); } 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 { if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } if (this .context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this .context; if (!cwac.isActive()) { if (cwac.getParent() == null ) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } 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; } } WebApplicationContext createWebApplicationContext (ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class .isAssignableFrom (contextClass )) { throw new ApplicationContextException("..." ); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } 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); } } }
从代码实现可以得出:
一个 Web 应用,根容器只能创建一次;
根容器的实现类可配置,默认为 XmlWebApplicationContext;
需要为根容器指定父容器,旨在实现父容器-子容器的继承架构。目前持保留实现,默认为 null;
根容器 WebApplicationContext 是挂载在应用上下文 ServletContext 下的,并指定属性名 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
下面介绍根容器的配置与刷新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void configureAndRefreshWebApplicationContext (ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null ) { wac.setId(idParam); } else { 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); } ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null ); } customizeContext(sc, wac); wac.refresh(); } 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("..." )); } this .contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); AnnotationAwareOrderComparator.sort(this .contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this .contextInitializers) { initializer.initialize(wac); } } }
从代码实现可以得出:
通过 <context-parm>configLocation</context-param> 为根容器添加配置信息;
因为只要刷新上下文,就要调用环境的 initPropertySources 方法,所以需要提前初始化环境属性,以保证根容器刷新之前的一些操作,如后置处理或初始化过程,都可以直接获取到 Servlet 属性;
在根容器添加配置后,刷新前,执行自定义操作。根据 ServletContext 的 contextInitializerClasses 和 globalInitializerClasses 配置加载所有的上下文初始化程序,并依次执行;
自定义根容器后,执行根容器的刷新。放在下一个专栏分解。