0


JDK动态代理详解

1.什么是动态代理

代理

可能很多小伙伴首次接触动态代理这个名词的时候,或者是在面试过程中被问到动态代理的时候,不能很好的描述出来,动态代理到底是个什么高大上的技术。不方,其实动态代理的使用非常广泛,例如我们平常使用的

Spring

中的

@Transactional

注解,其依赖于

AOP

,而

AOP

的底层实现便是动态代理,看到这里,是不是更有兴趣去了解动态代理了呢?

动态代理:可以分解为“动态”+“代理”。

  • 代理:“代理”一词,在我们的生活中也是随处可见的,例如房屋中介,其是对房主的一种代理,房主需要出租其房屋,但是可能没时间去接待租客,给租客介绍房屋信息,带领租客看房,但是房屋中介可以为租客提供这些服务,所以,代理其是对被代理对象的一个功能增强
  • 动态:“动态”通常与“静态”相比较,“静态”描述的是事物是固定存在的,“动态”则描述的是事物是随着需求而动态生成的。

所以,静态代理存在一定的局限性,不能很好的满足需求的千变万化,动态代理的出现,就是为了解决这些局限性。

我们先来看看静态代理。

2.静态代理

在开发中,通常需要为方法添加日志打印,能够记录程序的执行过程,以便后续出现异常问题的时候,能更好的排查定位。

假设我们现在已经完成了系统用户的增加、删除、修改等功能,这些功能在类

UserServiceImpl

中已经实现。

代码示例:

publicclassUserServiceImpl{publicvoidadd(){System.out.println("添加用户");}publicvoidupdate(){System.out.println("修改用户");}publicvoiddelete(){System.out.println("删除用户");}}

现在,我们需要在UserServiceImpl类中的方法添加日志功能,那么怎么才能更好地去实现这个需求呢?

1)直接在目标方法前后添加日志代码

代码示例:

publicclassUserServiceImpl{publicvoidadd(){System.out.println("====== add方法开始 ======");System.out.println("添加用户");System.out.println("====== add方法结束 ======");}publicvoidupdate(){System.out.println("====== update方法开始 ======");System.out.println("修改用户");System.out.println("====== update方法结束 ======");}publicvoiddelete(){System.out.println("====== delete方法开始 ======");System.out.println("删除用户");System.out.println("====== delete方法结束 ======");}}

观察上述代码,这种方式的缺点在于:

  • 如果UserServiceImpl类中,有很多的方法,修改量大,且存在大量重复代码,不利于后期维护。
  • 直接修改源代码,不符号开闭原则。应该对扩展开放,对修改关闭。

2)静态代理方式实现

静态代理需要我们将目标类的方法抽取到接口中,代理类和目标类实现同一个接口,既然要实现代理,代理类自然需要在其内部维护目标对象的引用,并通过构造函数为其赋值,然后在代理类的方法中调用目标对象的同名方法,并在调用前后完成功能的增强。

实现步骤:

  • 抽取UserService接口
  • 创建目标类UserServiceImpl实现UserService接口
  • 创建代理类UserServiceProxy实现UserService接口
  • 代理类中完成功能的增强

代码实现:

// 目标接口publicinterfaceUserService{voidadd();voidupdate();voiddelete();}// 目标类publicclassUserServiceImplimplementsUserService{@Overridepublicvoidadd(){System.out.println("添加用户");}@Overridepublicvoidupdate(){System.out.println("修改用户");}@Overridepublicvoiddelete(){System.out.println("删除用户");}}// 代理类publicclassUserServiceProxyimplementsUserService{privateUserService userService;publicUserServiceProxy(UserService userService){this.userService = userService;}@Overridepublicvoidadd(){System.out.println("====== add方法开始 ======");
        userService.add();System.out.println("====== add方法结束 ======");}@Overridepublicvoidupdate(){System.out.println("====== update方法开始 ======");
        userService.update();System.out.println("====== update方法结束 ======");}@Overridepublicvoiddelete(){System.out.println("====== delete方法开始 ======");
        userService.delete();System.out.println("====== delete方法结束 ======");}}

观察上述代码,静态代理遵循开闭原则,在不修改目标类的前提下,完成了功能的增强,但是依然存在大量重复的代码,且一个代理类只能代理一个目标类,如果有n个目标类需要被代理,就需要同比增加n个代理类。

那么,有没有办法可以使得我们不需要去定义这么多的代理类,就可以实现对目标类功能的增强?答案是有的:动态代理

2.JDK动态代理

前面,我们提到静态代理的实现方式:代理类和目标类都实现同一个接口,在代理类中维护目标类对象,并完成对目标类对象方法的增强,这种方式虽然遵循开闭原则,但是代理类和目标类至少是“一对一”的绑定关系,如果需要被代理的目标类个数越多,代理类就会越多,会产生大量重复的代码,也不利于后期的维护。

从静态代理中,我们知道代理类也是接口的一个实现类,代理对象的类型也是属于接口类型,我们来验证一下。

publicclassTest{publicstaticvoidmain(String[] args){UserServiceProxy userServiceProxy =newUserServiceProxy(newUserServiceImpl());System.out.println(userServiceProxy instanceofUserService);}}// 打印结果:true

那么,能不能动态生成这些代理对象呢?我们知道类是构造对象的模板,代理类都还不存在,怎么去构造代理对象呢?

除了不存在代理类,还剩下

UserService

接口和

UserServiceImpl

目标类,JDK动态代理的目的就是通过接口来生成代理类以及代理类的对象,我们知道接口是不能直接通过new关键字创建对象的。

那么JDK动态代理是怎么创建出代理类以及代理类对象的呢?

我们先来看看通过

new

关键字创建对象的过程。

UserServiceImpl userService =newUserServiceImpl();/*
创建对象的过程:
    1.执行new指令,如果类未加载,先执行类加载过程。
        1.加载:JVM通过ClassLoader将UserServiceImpl.class文件加载到方法区(Method Area),在堆内存中创建代表该类的Class对象。
        2.验证
        3.准备:为静态变量分配内存并设置类型初始值。
        4.解析
        5.初始化:为静态变量赋值、执行静态代码块
    2.为对象分配内存,将对象的实例字段初始化类型零值。
    3.执行构造方法,对对象进行初始化
*/

对象创建过程

追踪上述过程,我们得知创建对象,需要先得到该类的Class对象,通过Class对象去创建实例对象。为了验证这一点,我们不妨来看看通过反射的方式创建对象的过程。

publicclassTest{@SneakyThrowspublicstaticvoidmain(String[] args){// 获取Class对象Class<UserServiceImpl> userServiceClass =UserServiceImpl.class;// 获取构造器Constructor<?>[] constructors = userServiceClass.getConstructors();for(Constructor<?> constructor : constructors){// 通过构造器创建实例对象System.out.println(constructor.newInstance());}}}

现在,问题回归到接口不能直接new,也没有构造方法,并且不存在代理类的class文件,怎么获得Class对象了。

动态代理关键类

我们先来看看JDK动态代理的实战代码:

  • 需要自定义个CustomInvocationHandler实现InvocationHandler接口。
  • 利用Proxy.newProxyInstance构建实例对象。
// UserService接口publicinterfaceUserService{voidadd();voidupdate();voiddelete();}// 目标类publicclassUserServiceImplimplementsUserService{@Overridepublicvoidadd(){System.out.println("添加用户");}@Overridepublicvoidupdate(){System.out.println("修改用户");}@Overridepublicvoiddelete(){System.out.println("删除用户");}}// CustomInvocationHandlerpublicclassCustomInvocationHandlerimplementsInvocationHandler{// 目标对象privateObject target;publicCustomInvocationHandler(Object target){this.target = target;}@OverridepublicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}}publicclassTest{publicstaticvoidmain(String[] args){UserServiceImpl userService =newUserServiceImpl();// 关键代码UserService service =(UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),UserServiceImpl.class.getInterfaces(),newCustomInvocationHandler(userService));
        service.add();}}

从测试代码可以看出,Proxy类是关键。我们来看看Proxy为我们提供的方法:

Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

虽然被标注为过时方法,但是从名字上可以得知,其目的是为了获得代理类的Class对象。话不多说,我们来调用一下。

publicclassTest{publicstaticvoidmain(String[] args){Class<?> proxyClass =Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(),UserServiceImpl.class.getInterfaces());System.out.println(proxyClass.getName());for(Method method : proxyClass.getDeclaredMethods()){System.out.println(method.getDeclaringClass()+"."+ method.getName()+"()");}System.out.println(Arrays.toString(proxyClass.getConstructors()));}}


可以看到:

  • 获得的Class对象的名称为$Proxy0
  • 定义了我们需要的add()、update()、delete()方法。
  • 定义了一个有参构造方法$Proxy0(InvocationHandler handler)

虽然没有无参构造方法,我们还是得尝试一下调用一下这个有参的构造方法,需要我们传入一个

java.lang.reflect.InvocationHandler

对象

publicclassTest{@SneakyThrowspublicstaticvoidmain(String[] args){System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");Class<?> proxyClass =Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(),UserServiceImpl.class.getInterfaces());// 获取$Proxy0(InvocationHandler handler)构造方法Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService =(UserService) constructor.newInstance(newInvocationHandler(){@OverridepublicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{System.out.println(proxy.getClass());System.out.println(method.getDeclaringClass()+"."+ method.getName()+"()");returnnull;}});
        userService.add();}}


看的出来,**当我们获得代理对象之后,通过代理对象来调用接口方法,都会回调构造时传进来的

InvocationHandler

对象的

invoke(Object proxy, Method method, Object[] args)

方法,**该方法有3个参数:

  • Object proxy:代表的是代理对象本身。
  • Method method:代表的是被调用的方法的Method对象。
  • Object[] args:代表的是被调用方法的参数。

**可以猜测,JDK动态代理生成的代理类中,维护了InvocationHandler类的对象变量,并且在实现接口方法时,通过InvocationHandler对象调用了

invoke(Object proxy, Method method, Object[] args)

方法。**

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");

不知道大家没有看到这行代码哈,当添加了这行代码之后,可以将在项目目录下保存动态创建的class文件,

com/sun/proxy/$Proxy0.class

可以看到生成的代理类

$Proxy0

继承自

Proxy

类,并实现了

UserService

接口,并且在

add()

方法中通过其父类

Proxy

中维护的

InvocationHandler

对象调用

invoke()

方法,这也就成功的解释了前面调用

userService.add()

方法,会回调到invoke()方法。

这时候我们再把代码改造一下,如下:

publicclassCustomInvocationHandlerimplementsInvocationHandler{// 目标对象privateObject target;publicCustomInvocationHandler(Object target){this.target = target;}@OverridepublicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}}publicclassTest{@SneakyThrowspublicstaticvoidmain(String[] args){UserServiceImpl target =newUserServiceImpl();Class<?> proxyClass =Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(),UserServiceImpl.class.getInterfaces());Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService =(UserService) constructor.newInstance(newCustomInvocationHandler(target));
        userService.add();}}

这样就完成了对目标对象功能的增强,前面我们提到过

Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

已经被标注为过时,推荐我们使用

Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

方法。

动态代理设计思想

好的,到这里,我们来总结一下JDK动态的设计思想:

使用

JDK动态代理

,使得我们免去编写代理类,只需要将增强功能编写在

InvocationHandler

invoke

方法中。

标签: java

本文转载自: https://blog.csdn.net/weixin_43834401/article/details/129310250
版权归原作者 fkjavaer 所有, 如有侵权,请联系我们删除。

“JDK动态代理详解”的评论:

还没有评论