Java动态代理和静态代理

一、概念

  代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理类的创建时期,代理类可分为两种。

静态代理类:在编译的时候就确定了代理类具体类型,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态的创建出代理类及其对象

区别:
--静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
--静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
--动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
--还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
 

二、Java 静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为**代理三要素**{真实角色与代理角色的抽象接口,真实角色,代理角色}

 

1. 代理模式的作用是:

     为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

2. 代理模式一般涉及到的角色

(1)抽象角色:声明真实对象和代理对象的共同接口

(2)代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装

(3)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象

抽象角色

public interface Action {
    public void doSomething();
}

真实角色

public class RealObject implements Action{
 
    public void doSomething() {
        System.out.println("do something");
    }
}

代理角色

public class Proxy implements Action {
    private Action realObject;
 
    public Proxy(Action realObject) {
        this.realObject = realObject;
    }
    public void doSomething() {
        System.out.println("proxy do");
        realObject.doSomething();
    }
}

运行代码

    Proxy proxy = new Proxy(new RealObject());
    proxy.doSomething();

simple_proxy.png

由此可见:代理类可以为委托类预处理消息、把消息转发给委托类和事后处理消息等

这种代理模式也最为简单,就是通过proxy持有realObject的引用,并进行一层封装。

3.静态代理的优点和缺点

先看看代理模式的优点: 扩展原功能,不侵入原代码。

再看看这种代理模式的缺点:

假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代码可能是这样的:

realObject.doSomething();
realObject1.doAnotherThing();
realObject2.doTwoAnother();

为了解决这个问题,我们有方案一:

为这些方法创建不同的代理类,代理后的代码是这样的:

proxy.doSomething();
proxy1.doAnotherThing();
proxy2.doTwoAnother();

当然,也有方案二:

通过创建一个proxy,持有不同的realObject,实现Action1、Action2、Action3接口,来让代码变成这样:

proxy.doSomething();
proxy.doAnotherThing();
proxy.doTwoAnother();

于是你的代理模型会变成这样:

dynamic_proxy.png

毫无疑问,仅仅为了扩展同样的功能,在方案一种,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。

而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。

java 动态代理

搞清楚静态代理的缺点十分重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。

简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理的实现过程

具体有如下四步骤:

--创建一个实现接口InvocationHandler的调用处理器(类),它必须实现invoke方法;

--通通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个动态代理类;

--通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

--通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入,通过代理调用方法。

动态代理基本用法

使用动态代理,需要将要扩展的功能写在一个InvocationHandler 实现类里,用来响应代理的任何调用:

public class DynamicProxyHandler implements InvocationHandler {
    private Object realObject;
 
    public DynamicProxyHandler(Object realObject) {
        this.realObject = realObject;
    }
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理扩展逻辑
        System.out.println("准备工作之前:");

        //转调具体目标对象的方法
        Object object= method.invoke(realObject, args);

        System.out.println("工作已经做完了!");

        return object;
    }
}

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

到这里,需要先看一下这个handler的用法:

public static void main(String[] args) {
        RealObject realObject = new RealObject();
        Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
        proxy.doSomething();
}

Proxy.newProxyInstance 传入的是一个ClassLoader, 一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。

仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。

让我们再回顾一下代理三要素:真实对象:RealObject,代理接口:Action,代理实例:Proxy

上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。
 

看一下源码

private static final Class<?>[] constructorParams =
        { InvocationHandler.class };
 
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    Class<?> cl = getProxyClass0(loader, intfs);
    ...
    final Constructor<?> cons = cl.getConstructor(constructorParams);
 
    if (!Modifier.isPublic(cl.getModifiers())) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
            cons.setAccessible(true);
            return null;
        }
        });
    }
return cons.newInstance(new Object[]{h});
}

整体流程就是:

1、生成代理类Proxy的Class对象。

2、如果Class作用域为私有,通过 setAccessible 支持访问

3、获取Proxy Class构造函数,创建Proxy代理实例。

生成Proxy的Class文件

生成Class对象的方法中,先是通过传进来的ClassLoader参数和Class[] 数组作为组成键,维护了一个对于Proxy的Class对象的缓存。这样需要相同Proxy的Class对象时,只需要创建一次。

第一次创建该Class文件时,为了线程安全,方法进行了大量的处理,最后会来到ProxyClassFactory的apply方法中,经过以下流程:

1、校验传入的接口是否由传入的ClassLoader加载的。

2、校验传入是否是接口的Class对象。

3、校验是否传入重复的接口。

4、拼装代理类包名和类名,生成.class 文件的字节码。

5、调用native方法,传入字节码,生成Class对象。

这个时候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接双击利用 ide 反编译。

 
package com.sun.proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
 
public final class $Proxy0 extends Proxy implements Action {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
 
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
 
 
    public final void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    ...
 
    static {
        try {
           ...
            m3 = Class.forName("Action").getMethod("doSomething", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
 

省略一些无关代码,可以看到两个重要的方法。

一个就是我们的代理方法doSomething、另一个就是构造方法。

这个$Proxy0 继承 Proxy并调用了父类的构造方法,回忆一下上文提到的invokeSpecial,怎么样,对上了吧。

看一下Proxy中这个构造方法:

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

在看一下$Proxy0 的代理方法:

super.h.invoke(this, m3, (Object[])null);

再来回顾一下生成Proxy实例的过程:

private static final Class<?>[] constructorParams =
        { InvocationHandler.class };
...
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
return cons.newInstance(new Object[]{h});   

实newInstance生成Proxy实例时,通过$Proxy0的Class对象,选择了这个InvocationHandler为参数的构造方法,传入我们定义的InvocationHandler并生成了一个 Proxy0的实例!InvocationHandler 里有realObject的逻辑以及我们的扩展逻辑,当我们调用Proxy0的doSomething方法时,就会调用到我们InvocationHandler 里 实现的invoke方法。

对上面这个过程,做一张图总结一下:

flow.png

总结:

动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。

类图如下所示:

上面类图中使用的JDK中的Proxy类,所以是需要要办法来告诉Proxy类需要做什么,不能像静态代理一样,将代码放到Proxy类中,因为现在Proxy不是直接实现的。既然这样的代码不能放在Proxy类中,那么就需要一个InvocationHandler,InvocationHandler的工作就是响应代理的任何调用。

参考:https://blog.csdn.net/wangqyoho/article/details/77584832

https://blog.csdn.net/mine_song/article/details/71373305?locationNum=15&fps=1

猜你喜欢

转载自blog.csdn.net/qq_35923749/article/details/81316658