aop的jdk动态代理和cglib动态代理
创始人
2025-05-29 04:09:44

1.术语

  • Target:目标类,需要被代理的类,如:UserService
  • Advice:通知,所要增强或增加的功能,定义了切面的“什么”和“何时”,模式有Before、After、After-returning,After-throwing和Around
  • Join Point:连接点,应用执行过程中,能够插入切面的所有“点”(时机)
  • Pointcut:切点,实际运行中,选择插入切面的连接点,即定义了哪些点得到了增强。切点定义了切面的“何处”。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
  • Aspect:切面,把横切关注点模块化为特殊的类,这些类称为切面,切面是通知和切点的结合。通知和切点共同定义了切面的全部内容:它是什么,在何时和何处完成其功能
  • Introduction:引入,允许我们向现有的类添加新方法或属性
  • Weaving:织入,把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:编译期、类加载期、运行期

2.aop除代理方式外的其他实现方式

aop实现方式有三种,分别是ajc编译器,agent类加载,和代理方式。spring使用的是代理方式

2.1ajc编译器 

ajc编译器实现aop需要使用maven插件

     org.codehaus.mojoaspectj-maven-plugin1.14.01.81.81.8trueignoreUTF-8truefalsecompile

下载完插件后使用maven重新编译一下才可以使用,本地idea点击maven的compile。示例代码

定义切面

@Aspect
public class MyAspet {@Before("execution(* com.example.springtest.aop.aspet.MyService.foo())")public void before(){System.out.println("before...");}
}

定义目标类

public class MyService {public void foo(){System.out.println("foo......");}
}

测试与结果

public static void main(String[] args) {new MyService().foo();		
}

cce2ecb9078f4e27ac0ca6e13fe3e86a.png

ajc编译器使用并不广泛,示例中的切面和目标类并没有交给spring管理。它的一个小优势是可以处理静态方法,无论是静态还是非静态的方法都可以使用ajc编译器达到面向切面编程的效果。原因是在编译阶段更改.class源码。增强后的目标类代码▼

4e43380bbc5942e99b21a863b7edbd6f.png

 

2.2 agent 类加载

agent实现方式是在类加载时进行增强的。使用agent类加载方式实现aop需要在VM参数加上

-javaagent:E:\software\apache-maven-3.5.0\conf\rep\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar

代码可以使用上边例子的代码,可以达到一样的效果

 

3.jdk代理

代码示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JdkProxyDemo {interface  Foo{void foo();}static class Targent implements Foo{@Overridepublic void foo() {System.out.println("foo");}}public static void main(String[] args) {Targent targent = new Targent();ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();Foo o = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before............");Object invoke = method.invoke(targent, args);System.out.println("after............");return invoke;}});o.foo();}
}

d80962d1619446eea9425640c44ddea5.png

newProxyInstance方法参数

 public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
ClassLoader loader类加载器,因为代理类没有源码,所以加载类时需要类加载器
Class[] interfaces被代理的接口
InvocationHandler h抽象出来的接口,使得代理方法能在调用方实现

InvocationHandler接口方法参数

    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
Object proxy代理对象
Method method目标方法
Object[] args目标参数

注意点 

  • 直接生成代理类的字节码(ASM)
  • 只能针对接口
  • 代理对象与目标对象都实现了同一个接口,代理对象和目标对象是兄弟关系
  • 当调用第17次时优化调用方式,不再使用反射调用,而是生成真正的类调用(一个方法生成一个代理)

通过arthas的jad查看生成的代理类如下

final class $Proxy0
extends Proxy
implements JdkProxyDemo.Foo {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.example.springtest.aop.jdk.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void foo() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

4.cglib代理

代码示例

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CglibProxyDemo {static class Target{public void foo(){System.out.println("foo");}}public static void main(String[] args) {Target target = new Target();Target o = (Target) Enhancer.create(Target.class, new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {System.out.println("before...");Object invoke = method.invoke(target, args);System.out.println("after...");return invoke;}});o.foo();}
}

6501ae0cc95a428c9d1f62ec51193cf4.png

MethodInterceptor接口的intercept()方法参数说明

Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) 
throws Throwable;
  • Object var1              -->代理对象自己
  • Method var2             -->当前代理类正在执行的方法
  • Object[] var3             -->方法的实际参数
  • MethodProxy var4    -->当前代理类正在执行的方法,可以避免反射

执行目标方法除了Method.invoke()还可以使用intercept()方法的第4个参数MethodProxy执行目标方法,并且使用MethodProxy不会使用反射,理论上效率会高一些

methodProxy.invoke(target,args);
target是目标视力,args是目标方法参数


methodProxy.invokeSuper(o,args);
o是代理对象自己,args是目标方法参数

MethodProxy的FastClass机制

Cglib动态代理执行代理方法效率之所以比JDK高是因为Cglib采用了FastClass机制,他为代理类和被代理类各生成了一个class(jdk动态代理优化则是一个方法生成一个代理类),这个class会为代理类与被代理类的方法分类index。这个index作为方法参数,FastClass可以直接定位到要调用的方法进行调用,这样省去了反射调用,所以效率比JDK动态代理快。

注意点

  • 代理对象与目标类是父子关系,目标类是父类。因此目标类不能是final的,被代理的方法也不能是final,因为要重写方法。

5.区别

  • JDK 动态代理只能对接口进行代理,不能对普通的类进行代理,这是因为 JDK 动态代理生成的代理类,其父类是 Proxy,且 Java 不支持类的多继承。
  • CGLIB 能够代理接口和普通的类,但是被代理的类不能被 final 修饰,且接口中的方法不能使用 final 修饰。
  • JDK 动态代理使用 Java 反射技术进行操作,在生成类上更高效。
  • CGLIB 使用 ASM 框架直接对字节码进行修改,使用了 FastClass 的特性。在某些情况下,类的方法执行会比较高效。
     

 

 

相关内容

热门资讯

微信拼三张.究竟有挂吗 您好:微信拼三张这款游戏可以开挂,确实是有挂的,需要了解加客服微信【9752949】很多玩家在这款游...
科技推荐.天天麻将.有没有挂.... 科技推荐.天天麻将.有没有挂.[果然有挂]您好:天天麻将这款游戏可以开挂,确实是有挂的,需要了解加客...
[老玩家实测]“开心联盟怎么装... 您好:开心联盟这款游戏可以开挂,确实是有挂的,需要了解加客服微信【9951342】很多玩家在这款游戏...
<今日了解>... 有 亲,根据资深记者爆料中至吉安麻将是可以开挂的,确实有挂(咨询软件无需...
实测分享.“[新人海牛牛]怎么... 您好:【新人海牛牛】这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7482525】很多玩家在这...