一、代理是什么?
结合生活理解,比如,张三要租房,需要找房源(房东),然而张三现在并不知道哪里有房源,没有能力找到房东,这个时候,就出现了房屋中介,他们可以负责帮张三找到房源,其实房屋中介就是一种代理。
再比如,张三想吃外卖了,又不想去饭店取餐,那么就可以点外卖,让外卖小哥去取餐,给张三送过来,这里,外卖小哥就是代理。

代理模式,Proxy Pattern,是程序设计的一种设计模式。为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象(即客户端)不适合或者不能直接引用另一个对象(即目标对象),而代理类(或代理对象)可以在客户端和目标类(或目标对象)之间起到中介的作用。
在程序开发中我们经常强调“解耦”和“高扩展性”,代理模式其实就考虑到了这些,在不修改原对象代码的基础上,对原对象的功能进行修改或者增强。使用代理模式主要有两个作用:
- 功能增强:在客户类原有的功能上,不修改客户类,由代理增加额外的功能,新增加的功能,就叫做功能增强。
- 控制访问:通过代理类控制,不让客户类访问目标对象。
二、实现代理的方式
java中的代理主要有两种:静态代理和动态代理。而动态创建代理对象的方法又可以分为:基于JDK(接口)的动态代理和基于CGLIB(继承)的动态代理。
1.静态代理:(1)代理类是我们自己手动创建实现的;(2)而且目标类是固定的
静态代理,可以通过继承的方式来实现,也可以通过接口的方式来实现。下面通过顾客Customer让外卖小哥DeliveryClerk跑腿取餐的例子来用代码说明。
继承的方式:
/*
*顾客,想吃饭,得点餐,有点餐的方法order()
*/
package com.wjun.proxy.staticdemo;
public class Customer {
public String order(String food) {
return "下单成功,点了:"+ food;
}
}
/*
*外卖小哥,得知道点了什么才能取餐派送,通过继承的方式重写order方法
*/
package com.wjun.proxy.staticdemo;
public class DeliveryClerk extends Customer {
@Override
public String order(String food) {
String result = super.order(food);//目标方法的调用
System.out.println("外卖小哥已收到订单,正在取餐途中...");//功能增强
System.out.println("已经取餐,正在派送...");//功能增强
return result+",餐盒已消毒!";//功能增强
}
}
/*
*测试类
*/
package com.wjun.proxy.staticdemo;
public class StaticDemoTest {
public static void main(String[] args) {
//顾客想吃饭点了餐又不想去取,让外卖小哥取餐,派送,餐盒消毒
Customer customer = new DeliveryClerk();
String result = customer.order("麻辣鱼头");
System.out.println(result);
}
}
接口的方式:
/*
*点餐这件事,通过接口来定义
*/
package com.wjun.proxy.staticdemo;
public interface OrderInterface {
public String order(String food);
}
/*
*顾客,实现点餐的方法
*/
package com.wjun.proxy.staticdemo;
public class Customer implements OrderInterface{
@Override
public String order(String food) {
return "下单成功,点了:"+ food;
}
}
/*
*外卖小哥去取餐
*/
package com.wjun.proxy.staticdemo;
public class DeliveryClerk2 implements OrderInterface{
//必须知道要做什么事。把原来的对象传入,并保存到成员位置,也就是目标类对象
private OrderInterface source;
public DeliveryClerk2(OrderInterface source) {
this.source = source;
}
@Override
public String order(String food) {
String result = source.order(food);//目标方法调用
System.out.println("外卖小哥已收到订单,正在取餐途中...");//增强功能
System.out.println("已经取餐,正在派送...");//增强功能
return result+",餐盒已消毒!";//增强功能
}
}
/*
*测试类
*/
package com.wjun.proxy.staticdemo;
public class StaticDemoTest {
public static void main(String[] args) {
Customer customer = new Customer();
//创建代理对象,也就是外卖小哥对象
OrderInterface deliveryClerk = new DeliveryClerk2(customer);
//调用代理对象的方法,可以看到增强之后的效果
String result = deliveryClerk.order("红烧肉");
System.out.println(result);
}
}
代码中,代理类中做了两件事,目标方法的调用和功能增强。在目标类的方法调用之后,做的其他功能,都是增强的意思。
静态代理的缺点:
静态代理虽然能够实现我们所说的代理模式,完成了解耦,但是静态代理的代码维护依旧非常复杂。当目标类增加了,代理类可能也需要成倍增加,代理类数量过多;而且,一旦接口或父类发生了变动,则代理类的代码就得随之做出修改,代理类多的时候维护比较麻烦。所以在实际开发的时候,一般使用动态代理。
2.动态代理:
在静态代理中目标类很多的时候,可以使用动态代理,避免静态代理的缺点。动态代理中目标类即使很多,代理类数量可以很少,当修改了接口中的方法时,不会影响代理类。
动态代理,是在内存中生成代理对象的一种技术。也就是整个代理过程在内存中进行,我们不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在JVM中“凭空”创造出一个代理类对象供我们使用。
动态代理是一种创建java对象的能力,让你不用创建上例中的DeliveryClerk对象,就能创建代理类对象。
(1)基于JDK(接口)的动态代理
在程序的执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理目标类。
jdk自带的动态代理技术,需要使用一个静态方法即Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),来创建代理对象。它要求被代理对象,也就是目标类,必须实现接口。生成的代理对象和原对象都实现相同的接口,是兄弟关系。
依然借助外卖小哥取餐的例子:
/**
* 定义接口
*/
package com.wjun.proxy.jdk;
public interface OrderInterface {
public String order(String foodName);
//接口中可能不止一个方法
public void test();
public void test2();
}
/**
* 顾客类
*/
package com.wjun.proxy.jdk;
public class Customer implements OrderInterface {
@Override
public String order(String food) {
return "下单成功,点了:"+ food;
}
@Override
public void test() {
System.out.println("我是test");
}
@Override
public void test2() {
}
}
/**
* 测试类
*/
package com.wjun.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicTest {
public static void main(String[] args) {
//准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//使用jdk的API,动态生成一个代理对象
OrderInterface deliveryClerk = (OrderInterface) Proxy.newProxyInstance(
customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println("你调用了"+method.getName()+",执行的实际是我invoke"+",args是"+args[0]);
if ("order".equals(method.getName())) {
Object result = method.invoke(customer,args);
System.out.println("外卖小哥已收到订单,正在取餐途中...");
System.out.println("已经取餐,正在派送...");
return result+",餐盒已消毒!";
} else {
return method.invoke(customer, args);//使用method反射调用,在目标对象中执行该方法,并不修改其逻辑
//也就是说原封不动的调用原来的逻辑
}
}
}
);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("红烧肘子");
System.out.println(result);
//deliveryClerk.test();
}
}
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
主要关注参数列表:
1. ClassLoader loader:
固定写法,指定目标类对象的类加载器即可。
用于加载目标类及其接口的字节码文件。
通常,使用目标类的字节码对象调用getClassLoader()方法即可得到。
2. Class<?>[] interfaces:
固定写法,指定目标类的实现的所有接口的字节码对象的数组。
通常,使用目标类的字节码对象调用getInterfaces()方法即可得到。
3. InvocationHandler h:
这个参数是一个接口,主要关注它里面的唯一一个方法,invoke:
Object invoke(Object proxy, Method method, Object[] args)
这个invoke方法会在代理类对象调用方法时执行。
也就是说,我们在代理类对象中调用任何接口中的方法时,实际上都是会执行invoke;
所以,我们在此方法中完成对增强或扩展代码逻辑的编写。
proxy:就是代理类对象的一个引用,也就是Proxy.newProxyInstance的返回值,
此引用几乎不会用到,忽略即可。
method:对应的是触发invoke执行的方法的Method对象。
假如我们调用了xxx方法,该方法出发了invoke的执行,
那么,method就是xxx方法对应的反射对象(Method对象)
args:代理对象调用方法时传递的实际参数
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)究竟如何完成动态代理的呢?底层是如何实现的?我们来自己模拟实现一下:
package com.wjun.proxy.jdk.explain;
import com.wjun.proxy.jdk.OrderInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 此处我们模拟一下Proxy.newProxyInstance()中做的事情,从底层看一看到底jdk如何完成的动态代理
* Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
*/
public class DeliveryClerk implements OrderInterface {
//接收外部传递过来的InvocationHandler对象
private final InvocationHandler handler;
public DeliveryClerk(InvocationHandler handler) {
this.handler = handler;
}
@Override
public String order(String food) {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了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[]{food});
//将返回值返回
return (String) result;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
@Override
public void test() {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
//调用的是test方法,则反射获取test对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("test");
//调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void test2() {
//每个方法的实现,实际上并没有做其他的事情,而是直接调用了InvocationHandler中的invoke方法
try {
//调用的是test2方法,则反射获取test2对应的method对象,传入invoke中
Method method = OrderInterface.class.getMethod("test2");
//调用InvocationHandler中的invoke方法
Object result = handler.invoke(this, method, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
上面这部分代码就是动态代理在内存中做的事情,当然忽略了一些安全检查的细节。用下面的代码测试一下,发现结果跟之前是一样的:
package com.wjun.proxy.jdk.explain;
import com.wjun.proxy.jdk.Customer;
import com.wjun.proxy.jdk.OrderInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicTest2 {
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 {
//System.out.println("你调用了"+method.getName()+",执行的实际是我invoke"+",args是"+args[0]);
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("外卖小哥已收到订单,正在取餐途中...");
System.out.println("已经取餐,正在派送...");
return result + ",餐盒已消毒!";
} else {
return method.invoke(customer, args);//使用method反射调用,在目标对象中执行该方法,并不修改其逻辑
//也就是说原封不动的调用原来的逻辑
}
}
};
OrderInterface deliveryClerk = new DeliveryClerk(handler);
//调用代理对象,执行对应方法
String result = deliveryClerk.order("红烧肘子");
System.out.println(result);
//deliveryClerk.test();
}
}
基于接口的动态代理,实际上是在内存中生成了一个对象,该对象实现了指定的目标类对象拥有的接口。所以代理类对象和目标类对象是兄弟关系。
这里说明一下,兄弟关系,是并列的关系,不能互相转换,包容性比较差。在spring框架中,如果配置jdk的动态代理方式,一定要用接口类型接受代理类。
(2)cglib的动态代理
cglib时第三方的工具库,创建代理对象。原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改。它不要求目标类实现接口,但是要求目标类不能是最终类,也就是不能被final修饰。因为CGLIB是基于目标类生成该类的一个子类作为代理类,所以目标类必须可被继承。
Enchancer.create(Class type, Callback callback)
主要参数列表:
Class type:
指定我们要代理的目标类的字节码对象,也就是指定目标类的类型
Callback callback:
Callback是一个接口,由于该接口只是一个名称定义的作用,并不包含方法的声明。
所以,我们使用时通常使用它的一个子接口MethodInterceptor,(方法拦截器)
而MethodInterceptor接口中也只有一个方法——intercept。
Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)的解释:
proxy:就是代理类对象的一个引用,也就是Enchancer.create的返回值,
此引用几乎不会用到,忽略即可。
method:对应的是触发intercept执行的方法的Method对象。
假如我们调用了xxx方法,该方法出发了intercept的执行,
那么,method就是xxx方法对应的反射对象(Method对象)
args:代理对象调用方法时传递的实际参数
methodProxy:方法的代理对象,一般也不做处理,可以暂时忽略
代码示例:
/**
* 顾客类
*/
package com.wjun.proxy.cglib;
public class Customer {
public String order(String food) {
return "已经下单"+food;
}
public void test() {
System.out.println("我是test");
}
public void test2() {}
}
/**
* 测试类
*/
package com.wjun.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DynamicTest {
public static void main(String[] args) {
//创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//使用CGLIB创建代理对象
Customer deliveryClerk = (Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//判断,如果是order方法就增强
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("外卖小哥已收到订单,正在取餐途中...");
System.out.println("已经取餐,正在派送...");
return result+",餐盒已消毒!";
}else {
return method.invoke(customer, args);//原封不动调用原来的逻辑,反射调用
}
}
}
);
//调用代理类的方法
String result = deliveryClerk.order("鱼香肉丝");
System.out.println(result);
}
}
内存中是如何创建代理对象的?我们来模拟一下,看下面代码
package com.wjun.proxy.cglib.explain;
import com.wjun.proxy.cglib.Customer;
import net.sf.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
/**
* 模拟在内存中调用CGLIB的方法Enchancer.create后,该方法内部写了一个什么样的代理类
* Enchancer.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 food) {
try {
Method method = Customer.class.getMethod("order", String.class);
Object result = methodInterceptor.intercept(this, method, new Object[]{food}, null);
return (String) result;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return super.order(food);
}
@Override
public void test() {
try {
Method method = Customer.class.getMethod("test", String.class);
Object result = methodInterceptor.intercept(this, method, null, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void test2() {
try {
Method method = Customer.class.getMethod("test2", String.class);
Object result = methodInterceptor.intercept(this, method, null, null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
基于父类的动态代理,是在内存中生成了一个对象,该对象继承了原对象(目标类对象)。所以代理类对象实际上是目标类对象的儿子。
由于是父子关系,所以,代理类对象是可以用父类的引用接收的。
三、小结
代理模式在java开发中是广泛应用的,特别是在框架中低层原理经常涉及到代理模式(尤其是动态代理)。
静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自己手写代码,维护、修改非常繁杂,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用比较少。
基于jdk和基于cdlib,实际使用时两个都会用到。
- 在spring中,默认情况下它就支持了两种动态代理模式,如果你指定的目标类实现了接口,spring就会自动选择jdk的动态代理,而如果目标类没有实现接口,则spring就会使用cglib。
- 我们在开发时,由于基于jdk的动态代理要求比较多,更不容易实现,所以很多人习惯于同一配置为使用CGLIB进行代理。也就是CGLIB更为通用。
- 如果使用dubbo+zookeeper,底层进行代理时,最好配置定死使用CGLIB的方式进行处理。因为dubbo会使用基于包名的扫描方式进行类的处理,而jdk的代理类生成的包名类似于com.sun.proxy....格式。我们实际需要让代理类和目标类保持一样的包名。所以只有CGLIB能保持原包名不变生成代理类。