spring-bean-aop

114 阅读4分钟

AOP

aop-springaop-aspectj

www.baeldung.com/aspectj

aop

基于切面的编程思想(aspect); 实现基于动态代理;

概念: Advice、Advisor、Pointcut、Aspect、Joinpoint

实现方式:

  1. Spring 1.2 基于接口的配置
  2. Spring 2.0 schema-based-xml
  3. Spring 2.0 @AspectJ

jdk

  • 使用了注入(DI)的方式, 目标类必须实现某接口,getInterfaces()不能为null
  • 代理类与目标类的关系是依赖(has)关系
  • InvocationHandler,Proxy.newProxyInstance

cglib

  • 基于asm原理,基于对class字节码修改,生成代理子类
  • 代理类与目标类的关系是继承关系
  • 方法必可继承,不能private, final
  • MethodInterceptor,Enhancer.create()

一般默认使用jdk,基于接口编程,使用多态; 便于业务解耦

spring-aop

  • Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
  • Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。

aspectj

  • www.eclipse.org/aspectj

  • AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案

  • 属于静态织入,它是通过修改代码来实现的,它的织入时机(@Retention注解)可以是

    • Compile-time weaving(Source源码植入):编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B
    • Post-compile weaving(class植入):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
    • Load-time weaving(Runtime):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
  1. 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的
  2. Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好.

aspectj

annotation

  • @Target, 注解使用的位置
    • ElemenetType.CONSTRUCTOR 构造器声明
    • ElemenetType.FIELD 域声明(包括 enum 实例
    • ElemenetType.LOCAL_VARIABLE 局部变量声明
    • ElemenetType.METHOD 方法声明
    • ElemenetType.PACKAGE 包声明
    • ElemenetType.PARAMETER 参数声明
    • ElemenetType.TYPE 类,接口(包括注解类型)或enum声名
  • @Retention 注解的生命周期
    • RetentionPolicy.SOURCE 源码周期,注解将被编译器丢弃
    • RetentionPolicy.CLASS 字节码周期,注解在class文件中可用,但会被VM丢弃
    • RetentionPolicy.RUNTIME 运行周期,VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
  • @Documented 可以被javadoc文档化
  • @Inherited 可以继承使用

aspectj

  • @Aspect 指定一个类为切面类
  • @Pointcut("execution(* cn.itcast.e_aop_anno..(..))") 指定切入点表达式
  • @Before("pointCut_()") 前置通知: 目标方法之前执行
  • @After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
  • @AfterReturning("pointCut_()") 返回后通知,执行方法结束前执行(异常不执行)
  • @AfterThrowing("pointCut_()") 异常通知,出现异常时候执行
  • @Around("pointCut_()") 环绕通知,环绕目标方法执行
// true, 基于cglib实现类代理
// false, 基于JDK实现接口代理,默认
<aop:aspectj-autoproxy proxy-target-class="false"/>

execution

  • execution([修饰符] <返回:><类路径:package.><方法名:><参数:(..)>[异常]) 例如: execution( com.sample.service..(..)) 等价于 execution(* com.sample.service..*(..))
  • 修饰符默认 public,能够被代理
  • 返回: * 表示任意返回
  • 方法: com.sample.service.impl.. 包及子包下所有类的所有方法
    • (..) 任意参数
    • && 、|| 、! 符合表达式
    • @within
    • @target
    • @args
    • @annotation

ProceedingJoinPoint

//错误,拿到的是接口的方法,不是实现类的方法
Signature s = pjp.getSignature();
nature ms = (MethodSignature)s;
Method m = ms.getMethod(); 

//正确
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
    throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = pjp.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(),msig.getParameterTypes());

JoinPoint

  • 都用于获取切点的数据
  • ProceedingJoinPoint 适合@Around
  • 必须放置在第一个参数上:JoinPoint和ProceedingJoinPoint
  • 如果是 @Around,我们通常会使用其子类 ProceedingJoinPoint,因为它有 procceed()/procceed(args[]) 方法
  • JoinPoint主要用来获取参数信息