• 【java基础】动态代理


    一.概念

    动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制

    动态代理的方式:

    • JDK 自身提供的动态代理,利用反射机制
    • cglib 动态代理,基于ASM

    二.场景

    • 面向切面的编程(AOP)
    • RPC 调用

    三.代理解决的问题

    首先,它是一个代理机制。如果熟悉设计模式中的代理模式,我们会知道,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,通过代理,可以提供更加友善的界面。还可以通过代理,可以做一个全局的拦截器。

    四.使用

    1.jdk的动态代理

    I.使用
    public interface Animal {
        void eat();
    }
    
    • 1
    • 2
    • 3
    public class Cat implements Animal {
        @Override
        public void eat() {
            System.out.println("The cat is eating");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class Dog implements Animal {
        @Override
        public void eat() {
            System.out.println("The dog is eating");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class AnimalProxy implements InvocationHandler {
        private Object target; // 代理对象
    
        public Object getInstance(Object target) {
            this.target = target;
            // 取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("调用前");
            Object result = method.invoke(target, args); // 方法调用
            System.out.println("调用后");
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class Test {
        public static void main(String[] args) {
            // JDK 动态代理调用
            AnimalProxy proxy = new AnimalProxy();
            Animal dogProxy = (Animal) proxy.getInstance(new Dog());
            dogProxy.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • JDK Proxy 是通过实现 InvocationHandler 接口来实现的
    • 通过newProxyInstance获取到代理类对象
    • 重写invoke方法,以便代理类调用
    • JDK Proxy 只能代理实现接口的类
    II.为什么JDK Proxy只能代理实现接口的类?

    查看newProxyInstance的源码

    /**
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class to implement
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
      throws IllegalArgumentException
    {
      Objects.requireNonNull(h);
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
      			//省略其他代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
      if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
      }
    
      // If the proxy class defined by the given loader implementing
      // the given interfaces exists, this will simply return the cached copy; 缓存
      // otherwise, it will create the proxy class via the ProxyClassFactory  代理类工厂
      return proxyClassCache.get(loader, interfaces);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public V get(K key, P parameter) {
      Objects.requireNonNull(parameter);//校验接口列表是否为null
      expungeStaleEntries();
      Object cacheKey = CacheKey.valueOf(key, refQueue);
      // lazily install the 2nd level valuesMap for the particular cacheKey
      ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
      if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
          = map.putIfAbsent(cacheKey,
                            valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
          valuesMap = oldValuesMap;
        }
      }
      // create subKey and retrieve the possible Supplier stored by that
      // subKey from valuesMap
      Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
      Supplier<V> supplier = valuesMap.get(subKey);
      Factory factory = null;
      while (true) {
        if (supplier != null) {
          // supplier might be a Factory or a CacheValue instance
          V value = supplier.get();
          if (value != null) {
            return value;
          }
        }
        // else no supplier in cache
        // or a supplier that returned null (could be a cleared CacheValue
        // or a Factory that wasn't successful in installing the CacheValue)
        // lazily construct a Factory
        if (factory == null) {//没有缓存的时候,工厂也不存在,需要通过接口列表信息创建Factory
          factory = new Factory(key, parameter, subKey, valuesMap);
        }
        if (supplier == null) {
          supplier = valuesMap.putIfAbsent(subKey, factory);
          if (supplier == null) {
            // successfully installed Factory
            supplier = factory;
          }
          // else retry with winning supplier
        } else {
          if (valuesMap.replace(subKey, supplier, factory)) {
            // successfully replaced
            // cleared CacheEntry / unsuccessful Factory
            // with our Factory
            supplier = factory;
          } else {
            // retry with current supplier
            supplier = valuesMap.get(subKey);
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • loader:为类加载器,也就是 target.getClass().getClassLoader()
    • interfaces:接口代理类的接口实现列表

    根据注释说明,这个问题的源头,在于 JDK Proxy 的源码设计。首先会判断接口列表集合是否为空,为空直接抛出异常,不为空,如果缓存中不存在且工厂类也不存在,会通过接口列表信息创建工厂类.

    如果要执意动态代理,非接口实现类就会报错.

    2.cglib动态代理

    JDK 动态代理机制只能代理实现了接口的类,Cglib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

    I.引入包
    <dependency>
        <groupId>cglibgroupId>
        <artifactId>cglibartifactId>
        <version>3.2.9version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    II.代码实现
    public class Panda {
        public void eat() {
            System.out.println("The panda is eating");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class CglibProxy implements MethodInterceptor {
        private Object target; // 代理对象
    
        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            // 设置父类为实例类
            enhancer.setSuperclass(this.target.getClass());
            // 回调方法
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("调用前");
            Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
            System.out.println("调用后");
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    public class Test {
        public static void main(String[] args) {
            // CGLIB 动态代理调用
            CglibProxy proxy = new CglibProxy();
            Panda panda = (Panda) proxy.getInstance(new Panda());
            panda.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法;
    • 调用 invokeSuper 进行动态代理的;
    • 可以直接对普通类进行动态代理;

    五.对比

    JDK Proxy 的优势

    • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,更加可靠;
    • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版上能够使用;

    Cglib 框架的优势

    • 可调用普通类,不需要实现接口;
    • 高性能;
    • 内部复杂,需要借助Cglib包,底层是asm字节码插桩技术
  • 相关阅读:
    sql行转列三个方法
    springboot3.x集成SpringDoc Swagger3
    ShowLibAlias...
    Unity 2022 每次打开项目都会弹出“Unity is running as administrator“
    Python学习 day02(注意事项)
    Pandas索引loc[]与iloc[]的区别
    线路曲线坐标计算程2
    如何对element弹窗进行二次封装
    SiP封装技术将制霸“后摩尔时代”?利尔达首款SiP模组应运而生!
    【Linux】Linux权限
  • 原文地址:https://blog.csdn.net/qyj19920704/article/details/126290214