1、概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。在这个模式中,我们通过创建代理对象作为“替身”替代了原有对象,从而达到我们控制对象访问的目的。
通俗来说,代理=代替处理。是由另一个对象来代替原对象来处理某些逻辑。
举个例子:房产中介、代跑腿业务、送外卖、火车站卖票...
2、代理模式能带给我们什么?
代理模式能带给我们控制访问某个对象的能力。比如说,在某些情况下,一个对象的某些方法想要进行屏蔽或者逻辑上的控制,我们就可以通过代理模式进行操控,在不修改原对象代码的基础上,对原对象的功能进行修改或者增强。体现在以下两点:
- 解耦
当模块或组件耦合度过高,会带来难以扩展、维护性差、纠错困难等各种问题,所以我们在设计的时候,要尽可能的避免模块或组件之间的耦合度过高。在设计过程中,削减耦合度或者消除耦合度的操作,就是解耦。
当我们想要给某个对象添加一些额外的逻辑时(例如访问权限的控制,日志的记录...),使用代理模式。我们可以不修改原代码,只针对这些额外的功能进行编码。在整个过程中,原对象的逻辑和额外增加的逻辑完全解耦,互不干扰。
- 高扩展性
由于模块间是解耦的,所以我们随时可以添加任意的功能或者修改之前的功能而不回影响到原模块的正常执行。相当于我们的代码像钢铁侠的战衣一样,可以随时添加各种战斗模组以适应不同的作战环境。这就是高扩展性。
3、结构
3.1、代理模式角色
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类,也称目标类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,我们需要通过代理对象控制原对象的访问,扩展其功能。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
3.2、代理对象创建方式
Java中代理有三种方式来创建代理对象:
- 静态代理
- 基于JDK(接口)的动态代理
- 基于CGLIB(父类)的动态代理
4、静态代理
静态代理是代码在运行之前(我们对目标对象的每个方法的增强都是手动完成的),这个代理类就已经存在了。但是由于静态代理在代码运行前就已经存在代理类了,因此对于每一个代理对象都需要建一个代理类去代理,当需要代理的对象很多的时候就需要创建很多的代理类,一旦接口或者父类发生了变动,代理类的代码就得随之改动,非常的不灵活,严重降低程序的可维护性。实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
4.1、实现方式:
- 接口实现方式:
- 继承方式:
4.2、举例实现:顾客点外卖
比如说我们饿了,想要吃个东西,但是我又有事情,走不开,这个时候就可以点外卖,让外卖小哥帮你跑个腿,并且中途想要买点儿东西的话,可以在给小费情况下请求外卖小哥帮忙买点东西。我这个客户是目标对象,外卖小哥是代理对象。
4.2.1、接口实现方式
- 类图如下:
- 代码如下:
/** * 点餐接口 */ public interface OrderInterface { public String order(String foodName); } /** * 客户点餐类 */ public class Customer1 implements OrderInterface { public String order(String foodName) { return "顾客点了" + foodName; } } /** * 送餐员类 */ public class DeliveryClerk1 implements OrderInterface { //把原来的对象传入,并保存在一个成员变量中,也就是目标类对象 private OrderInterface orderInterface; public DeliveryClerk1(OrderInterface orderInterface) { this.orderInterface = orderInterface; } @Override public String order(String foodName) { String order = orderInterface.order(foodName); System.out.println(order); System.out.println("送餐员收到订单,准备取餐。。。"); System.out.println("送餐员取餐,准备送餐。。。"); return order+ ",送餐员送餐"; } } /** * 测试类 */ public class Main { public static void main(String[] args) { //创造一个顾客对象 Customer1 customer1 = new Customer1(); //创建一个代理对象,也就是外卖小哥对象 DeliveryClerk1 deliveryClerk1 = new DeliveryClerk1(customer1); //调用代理对象的方法,可以看到代理对象会调用目标对象的方法,同时代理对象可以做一些额外的操作 String order1 = deliveryClerk1.order("麻辣烫"); System.out.println(order1 + ",顾客收餐"); //顾客点了麻辣烫 //送餐员收到订单,准备取餐。。。 //送餐员取餐,准备送餐。。。 //顾客点了麻辣烫,送餐员送餐,顾客收餐 } }
4.2.2、继承方式
- 类图如下:
- 代码如下:
/** * 客户点餐类 */ public class Customer { public String order(String foodName) { return "顾客点了" + foodName; } } /** * 送餐员类 */ public class DeliveryClerk extends Customer { @Override public String order(String foodName) { String order = super.order(foodName); System.out.println(order); System.out.println("送餐员收到订单,准备取餐。。。"); System.out.println("送餐员取餐,准备送餐。。。"); return order + ",送货员送货"; } } /** * 测试类 */ public class Main { public static void main(String[] args) { Customer customer = new DeliveryClerk(); String order = customer.order("麻辣烫"); System.out.println(order+",顾客收餐"); //顾客点了麻辣烫 //送餐员收到订单,准备取餐。。。 //送餐员取餐,准备送餐。。。 //顾客点了麻辣烫,送货员送货,顾客收餐 } }
4,3、总结分析
静态代理虽然能够实现我们所说的代理模式,完成了解耦,但是静态代理类的代码维护依然非常复杂。一旦接口或者父类发生了变动,则代理类的代码就得随之修改,代理类多的时候维护比较麻烦。所以在实际开发的时候,一般使用动态代理的方式。
5、动态代理
相比于静态代理来说,动态代理更加灵活。动态代理技术,是在内存中生成代理对象的一种技术。也就是整个代理过程在内存中进行,我们不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在JVM中“凭空”造出一个代理类对象供我们使用。一般使用的动态代理技术有以下两种:
- 基于JDK(接口)的动态代理
- 基于CGLIB(父类)的动态代理
5.1、基于JDK(接口)的动态代理
JDK自带的动态代理技术,需要使用一个静态方法来创建代理对象。它要求被代理对象,也就是目标类,必须实现接口。生成的代理对象和原对象都实现相同的接口,是兄弟关系。
5.1.1、JDK动态代理方法介绍
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
.......
}
Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
主要关注参数列表:
- ClassLoader loader:固定写法,指定目标类对象的类加载器即可。用于加载目标类及其接口的字节码文件。通常,使用目标类的字节码对象调用getClassLoader()方法即可得到
- Class<?>[] interfaces:固定写法,指定目标类的实现的所有接口的字节码对象的数组。通常,使用目标类的字节码对象调用getInterfaces()方法即可得到
- InvocationHandler h:这个参数是一个接口,主要关注它里面唯一一个方法,invoke方法。它会在代理类对象调用方法时执行,也就是说,我们在代理类对象中调用任何接口中的方法时,都会执行到invoke中。所以,我们在此方法中完成对增强或者扩展代码逻辑的编写。
要实现动态代理的话,还必须需要实现 InvocationHandler 来自定义处理逻辑。当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
public interface InvocationHandler {
//我们在代理类对象中调用任何接口中的方法时,都会执行到invoke中
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
Object invoke(Object proxy, Method method, Object[] args)
主要参数列表:
- proxy:动态生成的代理类,就是代理类对象的一个引用,也就是Proxy.newProxyInstance的返回值,此引用几乎不回用到,忽略即可。
- method:对应的是触发invoke执行的方法的Method对象。假如我们调用了xxx方法,该方法触发了invoke的执行,那么,method就是xxx方法对应的反射对象(Method对象)
- args:代理对象调用方法时,传递的实际参数
也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
5.1.2、使用步骤以及代码示例
使用步骤:
- 定义一个接口及其实现类;
- 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
- 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
代码示例:
/**
* 点餐接口
*/
public interface OrderInterface {
String order(String foodName);
}
/**
* 客户点餐类
*/
public class Customer implements OrderInterface {
@Override
public String order(String foodName) {
System.out.println("顾客下单");
return "顾客下单了"+foodName;
}
}
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
// 准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用jdk的api,动态生成一个代理对象
OrderInterface orderInterface = (OrderInterface) Proxy.newProxyInstance(
customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
(proxy, method, args1) -> {
if ("order".equals(method.getName())) {
Object invoke = method.invoke(customer, args1);
System.out.println(invoke);
System.out.println("送餐员收到订单,准备取餐。。。");
System.out.println("送餐员取餐,准备送餐。。。");
return invoke+ ",送货员送货";
} else {
return method.invoke(customer, args1);//使用method反射调用,在原对象(目标类)中执行该方法,并不修改其原有的逻辑
}
});
//调用代理对象,执行对应方法
String order = orderInterface.order("饭");
System.out.println(order+ ",顾客收餐");
//顾客点了饭
//送餐员收到订单,准备取餐。。。
//送餐员取餐,准备送餐。。。
//顾客点了饭,送货员送货,顾客收餐
}
}
5.1.3、模拟JDK动态代理底层实现
代码示例:
/**
* 点餐接口
*/
public interface OrderInterface {
String order(String foodName);
}
/**
* 此处我们模拟一下Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法中做的事情,从底层看一看到底JDK如何完成的动态代理
*/
public class DeliveryClerk implements OrderInterface {
// 接收外部传递过来的InvocationHandler对象
private final InvocationHandler handler;
public DeliveryClerk(InvocationHandler handler) {
this.handler = handler;
}
@Override
public String order(String foodName) {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
//调用的是order方法,则反射获取order对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("order", String.class);
//调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method, new Object[]{foodName});
//将返回值返回
return (String) result;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
//准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//把InvocationHandler的定义抽取出来
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())) {
Object invoke = method.invoke(customer, args);
System.out.println(invoke);
System.out.println("送餐员收到订单,准备取餐。。。");
System.out.println("送餐员取餐,准备送餐。。。");
return invoke+ ",送货员送货";
} else {
return method.invoke(customer, args);//使用method反射调用,在原对象(目标类)中执行该方法,并不修改其原有的逻辑
}
}
};
DeliveryClerk deliveryClerk = new DeliveryClerk(handler);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result+ ",顾客收餐");
//顾客点了麻婆豆腐
//送餐员收到订单,准备取餐。。。
//送餐员取餐,准备送餐。。。
//顾客点了麻婆豆腐,送货员送货,顾客收餐
}
}
5.1.4、总结
基于接口的动态代理,实际上是在内存中生成了一个对象,该对象实现了指定的目标类对象拥有的接口。所以代理类对象和目标类对象是兄弟关系。而兄弟关系是并列的关系,不能互相转换,包容性比较差。比如说在使用Spring框架的时候,如果配置JDK的动态代理方式,一定要用接口类型接收代理类。
5.2、基于CGLIB(父类)的动态代理
基于jdk的动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 cglib的动态代理机制来避免。cglib是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为jdk的动态代理提供了很好的补充。
基于cglib(父类)的动态代理是第三方的动态代理技术,也是可以使用一个静态方法来创建代理对象。它不要求目标类实现接口,但是要求目标类不能是最终类,也就是不能被final修饰。因为cglib是基于目标类生成改类的一个子类作为代理类,所以目标类必须可被继承。
5.2.1、CGLIB动态代理方法介绍
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。
我们可以通过 Enhancer 类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
Enhancer.create(Class type, Callback callback)
主要参数:
- Class type:指定我们要代理的目标类的字节码对象,也就是指定目标类的类型
- Callback callback:此单词的意思叫做回调,意思就是我们提供一个方法,它会在合适的时候帮我们调用它。回来调用的意思。Callback是一个接口,由于该接口只是一个名称定义的作用,并不包含方法的声明。所以我们使用时通常使用它的一个子接口MethodInterceptor,此单词的意思叫做方法拦截器。
MethodInterceptor接口中也只有一个方法,叫做intercept
Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
主要参数:
- proxy:就是代理类对象的一个引用,也就是Enhancer.create的返回值,此引用几乎不回用到,忽略即可。
- method:对应的是触发intercept执行的方法的Method对象。假如我们调用了xxx方法,该方法触发了intercept的执行,那么,method就是xxx方法对应的反射对象(Method对象)
- args:代理对象调用方法时,传递的实际参数
- methodProxy:方法的代理对象,一般也不作处理,可以暂时忽略
5.2.2、使用步骤以及代码示例
使用步骤:
- 定义一个类;
- 通过 Enhancer 类的 create()方法创建代理对象;
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 jdk的动态代理中的 invoke 方法类似;在 intercept 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
代码示例:
cglib是第三方提供的包,所以需要引入jar包的坐标:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
/**
* 客户点餐类
*/
public class Customer {
public String order(String foodName) {
return "顾客点了" + foodName;
}
}
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
// 创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用cglib创建代理对象
Customer deliveryClerk = (Customer) Enhancer.create(Customer.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("order".equals(method.getName())) {
Object invoke = method.invoke(customer, args);
System.out.println(invoke);
System.out.println("送餐员收到订单,准备取餐。。。");
System.out.println("送餐员取餐,准备送餐。。。");
return invoke+ ",送货员送货";
} else {
return method.invoke(customer, args);//使用method反射调用,在原对象(目标类)中执行该方法,并不修改其原有的逻辑
}
}
});
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result+ ",顾客收餐");
//顾客点了麻婆豆腐
//送餐员收到订单,准备取餐。。。
//送餐员取餐,准备送餐。。。
//顾客点了麻婆豆腐,送货员送货,顾客收餐
}
}
5.2.3、模拟CGLIB动态代理底层实现
代码示例:
/**
* 客户点餐类
*/
public class Customer {
public String order(String foodName) {
return "顾客点了" + foodName;
}
}
/**
* 模拟在内存中调用CGLIB的Enhancer.create(Class type, Callback callback)方法后,该方法内部写了一个什么样的代理类
*/
public class DeliveryClerk extends Customer {
// 保存方法拦截器
private final MethodInterceptor methodInterceptor;
public DeliveryClerk(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
@Override
public String order(String foodName) {
try {
Method method = Customer.class.getMethod("order", String.class);
Object result = methodInterceptor.intercept(this, method, new Object[]{foodName}, null);
return (String) result;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return super.order(foodName);
}
}
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
//准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//把MethodInterceptor的定义抽取出来
MethodInterceptor handler = new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("order".equals(method.getName())) {
Object invoke = method.invoke(customer, args);
System.out.println(invoke);
System.out.println("送餐员收到订单,准备取餐。。。");
System.out.println("送餐员取餐,准备送餐。。。");
return invoke+ ",送货员送货";
} else {
return method.invoke(customer, args);//使用method反射调用,在原对象(目标类)中执行该方法,并不修改其原有的逻辑
}
}
};
DeliveryClerk deliveryClerk = new DeliveryClerk(handler);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("麻婆豆腐");
System.out.println(result+ ",顾客收餐");
//顾客点了麻婆豆腐
//送餐员收到订单,准备取餐。。。
//送餐员取餐,准备送餐。。。
//顾客点了麻婆豆腐,送货员送货,顾客收餐
}
}
5.2.4、总结
基于父类的动态代理,是在内存中生成了一个对象,该对象继承了原对象(目标类对象)。所以代理类对象实际上是目标类对象的儿子。形成一对父子关系,代理类对象是可以用父类的引用接收的。
6、三种代理的对比
- jdk代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。 - 动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
7、代理模式的优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 程序的性能没有直接调用高
- 增加了系统的复杂度;
8、使用场景
- aop
aop通过动态代理对目标对象进行了增强,比如我们最常用的前置通知、后置通知这些。 - 远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。 - 防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。 - 保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。