原理

由于某些原因需要给一些对象提供一个代理来控制对该对象的访问。此时,访问对象不是直接访问目标对象而是通过代理对象作为中介进行访问

Java中的代理按照代理类生成时机不同又分为静态代理动态代理。静态代理代理类在编译期生成,而动态代理代理类在Java运行时动态生成。动态代理又有JDK代理CGLib代理两种

结构

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。(规定代理类代理目标对象的规则
  • 真实主题(Real Subject)类:实现抽象主题中的具体业务,是代理对象所代表的真实对象,即目标对象
  • 代理(Proxy)类:提供与真实主题相同的接口,其内部含有对真实主题的引用,可以访问、控制、扩展真实主题的功能

1.静态代理案例

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
//接口
public interface SellTickets {
//卖票
void sell();
}
//实体类
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
//代理类
public class ProxyPoint implements SellTickets{
private TrainStation trainStation = new TrainStation();

@Override
public void sell() {
System.out.println("代售点收取服务费");
trainStation.sell();
}

}
//运行
public class Client {
public static void main(String[] args) {
//创建代理对象
ProxyPoint proxyPoint = new ProxyPoint();
//买票
proxyPoint.sell();
}
}

2.动态代理案例

Java中提供一个动态代理类Proxy,Proxy不是上述的代理对象的类,而是创建代理对象类的类,Proxy类中提供的静态方法newProxyInstance可以获取代理对象类(这是由于动态代理代理类在Java程序运行时动态生成这一特性决定的

JDK动态代理要求必须定义接口,对接口进行代理

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//接口
public interface SellTickets {
void sell();
}
//实体类
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
//代理对象工厂
public class ProxyFactory {
//声明目标对象
private TrainStation trainStation = new TrainStation();

public SellTickets getProxyObject(){
//返回代理对象
/**
* newProxyInstance方法中的三个参数
* ClassLoader loader : 类加载器,用于加载代理类,可以通过目标对象获得类加载器
* Class<?>[] interfaces : 代理类实现的接口的字节码对象
*InvocationHandler h :代理对象的调用处理程序
*/
return (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(), new InvocationHandler() {
/**
* Object proxy :代理对象,和proxyObject是同一个对象,在invoke中基本不用
* Method method : 对接口中的方法 (sell()方法) 进行封装的method对象
* Object[] args : 调用方法 (sell()方法) 的实际参数, 本例中sell()方法无参数
*
*返回值 就是sell()方法的返回值 ,由于sell()方法没有返回值 因此就是返回null
*/ @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//代理对象对目标对象增强
System.out.println("代售点收取服务费。。。。");
/**
*执行目标对象的方法 method就表示sell()方法
* 通过反射调用method对象中的invoke方法
* 参数表示 : trainStation要调用的sell方法所属类的实例对象 ,args发放的参数
*/
Object invoke = method.invoke(trainStation, args);
return invoke;
}
});
}
}
//运行
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
SellTickets proxyObject = proxyFactory.getProxyObject();
proxyObject.sell();
}
}

3.CGLib动态代理

CGLib是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK动态代理提供了很好的补充
CGLib是第三方提供的包,需要导入jar包

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
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
35
36
37
38
39
40
//接口
public interface SellTickets {
void sell();
}
//实体类
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
//代理类
public class ProxyFactory implements MethodInterceptor {

private TrainStation trainStation = new TrainStation();
public TrainStation getProxyObject(){
//创建Enhancer对象,类似与JDK代理中的proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数 MethodInterceptor子实现类也就是 本类this
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售点收取服务费...");


//要调用目标对象的方法
Object obj = method.invoke(trainStation,objects);

return null;
}
}


在JDK1.8及之后,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIE代理

动态代理和静态代理:

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我可以进集中处理,而不需要像静态代理那样每一个方法进行中转。

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

4.代理模式的优缺点及使用场景

优点:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
代理对象可以扩展目标对象的功能
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

缺点:
增加了系统的复杂度

使用场景:

远程(Remote)代理

本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。(RPC思想,例如:dubbo)

防火墙(Firewall)代理

当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。(VPN)

保护(Protect or Access)代理

控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。