欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > spring揭秘10-aop04-基于AspectJ类库注解织入横切逻辑

spring揭秘10-aop04-基于AspectJ类库注解织入横切逻辑

2024/11/30 12:33:55 来源:https://blog.csdn.net/PacosonSWJTU/article/details/141505329  浏览:    关键词:spring揭秘10-aop04-基于AspectJ类库注解织入横切逻辑

文章目录

  • 【README】
  • 【1】基于AspectJ类库注解实现spring aop
    • 【1.1】使用AspectJ类库织入器实现aop
      • 【1.1.1】基于AspectJ注解的硬编码织入
      • 【1.1.2】使用AspectJ注解与xml文件DTD配置实现自动代理织入
      • 【1.1.2】使用AspectJ注解与xml文件XSD配置实现自动代理织入
  • 【2】 基于AspectJ注解标注pointcut
    • 【2.1】AspectJ类库Pointcut注解的使用
    • 【2.2】AspectJ类库pointcut标志符
      • 【2.2.1】pointcut表达式标志符-execution
      • 【2.2.2】pointcut表达式标志符-within
      • 【2.2.3】pointcut表达式标志符-this和target
      • 【2.2.4】pointcut表达式标志符-args
      • 【2.2.5】pointcut表达式标志符-@within
      • 【2.2.6】pointcut表达式标志符-@target
      • 【2.2.7】pointcut表达式标志符-@args
      • 【2.2.8】pointcut表达式标志符-@annotation
    • 【2.3】AspectJ类库的Pointcut注解底层原理
  • 【3】AspectJ类库的Advice通知注解
    • 【3.1】AspectJ的Advice注解
      • 【3.1.1】前置与后置通知注解代码示例
      • 【3.1.2】环绕通知注解代码示例
      • 【3.1.3】 Introduction引入型通知注解代码示例
  • 【4】AspectJ类库中Aspect其他方面
    • 【4.1】 advice执行顺序
    • 【4.2】Aspect实例化模式
  • 【5】基于schema模式的aop
    • 【5.1】基于schema的aop配置概览
    • 【5.2】基于AspectJ注解的aop迁移到基于Schema的aop
      • 【5.2.1】前置与后置通知注解迁移到基于schema的通知配置
      • 【5.2.2】环绕通知注解迁移到基于Schema的通知配置
      • 【5.2.3】引入型通知迁移到基于Schema的通知配置
  • 【6】spring aop织入横切逻辑总结

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;



【1】基于AspectJ类库注解实现spring aop

1)概述:@AspectJ代表一种定义Aspect切面的风格,让我们能够以POJO形式定义Aspect,没有其他接口定义限制; 唯一需要的是使用相应的注解标注这些Aspect定义的POJO类; spring aop会根据注解扫描出Aspect类,然后织入横切逻辑;

  • 注意:spring aop使用AspectJ类库注解进行标注及注解解析, 但最终还是采用spring aop自身的代理模式实现横切逻辑的织入
  • 简单理解: spring aop使用了AspectJ类库注解,通过AspectJ类库的注解扫描与解析逻辑扫描得到Aspect切面,Advice通知,pointcut切点表达式;拿着 aspect,advice,pointcut,最终还是使用spring aop自身的代理模式(JDK动态代理或CGLIB动态代理)实现横切逻辑织入;


【1.1】使用AspectJ类库织入器实现aop

【1.1.1】基于AspectJ注解的硬编码织入

1)通过AspectJ类库注解重构切面(方法执行耗时统计):

【ManNoItfCallTaskAspectjAnnotationMain】基于AspectJ注解织入横切逻辑测试main

public class ManNoItfCallTaskAspectjAnnotationMain {public static void main(String[] args) {AspectJProxyFactory weaver = new AspectJProxyFactory();weaver.setProxyTargetClass(true);weaver.setTarget(new ManNoItfCallTask());weaver.addAspect(TimeCostAspectByAnnotation.class);// 获取代理对象Object proxy = weaver.getProxy();((ManNoItfCallTask) proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));System.out.println(proxy.getClass());}
}

【打印日志】

stopWatch.start()
人工拨打电话#call(): BusiMessage{msgId='任务编号001', msgText='您有待办任务需要处理'}
stopWatch.stop()
方法执行耗时2.0127219
class com.tom.springnote.common.aop.ManNoItfCallTask$$SpringCGLIB$$0  // 显然通过CGLIB代理

【TimeCostAspectByAnnotation】通过Aspect注解标注切面,包含pointcut注解标注切点表达式,around注解标注环绕通知(横切逻辑)

@Aspect
public class TimeCostAspectByAnnotation {@Pointcut("execution(public void *.call(..))")public void pointcutName() {// pointcut}@Around("pointcutName()")public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {StopWatch stopWatch = new StopWatch();try {System.out.println("stopWatch.start()");stopWatch.start();return joinPoint.proceed();} catch (Exception e) {System.out.println("抛出异常");e.printStackTrace();} finally {System.out.println("stopWatch.stop()");stopWatch.stop();System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));}return null;}
}

【ManNoItfCallTask】 目标对象POJO

public class ManNoItfCallTask {public void call(BusiMessage message) {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {System.out.println("抛出异常");throw new RuntimeException(e);}System.out.println("人工拨打电话#call(): " + message);}
}


【1.1.2】使用AspectJ注解与xml文件DTD配置实现自动代理织入

1)dtd:document type definition, 文档类型定义;用于描述xml文档结构,包括元素,属性名,属性值数据类型,嵌套元素等;

【AspectByAnnotationAndXmlDtdMain】

public class AspectByAnnotationAndXmlDtdMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/beans10aspectannotationdtd.xml");// 获取代理对象Object proxy = container.getBean("target");((ManNoItfCallTask) proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));System.out.println(proxy.getClass());}
}

【打印日志】

stopWatch.start()
人工拨打电话#call(): BusiMessage{msgId='任务编号001', msgText='您有待办任务需要处理'}
stopWatch.stop()
方法执行耗时2.0144192
class com.tom.springnote.common.aop.ManNoItfCallTask$$SpringCGLIB$$0 

【beans10aspectannotationdtd.xml】 ( 虽然只注册了切面和目标对象,我们没有手工织入,但 AnnotationAwareAspectJAutoProxyCreator 实现了自动织入

<?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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"><property name="proxyTargetClass" value="true" /></bean><bean id="timeCostAspectByAnnotation" class="com.tom.springnote.chapter10.aspectjannotation.TimeCostAspectByAnnotation" /><bean id="target" class="com.tom.springnote.common.aop.ManNoItfCallTask"/>
</beans>


【1.1.2】使用AspectJ注解与xml文件XSD配置实现自动代理织入

1)XSD: xml schema definition, xml文件模式定义;与dtd类似, 用于描述xml文档结构,如文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或属性的默认和固定值; (用于替换dtd)

  • 基于xml的自动代理实现自动织入的配置方式
    • DTD格式: 注册 AnnotationAwareAspectJAutoProxyCreator,AspectJ注解装配自动代理创建者bean
    • XSD元素格式:使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器;

【AspectByAnnotationAndXmlXsdMain】使用AspectJ注解与xml文件XSD配置实现自动代理织入测试main

public class AspectByAnnotationAndXmlXsdMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/beans10aspectannotationxsd.xml");// 获取代理对象Object proxy = container.getBean("target");((ManNoItfCallTask) proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));System.out.println(proxy.getClass());}
}

【打印日志】

stopWatch.start()
人工拨打电话#call(): BusiMessage{msgId='任务编号001', msgText='您有待办任务需要处理'}
stopWatch.stop()
方法执行耗时2.0148879
class com.tom.springnote.common.aop.ManNoItfCallTask$$SpringCGLIB$$0

【beans10aspectannotationxsd.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --> <aop:aspectj-autoproxy proxy-target-class="true" /><bean id="timeCostAspectByAnnotation" class="com.tom.springnote.chapter10.aspectjannotation.TimeCostAspectByAnnotation" /><bean id="target" class="com.tom.springnote.common.aop.ManNoItfCallTask"/>
</beans>

2)aop:aspectj-autoproxy 元素定义:( 底层还是使用 AnnotationAwareAspectJAutoProxyCreator自动代理类实现自动织入

<xsd:element name="aspectj-autoproxy"><xsd:annotation><xsd:documentation source="java:org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"><![CDATA[
Enables the use of the @AspectJ style of Spring AOP.See org.springframework.context.annotation.EnableAspectJAutoProxy Javadoc
for information on code-based alternatives to this XML element.]]></xsd:documentation></xsd:annotation><xsd:complexType><xsd:sequence><xsd:element name="include" type="includeType" minOccurs="0" maxOccurs="unbounded"><xsd:annotation><xsd:documentation><![CDATA[
Indicates that only @AspectJ beans with names matched by the (regex)
pattern will be considered as defining aspects to use for Spring autoproxying.]]></xsd:documentation></xsd:annotation></xsd:element></xsd:sequence><xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false"><xsd:annotation><xsd:documentation><![CDATA[
Are class-based (CGLIB) proxies to be created? By default, standard
Java interface-based proxies are created.]]></xsd:documentation></xsd:annotation></xsd:attribute><xsd:attribute name="expose-proxy" type="xsd:boolean" default="false"><xsd:annotation><xsd:documentation><![CDATA[
Indicate that the proxy should be exposed by the AOP framework as a
ThreadLocal for retrieval via the AopContext class. Off by default,
i.e. no guarantees that AopContext access will work.]]></xsd:documentation></xsd:annotation></xsd:attribute></xsd:complexType>
</xsd:element>

【补充】推荐使用spring容器内的自动代理;



【2】 基于AspectJ注解标注pointcut

【2.1】AspectJ类库Pointcut注解的使用

1)@Pointcu注解,用于标注 被@Aspect注解标注的类的方法; 用于描述切点位置表达式;

2)@Pointcut注解中可以引入当前Aspect或者外部Aspect的pointcut ;且可以做逻辑运算;

【AspectJPointcutAnnotation】AspectJ类库的Pointcut注解测试

@Aspect
public class AspectJPointcutAnnotation { // @Pointcut注解的逻辑运算@Pointcut("execution(public void *.call(..)) || execution(public void *.call2(..))") // pointcut表达式private void callPointcut() {  // pointcut签名// pointcut}// 引用Aspect内部pointcut@Pointcut("callPointcut()")public void referInnerPointcut() {// pointcut}// 引用Aspect外部pointcut@Pointcut("com.tom.springnote.chapter10.aspectjannotation.TimeCostAspectByAnnotation.pointcutName()")public void referOuterPointcut() {// pointcut}@Around("referInnerPointcut()")public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {StopWatch stopWatch = new StopWatch();try {System.out.println("stopWatch.start()");stopWatch.start();return joinPoint.proceed();} catch (Exception e) {System.out.println("抛出异常");e.printStackTrace();} finally {System.out.println("stopWatch.stop()");stopWatch.stop();System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));}return null;}
}

3)@Pointcu注解有2部分组成:

  • pointcut表达式:描述切点位置的表达式;
    • pointcut标志符;如 execution ;within;
    • pointcut表达式匹配模式;如 public void *.call(…)
  • pointcut签名: 声明pointcut名字,以便其他pointcut注解可以引用;


【2.2】AspectJ类库pointcut标志符

1)pointcut表达式的标志符:

  • execution: 匹配指定方法签名的切点;(静态匹配)
  • within: 匹配指定类型所有切点;
  • this和target: this表示匹配目标对象的代理对象; target表示匹配目标对象; 可以使用this(ObjectType)或target(ObjectType),分别匹配类型为ObjectType的目标对象的代理对象或目标对象下所有切点;
  • args: 匹配指定参数类型,指定参数数量的方法级切点; (运行时动态匹配)
  • @within:匹配被指定注解类型标注的类下的所有切点;(静态匹配)
  • @target:匹配被指定注解类型标注的类下的所有切点;(运行时动态匹配)
  • @args:匹配方法参数类型被指定注解类型标注的方法;
  • @annotation:匹配被指定注解类型标注的所有方法切点;(如应用于基于注解的事务管理)

补充: spring aop在原来表达式基础上新增了一个标志符,如 bean(…)



【2.2.1】pointcut表达式标志符-execution

1)execution: 匹配指定方法签名的切点;可以使用通配符 * 与 … ;

2)execution格式: execution([访问类型] 返回类型 [声明类型模式] 名称模式(参数类型模式) [抛出异常模式]) ;其中 方法返回类型, 方法名及参数部分的匹配模式必须指定 ,其他非必输;

// 目标类及方法定义 
public class Foo {public void doSomething(String arg) {// do nothing.}
}

使用pointcut的execution指定匹配表达式,如下:

execution(public void Foo.doSomething(String))

可以简化如下(省略访问类型public, 类型名Foo, 抛出异常模式):

execution(void doSomething(String))

3)使用通配符 * 指定execution标志符:* 可以用于任何部分的匹配模式中; 可以匹配相邻的多个字符,即一个word;使用* 之后,以上 execution可以简化为:(使用* 替代方法方法类型void, 方法名doSomething)

execution(* *(String))

继续简化使用* 替代 方法参数类型String:

execution(* *(*))

4)使用通配符 … (2点)指定execution标志符: 可以在声明类型模式使用,及在方法参数模式使用;如果替换类型模式,可以指定多个层次的类型声明;

execution(void com.tom.spring.*.doSomething(*)) // 匹配 com.tom.spring 包下所有类型的 doSomething方法;(无法匹配com.tom.spring下的子包)
execution(void com.tom.spring..*.doSomething(*)) // 匹配  com.tom.spring 包及其子包下所有类型的 doSomething方法 
execution(void *.doSomething(..)) // .. 用于匹配方法参数,可以匹配0个到多个参数,类型不限制; 

补充:* 用于匹配方法参数,仅可以匹配1个参数;



【2.2.2】pointcut表达式标志符-within

1)within: 匹配指定类型所有切点 ;

within(com.tom.spring.Foo) // 匹配类型
within(com.tom.spring.*) //  匹配com.tom.spring包下所有类型内部的方法级别切点(不匹配com.tom.spring包下的子包)
within(com.tom.spring..*) // 匹配com.tom.spring包及其子包下所有类型内部的方法级别切点 

【2.2.3】pointcut表达式标志符-this和target

1)this和target: this表示匹配目标对象的代理对象; target表示匹配目标对象; 可以使用this(ObjectType)或target(ObjectType),分别匹配类型为ObjectType的目标对象的代理对象或目标对象下所有切点;(一般而言,目标对象与目标对象的代理对象,两者的类型是相同的,因为目标对象与其代理对象实现相同接口,即便使用CGLIB动态代理,代理对象是目标对象的子类;即使用this或者target做匹配,效果是差不多的)

  • 通过 标志符this与target 通常与其他标志符结合使用(通过逻辑运算)

【例】

this(com.tom.spring.Foo) && target(com.tom.spring.Foo) && execution(void doSomething(String))

【2.2.4】pointcut表达式标志符-args

1) args: 匹配指定参数类型,指定参数数量的方法级切点;

2)目标对象类

public class ManNoItfCallTask {public void call(BusiMessage message) {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {System.out.println("抛出异常");throw new RuntimeException(e);}System.out.println("人工拨打电话#call(): " + message);}
}

3)通过@Pointcut的args指定切点位置

args(com.tom.springnote.common.aop.BusiMessage)// 使用execution在指定切点位置,可以达到相同效果
execution(* *(com.tom.springnote.common.aop.BusiMessage))   

4)execution与args区别:方法签名为 call(Object object) ,调用方法为 call(BusiMessage msg) ;

  • execution:属于静态pointcut(编译时匹配) ;无法匹配方法调用call(BusiMessage msg) ; 因为方法声明是call(Object object) ;
  • args: 属于动态pointcut(运行时匹配) ; 可以匹配所有参数类型为BusiMessage的方法调用;


【2.2.5】pointcut表达式标志符-@within

1)@within:匹配被指定注解类型标注的类下的所有切点;(静态匹配)

【CustomJoinpointAnnotation】自定义 joinpoint注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CustomJoinpointAnnotation {
}

使用注解@CustomJoinpointAnnotation标注目标类

@CustomJoinpointAnnotation
public class Foo {public void doSomething(String arg) {// do nothing.}
}

@pointcut指定的表达式

@within(CustomJoinpointAnnotation)


【2.2.6】pointcut表达式标志符-@target

1)匹配被指定注解类型标注的类下的所有切点;(运行时动态匹配)

参考 @within ;

【2.2.7】pointcut表达式标志符-@args

1)@args:匹配方法参数类型被指定注解类型标注的方法;

参考 @within ;

【2.2.8】pointcut表达式标志符-@annotation

1)@annotation:匹配被指定注解类型标注的所有方法切点;(如应用于基于注解的事务管理)

参考 @within ;



【2.3】AspectJ类库的Pointcut注解底层原理

1)Aspect类库的Pointcut注解声明后, spring aop内部会通过解析,转化为具体的Pointcut对象,即 AspectJExpressionPointcut 对象,类图如下;

AspectJExpressionPointcut 内部还是通过 ClassFilter 和 MethodMatcher 匹配切点;
在这里插入图片描述



【3】AspectJ类库的Advice通知注解

【3.1】AspectJ的Advice注解

1)背景: 可以通过实现接口,如BeforeAdvice,MethodInterceptor 定义通知类; 为了简化代码,通过通知注解来标注通知对应的方法;

2)AspectJ类库通知注解列表:

  • @Before: 前置通知注解; (标注方法)
  • @AfterReturning: 方法正常返回后置通知注解;(标注方法)
  • @AfterThrowing: 方法异常返回后置通知注解;(标注方法)
  • @After:方法finally后置通知注解(无论方法正常或异常返回,都会执行);(标注方法)
  • @Around: 环绕通知注解;(标注方法)
  • @DeclareParents: 用于Introduction引入型通知的注解; (标注属性)

环绕通知代码示例参见: AspectJPointcutAnnotation ;



【3.1.1】前置与后置通知注解代码示例

【AspectJAdviceAnnotationMain】通知注解实现织入main测试

public class AspectJAdviceAnnotationMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/advice/beans10AspectjAdviceAnnotationXsd.xml");// 获取代理对象Object proxy = container.getBean("target");// 调用方法 BusiMessage busiMessage = ((MessageDAO) proxy).qryMsg("task001");System.out.println("main: " + busiMessage);}
}

【打印日志】

[@Before]方法执行开始,方法参数=[task001]
[@Before]方法执行开始,方法参数=task001
MessageDAO#qryMsg() 被调用
[@AfterReturning]方法执行结束(成功返回): 方法执行返回值=BusiMessage{msgId='task001', msgText='您有待办任务需要处理'}
[@After]方法执行结束afterFinally
main: BusiMessage{msgId='task001', msgText='您有待办任务需要处理'}

【beans10AspectjAdviceAnnotationXsd.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --><aop:aspectj-autoproxy proxy-target-class="true" /><bean class="com.tom.springnote.chapter10.aspectjadviceannotation.MethodCallCallLogAdviceAnnotation" /><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>

【MethodCallCallLogAdviceAnnotation】 方法调用日志通知注解

@Aspect
public class MethodCallCallLogAdviceAnnotation {@Pointcut("execution(* *.qryMsg(..)) && args(String)")public void pointcut() {// pointcut}// 带上JoinPoint类型参数,获取方法参数列表(joinPoint必须作为第一个参数)// 通过 args(msgId) 把声明pointcut的参数名称绑定到方法调用的参数上@Before(value = "args(msgId)")public void printLogBefore(JoinPoint joinPoint, String msgId) {System.out.println("[@Before]方法执行开始,方法参数=" + Arrays.toString(joinPoint.getArgs()));System.out.println("[@Before]方法执行开始,方法参数=" + msgId);}// 引用定义的pointcut// 异常处理通知,获取异常对象e@AfterThrowing(pointcut = "pointcut()", throwing = "e")public void printLogAfterFinally(RuntimeException e) {System.out.println("[@AfterThrowing]异常处理开始");e.printStackTrace();System.out.println("[@AfterThrowing]异常处理结束");} // 通过 returning 属性绑定方法调用返回值@AfterReturning(value = "pointcut()", returning = "resultValue")public void printLogAfterReturning(BusiMessage resultValue) {System.out.printf("[@AfterReturning]方法执行结束(成功返回): 方法执行返回值=%s\n", resultValue);}// 1. 不使用定义的pointcut,通知注解直接绑定pointcut表达式@After("execution(* *.qryMsg(..)) && args(String)")public void printLogAfterFinally() {System.out.println("[@After]方法执行结束afterFinally");}
}

【MessageDAO】目标类

public class MessageDAO {public BusiMessage qryMsg(String msgId) {System.out.println("MessageDAO#qryMsg() 被调用");return BusiMessage.build(msgId, "您有待办任务需要处理");}
} 

【小结】 ( 非常重要,这些小技巧

  • 1)如何在通知方法中获取目标方法调用的入参

    • 方法1: 通过 JoinPoint (注意是 org.aspectj.lang.JoinPoint )
    • 方法2:通过 args(argName) 把声明pointcut的参数名称绑定到方法调用的参数上 ; ;
    • 此外: this, target, @within, @target, @annotation, @args 也具备类似功能
  • 2)如何获取目标方法执行返回值:通过 returning把返回值绑定到方法调用参数上;

  • 3)如何获取目标方法抛出的异常: 通过 throwing 绑定

【补充】 @After(finally)注解通常用于处理网络连接的释放,数据库资源的释放



【3.1.2】环绕通知注解代码示例

1)环绕通知的通知或横切逻辑方法的第一个参数类型是 ProceedingJoinPoint ;

  • 环绕通知具体方法内部可以实现偷梁换柱的效果

2)测试场景:为方法调用上下文新增日志;

【AspectJAroundAdviceAnnotationMain】环绕通知测试main

public class AspectJAroundAdviceAnnotationMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/advice/beans10AspectjAroundAdviceAnnotationXsd.xml");// 获取代理对象Object proxy = container.getBean("target");// 根据任务编号查询待发送消息BusiMessage busiMessage = ((MessageDAO) proxy).qryMsg("task001");System.out.println("main: " + busiMessage);}
}

【打印日志】

[@Around]方法调用开始
[@Around]参数值 = task001
MessageDAO#qryMsg() 被调用
[@Around]方法调用结束
main: BusiMessage{msgId='task001', msgText='您有待办任务需要处理'}

【beans10AspectjAroundAdviceAnnotationXsd.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --><aop:aspectj-autoproxy proxy-target-class="true" /><bean class="com.tom.springnote.chapter10.aspectjadviceannotation.MethodCallCallLogAroundAdviceAnnotation" /><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>

【MethodCallCallLogAroundAdviceAnnotation】 环绕通知 (环绕通知对应方法的第一个参数类型是 ProceedingJoinPoint

@Aspect
public class MethodCallCallLogAroundAdviceAnnotation {// 环绕通知对应方法的第一个参数类型是 ProceedingJoinPoint@Around("execution(* *.qryMsg(..)) && args(msgId)")public Object printLogAroundAdvice(ProceedingJoinPoint joinPoint, String msgId) {System.out.println("[@Around]方法调用开始");System.out.println("[@Around]参数值 = " + msgId);try {
//            joinPoint.proceed();  // 可以无参调用return joinPoint.proceed(new Object[]{msgId}); // [有参调用] 显然这里可以偷梁换柱} catch (Throwable e) {throw new RuntimeException(e);} finally {System.out.println("[@Around]方法调用结束");}}
}

【注意】joinPoint.proceed(args)有参调用,可以实现偷梁换柱



【3.1.3】 Introduction引入型通知注解代码示例

1)引入型通知使用注解织入:通过 @DeclareParents 注解 标注实例变量;(非引入型通知如Before使用 @Before注解标注方法)

  • 引入型通知回顾: 引入型通知是给目标对象织入新方法,不会修改目标对象已有方法; 织入对象是目标对象,不是目标类;即需要指定被织入的目标对象;
    • 此外:引入型通知(横切逻辑)必须实现接口;

2)引入型通知注解代码示例

【MethodCallLogIntroductionAdviceAnnotationMain】引入型通知测试main

public class MethodCallLogIntroductionAdviceAnnotationMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/advice/beans10AspectjIntroductionAdviceAnnotationXsd.xml");// 获取代理对象Object proxy = container.getBean("target");// 根据任务编号查询待发送消息((MessageDAO) proxy).qryMsg("task001");// 转为Introduction通知接口,调用横切逻辑方法((IAccessLog) proxy).sendAccessLog();}
}

【打印日志】

MessageDAO#qryMsg() 被调用
AccessLogImpl#sendAccessLog(): 发送访问日志

【beans10AspectjIntroductionAdviceAnnotationXsd.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --> <aop:aspectj-autoproxy proxy-target-class="true" /><bean class="com.tom.springnote.chapter10.aspectjadviceannotation.MethodCallLogIntroductionAdviceAnnotation" /><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>

【MethodCallLogIntroductionAdviceAnnotation】基于注解@DeclareParents 定义引入型通知

@Aspect
public class MethodCallLogIntroductionAdviceAnnotation {@DeclareParents(value="com.tom.springnote.chapter10.target.MessageDAO", defaultImpl = AccessLogImpl.class)public IAccessLog accessLog;
}

【IAccessLog】 引入型通知接口

public interface IAccessLog {void sendAccessLog();
}

【AccessLogImpl】引入型通知接口实现类

public class AccessLogImpl implements IAccessLog {@Overridepublic void sendAccessLog() {System.out.println("AccessLogImpl#sendAccessLog(): 发送访问日志");}
}

3)DeclareParents注解定义

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface DeclareParents {String value();  // 目标类全限定类名 (可以通过通配符*指定一批目标对象,如com.tom.spring.target.*) Class defaultImpl() default DeclareParents.class; // 指定织入的横切逻辑接口的实现类class
}


【4】AspectJ类库中Aspect其他方面

【4.1】 advice执行顺序

1) 问题: 一个切点可能匹配多个 advice, 这些advice执行 顺序是什么?

2)解决方法:

  • 如果 Advice定义在同一个Aspect, 根据其定义顺序决定其执行顺序;
  • 如果Advice定义在不同Aspect,这时可以通过实现 Ordered接口; 值越小,优先级越高;越先执行;

3)多个切面实现 Ordered实现有序调用

【MultiAspectOrderMain】 测试main

public class MultiAspectOrderMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/advice/beans10AspectjMultiAspectAnnotationXsd.xml");Object proxy = container.getBean("target");((MessageDAO) proxy).qryMsg("task001");}
}

【打印日志】

SecondMethodCallLogAspect#requestLog(): 方法被调用
FirstMethodCallLogAspect#requestLog(): 方法被调用
MessageDAO#qryMsg() 被调用

【beans10AspectjMultiAspectAnnotationXsd.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><aop:aspectj-autoproxy proxy-target-class="true" /><bean class="com.tom.springnote.chapter10.multiaspect.FirstMethodCallLogAspect" /><bean class="com.tom.springnote.chapter10.multiaspect.SecondMethodCallLogAspect" /><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>

【FirstMethodCallLogAspect】第1个切面 (order为100,值越大 优先级越小,越后执行 )

@Aspect
public class FirstMethodCallLogAspect implements Ordered {@Before("execution(* *.qryMsg(..))")public void requestLog() {System.out.println("FirstMethodCallLogAspect#requestLog(): 方法被调用");}@Overridepublic int getOrder() {return 100;}
}

【SecondMethodCallLogAspect】第2个切面 (order为0,值越小, 优先级越高,越先执行 )

@Aspect
public class SecondMethodCallLogAspect implements Ordered {@Before("execution(* *.qryMsg(..))")public void requestLog() {System.out.println("SecondMethodCallLogAspect#requestLog(): 方法被调用");}@Overridepublic int getOrder() {return 0;}
}


【4.2】Aspect实例化模式

1)Aspect类:被Aspect注解标注的类;

2)spring会扫描Aspect类并进行实例化; 与pojo实例化模式有Singleton与prototype类似, aspect类的实例化也有模式(默认 singleton),aspect实例化模式清单如下:perthis, pertarget, percflow, percflowbelow, perwithin ;



【5】基于schema模式的aop

1)背景:spring提倡的容器xml配置方式从 基于 DTD 转向 基于Schema; 因为基于Schema的xml,功能更加丰富与灵活;

【5.1】基于schema的aop配置概览

1)基于schema的aop配置,使用 <aop:config> 元素配置, <aop:config>元素包含 Pointcut, Advisor 及 Aspect共3个子元素;

2)使用 <aop:config> 元素实现aop

【BasedSchemaXmlConfAopMain】基于schema的aop

public class BasedSchemaXmlConfAopMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/basedSchema/beans10AspectjBasedSchema.xml");// 获取代理对象Object proxy = container.getBean("target");((MessageDAO) proxy).qryMsg("task001");}
}

【打印日志】

MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用前
MessageDAO#qryMsg() 被调用
MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用后

【beans10AspectjBasedSchema.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><aop:config proxy-target-class="true"><aop:pointcut id="pointcut" expression="execution(* *.qryMsg(..))"/><aop:advisor id="advisor" pointcut-ref="pointcut" advice-ref="methodLogAroundMethodInterceptorImpl" order="1" /></aop:config><bean id="methodLogAroundMethodInterceptorImpl"class="com.tom.springnote.chapter10.basedschema.advice.MethodLogAroundMethodInterceptorImpl" /><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/></beans>

【MethodLogAroundMethodInterceptorImpl】环绕通知

public class MethodLogAroundMethodInterceptorImpl implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用前");try {return invocation.proceed();} finally {System.out.println("MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用后");}}
}


【5.2】基于AspectJ注解的aop迁移到基于Schema的aop

1)aspectj通知注解,包括 @Before, @After(finally), @AfterReturning, @AfterThrowing , @Around, @DeclareParents ;

  • 这些Aspectj通知注解,也可以通过基于schema的xml元素来实现;

【5.2.1】前置与后置通知注解迁移到基于schema的通知配置

1)AspectJ通知注解代码,参见 AspectJAdviceAnnotationMain ;

2)基于schema通知配置的aop

【AdviceBasedSchemaXmlConfAopMain02】

public class AdviceBasedSchemaXmlConfAopMain02 {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/basedSchema/beans10AspectjAdviceBasedSchema.xml");// 获取代理对象Object proxy = container.getBean("target");((MessageDAO) proxy).qryMsg("task001");}
}

【打印日志】

[@Before]方法执行开始,方法参数=[task001]
[@Before]方法执行开始,方法参数=task001
[@Around]方法调用开始
[@Around]参数值 = task001
MessageDAO#qryMsg() 被调用
[@Around]方法调用结束
[@After]方法执行结束afterFinally
[@AfterReturning]方法执行结束(成功返回)

【beans10AspectjAdviceBasedSchema.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><aop:config proxy-target-class="true"><aop:pointcut id="pointcut" expression="execution(* *.qryMsg(..)) and args(String)"/><aop:pointcut id="printLogBefore" expression="args(msgId)"/><aop:aspect id="aspect01" ref="basedSchemaMethodLogAdvice" order="100"><aop:before pointcut-ref="pointcut" method="printLogBefore" /><aop:after-returning pointcut-ref="pointcut" method="printLogAfterReturning" /><aop:after pointcut-ref="pointcut" method="printLogAfterFinally" /><aop:after-throwing pointcut-ref="pointcut" method="printLogAfterThrowing" throwing="e" /><aop:around pointcut-ref="pointcut" method="printLogAroundAdvice" /> <!-- 配置环绕通知 --> </aop:aspect></aop:config><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/><bean id="basedSchemaMethodLogAdvice" class="com.tom.springnote.chapter10.basedschema.advice.BasedSchemaMethodLogAdvice" />
</beans>

【BasedSchemaMethodLogAdvice】基于schema配置的通知定义POJO

public class BasedSchemaMethodLogAdvice {public void printLogBefore(JoinPoint joinPoint, String msgId) {System.out.println("[@Before]方法执行开始,方法参数=" + Arrays.toString(joinPoint.getArgs()));System.out.println("[@Before]方法执行开始,方法参数=" + msgId);}public void printLogAfterThrowing(RuntimeException e) {System.out.println("[@AfterThrowing]异常处理开始");e.printStackTrace();System.out.println("[@AfterThrowing]异常处理结束");}public void printLogAfterReturning() {System.out.printf("[@AfterReturning]方法执行结束(成功返回)\n");}public void printLogAfterFinally() {System.out.println("[@After]方法执行结束afterFinally");}public Object printLogAroundAdvice(ProceedingJoinPoint joinPoint, String msgId) {System.out.println("[@Around]方法调用开始");System.out.println("[@Around]参数值 = " + msgId);try {
//            joinPoint.proceed();  // 可以无参调用return joinPoint.proceed(new Object[]{msgId}); // [有参调用] 显然这里可以偷梁换柱} catch (Throwable e) {throw new RuntimeException(e);} finally {System.out.println("[@Around]方法调用结束");}}
}


【5.2.2】环绕通知注解迁移到基于Schema的通知配置

参见 【beans10AspectjAdviceBasedSchema.xml】 ;



【5.2.3】引入型通知迁移到基于Schema的通知配置

【IntroductionAdviceBasedSchemaXmlConfAopMain03】

public class IntroductionAdviceBasedSchemaXmlConfAopMain03 {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter10/basedSchema/beans10AspectjIntroductionAdviceBasedSchema.xml");// 获取代理对象Object proxy = container.getBean("target");((MessageDAO) proxy).qryMsg("task001");// 调用引入型通知接口代理对象的方法((IAccessLog) proxy).sendAccessLog();}
}

【打印日志】

[@Around]方法调用开始
[@Around]参数值 = task001
MessageDAO#qryMsg() 被调用
[@Around]方法调用结束
AccessLogImpl#sendAccessLog(): 发送访问日志

【beans10AspectjIntroductionAdviceBasedSchema.xml】

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><aop:config proxy-target-class="true"><aop:pointcut id="pointcut" expression="execution(* *.qryMsg(..)) and args(String)"/><!-- 第1个切面 --><aop:aspect id="aspect01" ref="basedSchemaMethodLogAdvice" order="100"><aop:around pointcut-ref="pointcut" method="printLogAroundAdvice" /></aop:aspect><!-- 第2个切面 --><aop:aspect id="aspect02" ref="basedSchemaMethodLogAdvice" order="1"><aop:declare-parents types-matching="com.tom.springnote.chapter10.target.MessageDAO"implement-interface="com.tom.springnote.chapter10.introductionadviceannotation.IAccessLog"default-impl="com.tom.springnote.chapter10.introductionadviceannotation.AccessLogImpl" /></aop:aspect></aop:config><bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/><bean id="basedSchemaMethodLogAdvice" class="com.tom.springnote.chapter10.basedschema.advice.BasedSchemaMethodLogAdvice" /></beans>

【IAccessLog】引入型通知接口

public interface IAccessLog {void sendAccessLog();
}

【AccessLogImpl】 引入型通知接口实现类

public class AccessLogImpl implements IAccessLog {@Overridepublic void sendAccessLog() {System.out.println("AccessLogImpl#sendAccessLog(): 发送访问日志");}
}


【6】spring aop织入横切逻辑总结

1)spring aop的3种实现方式:

  • 方式1(spring 1.0版本):基于接口声明Advice通知: 即自定义通知实现接口,如前置通知实现BeforeAdvice接口,环绕通知实现 MethodInterceptor接口; (硬编码实现)
  • 方式2(spring 2.0版本):基于AspectJ类库注解声明Aspect切面与Advice通知;以POJO声明切面与通知,并通过注解标注相应类与方法; ( 墙裂推荐,使用注解简单
  • 方式3(spring 2.0可选版本):在方式2的基础上,基于Schema的xml配置方式说明切面与通知(把通过注解的方式,转为通过schema xml元素配置方式); (不推荐,因为切面,通知,pointcut配置在xml文件,而其具体实现是POJO,代码比较分散,比较复杂)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com