从代理模式说起,简单聊聊Java的动态代理

从代理模式说起

「代理模式」是设计模式的一种,代理模式中有两个关键的成员:「代理类」(Proxy)和「被代理类」(RealSubject)

从代理模式说起,简单聊聊Java的动态代理_第1张图片

那Proxy有啥用呢,直接访问RealSubject不行嘛?

  • 如果「被代理类」十分庞大(消耗内存空间),但真正需要它的时候很少,我们不希望立即初始化「被代理类」从而占用内存,交给轻量级的「代理类」Proxy完成任务即可。
  • 我们希望对「被代理类」进行一些增强,比如说在方法开始执行前后打印参数的变化,执行结果等等信息,为了解耦「被代理类」的业务实现和这种与业务无关的行为逻辑,我们需要将这些与业务无关的行为逻辑剥离出来,就可以封装在「代理类」中
  • 往往这种与业务无关的行为逻辑有很多共性,这些逻辑可以被抽象为「切面」,也就是AOP,面向切面编程,通过代理模式可以极大地减少重复代码。

代理模式的实现方法

代理模式一般有两种实现方法:静态代理和动态代理。

静态代理

静态代理就是上述UML图的实现方法,可以看到「代理类」Proxy内聚一个RealObject,实现共同的接口,可以很轻松地在这个方法上做加强。

接口:


csharp

复制代码

public interface Subject {    void dosth(); }

被代理类:


java

复制代码

public class RealSubject implements Subject{    @Override    public void dosth(){        System.out.println("dosth...");   } }

代理类:


java

复制代码

public class Proxy implements Subject{    private Subject subject;    @Override    public void dosth(){        System.out.println("before-------执行前增强逻辑");        subject.dosth();        System.out.println("after--------执行后增强逻辑");   } }

静态代理的局限性
  • 每需要一个类被代理,就需要为之编写一个代理类,这会导致文件数量膨胀。
  • 上文提到的Proxy的第三个好处,即AOP的功能还没有实现,我们希望一个代理类能代理多个类,并且被代理的方法是我们可以指定的,这就需要动态代理来解决了。

动态代理

动态代理又有两种常见的实现:JDK动态代理和CGLIB动态代理。

JDK动态代理

基本使用

JDK动态代理中:「代理类」Proxy不再实现Subject接口,而是implements InvocationHandler,但仍聚合了被代理类(通过构造函数传入被代理类,Object类型)

代理类implements InvocationHandler,重写invoke方法


java

复制代码

public class TestInterceptor implements InvocationHandler {    private Object target;//目标对象的引用,这里设计成Object类型,更具通用性      public TestInterceptor(Object target){          this.target = target;     }        public Object invoke(Object proxy, Method method, Object[] arg)  throws Throwable {          System.out.println("before-------执行前逻辑");          Object result = method.invoke(target, arg);//调用目标对象的方法          System.out.println("Before return:"+result);          return result;     }   }  

利用Proxy类.newProxyInstance方法


java

复制代码

public class Main {      public static void main(String[] args) {          RealSubject target = new RealSubject();//生成目标对象          //接下来创建代理对象          Subject proxy = (Subject) Proxy.newProxyInstance(                  target.getClass().getClassLoader(),                  target.getClass().getInterfaces(), new TestInterceptor(target));          //调用代理类的方法,有切面逻辑        proxy.dosth();     }   }  

获取proxy的名称,发现是$Proxy0

实现原理

通过前文的基本使用,我们了解到了两个关键点:InvocationHandler接口和Proxy代理类。

InvocationHandler只有一个invoke方法


typescript

复制代码

public interface InvocationHandler {   public Object invoke(Object proxy, Method method, Object[] args)        throws Throwable; }

那么重点就是Proxy.newProxyInstance方法


java

复制代码

public static Object newProxyInstance(ClassLoader loader,                                      Class[] interfaces,                                      InvocationHandler h) {    // 1. 获取到代理类的class对象    Class cl = getProxyClass0(loader, intfs);    // 2. 拿到代理类的构造器,通过反射创建出一个代理类实例    final Constructor cons = cl.getConstructor(constructorParams);    final InvocationHandler ih = h;    if (!Modifier.isPublic(cl.getModifiers())) {        AccessController.doPrivileged(new PrivilegedAction() {         public Void run() {              cons.setAccessible(true);                 return null;             }           });       }    return cons.newInstance(new Object[]{h}); }

那getProxyClass0这个方法是如何拿到代理的class的呢

其实这个方法做了参数的校验,然后直接调用proxyClassCache.get方法


kotlin

复制代码

return proxyClassCache.get(loader, interfaces);

这个Cache也很好理解,我们两次获取代理对象,没必要生成两个class对象,因此要复用代理类的class,我们来关注cache未命中的情况,proxyClass是如何生成的。

这个proxyClassCache是Proxy内部的一个字段如下(注意两个入参):


swift

复制代码

private static final WeakCache[], Class>    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

get方法最重要的部分如下:


java

复制代码

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // 最终这个class对象就是supplier.get方法返回的 // Supplier是一个lambda表达式 Supplier supplier = valuesMap.get(subKey); Factory factory = null; ​ while (true) {    // 如果有supplier了 直接get,正常就返回(一般缓存命中就在这里直接return了)    if (supplier != null) {        V value = supplier.get();        if (value != null) {            return value;       }   } // 一般缓存未命中 会先创建一个Factory , Factory是一个实现了supplier的内部类    if (factory == null) {        factory = new Factory(key, parameter, subKey, valuesMap);   } // 尝试放入缓存    if (supplier == null) {        supplier = valuesMap.putIfAbsent(subKey, factory);        if (supplier == null) {            supplier = factory;       }   } else {        if (valuesMap.replace(subKey, supplier, factory)) {            supplier = factory;       } else {            supplier = valuesMap.get(subKey);       }   } }

那么核心就是Factory.get方法了:


ini

复制代码

value = Objects.requireNonNull(valueFactory.apply(key, parameter)); return value;

兜兜转转又回到了proxyClassCache的构造函数的第二个参数了,就是ProxyClassFactory

核心:ProxyClassFactory

代理类名的由来


java

复制代码

// 前缀 private static final String proxyClassNamePrefix = "$Proxy"; // 原子long private static final AtomicLong nextUniqueNumber = new AtomicLong(); // 代理类名 long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;

最后调用到:


java

复制代码

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(   proxyName, interfaces, accessFlags); return defineClass0(loader, proxyName,                    proxyClassFile, 0, proxyClassFile.length);

发现generateProxyClass非常晦涩难懂,并且defineClass0是个native方法,总之是生成了字节码文件并加载了class对象,只能反编译查看代理类的实现。

发现代理类继承了Proxy,被代理的方法以Method的形式存储,最终通过反射的方式来调用。

CGLIB动态代理

cglib不再需要被代理类实现一个接口,这是和JDK动态代理与静态代理的不同点,这可以帮助我们远离必须实现接口的困扰。

基本使用

被代理类:


java

复制代码

public class RealSubject{    public void dosth(){        System.out.println("dosth...");   } } ​

代理类, 实现 MethodInterceptor接口


java

复制代码

public class CglibProxy implements MethodInterceptor {    @Override    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {        System.out.println("xxx");        methodProxy.invokeSuper(object, args);        System.out.println("xxx");        return null;   } }

利用Enhancer创建代理对象。


java

复制代码

public class CglibTest {    public static void main(String[] args) {        CglibProxy cglibproxy = new CglibProxy();        Enhancer enhancer = new Enhancer();        // 设置enhancer对象的父类(被代理类)        enhancer.setSuperclass(RealSubject.class);        // 设置enhancer的回调对象(代理类)        enhancer.setCallback(cglibproxy);        // 创建代理对象        RealSubject proxy = (RealSubject) enhancer.create();        proxy.dosth();   } }

实现原理

如果要从ASM开始了解太费精力了,一种做法是直接看cglib生成的所有类反向推。

cglib主要通过ASM直接修改字节码文件,再通过类加载器加载生成一个class对象,就是我们的代理类Proxy,最后依然通过反射拿到Proxy的构造方法并创建一个实例对象并返回。

cglib的代理类Proxy实际上是继承被代理类RealSubject的,并且实现了Factory接口,因此cglib的局限性是:final类是无法被代理的

cglib实际会生成五个字节码文件,比较重要的有三个,代理类,以及两个FastClass分别对应代理类和被代理类,所以在生成代理对象时会慢一些。cglib调用原始方法是通过FastClass的下标进行调用的。而JDK动态代理是通过反射进行调用的。

建议自己动手反编译代理类看一下,篇幅原因这里就不贴了,实在不想动手也可以参考:分析cglib动态代理的实现

如何理解静态与动态的区别

静态代理,是一对一的关系。是确定了「被代理类」,专为此「被代理类」创建了一个代理类。

而动态代理,是多对多关系。解耦了「代理逻辑」与「被代理类」,彼此不相干。静态代理的主体是「被代理类」,而动态代理的主体既是「被代理类」,也是「代理逻辑」。只是我们在真正需要代理的时候,才把他们结合到一起。这里说「代理逻辑」,是因为真正的「代理类」是动态生成的,在此之前并不知道会用什么「代理逻辑」。而静态代理的「代理类」是静态的,代理逻辑是确定的。所以也可以说,静态代理我们是在编写「代理类」,而动态代理我们是在编写「代理逻辑」。

JDK与cglib的区别

  • JDK的核心是反射,cglib的核心是ASM
  • 通常JDK的效率更高,一种佐证是:Spring默认使用JDK,没有实现接口才用cglib
  • JDK是委托机制,cglib是继承关系
  • JDK要求被代理类必须实现某个接口,而cglib要求被代理类不被final修饰

SpringBoot默认cglib实现AOP

SpringBoot关于Spring AOP的配置类:AopAutoConfiguration,默认使用 Cglib 来实现AOP。

这样设置的原因是:避免在没有实现接口时报错(无法使用JDK动态代理)

可以通过在配置文件中输入如下命令关闭cglib


properties

复制代码

spring.aop.proxy-target-class=false

你可能感兴趣的:(代理模式,java,开发语言)