ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java动态代理 [打印本页]

作者: 农妇山泉一亩田    时间: 2023-3-11 11:14
标题: Java动态代理
前言

这周工作比较忙,在学习上面就温习了一下动态代理,以前也看过这块的知识,但是一直没有动手去写下代码。学习就是这样,不动手写一写总有种没有掌握的感觉,记录下这个学习过程
动态代理有什么用呢? 它的作用就是增强,对于一个方法,我想在执行它之前执行另外一个log方法,那我可以修改代码,我可以直接去加,但是假如有很多方法都需要加这个log,那工作量就大了,我们可以用动态代理来加上这个log方法,然后通过代理来调用原来的方法,达到不修改原来方法,增加功能的目的。
动态代理实现上有两种方法JDK动态代理和CGLib动态代理,下面我们分别来演示一下
JDK动态代理

JDK动态代理是JDK自带的,通过反射来实现,需要基于接口来做,
首先我们定义一个需要代理的接口和它的实现类
  1. public interface IHelloWorld {
  2.     String say();
  3. }
  4. public class HelloWorld implements  IHelloWorld{
  5.     @Override
  6.     public String say() {
  7.         System.out.println("this is helloworld");
  8.         return "Hello World";
  9.     }
  10. }
复制代码
我们需要在调用say前面和后面都加些日志,直接上动态代理的代码
  1. public class DynamicProxy implements InvocationHandler {
  2.     private Object target;
  3.     public DynamicProxy(Object target){
  4.         this.target = target;
  5.     }
  6.     private void before(){
  7.         System.out.println("this is before");
  8.     }
  9.     private void after(){
  10.         System.out.println("this is after");
  11.     }
  12.     @Override
  13.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  14.         before();
  15.         Object result = method.invoke(this.target, args);
  16.         after();
  17.         return result;
  18.     }
  19. }
复制代码
在main方法中加入如下代码来调用这个动态代理
  1.         IHelloWorld helloWorld = new HelloWorld();
  2.         DynamicProxy dynamicProxy1 = new DynamicProxy(helloWorld);
  3.         IHelloWorld helloWorldProxy = (IHelloWorld) Proxy.newProxyInstance(helloWorld.getClass().getClassLoader(),
  4.                 helloWorld.getClass().getInterfaces(), dynamicProxy1);
  5.         String hello = helloWorldProxy.say();
复制代码
输出如下
  1. this is before
  2. this is helloworld
  3. this is after
复制代码
我们可以看到调用helloWorldProxy.say()方法后,
先调用了before(),然后调用helloWorld的say(), 然后再调用了after().
也就是DynamicProxy里面的invoke方法里面的顺序来执行。
这样写代码有点复杂了
IHelloWorld helloWorld1 = (IHelloWorld) Proxy.newProxyInstance(helloWorld.getClass().getClassLoader(),
helloWorld.getClass().getInterfaces(), dynamicProxy1);
在黄勇的《架构探险-从零开始写Java Web框架》中又包装了一下,在DynamicProxy中定义了这个方法
  1. @SuppressWarnings("unchecked")
  2.     public <T> T getProxy()
  3.     {
  4.         return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
  5.                 target.getClass().getInterfaces(), this);
  6.     }
复制代码
调用起来简单明了很多
  1.     IHelloWorld helloWorld = new HelloWorld();
  2.     DynamicProxy dynamicProxy1 = new DynamicProxy(helloWorld);
  3.     IHelloWorld helloWorldProxy = dynamicProxy1.getProxy();
  4.     helloWorldProxy.say();
复制代码
如果我们又有个方法需要加before()和after(),那么就可以直接用
  1.         IUserService userService = new UserService();
  2.         DynamicProxy dynamicProxy = new DynamicProxy(userService);
  3.         IUserService userServiceProxy = dynamicProxy.getProxy();
  4.         userServiceProxy.send();
复制代码
这就是JDK动态代理,缺点就是必须基于接口,
也就是必须这样写
IUserService userServiceProxy = dynamicProxy.getProxy();
直接写
UserService userServiceProxy = dynamicProxy.getProxy();
会报如下的转化错误
“java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class ken.proxy.demo.UserService”
CGLib动态代理

CGLib动态代理需要引入一个包
  1.         <dependency>
  2.             <groupId>cglib</groupId>
  3.             <artifactId>cglib</artifactId>
  4.             <version>3.3.0</version>
  5.         </dependency>
复制代码
代码比较简单,直接贴代码出来
  1. public class CGLibProxy implements MethodInterceptor {
  2.     private void before(){
  3.         System.out.println("this is before");
  4.     }
  5.     private void after(){
  6.         System.out.println("this is after");
  7.     }
  8.     public <T> T getProxy(Class<T> cls) {
  9.         return (T) Enhancer.create(cls,this);
  10.     }
  11.     @Override
  12.     public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  13.         before();
  14.         Object result =  methodProxy.invokeSuper(obj, args);
  15.         after();
  16.         return result;
  17.     }
  18. }
复制代码
调用代码
  1. CGLibProxy cgLibProxy = new CGLibProxy();
  2. HelloWorld helloWorld = cgLibProxy.getProxy(HelloWorld.class);
  3. helloWorld.say();
复制代码
输出和JDK动态代理一样。  它不要求HelloWorld一定去实现某个接口,相对比JDK动态代理要好,据说许多框架都用到了它。
这里遇到个坑, 我用Java19 运行这段代码会报如下错误
  1. Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @182decdb
复制代码
网上查找是Java兼容性错误,在VM Option里面加上“--add-opens java.base/java.lang=ALL-UNNAMED” 可以解决。 如果直接用Java8 也不会出现这个错误。
总结

动态代理这块的内容比较基础,关键是要尝试在实际的项目中用到它,理解它的思想,然后实际应用上去就比较好了。 这周内容比较简单,坚持学习,加油!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4