Spring Framework--ApplicationComtext(2)以ClassPathXmlApplicationContext看ApplicationContext
来源:互联网 发布:软件测试工程师的出路 编辑:程序博客网 时间:2024/06/05 23:05
前言
上篇博客我们简单介绍了ApplicationContext,说实话,讲得太糙了,自己都看不下去了。所以打算在本文和后面的文章以稍微详细的说明来弥补之前的不足。本文将以debug ClassPathXmlApplicationContext的方式一步一步去了解Application。
1. 概述
首先让我们来看个spring的测试用例:
private static final String PATH = "/org/springframework/context/support/"; private static final String CONTEXT_A = "test/contextA.xml"; private static final String CONTEXT_B = "test/contextB.xml"; private static final String CONTEXT_C = "test/contextC.xml"; private static final String FQ_CONTEXT_A = PATH + CONTEXT_A; private static final String FQ_CONTEXT_B = PATH + CONTEXT_B; private static final String FQ_CONTEXT_C = PATH + CONTEXT_C;@Test public void testMultipleConfigLocations() { // 根据多个xml路径实例化beans ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( FQ_CONTEXT_B, FQ_CONTEXT_C, FQ_CONTEXT_A); // 断言,BeanFacoty(containsBean是ClassPathXmlApplication的纠结父类BeanFantocty的方法)里面是否包含传入参数的方法。 assertTrue(ctx.containsBean("service")); assertTrue(ctx.containsBean("logicOne")); assertTrue(ctx.containsBean("logicTwo")); // 通过getBean获取相应的实例化bean // re-refresh (after construction refresh) Service service = (Service) ctx.getBean("service"); ctx.refresh(); assertTrue(service.isProperlyDestroyed()); // regular close call service = (Service) ctx.getBean("service"); ctx.close(); assertTrue(service.isProperlyDestroyed()); // re-activating and re-closing the context (SPR-13425) ctx.refresh(); service = (Service) ctx.getBean("service"); ctx.close(); assertTrue(service.isProperlyDestroyed()); }
上面是一个ClassPathXmlApplicationContext的应用,通过xml的路径,获取xml下的bean配置,从而获取实例化bean。
1.1 初识ClassPathXmlApplicationContext
下面看看Spring中开发者对ClassPathXmlApplicationContext的讲解
/** * Standalone XML application context, taking the context definition files * from the class path, interpreting plain paths as class path resource names * that include the package path (e.g. "mypackage/myresource.txt"). Useful for * test harnesses as well as for application contexts embedded within JARs. * 独立XML应用程序上下文,从类路径中获取上下文定义文件,将纯路径解释为包含程序包路径的类路径资源名称(例如“mypackage / myresource.txt”)。对测试工具以及嵌入JAR的应用程序上下文非常有用。 * <p>The config location defaults can be overridden via {@link #getConfigLocations}, * Config locations can either denote concrete files like "/myfiles/context.xml" * or Ant-style patterns like "/myfiles/*-context.xml" (see the * {@link org.springframework.util.AntPathMatcher} javadoc for pattern details). * * <p>Note: In case of multiple config locations, later bean definitions will * override ones defined in earlier loaded files. This can be leveraged to * deliberately override certain bean definitions via an extra XML file. * * <p><b>This is a simple, one-stop shop convenience ApplicationContext. * Consider using the {@link GenericApplicationContext} class in combination * with an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader} * for more flexible context setup.</b> * * @author Rod Johnson * @author Juergen Hoeller * @see #getResource * @see #getResourceByPath * @see GenericApplicationContext */
下面是ClassPathXmlApplicationContext类图!
由图可知,ClassPathXmlApplicationContext继承关系为:
@startuml
ApplicationContext<|—AbstactApplicationContext<|—AbstractRefreshableApplicationContext<—AbstractRefreshableConfigApplicationContext<|—AbstractXmlApplicationContext<|–ClassPathXmlApplicationContext
@enduml
下图为ClassPathXmlApplicationContext所包含的方法:
又上图可知,ClassPathXmlApplicationContext几乎全是构造方法的重载(Overload)
/** * 不难看出,其造函数主要分为两类: * 1.指定xml文件配置路径,不指定需要获取的bean实例化对象。 * 2.指定xml文件配置路径,指定需要获取的bean实例化对象。 * 分别为一下两种方法。 *//** * Create a new ClassPathXmlApplicationContext with the given parent, * loading the definitions from the given XML files. * @param configLocations array of resource locations * @param refresh whether to automatically refresh the context, * loading all bean definitions and creating all singletons. * Alternatively, call refresh manually after further configuring the context. * @param parent the parent context * @throws BeansException if context creation failed * @see #refresh() */public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }/** * Create a new ClassPathXmlApplicationContext with the given parent, * loading the definitions from the given XML files and automatically * refreshing the context. * @param paths array of relative (or absolute) paths within the class path * @param clazz the class to load resources with (basis for the given paths) * @param parent the parent context * @throws BeansException if context creation failed * @see org.springframework.core.io.ClassPathResource#ClassPathResource(String, Class) * @see org.springframework.context.support.GenericApplicationContext * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader */public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent) throws BeansException { super(parent); Assert.notNull(paths, "Path array must not be null"); Assert.notNull(clazz, "Class argument must not be null"); this.configResources = new Resource[paths.length]; for (int i = 0; i < paths.length; i++) { this.configResources[i] = new ClassPathResource(paths[i], clazz); } refresh(); }
2. 一起学习源码—debug spring test
下面将debug org.springframework.context.support.ClassPathXmlApplicationContextTests 中的testConfigLocationPattern,以此了解ApplicationContext中的ClassPathXmlApplicationContext的初始化过程。
private static final String PATH = "/org/springframework/context/support/";private static final String CONTEXT_WILDCARD = PATH + "test/context*.xml";@Test public void testConfigLocationPattern() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD); assertTrue(ctx.containsBean("service")); assertTrue(ctx.containsBean("logicOne")); assertTrue(ctx.containsBean("logicTwo")); Service service = (Service) ctx.getBean("service"); ctx.close(); assertTrue(service.isProperlyDestroyed()); }
可知,此处是一个通过通配符获取test目录下所有已context打头的所有xml配置文件,以此初始化ClassPathXmlApplicationContext,从而实例化beans。
以下一段只是觉得搞笑,当做注释吧!
哈哈,太好玩了。当我进入到
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
这个函数时,没想到第一步居然是跳到了AbstractApplicationContext下的一段静态代码块
static { // Eagerly load the ContextClosedEvent class to avoid weird classloader issues // on application shutdown in WebLogic 8.1. (Reported by Dustin Woods.) ContextClosedEvent.class.getName(); }
注意看注释,他说,这么急切的加载ContextClosedEvent类,是为了避免WebLogic 8.1中在关闭应用程序的时候出现奇怪的类加载器问题。
~QAQ~ 莫名的喜感
然后进入到ClassPathXmlApplicationContext的构造函数中,最终会调用以下这个构造函数
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
(1)Super(parent)—>首先会去加载父类的构造方法;
org/springframework/context/support/AbstractApplicationContext.java/** * Create a new AbstractApplicationContext with the given parent context. * @param parent the parent context */public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } //--------------------------------------------------------------------- // Implementation of ConfigurableApplicationContext interface //--------------------------------------------------------------------- /** * Set the parent of this application context. * <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is * {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with * this (child) application context environment if the parent is non-{@code null} and * its environment is an instance of {@link ConfigurableEnvironment}. * @see ConfigurableEnvironment#merge(ConfigurableEnvironment) */ @Override public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } }
已知这里通过传进来的ApplicationContext获取配置环境,但是显然,我们调用的ClassPathXmlApplicationContext的构造方法传入参数为null。
(2) setConfigLocations(configLocations);——设置spring的配置文件
public void setConfigLocations(@Nullable String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
易知,这里根据传入的xml路径,去解析beans路径。
这里通过resolvePath去获取一个StandardEnvironment环境的。这里值得一提的是对通配符*的解析:
org.springframework.util.PropertyPlaceholderHelperprotected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }
最后调用的是org.springframework.util.PropertyPlaceholderHelper 里的parseStringValue方法去解析通配符,其时序图如下附图1。这个后期认真学习。
(3)refresh();——//调用父类的refresh函数,进行一系列初始化
/** * Load or refresh the persistent representation of the configuration, * which might an XML file, properties file, or relational database schema. * <p>As this is a startup method, it should destroy already created singletons * if it fails, to avoid dangling resources. In other words, after invocation * of that method, either all or no singletons at all should be instantiated. * @throws BeansException if the bean factory could not be initialized * @throws IllegalStateException if already initialized and multiple refresh * attempts are not supported */@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
关于refresh函数里面的一些具体函数,后面的博客将逐一介绍。
注:
- parseStringValue 转义通配符后期需要好好学习。
- refresh函数后期介绍
附图一: