Java静态代理以及动态代理

Java静态代理以及动态代理基本使用

Posted by Jack on May 26, 2018

Java静态代理以及动态代理

Java的代理模式在开发中经常使用,作为设计模式的一种,在很多场景下都有应用。

代理模式通常分为两种

  • 静态代理:硬编码实现的代理类,在程序被编译成.class文件时代理类就已经存在。
  • 动态代理:代理类在运行时产生,运用jdk的反射机制动态创建而成。

没有代理的世界

通常来说,代理模式是用来解耦的,可以把代理模式认为是加入了中介的 Producer/Consumer 模式。 在没有代理的情况下,生产者直接和消费者耦合,这会导致一些问题,比如对某一方的逻辑调整会导致大面积的修改代码。

举一个场景,有个网页向用户提供阅读的功能,产品说你们先把阅读功能上线。 好了现在我们知道有一个统一的动作 read(),抽象出来做为接口吧。

interface Func {
    void read();
}

每个用户都来实现下这个接口,

public class User implements Func {
     public void read(){
        System.out.println("user read");
    }
}

现在当用户进入我们的网页的时候,我们就可以实例化一个用户对象,然后调用它的read方法。 为了初步解耦,我们用接口来声明,

Func user = new User();
user.read();

so far so good… 虽然我们的代码中到处充斥着这种样板代码,重复的实例化和调用接口,但是需求算是实现了。 然而这样的耦合程度会给自己挖很深的坑,特别是当需求发生变更的时候。 比如下面这样…

引入静态代理

第一版上线后,用户广泛好评,产品说,既然如此,我们不能免费给用户使用啊,不如我们插个广告吧!

好了,现在我们有两个选择 · 在项目中每个实例调用read()的地方前面插一句 playAdv() · 修改 User的 read()实现逻辑,在前面加个 playAdv()

第一个明显会加大我们的工作量,实例化的地方少还好,要是项目里有几百处地方直接调用实例的接口,我们估计要跪… 第二种虽然相对优雅,可是坑也不小,因为”播放广告”这个行为并不是用户的,我们希望功能尽可能的纯粹。 如果考虑到以后还要区分注册用户/付费用户,第二种修改方式带来的问题也很明显。

其实我们还有第三种选择,引入代理。 先上代理的代码

public class Proxy implements Func {
    private Func mUser;

    public Proxy(Func user) {
        this.mUser = user;
    }

    public void read() {
        mUser.read();
    }
}

上面就是一个简单的代理类的代码了,它也实现了Func的功能, 重点是它的通过构造方法传入了一个被代理的 User对象。 现在我们来看看引入代理有什么好处。

我们的之前调用 User并实例化的地方会变成这样

Proxy proxy = new Proxy(new User());
proxy.read();

咋一看,没什么变化,只是从对 User的调用变成对代理的调用。

但只要仔细思考一下就明白,这样一来,我们就不用关心具体对象的行为了,所有的事情都交给了代理。 当产品说要加个广告时,我们也不需要往用户类里加新逻辑(因为那不是用户行为),我们只需要修改一下代理类,

public class Proxy implements Func {
    private Func mUser;

    public Proxy(Func user) {
        this.mUser = user;
    }

    public void read() {
        AdvUtil.playAdv();
        mUser.read();
    }
}

至于调用 Proxy的地方,啥都不用改。 我们用 Proxy,在Producer和Consumer之间加了一层中介,这样一来即使要对Consumer的行为进行干预,也不用到处去修改代码了。

现在因为 Proxy的存在,就算产品再怎么改需求也不怕了。 想加个广告?改一下代理就行。 想加个会员免广告?改一下代理就行。

代理模式总结起来就是通过一层 Proxy层,把 Producer和Consumer之间隔开,让他们之间尽可能的少耦合。 这样当需要操作 Consumer的行为时,只需要修改 Proxy层,而不需要到处去调整 Producer的代码。

但是静态代理的弊端也是很明显的。 当接口的实现类变多时,每次的接口调整也需要修改很多代码。 可以想象一下,加入现在有 免费用户/包月用户/包年用户等类型的用户,包括 Proxy,他们都实现了 Func接口, 此时如果说要给 Func加一个接口,或者给 read()接口加一个参数, 需要调整的代码量也是很可怕的。 所以相对静态代理,就有了动态代理这种神奇的东西。

动态代理的实现

动态代理的实现步骤基本如下:

  • 定义一个公共接口(像 Func)和实现类(像 User),这部分跟静态代理一样
  • 定义一个 DynamicProxy类实现 InvocationHandler 接口,这个Proxy类似于静态代理的 Proxy然而不用实现 Func接口
  • 调用 Proxy类来构造代理对象

在动态代理的实现中有两个东西非常重要,一个是 Proxy类,一个是 InvocationHandler接口,都位于 reflect包下, 我们来看第二个步骤所定义的 DynamicProxy是怎样的吧

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {

  private Func user;

  public DynamicProxy(Func user) {
    this.user = user;
  }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("before invoke user method");
        method.invoke(this.user, objects);
        System.out.println("after invoke user method");
        return null;
    }
}

它同样持有了委托对象实例,但是和静态代理不同, 它并没有实现委托对象的接口方法, 而只实现了 InvocationHandler的 invoke方法,然后调用了 method.invoke(this.user, objects);

这里再贴一下 Client类的代码,也就是使用 Proxy的地方,

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {

  public static void main(String[] args) {
      Func user = new User();
      InvocationHandler handler = new DynamicProxy(user);
      Func proxyUser = (Func) Proxy.newProxyInstance(handler.getClass().getClassLoader(), user.getClass().getInterfaces(), handler);
      proxyUser.read();
  }
}

跟静态代理不同的地方在于,虽然这里也需要实例化一个委托类的对象,并传给 Proxy的构造方法, 但这里所实例化的是 InvocationHandler对象,而不是 DynamicProxy的对象。 到这里就完成了一个动态代理的代码,输出结果如下

before invoke user method user read after invoke user method

静态代理与动态代理的异同

动态代理跟静态代理不同的地方在于,静态代理需要实现 Func接口的 read()方法,而动态代理实现的是 InvocationHandler的 invoke方法。 静态代理需要在不同的接口中去调用 User 接口的不同方法, 而动态代理在invoke被调用的过程中不需要关心需要调用 User 的哪个具体方法, 方法被封装在 method对象中,而所需要的参数则在 Object[] objects, 直接调用就可以

 method.invoke(this.user, objects);

这里就意味着即使以后增加了 Func的接口,对于 DynamicProxy来说也不需要增加额外的维护量。

· Proxy.newProxyInstance干了什么 在静态代理里面,我们会直接用 new StaticProxy(user)构造出来的静态对象直接操作, 而在动态代理里面,我们操作的是 Proxy.newProxyInstance所构造出来的动态代理对象 proxyUser, 可能初次接触动态代理的同学在这里就概念混乱了, “难道代理类不是 new DynamicProxy出来的对象吗?” 其实不是的,如果把 proxyUser的类名打印出来的话, 它会以 $ProxyN的形式存在,N从0开始,这个就是动态代理所生成的真正代理对象, 动态代理的意义就在于这里,$ProxyN 这个对象是在运行时创建的, 如果用代码来解释的话,$ProxyN的代码会像下面这样

public final class $Proxy1 extends Proxy implements Func{
    private InvocationHandler h;
    private $Proxy1(){}
    public $Proxy1(InvocationHandler h){
        this.h = h;
    }
    public void read(){
        Method method = Subject.class.getMethod("read"); //创建method对象
        h.invoke(this, method, null); //调用了invoke方法
    }
}

这个才是真正的代理类。