Spring源码-xml解析bean(1)

        在SpringAOP初探中,我们大概理解了aop的用法,现在从源码的角度来解析aop的运作原理。很巧的是,在spring源码(一)获取bean中我们分析了怎么获取bean的源码,现在通过xml解析的方式来了解在获取bean之前,这些在xml中定义的bean是怎么被解析、加载的。

 基本概念:

     IOC: (Inversion of Control) 指控制反转或反向控制。在Spring框架中我们通过配置创建类对象,由Spring容器在运行阶段实例化、组装对象。

    DI:      (dependency inject )   依赖注入。向类的属性设置值(设置具体的值,或另一个bean)

   IOC和DI关系密切。IOC形容一种状态,DI是形容一个过程。DI是在IOC基础才能实现。

   

     DI分为手动装配和自动装配 

  • 手动装配:一般进行配置信息都采用手动。基于xml装配:构造方法、setter方法

  • 自动装配(基于注解装配)

1、手动装配

 <bean> 标签的属性有:

  (1)id属性:用于指定配置对象的名称,不能包含特殊符号。 
  (2)class属性:创建对象所在类的全路径。 
  (3)name属性:功能同id属性一致。但是在name属性值中可以包含特殊符号。 
  (4)scope属性

  • singleton:默认值,单例 

    单例模式下,在程序下只有一个实例。非单态模式下,每次请求该Bean,都会生成一个新的对象。

  • prototype:多例

  • request:创建对象后将对象存放到request域

  • session:创建对象后将对象存放到session域

  • globalSession:创建对象后将对象存放到globalSession域

初始化bean的时候,有多种方式可以设置属性值:

假设现在有一个类,RedisCacheUtil ,现在用xml的方式配置这个bean
  


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

public class RedisCacheUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisCacheUtil.class);
    private String access;
    private String secret;
    private Map<String, String> map;
    private SpringContextUtil springContextUtil;

    RedisCacheUtil(){
    }

    RedisCacheUtil(String access,String secret){
        this.access = access;
        this.secret = secret;
    }

    public void init(){
        logger.info("this is RedisCacheUtil.init");
        logger.info("access",access);
        logger.info("secret",secret);
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public SpringContextUtil getSpringContextUtil() {
        return springContextUtil;
    }

    public void setSpringContextUtil(SpringContextUtil springContextUtil) {
        this.springContextUtil = springContextUtil;
    }

    public String getAccess() {
        return access;
    }

    public void setAccess(String access) {
        this.access = access;
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public static void main(String[] args) {

    }
}

(1)构造方法的方式

   可以用索引,也可以用name

  <bean id="redisCacheUtil" class="com.cainiao.arrow.arrowcommon.util.RedisCacheUtil" init-method="init">
        <constructor-arg index="0">
            <value>ddddd</value>
        </constructor-arg>
        <constructor-arg name="secret">
            <value>ccccc</value>
        </constructor-arg>
    </bean>

 (2)使用set方法注入属性

    用 <property> 标签代表属性。注意<property> 标签依赖set,所以bean里要有set方法。

   对应较为复杂的类型,也可以加对应的标签,赋值。例如map、List都是支持的

 <bean id="redisCacheUtil" class="com.cainiao.arrow.arrowcommon.util.RedisCacheUtil" init-method="init">
        <property name="access" value="dsdds"/>
        <property name="secret" value="33333"/>
        <property name="map">
            <map>
                <entry key="key1" value="Value1"/>
                <entry key="key2" value="Value2"/>
                <entry key="key3" value="Value3"/>
            </map>
        </property>
    </bean>

  (3)注入对象类型属性

  有时候引用了别的bean,此时就需要ref指向bean

<bean id="springContextUtil" class="com.cainiao.arrow.arrowcommon.util.SpringContextUtil">
    </bean>


    <bean id="redisCacheUtil" class="com.cainiao.arrow.arrowcommon.util.RedisCacheUtil" init-method="init">
        <property name="access" value="dsdds"/>
        <property name="secret" value="33333"/>
        <property name="springContextUtil" ref="springContextUtil"/>
    </bean>

    springContextUtil也可以是注解形式的,只要ref对应的bean名称能对应的上,就可以成功加载。

2.自动装配

   自动装配的实现是基于注解装配,常用的@Service @Component 都可以装配一个bean。

  详细的注解可以看 https://blog.csdn.net/gq0811/article/details/107876464 

 

3.代码解析

 public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/beans.xml");

        RedisCacheUtil redisCacheUtil =  (RedisCacheUtil)context.getBean("redisCacheUtil");
        System.out.printf(redisCacheUtil.getAccess());
    }

    用上述代码来追溯一下,解析的过程。

  大概看一下ClassPathXmlApplicationContext的继承关系

    

    BeanFactory是Spring的心脏,ApplicationContext就是完整的躯体。ApplicationContext继承了BeanFactory,ResourceLoader提供了类的管理以及资源的加载,同时又继承了EnvironmentCapableMessageSource, ApplicationEventPublisher等,将环境、消息、事件等统一管理。   

继承关系:

ClassPathXmlApplicationContext   --》 AbstractXmlApplicationContext  --》AbstractRefreshableConfigApplicationContext

除了ClassPathXmlApplicationContext,FileSystemXmlApplicationContext也继承了AbstractXmlApplicationContext,然后AbstractXmlApplicationContext又继承了AbstractRefreshableConfigApplicationContext。

时序图:

按照这个图的关系,来梳理流程,会更清晰一点。

(一)ClassPathXmlApplicationContext,最终到了下面的这个构造方法里:

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}


//省略

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

参数含义:

(1)configLocations,存放着读取的文件地址。

(2)refresh,是否自动刷新上下文、加载所有bean定义并创建所有单例。或者,在进一步配置上下文后手动调用刷新。

(3)parent :父亲上下文。

   总的流程,设置父亲上下文并做初始化,然后设置取的文件地址。

(二)AbstractApplicationContext

      重点看AbstractApplicationContext中的refresh主流程,在这里初始化BeanFactory,解析XML加载BeanDefinition,注册bean处理器,注册事件添加监听等:  

@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) {
				。。。
			}

			finally {
				。。。
			}
		}
	}

 

1 prepareRefresh(),初始化基础属性值,

2 obtainFreshBeanFactory(),

  (1)执行refreshBeanFactory时候创建一个默认的BeanFactory:DefaultListableBeanFactory。如果之前已经创建过DefaultListableBeanFactory,则需要两个步骤,

  •     destroyBeans   这个步骤会删除单例,会把disposableBeans这个map中的对象在三级缓存中清除掉,另外把一些相关的map全部clear掉。例如containedBeanMap、dependentBeanMap等
  •     closeBeanFactory 这个步骤会设置this.beanFactory = null;

(2)加载BeanDefinition是会创建一个XmlBeanDefinitionReader对象,交由XmlBeanDefinitionReader去进行加载。这个在后面细说。

  •   XmlBeanDefinitionReader: 用beanFactory作为参数,新建一个XmlBeanDefinitionReader对象。
  •   initBeanDefinitionReader:对xmlBeanDefinitionReader做初始化,例如设置validating属性,决定是否用XML Validation
  •   loadBeanDefinitions:加载xmlBeanDefinitionReader所需的资源。

3 prepareBeanFactory(beanFactory),为beanFactory设置一些属性如ClassLoader,BeanExpressionResolver,PropertyEditorRegistrar,BeanPostProcessor等

4 invokeBeanFactoryPostProcessors(beanFactory),为beanFactory注册BeanFactoryPostProcessor

5 registerBeanPostProcessors(beanFactory),注册当Bean创建时候的BeanPostProcessor

6 initMessageSource()初始化上下文的消息源:DelegatingMessageSource

7 initApplicationEventMulticaster()初始化了一个事件传播器:SimpleApplicationEventMulticaster

8 registerListeners()获取ApplicationListener,并在事件传播器中注册他们

9 finishBeanFactoryInitialization(beanFactory),获取LoadTimeWeaverAware并初始化他们,初始化单例并且非懒加载的Bean

10 finishRefresh()完成refresh Context操作,初始化LifecycleProcessor并start,发布ContextRefreshedEvent事件

11 resetCommonCaches()  清理缓存

 

接下来对bean文件的解析部分重点描述:

    上面是XmlBeanDefinitionReader的继承关系,比较简单,主要就在XmlBeanDefinitionReader和AbstractBeanDefinitionReader中实现bean的解析。

这是几个类之间的大致时序图关系,先捋清楚大致的调用关系,再往下看。

  在AbstractXmlApplicationContext中的调用:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

直接进入到AbstractBeanDefinitionReader中:

(三)AbstractBeanDefinitionReader

	@Override
	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int counter = 0;
		for (Resource resource : resources) {
			counter += loadBeanDefinitions(resource);
		}
		return counter;
	}

	@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}

这里的loadBeanDefinitions有多种实现:(1)GroovyBeanDefinitionReader(2)PropertiesBeanDefinitionReader(3)XmlBeanDefinitionReader ,这里走的是XmlBeanDefinitionReader的loadBeanDefinitions方法:

(四)XmlBeanDefinitionReader

//做了优化,先把resourcesCurrentlyBeingLoaded做了初始化,这样代码里就不用判断空了
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
			new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded"){
				@Override
				protected Set<EncodedResource> initialValue() {
					return new HashSet<>(4);
				}
			};

//省略

/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

  这一部分还只是生成bean definition,做了三件事:

(1)判断当前是否重复加载了resource,在threadLocal里维护了一个set。获取当前正在加载的EncodedResource集合,然后判断!currentResources.add(encodedResource),如果为true,说明添加失败,即当前Resource已经正在加载了,会直接抛异常。

(2)组装inputSource,调用真正加载bean definition的方法。

	/**
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 * @see #doLoadDocument
	 * @see #registerBeanDefinitions
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
	    ...
	}

 在doLoadBeanDefinitions中生成了一个Document对象,然后执行注册:

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

这里新建了一个BeanDefinitionDocumentReader来执行注册,并且返回注册的个数。

   (1)使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader

   (2)记录统计前BeanDefinition的加载个数

   (3)根据resource创建一个XmlReaderContext

   (4)加载及注册Bean定义,返回新增的注册个数

 

(五)DefaultBeanDefinitionDocumentReader

  终于来到真正执行注册的方法了,DefaultBeanDefinitionDocumentReader是BeanDefinitionDocumentReader接口的一个实现类:

	/**
	 * This implementation parses bean definitions according to the "spring-beans" XSD
	 * (or DTD, historically).
	 * <p>Opens a DOM Document; then initializes the default settings
	 * specified at the {@code <beans/>} level; then parses the contained bean definitions.
	 */
	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}

  在这里,会得到一个Element对象,代表<beans/>节点,然后在doRegisterBeanDefinitions中对这个节点中的内容做具体的注册。

	/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
        //判断命名空间是否是"http://www.springframework.org/schema/beans"
		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isInfoEnabled()) {
						logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}

		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}

大致做了三件事:

(1)createDelegate的时候创建了一个新的代理,并且设置了一些默认值

(2)处理bean的profile

     beans.xml可以用profile来区分不同的环境,然后在web.xml中设置哪种环境生效。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">
    <bean id="transferService" class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository" />
        <constructor-arg ref="feePolicy" />
    </bean>
    <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource" />
    </bean>
    <bean id="feePolicy" class="com.bank.service.internal.ZeroFeePolicy" />
    <beans profile="dev">
        。。。
    </beans>
    <beans profile="production">
        。。。
    </beans>
</beans>

    web.xml文件: 

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

(3)解析beanDefinitions,

默认值就是我们在头部设置的一些属性,例如:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
       default-autowire="byName">
...

其中default-autowire="byName"就是设置的一种属性,其他的还有:

DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init";
DEFAULT_MERGE_ATTRIBUTE = "default-merge";
DEFAULT_AUTOWIRE_ATTRIBUTE = "default-autowire";
DEFAULT_DEPENDENCY_CHECK_ATTRIBUTE = "default-dependency-check";
DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = "default-autowire-candidates";
DEFAULT_INIT_METHOD_ATTRIBUTE = "default-init-method";
DEFAULT_DESTROY_METHOD_ATTRIBUTE = "default-destroy-method"

如何判断默认命名空间还是自定义命名空间?

默认的命名空间为:http://www.springframework.org/schema/beans,其他都是自定义命名空间。

进入到parseBeanDefinitions方法:

	/**
	 * Parse the elements at the root level in the document:
	 * "import", "alias", "bean".
	 * @param root the DOM root element of the document
	 */
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

  关键来了,前面铺垫了这么多,主要是梳理了注册bean的过程,在delegate.isDefaultNamespace(ele)这步,才涉及到aop的东西。前面说过,defaultNamespace是"http://www.springframework.org/schema/beans",而aop标签的namespace则是"http://www.springframework.org/schema/aop",所以进入下面的方法,

(六)BeanDefinitionParserDelegate

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

  在这里,根据不同的nameSpace得到对应的handler,AopNamespaceHandler:

这里提一下,namespace和其对应的handler的关系是在spring.handlers中定义好的:

 

里面的内容是:http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

而在spring.schemas中定义了url和本地xsd标签解析文件的对应关系。

http\://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsd http\://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd http\://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsd

.....

这些xsd文件中定义的是AopNamespaceHandler对于标签处理的规则。

public class AopNamespaceHandler extends NamespaceHandlerSupport {

	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

  这里注册了几个parser,用来解析aop相关的标签。

到这里,aop神秘的面纱逐渐被揭开了,接下来继续看下去:

(1)registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());

  注册了BeanDefinitionParser,会执行相应Parser的parse方法(Q:怎么找到的?)。看一下ConfigBeanDefinitionParser:

@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        //生成一个复合组件定义对象,并放到转换器的上下文中。
		CompositeComponentDefinition compositeDef =
				new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
		parserContext.pushContainingComponent(compositeDef);
        //proxy-target-class=true时,会强制设置代理类
		configureAutoProxyCreator(parserContext, element);

		List<Element> childElts = DomUtils.getChildElements(element);
        //按照格式去解析,aop的config的二级标签为三种。
		for (Element elt: childElts) {
			String localName = parserContext.getDelegate().getLocalName(elt);
			if (POINTCUT.equals(localName)) {
				parsePointcut(elt, parserContext);
			}
			else if (ADVISOR.equals(localName)) {
				parseAdvisor(elt, parserContext);
			}
			else if (ASPECT.equals(localName)) {
				parseAspect(elt, parserContext);
			}
		}

		parserContext.popAndRegisterContainingComponent();
		return null;
	}

接下来,把三种标签一个个看过去。为了方便对照,把aop(一)中定义的标签拿过来:

 <aop:pointcut expression="execution(* com.cainiao.arrow.autoconfig. add(..))" id="pointcut"/>

先看parsePointcut方法:

	/**
	 * Parses the supplied {@code <pointcut>} and registers the resulting
	 * Pointcut with the BeanDefinitionRegistry.
	 */
	private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
		//拿到id和expression属性的值
         String id = pointcutElement.getAttribute(ID);
		String expression = pointcutElement.getAttribute(EXPRESSION);

		AbstractBeanDefinition pointcutDefinition = null;

		try {
			this.parseState.push(new PointcutEntry(id));
			pointcutDefinition = createPointcutDefinition(expression);
			pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
            
			String pointcutBeanName = id;
            //对bean定义进行注册,切点名称存在时用定义的名字,没有则生成默认的名称
			if (StringUtils.hasText(pointcutBeanName)) {
				parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
			}
			else {
				pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
			}
            //注册一个切点的组件
			parserContext.registerComponent(
					new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
		}
		finally {
			this.parseState.pop();
		}

		return pointcutDefinition;
	}

  ParseState这个类有点意思,里面放了一个栈,用来存放当前的切点id名称,最终当前切点id名称出栈。(Q:这个设计是干嘛用的?ParseState的toString() 可以打印树形结构,只是来做展示用?)

  parsePointcut这个方法最终返回了一个pointcutDefinition,这个类继承BeanDefinition,aop的定义最终也会被统一作为bean来处理。

    接下来是parseAdvisor方法,先上一个advisor配置的例子:

<aop:config>
  <aop:advisor advice-ref="myAdvice" 
          pointcut="execution(* com.nhq.aop.*(..))" order="4"/>
</aop:config>
  advisor是一种特殊的acpect,可以在advice-ref属性中直接定义。order定义解析的顺序,不定义则按照自然顺序。

private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
		AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
		String id = advisorElement.getAttribute(ID);
        //前面部分做的东西和pointcut类似
		try {
			this.parseState.push(new AdvisorEntry(id));
			String advisorBeanName = id;
			if (StringUtils.hasText(advisorBeanName)) {
				parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
			}
			else {
				advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
			}
            //把advisor中定义的切点取出来。
			Object pointcut = parsePointcutProperty(advisorElement, parserContext);
			if (pointcut instanceof BeanDefinition) {
				advisorDef.getPropertyValues().add(POINTCUT, pointcut);
				parserContext.registerComponent(
						new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
			}
			else if (pointcut instanceof String) {
				advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
				parserContext.registerComponent(
						new AdvisorComponentDefinition(advisorBeanName, advisorDef));
			}
		}
		finally {
			this.parseState.pop();
		}
	}

        进去看看parsePointcutProperty的判断逻辑:

private Object parsePointcutProperty(Element element, ParserContext parserContext) {
        if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
			parserContext.getReaderContext().error(
					"Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
					element, this.parseState.snapshot());
			return null;
		}
		else if (element.hasAttribute(POINTCUT)) {
			// Create a pointcut for the anonymous pc and register it.
			String expression = element.getAttribute(POINTCUT);
			AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
			pointcutDefinition.setSource(parserContext.extractSource(element));
			return pointcutDefinition;
		}
		else if (element.hasAttribute(POINTCUT_REF)) {
			String pointcutRef = element.getAttribute(POINTCUT_REF);
			if (!StringUtils.hasText(pointcutRef)) {
				parserContext.getReaderContext().error(
						"'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
				return null;
			}
			return pointcutRef;
		}
		else {
			parserContext.getReaderContext().error(
					"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
					element, this.parseState.snapshot());
			return null;
		}
	}

 (a)不能同时定义pointcut' 和'pointcut-ref标签,要不就是advice-ref,pointcut-ref索引形式,要不就是<aop:pointcut><aop:advice>这种标签定义形式。

 (b)如果是pointcut新定义的方式,就重新解析生成pointcutDefinition返回。

 (c)如果是ref形式就直接返回。如果取定义的时候,这切点还没注册好,会直接报错。所以没定义order的时候注意自己aop标签的定义顺序!

   接下来是parseAspect方法,aspect标签中的灵活度比较大,所以要考虑很多情况,稍微复杂一点:

  上个例子先:

     <!-- pointcut可以定义在aspect内,也可以在外面 -->

         <aop:pointcut expression="execution(* com.cainiao.arrow.autoconfig. add(..))" id="pointcut"/>
        <aop:aspect ref="LogTest">
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
        </aop:aspect>

<!-- declare-parents标签定义了一种静态代理,例如:为types-matching指定的类指定了代理的接口,并且指定了这个接口的默认实现impl -->

<aop:aspect>

            <aop:declare-parents types-matching="com.nhq.aop.declareparent.Man"

                    implement-interface="com.nhq.aop.declareparent.IronMan"

                    default-impl="com.nhq.aop.declareparent.IronManImpl"/>

</aop:aspect>

	private void parseAspect(Element aspectElement, ParserContext parserContext) {
		String aspectId = aspectElement.getAttribute(ID);
		String aspectName = aspectElement.getAttribute(REF);

		try {
			this.parseState.push(new AspectEntry(aspectId, aspectName));
			List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
			List<BeanReference> beanReferences = new ArrayList<BeanReference>();
            //把declare-parents标签收集起来,解析之后一起放到beanDefinitions中
			List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
			for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
				Element declareParentsElement = declareParents.get(i);
				beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
			}

            //如果遇到advice或者advice类型,需要循环去获取正确的顺序语义
			NodeList nodeList = aspectElement.getChildNodes();
			boolean adviceFoundAlready = false;
			for (int i = 0; i < nodeList.getLength(); i++) {
				Node node = nodeList.item(i);
				if (isAdviceNode(node, parserContext)) {
					if (!adviceFoundAlready) {
						adviceFoundAlready = true;
						if (!StringUtils.hasText(aspectName)) {
							parserContext.getReaderContext().error(
									"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
									aspectElement, this.parseState.snapshot());
							return;
						}
						beanReferences.add(new RuntimeBeanReference(aspectName));
					}
					AbstractBeanDefinition advisorDefinition = parseAdvice(
							aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
					beanDefinitions.add(advisorDefinition);
				}
			}

			AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
					aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
			parserContext.pushContainingComponent(aspectComponentDefinition);

			List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
			for (Element pointcutElement : pointcuts) {
				parsePointcut(pointcutElement, parserContext);
			}

			parserContext.popAndRegisterContainingComponent();
		}
		finally {
			this.parseState.pop();
		}
	}

  (a)把declare-parents标签收集起来,解析之后一起放到beanDefinitions中

  (b)在isAdviceNode中判断是否有"before","after","after-returning","after-throwing","around"标签。有则为advice类型标签,那么此时aspect中必须得定义ref,表明是定义的哪个bean(advice)中的方法,作为advice类型标签。

  (c)parseAdvice用来解析advice类型标签。

    (d)  parserContext.getReaderContext().error()用到了state的快照。现在可以回答刚才的问题,所以这个parseState确实是打印错误的时候用的,让你知道当前是在那个位置解析的时候报错的。

    看看parseAdvice方法:

	private AbstractBeanDefinition parseAdvice(
			String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
			List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

		try {
			this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));

			//解析advice节点中的"method"属性,生成MethodLocatingFactoryBean对象
			RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
			methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
			methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
			methodDefinition.setSynthetic(true);

			//关联aspectName,生成SimpleBeanFactoryAwareAspectInstanceFactory对象
			RootBeanDefinition aspectFactoryDef =
					new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
			aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
			aspectFactoryDef.setSynthetic(true);

			//对point-cut属性进行解析,并结合上面的两个bean最终生成AbstractAspectJAdvice通知对象
			AbstractBeanDefinition adviceDef = createAdviceDefinition(
					adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
					beanDefinitions, beanReferences);

			//最终生成AspectJPointcutAdvisor对象
			RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
			advisorDefinition.setSource(parserContext.extractSource(adviceElement));
			advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
			if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
				advisorDefinition.getPropertyValues().add(
						ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
			}

			// register the final advisor
			parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

			return advisorDefinition;
		}
		finally {
			this.parseState.pop();
		}
	}

  最后看看declareParents属性是怎么处理的:

	private AbstractBeanDefinition parseDeclareParents(Element declareParentsElement, ParserContext parserContext) {
        //设置根bean定义(rootBeanDefinition)。declare-parents标签内部实现方式是创建了一个切面
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DeclareParentsAdvisor.class);

        //添加构造函数的参数	          builder.addConstructorArgValue(declareParentsElement.getAttribute(IMPLEMENT_INTERFACE));
        builder.addConstructorArgValue(declareParentsElement.getAttribute(TYPE_PATTERN));
        //新加接口的默认实现和实现引用,两者取其一即可
		String defaultImpl = declareParentsElement.getAttribute(DEFAULT_IMPL);
		String delegateRef = declareParentsElement.getAttribute(DELEGATE_REF);

		if (StringUtils.hasText(defaultImpl) && !StringUtils.hasText(delegateRef)) {
			builder.addConstructorArgValue(defaultImpl);
		}
		else if (StringUtils.hasText(delegateRef) && !StringUtils.hasText(defaultImpl)) {
			builder.addConstructorArgReference(delegateRef);
		}
		else {
			parserContext.getReaderContext().error(
					"Exactly one of the " + DEFAULT_IMPL + " or " + DELEGATE_REF + " attributes must be specified",
					declareParentsElement, this.parseState.snapshot());
		}
        //获取bean定义并且注册bean
		AbstractBeanDefinition definition = builder.getBeanDefinition();
		definition.setSource(parserContext.extractSource(declareParentsElement));
		parserContext.getReaderContext().registerWithGeneratedName(definition);
		return definition;
	}

总结

 总的来讲,bean文件的解析就是:

1 、初始化基础属性值

2、 创建一个默认的BeanFactory:DefaultListableBeanFactory

3、创建一个XmlBeanDefinitionReader对象,根据传入的bean文件路径,生成resource对象;然后组装inputSource对象,和resource一起加载bean的定义

4、根据inputSource和resource加载XML文件,并封装成Document对象。并在注册bean的定义时,解析Document中的Element,也就是对应bean文件中的各个标签,判断是否是默认空间分别进行处理。

5、prepareBeanFactory(beanFactory),为beanFactory设置一些属性如ClassLoader,BeanExpressionResolver,PropertyEditorRegistrar,BeanPostProcessor等

6、invokeBeanFactoryPostProcessors(beanFactory),为beanFactory注册BeanFactoryPostProcessor

7、其他的一些注册和初始化事件:

   a)注册当Bean创建时候的BeanPostProcessor

   b)initMessageSource()初始化上下文的消息源:DelegatingMessageSource

   c)  initApplicationEventMulticaster()初始化了一个事件传播器:SimpleApplicationEventMulticaster

   d)  registerListeners()获取ApplicationListener,并在事件传播器中注册他们

   e)  finishBeanFactoryInitialization(beanFactory),获取LoadTimeWeaverAware并初始化他们,初始化单例并且非懒加载的Bean

   f)  finishRefresh()完成refresh Context操作,初始化LifecycleProcessor并start,发布ContextRefreshedEvent事件

6、 resetCommonCaches()  清理缓存

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值