您的位置:首页 > 编程语言 > Java开发

Spring源码情操陶冶-AOP之Advice通知类解析与使用

2017-10-25 20:34 746 查看

阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析


入口

根据前文讲解,我们知道通知类的解析主要建立在
aop:aspect
节点的解析上。废话少说我们直接观察
ConfigBeanDefinitionParser#parseAdvice()
方法

ConfigBeanDefinitionParser#parseAdvice()-解析通知类并注册到bean工厂

先奉上源码

/**
* Parses one of '{@code before}', '{@code after}', '{@code after-returning}',
* '{@code after-throwing}' or '{@code around}' and registers the resulting
* BeanDefinition with the supplied BeanDefinitionRegistry.
* @return the generated advice RootBeanDefinition
*/
/**
** 这稍微对入参作下备注
** @param aspectName  待绑定的切面名
** @param order 排序号
** @param aspectElement <aop:aspect>节点
** @param adviceElement <aop:advice>节点
** @param parserContext 解析节点的上下文对象
** @param beanDefinitions 与aspect相关的所有bean对象集合
** @param beanReferences  与aspect相关的所有bean引用对象集合
**/
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
// 解析advice节点中的"method"属性,并包装为MethodLocatingFactoryBean对象
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
// 关联aspectName,包装为SimpleBeanFactoryAwareAspectInstanceFactory对象
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);

// register the pointcut
// 涉及point-cut属性的解析,并结合上述的两个bean最终包装为AbstractAspectJAdvice通知对象
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences);

// configure the advisor,最终包装为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();
}
}

由代码可知,最终解析得到的bean对象为
AspectJPointcutAdvisor.class
类型的,其内部拥有
Advice
的接口对象属性,而具体的解析则需要查看
ConfigBeanDefinitionParser#createAdviceDefinition()
方法。

ConfigBeanDefinitionParser#createAdviceDefinition()-具体解析通知类

源码奉上

/**
* Creates the RootBeanDefinition for a POJO advice bean. Also causes pointcut
* parsing to occur so that the pointcut may be associate with the advice bean.
* This same pointcut is also configured as the pointcut for the enclosing
* Advisor definition using the supplied MutablePropertyValues.
*/
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, ParserContext parserContext, String aspectName, int order,
RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
// 首先根据adviceElement节点分析出是什么类型的Advice。
RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
// 设置aspectName属性和declarationOrder属性
adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
// 解析节点是否含有`returning`/`throwing`/`arg-names`,有则设置
if (adviceElement.hasAttribute(RETURNING)) {
adviceDefinition.getPropertyValues().add(
RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
}
if (adviceElement.hasAttribute(THROWING)) {
adviceDefinition.getPropertyValues().add(
THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
}
if (adviceElement.hasAttribute(ARG_NAMES)) {
adviceDefinition.getPropertyValues().add(
ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
}
// 设置构造函数的入参变量
// Method/AspectJExpressionPointcut/AspectInstanceFactory三个入参
ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
// 解析point-cut属性
Object 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;
}



Advice
接口类与节点对应关系如下,其均是
AbstractAspectJAdvice.class
的子类

aop:before
对应
AspectJMethodBeforeAdvice.class


aop:after
对应
AspectJAfterAdvice.class


aop:after-returning
对应
AspectJAfterReturningAdvice.class


aop:after-throwing
对应
AspectJAfterThrowingAdvice.class


aop:around
对应
AspectJAroundAdvice.class




通知类生成的bean对象,其会设置
aspectName
切面名、
declarationOrder
序列等属性;且对其公共构造函数三个入参
Method/AspectJExpressionPointcut/AspectInstanceFactory
都会进行设置

parseAdvice()
最主要的目的是使
aspect
对象中的方法与通知类结合起来,从而起到多样化的作用,下面的简单实例就是如此

例子结尾

public class TestAdvice {

/**
* 在核心业务执行前执行,不能阻止核心业务的调用。
* @param joinPoint
*/
private void doBefore(JoinPoint joinPoint) {
System.out.println("-----doBefore().invoke-----");
System.out.println(" 此处意在执行核心业务逻辑前,做一些安全性的判断等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doBefore()------");
}

/**
* 手动控制调用核心业务逻辑,以及调用前和调用后的处理,
*
* 注意:当核心业务抛异常后,立即退出,转向After Advice
* 执行完毕After Advice,再转到Throwing Advice
* @param pjp
* @return
* @throws Throwable
*/
private Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----doAround().invoke-----");
System.out.println(" 此处可以做类似于Before Advice的事情");

//调用核心逻辑
Object retVal = pjp.proceed();

System.out.println(" 此处可以做类似于After Advice的事情");
System.out.println("-----End of doAround()------");
return retVal;
}

/**
* 核心业务逻辑退出后(包括正常执行结束和异常退出),执行此Advice
* @param joinPoint
*/
private void doAfter(JoinPoint joinPoint) {
System.out.println("-----doAfter().invoke-----");
System.out.println(" 此处意在执行核心业务逻辑之后,做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doAfter()------");
}

/**
* 核心业务逻辑调用正常退出后,不管是否有返回值,正常退出后,均执行此Advice
* @param joinPoint
*/
private void doReturn(JoinPoint joinPoint) {
System.out.println("-----doReturn().invoke-----");
System.out.println(" 此处可以对返回值做进一步处理");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doReturn()------");
}

/**
* 核心业务逻辑调用异常退出后,执行此Advice,处理错误信息
* @param joinPoint
* @param ex
*/
private void doThrowing(JoinPoint joinPoint,Throwable ex) {
System.out.println("-----doThrowing().invoke-----");
System.out.println(" 错误信息:"+ex.getMessage());
System.out.println(" 此处意在执行核心业务逻辑出错时,捕获异常,并可做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doThrowing()------");
}
}

对应的spring配置如下

<bean id="xmlHandler" class="com.jing.aop.TestAdvice" />
<aop:config>
<aop:aspect id="aspect" ref="xmlHandler">
<aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>

<aop:before method="doBefore"  pointcut-ref="pointUserMgr"/>
<aop:after method="doAfter"  pointcut-ref="pointUserMgr"/>
<aop:around method="doAround"  pointcut-ref="pointUserMgr"/>
<aop:after-returning method="doReturn"  pointcut-ref="pointUserMgr"/>
<aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>

</aop:aspect>
</aop:config>


具体的如何触发相应的Advice我们放在后续的篇章讲解,敬请期待
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: