1.引子
通过前面几篇的学习,我们已经知道了aop实现的基本原理,但是定义一个切面的过程是比较繁琐的,我们需要自己是实现特定的接口。事实上,Spring提供了更加简单的方式来定义切面,比如说使用@AspectJ注解或者基于Schema配置的方式。今天我们就基于Schema的方式,看一下这种方式下aop的配置时如何被加载的。话不多说,先上一个例子看看如何基于Schema配置一个切面。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="adviceMethods" class="com.youzan.shys.advisor.AdviceMethods" />
<bean id="waiterTarget" class="com.youzan.shys.advisor.NaiveWaiter" />
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethods">
<aop:before method="preGreeting" pointcut="target(com.youzan.shys.advisor.NaiveWaiter) and execution(* greetTo(..))" />
</aop:aspect>
</aop:config>
</beans>
在这个例子中,我们通过切点表达式,对NaiveWaiter类的greetTo()方法添加了一个前置增强,具体增强为AdviceMethods类的preGreeting()方法。接下来我们主要就来看看这些配置是如何被加载的。
2.配置文件读取流程
Location ——> ResourceLoader.getResource
Resource ——> DocumentLoader.loadDocument
Document/Element ——> BeanDefinitonParser
BeanDefinition
2.1获取命名空间
熟悉Spring IOC的朋友都知道,Spring从配置文件中读取bean配置并将其转换成BeanDefinition大致上需要经过以上几个步骤,本文重点关注最后一步,即Spring是如何将Element转化为BeanDefinition的,代码直接定位到DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 是否为默认的命名空间,即判断root的命名空间uri是否为空或者等于http://www.springframework.org/schema/beans
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)) {
// 默认命名空间,比如<bean id="xxx" />
parseDefaultElement(ele, delegate);
}
else {
// 非默认命名空间,比如<aop:config />
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在解析bean定义的时候,根据命名空间分成了两个分支。显然,由于在我们的例子中不是默认命名空间,因此会进入第二个分支,进去看一看:
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取命名空间uri,即在xml头部中配置的这一串内容xmlns:aop="http://www.springframework.org/schema/aop"
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 这里比较简单,就是根据namespaceUri实例化对应的namespaceHandler,在这里就是AopNamespaceHandler
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));
}
在解析自定义元素的时候,首先获取命名空间uri,根据uri获取对应的hander,然后再进行解析。AopNamespaceHandler中针对每一个标签,都有一个具体的解析器来负责解析。
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());
}
2.2 解析具体标签
可以看到,AopNamespace中一共只有4个标签,在我们的例子中,首先就是config标签,于是定位到ConfigBeanDefinitionParser#parse:
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注册一个名为org.springframework.aop.config.internalAutoProxyCreator的bean,默认实现为AspectJAwareAdvisorAutoProxyCreator
// 同时根据配置设置一些代理属性,比如proxy-target-class和expose-proxy
configureAutoProxyCreator(parserContext, element);
// 获取config的子标签,并分别进行解析
List<Element> childElts = DomUtils.getChildElements(element);
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;
}
config一共只有3种类型的子标签,分别为advisor、pointcut和aspect。在我们的例子中,首先解析的应该就是aspect,于是进入parseAspect方法,该方法主要做了两件事:
- 加载增强节点bean定义(6种增强,包括引介增强)
- 获取aspect的所有子标签并进行解析
private void parseAspect(Element aspectElement, ParserContext parserContext) {
String aspectId = aspectElement.getAttribute(ID);
String aspectName = aspectElement.getAttribute(REF);
try {
// ParseState是一个基于LinkedList的结构,每个LinkedList里存放的是Entry类型的对象
// 在解析bean的过程中,每次操作开始时将一个Entry入栈,每次操作结束将Entry出栈
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
// 如果配置了引介增强,获取其bean定义
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));
}
// 获取aspect的所有子标签并进行解析
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 如果是增强节点,将aspect节点对应的bean引用加入beanReferences列表
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);
// aspect节点下可以配置pointcut属性,这里获取所有的切点并进行解析
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
2.3 advice标签解析
下面分别看下这里涉及到的几个主要方法,也就是isAdviceNode、parseAdvice和parsePointcut。
isAdviceNode方法很简单,就是用来判断该节点是否是5中增强节点之一:
private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
if (!(aNode instanceof Element)) {
return false;
}
else {
String name = parserContext.getDelegate().getLocalName(aNode);
return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
}
}
parseAdvice方法逻辑其实也很简单:先根据增强类型生成bean定义,然后将增强封装成切面,最后将切面注册到beanFactory中。
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)));
// create the method factory bean
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true);
// create instance factory definition
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);
// 根据增强类型生成bean定义
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences);
// 将advice封装成advisor,同时设置order属性
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));
}
// 将advisor注册到DefaultListableBeanFactory中
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
2.3.1 生成增强的bean定义
根据增强类型生成bean定义主要包含两部分内容:
1.根据增强类型如<aop:before />选择对应的增强类对象,这部分在getAdviceClass中完成;
2.解析<aop:before />标签中的pointcut属性,这部分在parsePointcutProperty中完成。
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, ParserContext parserContext, String aspectName, int order,
RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
// 根据增强类型生成bean定义
RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
......中间是一堆属性设置,代码省略......
// 解析pointcut属性
pointcut = parsePointcutProperty(adviceElement, parserContext);
if (pointcut instanceof BeanDefinition) {
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
beanDefinitions.add((BeanDefinition) pointcut);
}
else if (pointcut instanceof String) {
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
beanReferences.add(pointcutRef);
}
cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
return adviceDefinition;
}
getAdviceClass代码如下,一共5种增强类型,与前面isAdviceNode方法一致
private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
String elementName = parserContext.getDelegate().getLocalName(adviceElement);
if (BEFORE.equals(elementName)) {
return AspectJMethodBeforeAdvice.class;
}
else if (AFTER.equals(elementName)) {
return AspectJAfterAdvice.class;
}
else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
return AspectJAfterReturningAdvice.class;
}
else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
}
else if (AROUND.equals(elementName)) {
return AspectJAroundAdvice.class;
}
else {
throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
}
}
parsePointcutProperty方法如下,从这里可以看出,在aspect标签中,必须且只能设置pointcut、pointcut-ref中的一个属性。
private Object parsePointcutProperty(Element element, ParserContext parserContext) {
// 不能同时设置pointcut属性和pointcut-ref属性
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;
}
// pointcut属性和pointcut-ref属性必须设置其中一个
else {
parserContext.getReaderContext().error(
"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
}
2.3.2 将增强bean定义封装成切面bean定义
这一步比较简单,只是把adviceDef用AspectJPointcutAdvisor类型重新包装了一下,同时设置了order属性
2.3.3 将切面bean注册到BeanFactory中
这就是parseAdvice()最后一步的工作
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
// 获取bean注册的名字
String generatedName = generateBeanName(beanDefinition);
// 注册bean定义
getRegistry().registerBeanDefinition(generatedName, beanDefinition);
return generatedName;
}
获取bean名称时,如果有重复的bean名称存在,默认会在原始bean名称后面通过增加“#+数字”的方式来生成新的bean名称以示区分。具体可参考我的另外一篇文章Spring配置文件id/name重复定义问题
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
......
String id = generatedBeanName;
if (isInnerBean) {
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// Top-level bean: use plain class name.
// Increase counter until the id is unique.
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
}
return id;
}
至此,parseAdvice分析完成,另外两个标签的解析parseAdvisor和parsePointCut思路类似,大家可以自行分析😆
网友评论