03-动态代理

举报
kwan的解忧杂货铺 发表于 2024/05/27 22:13:28 2024/05/27
【摘要】 一.概念动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制动态代理的方式:JDK 自身提供的动态代理,利用反射机制cglib 动态代理,基于 ASM 二.场景面向切面的编程(AOP)RPC 调用 三.代理解决的问题首先,它是一个代理机制。如果熟悉设计模式中的代理模式,我们会知道,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。通过...

一.概念

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

动态代理的方式:

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

二.场景

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

三.代理解决的问题

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

四.使用

1.jdk 的动态代理

I.使用

public interface Animal {
    void eat();
}
public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("The cat is eating");
    }
}
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("The dog is eating");
    }
}
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;
    }
}
public class Test {
    public static void main(String[] args) {
        // JDK 动态代理调用
        AnimalProxy proxy = new AnimalProxy();
        Animal dogProxy = (Animal) proxy.getInstance(new Dog());
        dogProxy.eat();
    }
}
  • 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);
  			//省略其他代码
}
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);
}
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<V> 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<V> 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);
      }
    }
  }
}
  • loader:为类加载器,也就是 target.getClass().getClassLoader()
  • interfaces:接口代理类的接口实现列表

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

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

2.cglib 动态代理

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

I.引入包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.9</version>
</dependency>

II.代码实现

public class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
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;
    }
}
public class Test {
    public static void main(String[] args) {
        // CGLIB 动态代理调用
        CglibProxy proxy = new CglibProxy();
        Panda panda = (Panda) proxy.getInstance(new Panda());
        panda.eat();
    }
}
  • cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法;
  • 调用 invokeSuper 进行动态代理的;
  • 可以直接对普通类进行动态代理;

五.对比

JDK Proxy 的优势

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,更加可靠;

  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版上能够使用;

Cglib 框架的优势

  • 可调用普通类,不需要实现接口;
  • 高性能;
  • 内部复杂,需要借助 Cglib 包,底层是 asm 字节码插桩技术
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。