700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Spring原理学习(七)JDK动态代理与CGLIB代理底层实现

Spring原理学习(七)JDK动态代理与CGLIB代理底层实现

时间:2020-02-16 02:00:58

相关推荐

Spring原理学习(七)JDK动态代理与CGLIB代理底层实现

AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能。

除此以外,aspectj 提供了两种另外的 AOP 底层实现:

第一种是通过 ajc 编译器在编译class 类文件时,就把通知的增强功能,织入到目标类的字节码中

第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能

作为对比,之前学习的代理是运行时生成新的字节码

简单比较的话:

aspectj 在编译和加载时,修改目标字节码,性能较高

aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强

但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

这里开拓一下视野,在实际开发中,我们还是经常使用代理为主。

一、AOP 实现之 ajc 编译器

需要导入的依赖和插件

注意事项:

版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器

<dependencies><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.14.0</version><configuration><complianceLevel>1.8</complianceLevel><source>8</source><target>8</target><showWeaveInfo>true</showWeaveInfo><verbose>true</verbose><Xlint>ignore</Xlint><encoding>UTF-8</encoding></configuration><executions><execution><goals><!-- use this goal to weave all your main classes --><goal>compile</goal><!-- use this goal to weave all your test classes --><goal>test-compile</goal></goals></execution></executions></plugin></plugins></build>

主程序类

@SpringBootApplicationpublic class A09Application {private static final Logger log = LoggerFactory.getLogger(A09Application.class);public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(A09Application.class, args);MyService service = context.getBean(MyService.class);log.debug("service class: {}", service.getClass());service.foo();context.close();}}

增强类

@Aspect // 注意此切面并未被 Spring 管理public class MyAspect {private static final Logger log = LoggerFactory.getLogger(MyAspect.class);@Before("execution(* com.itheima.service.MyService.foo())")public void before() {log.debug("before()");}}

被增强类

@Servicepublic class MyService {private static final Logger log = LoggerFactory.getLogger(MyService.class);public static void foo() {log.debug("foo()");}}

结果:MyService的类型是原始目标,而不是代理

[main] com.itheima.A09Application : service class: class com.itheima.service.MyService[main] com.itheima.aop.MyAspect : before()[main] com.itheima.service.MyService : foo()

这里的增强并不是spring做的增强,因为切面类并未被管理,是通过ajc编译器实现的。

查看MyService的class文件,发现foo()调用了增强类的方法。

那我们去掉与spring相关的东西,重新再测试

public class A09Application {private static final Logger log = LoggerFactory.getLogger(A09Application.class);public static void main(String[] args) {new MyService().foo();}}

发现还是被增强了,原因:修改的是class文件,自然可以生效

[main] DEBUG com.itheima.aop.MyAspect - before()[main] DEBUG com.itheima.service.MyService - foo()

总结

编译器也能修改 class 实现增强

编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

二、AOP 实现之 agent 类加载

类加载时可以通过 agent 修改 class 实现增强

需要导入的依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId></dependency>

主程序类

@SpringBootApplicationpublic class A10Application {private static final Logger log = LoggerFactory.getLogger(A10Application.class);public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(A10Application.class, args);MyService service = context.getBean(MyService.class);log.debug("service class: {}", service.getClass());service.foo();}}

增强类

@Aspect // 注意此切面并未被 Spring 管理public class MyAspect {private static final Logger log = LoggerFactory.getLogger(MyAspect.class);@Before("execution(* com.itheima.service.MyService.foo())")public void before() {log.debug("before()");}}

被增强类

@Servicepublic class MyService {private static final Logger log = LoggerFactory.getLogger(MyService.class);public static void foo() {log.debug("foo()");}}

运行时需要在 VM options里加入

-javaagent:C:/Users/manyh/.m2/repository/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar

把其中 C:/Users/manyh/.m2/repository 改为你自己 maven 仓库起始地址类加载阶段,在class文件看不到。

结果:MyService的类型是原始目标,而不是代理

[main] com.itheima.A09Application : service class: class com.itheima.service.MyService[main] com.itheima.aop.MyAspect : before()[main] com.itheima.service.MyService : foo()

由于是在类加载阶段(即运行期间)被增强,在class文件看不到,我们需要借助Arthas工具查看。可以进入官网下载jar包。

具体操作如下:

反编译命令:

jad com.itheima.service.MyService

发现MyService在运行期间被增强

三、AOP 实现之 proxy

代理类与普通类的差别:

普通类:java原代码 -> 字节码 -> 类加载 -> 使用代理类:运行期间直接生成代理类的字节码

1、JDK动态代理

public class JdkProxyDemo {interface Foo {void foo();}static class Target implements Foo {public void foo() {System.out.println("target foo");}}// jdk 只能针对接口代理public static void main(String[] param) throws IOException {//目标对象Target target = new Target();//用来加载在运行期间动态生成的字节码ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();//参数二:代理类要实现的接口 参数三:代理类调用代理类方法时执行的行为Foo proxy = (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 result = method.invoke(target);System.out.println("after...");return result; //让代理类返回目标方法执行的结果}});proxy.foo();}}

结果:

before...target fooafter...

注意:

jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关

2、Cglib代理

public class CglibProxyDemo {static class Target {public void foo() {System.out.println("target foo");}}// 代理是子类型, 目标是父类型public static void main(String[] param) {Target target = new Target();Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {@Overridepublic Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before...");//用反射调用目标方法Object result = method.invoke(target);System.out.println("after...");return result;}});proxy.foo();}}

结果:

before...target fooafter...

注意:

cglib不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系

根据上述分析 final 类,final方法无法被 cglib 增强,因为是子父类的关系,相当于代理类对目标类方法的重写。

invoke 与 invokeSuper的区别

与jdk动态代理不同的是,cglib可以避免使用反射调用目标方法。

使用methodProxy的invoke 或invokeSuper方法

public static void main(String[] param) {Target target = new Target();Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {@Overridepublic Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before...");Object result = methodProxy.invoke(target, args);System.out.println("after...");return result;}});proxy.foo();}

两者区别

//需要目标对象methodProxy.invoke(target, args)//需要代理对象methodProxy.invokeSuper(p, args);

spring使用的是invoke方法

四、JDK动态代理进阶

学习目标:代理类的内部原理

方法重写可以增强逻辑通过接口回调将【增强逻辑】置于代理类之外配合接口方法反射(也是多态),就可以再联动调用目标方法

1、模拟JDK动态代理

public class A12 {interface Foo {void foo();int boo();}//目标类static class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic int boo() {System.out.println("target boo");return 100;}}//提供动态执行增强逻辑的方法interface InvocationHandler {Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}}

代理类

public class $Proxy0 implements Foo {private InvocationHandler invocationHandler;public $Proxy0() {}public $Proxy0(InvocationHandler invocationHandler) {this.invocationHandler = invocationHandler;}static Method foo;static Method boo;static {try {//根据方法名获取Method属性foo = Foo.class.getMethod("foo");boo = Foo.class.getMethod("boo");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void foo() {try {invocationHandler.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {throw e;}catch (Throwable e){throw new UndeclaredThrowableException(e);}}@Overridepublic int boo() {try {int result = (int) invocationHandler.invoke(this, boo, new Object[0]);return result;} catch (RuntimeException | Error e) {throw e;}catch (Throwable e){throw new UndeclaredThrowableException(e);}}}

这里说明一下异常处理:

运行时异常:RuntimeException,Error,无需捕获,直接抛出检查异常:Throwable,接口不一定有Throwable,需要将检查异常转换为运行时异常抛出

测试

Foo foo = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before...");Object result = method.invoke(target);System.out.println("after...");return result;}});foo.foo();foo.boo();

结果:

before...target fooafter...before...target booafter...

JDK生成代理类时并没有经历源码阶段,编译阶段,而是直接进入字节码阶段,现在看到的java源码是通过Arthas工具对它进行了反编译, 直接生成字节码的底层技术是ASM,被广泛应用于JDK,Spring等框架,它的作用就是在运行期间动态生成字节码。

和我们自己写的代理类进行比较发现比我们多实现了Object中的toString,equals,hashCode方法,并且继承了Proxy类。

package com.itheima.a11;import com.itheima.a11.JdkProxyDemo;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;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.itheima.a11.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);}}}

Proxy类内部已经定义了InvocationHandler

public class Proxy implements java.io.Serializable {protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}}

注意:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现

2、方法反射优化

由于ASM学习成本比较高,这里直接给出结论,感兴趣的小伙伴可以去看视频。

优化:

前 16 次反射性能较低

第 17 次调用会生成代理类,优化为非反射调用

五、cglib 代理进阶

和 JDK 动态代理原理查不多

回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor

调用目标时有所改进,见下面代码片段

method.invoke 是反射调用,必须调用到足够次数才会进行优化

methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)

methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象

1、模拟cglib代理

目标类

public class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long j) {System.out.println("save(long)");}}

代理类

public class Proxy extends Target{private MethodInterceptor methodInterceptor;public Proxy() {}public Proxy(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], null);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, null);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, null);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}}

测试

public class A13 {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy(new MethodInterceptor() {@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before...");return method.invoke(target, args);}});proxy.save();proxy.save(1);proxy.save(2L);}}

结果:

before...save()before...save(int)before...save(long)

上面的代码其实和模拟JDK动态代理的代码差不多,重点是下面的避免反射调用的代码。

2、cglib 避免反射调用

给代理类增加原始功能的方法以及MethodProxy

public class Proxy extends Target{private MethodInterceptor methodInterceptor;public Proxy() {}public Proxy(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static MethodProxy save0Proxy;static MethodProxy save1Proxy;static MethodProxy save2Proxy;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);/*参数一:目标类型 参数二:代理类型参数三:方法参数描述符参数四:带增强功能的方法名 参数五:带原始功能的方法名*/save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}//>>>>>>>>>>>>>>>>>>>>>>>带原始功能的方法public void saveSuper(){super.save();}public void saveSuper(int i){super.save(i);}public void saveSuper(long j){super.save(j);}//>>>>>>>>>>>>>>>>>>>>>>>带增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);} catch (RuntimeException | Error e){throw e;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}}

当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类

ProxyFastClass 配合代理对象一起使用, 避免反射TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)

这两个类会继承FastClass抽象类,为了演示简单,下面的getIndex 和 invoke是实现它的两个方法。

1)invoke方法的无反射演示

public class TargetFastClass {//方法签名static Signature s0 = new Signature("save","()V");static Signature s1 = new Signature("save","(I)V");static Signature s2 = new Signature("save","(J)V");// 获取目标方法的编号/*Targetsave() 0save(int) 1save(long)2signature 包括方法名字、参数返回值*/public int getIndex(Signature signature) {if (signature.equals(s0)){return 0;} else if (signature.equals(s1)){return 1;} else if (signature.equals(s2)){return 2;}return -1;}// 根据方法编号, 正常调用目标对象方法public Object invoke(int index, Object target, Object[] args) {if (index == 0){((Target) target).save();return null;} else if (index == 1){((Target) target).save((int) args[0]);return null;}else if (index == 2){((Target) target).save((long) args[0]);return null;}else {throw new RuntimeException("无此方法");}}//模拟操作public static void main(String[] args) {//首次使用MethodProxy方法时被创建TargetFastClass fastClass = new TargetFastClass();//MethodProxy在创建时由于记录了方法签名,所以能够调用getIndex方法得到方法编号int i = fastClass.getIndex(new Signature("save", "()V"));//调用MethodProxy的invoke方法,间接会调用到fastClass的invoke方法fastClass.invoke(i, new Target(), new Object[0]);}}

2)invokeSuper方法的无反射演示

public class ProxyFastClass {/*调用的是代理类带原始功能的方法而不是增强方法,不然会陷入死循环*/static Signature s0 = new Signature("saveSuper","()V");static Signature s1 = new Signature("saveSuper","(I)V");static Signature s2 = new Signature("saveSuper","(J)V");// 获取代理方法的编号/*TargetsaveSuper() 0saveSuper(int) 1saveSuper(long)2signature 包括方法名字、参数返回值*/public int getIndex(Signature signature) {if (signature.equals(s0)){return 0;} else if (signature.equals(s1)){return 1;} else if (signature.equals(s2)){return 2;}return -1;}// 根据方法编号, 正常调用目标对象方法public Object invoke(int index, Object proxy, Object[] args) {if (index == 0){((Proxy) proxy).saveSuper();return null;} else if (index == 1){((Proxy) proxy).saveSuper((int) args[0]);return null;}else if (index == 2){((Proxy) proxy).saveSuper((long) args[0]);return null;}else {throw new RuntimeException("无此方法");}}public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int i = fastClass.getIndex(new Signature("saveSuper", "()V"));fastClass.invoke(i, new Proxy(), new Object[0]);}}

总结

为什么有这么麻烦的一套东西呢?

避免反射,提高性能,代价是一个代理类配两个 FastClass 类,代理类中还得增加仅调用 super 的一堆方法

用编号处理方法对应关系比较省内存,另外,最初获得方法顺序是不确定,这个过程没法固定死

与JDK动态代理相比,CGLIB代理类数目相对较少,只有两个类。而JDK动态代理在调用到第十七次后会生成代理类去优化为非反射调用,并且是一个方法对应一个代理类。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。