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
方法中。
版权归原作者 fkjavaer 所有, 如有侵权,请联系我们删除。