Java代理模式: 为其他对象提供一种代理以控制对这个对象的访问。

在学习代理模式之前建议先了解Java反射技术

概述

为其他对象提供一种代理以控制对这个对象的访问。

如何理解呢,我先给大家举一个贴近生活的例子:
你的公司是一家软件公司,你是一位软件工程师。客户带着需求去找公司显然不会直接和你谈,而是去找商务谈,此时客户会认为商务就代表公司。

proxy1
proxy1

显然客户是通过商务去访问软件工程师的,那么商务(代理对象)的意义在于什么呢?商务可以进行谈判,软件的价格、交付、进度的时间节点等,或者项目完成后的商务追讨应收账单等,这些都不需要软件工程师来处理。因此,代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则是否使用真实对象, 显然在这个例子中商务控制了客户对软件工程师的访问。

经过上面的讨论,我们知道商务和软件工程师是代理和被代理的关系,客户就是经过商务去访问软件工程师的。此时客户就是程序中的调用着,商务就是代理对象,软件工程师就是真实对象。我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理必须分为两个步骤:

  1. 代理对象和真实对象建立代理关系。
  2. 实现代理对象的代理逻辑方法。

静态代理

  • 抽象角色
    我们先定义一个工程师的接口,他有实现用户需求的方法。

    1
    2
    3
    public interface Icoder {
    public void implDemands(String demandName);
    }
  • 真实角色
    接着定义一个Java工程师类,他通过Java语言实现需求。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class JavaCoder implements ICoder{
    private String name;
    public JavaCoder(String name){
    this.name = name;
    }
    @Override
    public void implDemands(String demandName) {
    System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
    }
  • 代理角色
    委屈一下商务,将其命名为工程师代理类,同时让他实现ICoder接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class CoderProxy implements ICoder{
    private ICoder coder;
    public CoderProxy(ICoder coder){
    this.coder = coder;
    }
    @Override
    public void implDemands(String demandName) {
    coder.implDemands(demandName);
    }
    }

上面一个接口,两个类,就实现了代理模式。Are you kidding me?这么简单?是的,就是这么简单。 我们通过一个场景类,模拟用户找商务提需求。

1
2
3
4
5
6
7
8
9
10
public class Customer {
public static void main(String args[]){
//定义一个java码农
ICoder coder = new JavaCoder("Zhang");
//定义一个产品经理
ICoder proxy = new CoderProxy(coder);
//让产品经理实现一个需求
proxy.implDemands();
}
}

运行程序结果如下:

1
Zhang implemented demand:Add user manageMent in JAVA!

这样我们就可以知道代理模式的优点了:

  1. 职责清晰真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可。
  2. 高扩展性 不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动。

动态代理

前面讲的主要是静态代理。那么什么是动态代理呢?

假设有这么一个需求,在方法执行前和执行完成后,打印系统时间。这很简单嘛,非业务逻辑,只要在代理类调用真实角色的方法前、后输出时间就可以了。像上例,只有一个implDemands方法,这样实现没有问题。但如果真实角色有10个方法,那么我们要写10遍完全相同的代码。有点追求的码农,肯定会对这种方法感到非常不爽。让我们接着往下看:

代理类在程序运行时创建的代理方式被称为动态代理。也就是说,代理类并不需要在Java代码中定义,而是在运行时动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。对于上例打印时间的需求,通过使用动态代理,我们可以做一个“统一指示”,对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

Java中有多种动态代理技术,接下来我们会谈论两种最常使用的动态代理技术:JDK和CGLIB。

JDK动态代理

JDK动态代理是JDK自带的功能,它必须借助一个接口才能产生代理对象

与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。因此,抽象角色、真实角色,参考ICoder和JavaCoder。

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,也叫动态代理类,这个类被要求实现InvocationHandler接口:

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
public class CoderDynamicProxy implements InvocationHandler{
//真实对象
private Object target;

/**
* 建立代理对象和真实对象的代理关系
* @param target真实对象
* @return 代理对象
**/
public Object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

/**
* @param proxy 代理对象
* @param method 当前调度的方法
* @param args 当前方法参数
* @return 代理结果返回
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(System.currentTimeMillis());
//这行代码相当于调度真实对象的方法(只是通过反射实现而已)
Object result = method.invoke(target, args);
System.out.println(System.currentTimeMillis());
return result;
}
}

当我们调用代理类对象的方法时,这个“调用”会转送到中介类的invoke方法中,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。

我们通过一个场景类,模拟用户找产品经理更改需求。

1
2
3
4
5
6
7
8
9
public class DynamicClient {
public static void main(String args[]){
CoderDynamicProxy jdk = new CoderDynamicProxy();
//绑定关系
ICoder proxy = (ICoder)jdk.bind(new JavaCoder("Zhang"));
//此时Icode对象已经是一个代理对象,它会进入逻辑方法invoke中
proxy.implDemands("Modify user management");
}
}

执行结果如下:

1
2
3
1556694750883
Zhang implemented demand:Modify user management in JAVA!
1556694750883

通过上述代码,就实现了,在执行委托类的所有方法前、后打印时间。还是那个熟悉的小张,但我们并没有创建代理类,也没有时间ICoder接口。这就是动态代理。

CGLIB动态代理

JDK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采取其他第三方技术,比如:CGLIB动态代理。它的优势在于不需要提供接口,只要一个非抽象类就能实现动态代理。

我们先修改一下JavaCoder,不实现ICoder接口

1
2
3
4
5
6
7
8
9
10
public class JavaCoder {
private String name;
public JavaCoder(String name){
this.name = name;
}

public void implDemands(String demandName) {
System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
}
}

接下来使用CGLIB动态代理技术,如下代码所示:

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
public class CoderDynamicProxy implements MethodInterceptor {
/**
* 生成CGLIB代理对象
* @param cls -- Class类
* @return Class类的CGLIB代理对象
**/
public Object getProxy(Class cls, String name){
//CGLIB增强类对象
Enhancer enhancer = new Enhancer();
//设置增强类型
enhancer.setSuperclass(cls);
//定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法
enhancer.setCallback(this);
//生成并返回代理对象
return enhancer.create(new Class[]{String.class},new Object[]{name});
}

/**
* 代理逻辑方法
* @param proxy 代理对象
* @param method 方法
* @param args 方法参数
* @param methodProxy 方法代理
* @return 代理逻辑返回
**/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(System.currentTimeMillis());
//这行代码相当于调度真实对象的方法(只是通过反射实现而已)
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println(System.currentTimeMillis());
return result;
}
}

测试CGLIB动态代理:

1
2
3
4
5
6
7
8
9
public class DynamicClient {
public static void main(String args[]){
CoderDynamicProxy cglib = new CoderDynamicProxy();

JavaCoder proxy = (JavaCoder)cglib.getProxy(JavaCoder.class, "zhang");

proxy.implDemands("Modify user management");
}
}

执行结果如下:

1
2
3
1556694088088
zhang implemented demand:Modify user management in JAVA!
1556694088104

动态代理的优点

  1. Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大
  2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用
  3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变