引言
spring的IOC系列已经正式完结了,这一个星期一直在想着接下来更新什么系列,想了想,面试里面不就是IOC和AOP考的最多吗?那么就索性接下来开始更新AOP系列的文章,就非常的河狸
aop:config
aop部分的解析器由AopNamespaceHandler注册,其init方法:
@Override
public void init() {
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
}
而init方法的调用则是在Spring解析XMl文件过程中的DefaultNamespaceHandlerResolver#resolve方法中。
其中ConfigBeanDefinitionParser用于解析aop:config,的此标签用以配置pointcut, advisor, aspect,实例:
<aop:config expose-proxy="true" proxy-target-class="true">
<aop:pointcut expression="execution(* exam.service..*.*(..))" id="transaction"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transaction"/>
<aop:aspect ref="" />
</aop:config>
ConfigBeanDefinitionParser.parse:
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
configureAutoProxyCreator(parserContext, element);
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;
}
还是分部分讲解:
- 解析
- 代理子类的生成
这篇文章只讲解第一部分“解析过程”
解析
解析的过程主要分为以下几个部分。
proxy-target-class & expose-proxy
对应着aop:config的两个属性,前者代表是否为被代理这生成CGLIB子类,默认false,只为接口生成代理子类(话说如果不生成子类那么怎么拦截?)。后者代表是否将代理bean暴露给用户,如果暴露,可以通过Spring AopContext类获得,默认不暴露。
解析的过程无非就是属性的读取,不再详细说明。
对应代码:
AopNamespaceUtils:
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
aop:pointcut
pointcut的解析是生成一个BeanDefinition并将其id, expression等属性保存在BeanDefinition中。注意以下几点:
- BeanDefinition的ID来自于id属性,如果没有,那么自动生成。
- BeanDefinition的class是AspectJExpressionPointcut
- BeanDefinition的scope为prototype。
AspectJExpressionPointcut类图:
对应代码:
ConfigBeanDefinitionParser:
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
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;
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;
}
aop:advisor
首先是其所有属性的示例:
<aop:advisor id="" order="" advice-ref="aopAdvice" pointcut="" pointcut-ref="" />
advisor概念是Spring独有的,来自于上古时代,应该是较早时候的aop概念的实现: AOP Alliance (Java/J2EE AOP standards)。Spring官方的说法: aop-schema-advisors。
advice-ref是必须的属性,并且这里的advice必须实现org.aopalliance.aop.Advice的子接口。这些子接口指的什么呢?比如org.aopalliance.intercept.MethodInterceptor
最常见的用途就是结合事务使用:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="find*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* exam.service..*.*(..))" id="transaction"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transaction"/>
</aop:config>
解析的套路和上面类似,只不过此处的beanClass是DefaultBeanFactoryPointcutAdvisor,其类图:
另外注意对于pointcut和pointcut-ref两者处理的区别,对于pointcut属性,Spring会同样创建一个AspectJExpressionPointcut类型的BeanDefinition,对于pointcut-ref会生成一个RuntimeBeanReference对象指向原pointcut的引用。此类的类图:
可以看出,这种aop的实现需要实现各种接口,所以不应该再使用此种方式进行aop,除了Spring内部的实现。
对应代码:
/**
* Parses the supplied {@code <advisor>} element and registers the resulting
* {@link org.springframework.aop.Advisor} and any resulting {@link org.springframework.aop.Pointcut}
* with the supplied {@link BeanDefinitionRegistry}.
*/
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);
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);
}
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();
}
}
aop:aspect
配置举例:
<bean id="aopAdvice" class="base.aop.AopDemoAdvice" />
<!-- 必须配置,因为被代理的对象必须在Spring容器中 -->
<bean id="aopDemo" class="base.aop.AopDemo" />
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* base.aop.AopDemo.send())" />
<aop:aspect ref="aopAdvice">
<aop:before method="beforeSend" pointcut-ref="pointcut" />
<aop:after method="afterSend" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
解析形成的BeanDefinition结构如下:
AspectComponentDefinition
beanRefArray
RuntimeBeanReference(aop:aspect的ref属性)
beanDefArray
// 被注册
RootBeanDefinition(aop:declare-parents)
beanClass: DeclareParentsAdvisor
ConstructorArg
implement-interface
types-matching
default-impl
delegate-ref
// 被注册
RootBeanDefinition(aop:before,aop:after...)
beanClass: AspectJPointcutAdvisor
ConstructorArg
RootBeanDefinition
beanClass: 由子标签决定
ConstructorArg
RootBeanDefinition
beanClass: MethodLocatingFactoryBean
properties
targetBeanName: aspectName
methodName: method属性
RootBeanDefinition
beanClass: SimpleBeanFactoryAwareAspectInstanceFactory
properties
aspectBeanName: aspectName
//还有pointcut定义和引用...
结构图里面的aspectName来自于aop:aspect的ref属性,此属性是必须配置的,因为Spring要知道aop:before等标签指定的方法是哪个bean/类/对象的方法。
aop:declare-parents 对于aop:declare-parents子标签,其决定的是代理子类应该实现哪些接口:
<aop:declare-parents types-matching="" implement-interface="" />
此标签最终被解析成为beanClass为DeclareParentsAdvisor的BeanDefinition,并注册到容器中。其类图:
其它
此处的其它指的是aop:before, aop:after等最核心的标签。其最终被解析为beanClass为AspectJPointcutAdvisor的BeanDefinition,类图:
正如上面结构图里所描述的,其构造参数为一个BeanDefintion,此对象的beanClass是不确定的,由aop:before/after中的before和after决定,代码:
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;
}
}
而此BeanDefintion的构造参数又由以下几个部分组成:
- MethodLocatingFactoryBean
- SimpleBeanFactoryAwareAspectInstanceFactory
MethodLocatingFactoryBean
第一个便是beanClass为此类型的BeanDefinition。其内部有一个methodName属性,存储的便是标签的method属性的值。其类图:
这个东西是干什么用的呢?其实是用于在指定的advice(aop:aspect的ref属性)中得到Method对象。入口在setBeanFactory方法:
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Class<?> beanClass = beanFactory.getType(this.targetBeanName);
this.method = BeanUtils.resolveSignature(this.methodName, beanClass);
}
SimpleBeanFactoryAwareAspectInstanceFactory
其类图:
此类用于在BeanFactory中定位aspect bean,这个bean指的是谁?
<bean id="aopAdvice" class="base.aop.AopDemoAdvice" />
就是它!查找很简单:
@Override
public Object getAspectInstance() {
return this.beanFactory.getBean(this.aspectBeanName);
}
从整个aop:aspect标签最终被解析为一个AspectJPointcutAdvisor来看,Spring在实现上仍将其作为Advisor的概念。
稍微总结一下标签与BeanDefinition中的beanClass之间的关系:
| 标签 | BeanDefinition.beanClass |
|---|---|
| aop:pointcut | AspectJExpressionPointcut |
| aop:advisor | DefaultBeanFactoryPointcutAdvisor |
| aop:declare-parents | DeclareParentsAdvisor |
| aop:before,aop:after... | AspectJPointcutAdvisor |