代理(Proxy)设计模式
百度百科对代理模式的解释:代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
换句话说, 使用代理对象,是为了在不修改目标对象的基础上, 增强主业务逻辑。客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。
举个例子:
例如: 有 A, B, C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许A 类调用其方法,但 B 类可以调用 C 类的方法。 A 类通过 B 类调用 C 类的方法。这里 B 是 C的代理。 A 通过代理 B 访问 C。
原来的访问关系:
通过代理的访问关系:
再举个常见的例子:Window 系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。
代理模式的作用:
A、 控制访问
B、 增强功能
一、静态代理
所谓静态代理,代理类在程序运行前就已经定义好.java
源文件,其与目标类的关系在程序运行前就已经确立。 在程序运行前代理类已经编译为.class
文件。也就是说代理对象是程序员自己手动创建的。
现有如下需求:
用户需要购买 u 盘, u 盘厂家不单独接待零散购买,厂家规定一次最少购买 1000个以上,用户可以通过淘宝的代理商,那里进行购买。
- 淘宝上的商品,微商都是 u 盘工厂的代理商, 他们代理对 u 盘的销售业务。
流程:用户购买(相当于上面的A类)-------代理商(淘宝)(相当于上面的B类) ----- u 厂家(闪迪等不同的厂家)(相当于上面的C类)
设计这个业务需要的类:
- 商家和厂家都是提供 sell 购买 u 盘的方法。定义购买 u 盘的接口 UsbSell
- 金士顿(King)对购买 1 千以上的价格是 85, 3 千以上是 80。 定义 UsbKingFactory 类,实现 UsbSell
- 定义淘宝的代理商 TaoBao ,实现 UsbSell
- 定义用户类,通过淘宝购买 u 盘
1. 购买 u 盘的接口 UsbSell
public interface UsbSell {
//amount:购买的数量, float:单价
public float sell(int amount) throws Exception;
}
2. 目标类 UsbKingFactory
目标类 UsbKingFactory(金士顿 u 盘),该类实现了业务接口。(金士顿 u 盘),该类实现了业务接口。
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) throws Exception {
int price = 120; //一个的价格
if (amount >= 3000) {
price = 80;
} else if (amount >= 1000) {
price = 85;
} else {
throw new Exception("至少购买1000个");
}
return price;
}
}
3. 创建代理类TaoBao
TaoBao 就是一个代理类, 代理厂家销售 u 盘,代理类在原有的功能上,对目标新增了功能
public class TaoBao implements UsbSell {
//创建目标,厂家对象
private UsbKingFactory factory = new UsbKingFactory();
@Override
public float sell(int amount) throws Exception {
float price = factory.sell(3000);
//在单价之上,加入利润
price = price + 25;
return price;
}
}
4. 创建用户类User
客户端调用者,购买商品类
public class User {
public static void main(String[] args) {
float price = 0;
//购买u盘,通过代理商购买
TaoBao taoBao = new TaoBao();
try {
price = taoBao.sell(1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("通过淘宝的代理商购买的单价:"+ price);
}
}
5. 静态代理的缺点
- 代码复杂,难于管理
代理类和目标类实现了相同的接口, 每个代理都需要实现目标类的方法, 这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 - 代理类依赖目标类,代理类过多
代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。
二、JDK动态代理
动态代理是指代理类对象在程序运行时由 JVM
根据反射机制动态生成的。 动态代理不需要定义代理类的.java
源文件。通俗一点,动态代理其实就是 jdk
运行期间, 动态创建 .class
字节码并加载到 JVM
。
jdk
动态代理是基于 Java
的反射机制实现的。使用 jdk
中接口和类实现代理对象的动态创建。
jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。
从 jdk1.3
以来, java 语言通过 java.lang.reflect
包提供三个类支持代理模式 Proxy
, Method
和 InovcationHandler
。
1. 相关类
(1)InvocationHandler 接口
InvocationHandler
接口叫做调用处理器,负责完调用目标方法,并增强功能。
通 过 代 理 对 象 执 行 目 标 接 口 中 的 方 法 , 会 把 方 法 的 调 用 分 派 给 调 用 处 理 器(InvocationHandler
)的实现类,执行实现类中的 invoke()
方法,我们需要把功能代理写在 invoke()
方法中 。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
实现了 InvocationHandler
接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke()
,具体加强的代码逻辑就是定义在该方法中的。 通过代理对象执行接口中的方法时,会自动调用 invoke()
方法。invoke()
方法的介绍如下:
proxy
:代表生成的代理对象method
:代表目标方法args
:代表目标方法的参数
(2)Method 类
上面InvocationHandler
接口中invoke()
方法的第二个参数为 Method
类对象,该类有一个方法也叫 invoke()
(不要与接口中的invoke()
方法混淆),可以调用目标方法。这两个 invoke()
方法,虽然同名,但无关。
public Object invoke ( Object obj, Object... args)
obj
:表示目标对象args
:表示目标方法参数,就是InvocationHandler
接口中的invoke()
方法的第三个参数
该方法的作用是:调用执行obj
对象所属类的方法,这个方法由其调用者Method
对象确定。
(3)Proxy 类
通 过 JDK
的 java.lang.reflect.Proxy
类 实 现 动 态 代 理 , 会 使 用 其 静 态 方 法newProxyInstance()
,依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。
public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)
loader
:目标类的类加载器,通过目标对象的反射可获取interfaces
:目标类实现的接口数组,通过目标对象的反射可获取handler
: 调用处理器。
2. 新建一个接口,作为目标接口
jdk
动态代理是代理模式的一种实现方式,其只能代理接口。
public interface UsbSell {
float sell(int amount) throws Exception;
}
3. 为接口创建一个实现类,是目标类
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) throws Exception {
System.out.println("我是实现的目标方法UsbKingFactory");
if(amount >= 3000){
price = 80;
} else if( amount >=1000){
price= 85;
} else {
throw new Exception("至少购买1000个");
}
return price;
}
}
4. 创建类实现 java.lang.reflect.InvocationHandler
接口
创建类实现 java.lang.reflect.InvocationHandler
接口,调用目标方法并增加其他功能代码
细节:
- 创建一个私有的目标类对象(要增强的那个对象),通过构造方法传入目标对象
- 接口的
invoke()
方法中调用method.invoke(target,args)
实际上就是调用了目标类的方法 - 注意:
method.invoke(target,args)
方法中的rarget
不能传递proxy
public class MyInvocationHandler implements InvocationHandler {
//目标类,即要增强的目标对象
private Object target = null;
//通过构造方法传入目标对象
public MyInvocationHandler(Object target) {
this.target = target;
}
//实现代理对象要完成的功能,主要是:1.执行目标方法, 2.实现功能增强(利润)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("InvocationHandler中的invoke()");
Object ret = null;
System.out.println("在目标方法之前,记录日志,方法的执行时间等功能");
//注意这里的第一个参数是target,而不是proxy,如果是proxy就成了递归调用
ret = method.invoke(target, 3000); //UsbKingFactory.sell(3000)
System.out.println("在目标方法执行之后,增加的文件的处理功能");
// 修改返回ret的值,增加利润
if( ret != null){
Float f = (Float)ret;
ret = f + 25.0F;
}
return ret;
}
}
5. 创建动态代理对象Proxy
要点:
- 用下面的代码把生成动态代理对象的
class
文件保存到磁盘上,位置在项目根目录下System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
完整代码:
public class ProxyApplication {
public static void main(String[] args) throws Exception {
//把生成动态代理对象的class文件保存到磁盘上,位置在项目根目录下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//创建动态代理对象
//1.创建目标对象
UsbKingFactory factory = new UsbKingFactory();
//UsbSanFactory factory = new UsbSanFactory();
//2.创建调用处理器对象
InvocationHandler handler = new MyInvocationHandler(factory);
//3.使用jdk中的方法创建 代理对象 (内存中的,通过参数保存到本地磁盘),类名为:com.sun.proxy.$Proxy0
UsbSell taobao = (UsbSell) Proxy.newProxyInstance(
factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
// 通过代理对象执行业务方法,会执行handler对象中的invoke()
float price = taobao.sell(1); // super.h.invoke(this, m3, new Object[]{var1});
System.out.println("通过taobao的代理商购买的价格:"+price);
}
}
6. 生成的代理类对象
将保存到本地的代理类对象进行反编译,这里只选出了其中的一段代码,该代理类对象还重写了hashCode()/toString()/equals()
这三个方法
private static Method m3;
public final double sale(Integer var1) throws Exception {
try {
//这里的h是指InvocationHandler接口的实现类
return (Double)super.h.invoke(this, m3, new Object[]{var1});
} catch (Exception | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
m3 = Class.forName("com.bjpowernode.SaleUsb").getMethod("sale", Class.forName("java.lang.Integer"));
}
三、CGLIB动态代理
JDK动态代理是通过接口来生成代理对象,所以要求目标类必须实现了接口,而CGLIB是为没有实现接口的目标类来生成代理对象的,可以理解为通过继承实现
1. 创建目标类(父类)
注意这里没有实现接口
//工厂类,没有实现接口
public class UsbKingFactory {
public double sale(Integer count) throws Exception {
if (count > 3000) {
return 70;
} else if (count > 1000) {
return 90;
} else {
throw new Exception("小于1000不卖!!");
}
}
}
2. 创建代理对象
//CGLIB代理的原理是:子类增强父类,即CGLI会生成目标类的子类对象,来增强目标类
public class CGLIBProxyFactory implements MethodInterceptor {
//目标对象
private UsbKingFactory target;
public CGLIBProxyFactory(UsbKingFactory target) {
this.target = target;
}
//创建一个方法,该方法用于生成代理对象,返回值类型为父类类型(多态)
public UsbKingFactory myProxyCreator() {
//创建一个增强器对象
Enhancer enhancer = new Enhancer();
//指定要增强的目标类,即父类
enhancer.setSuperclass(UsbKingFactory.class);
//回调方法
enhancer.setCallback(this);
//生成器生成子类对象,即代理对象
return (UsbKingFactory) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用父类的方法
Double price = (Double) method.invoke(target, 3000);
//新增的功能
if (price != null) {
price += 30;
}
return price;
}
}
3. 用户类
public class User {
public static void main(String[] args) throws Exception {
//生成的代理对象在内存中,让jvm保存到磁盘上,位置为第二个参数的位置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
UsbKingFactory usbKingFactory = new UsbKingFactory();
//生成的代理对象是UsbKingFactory的子类,通过多态用父类类型来接收
UsbKingFactory taobaoProxy = new CGLIBProxyFactory(usbKingFactory).myProxyCreator();
double price = taobaoProxy.sale(1);
System.out.println(price);
}
}
4. 生成的代理对象
除了实现了父类的方法外,还实现了Object类的finalize()/equals()/toString()/hashCode()/clone()
方法
部分代码:
//继承了UsbKingFactory类
public class UsbKingFactory$$EnhancerByCGLIB$$4c3ae42b extends UsbKingFactory implements Factory {
//上面第二步创建的对象
private MethodInterceptor CGLIB$CALLBACK_0;
static{
CGLIB$sale$0$Method = ReflectUtils.findMethods(new String[]{"sale", "(Ljava/lang/Integer;)D"}, (var1 = Class.forName("com.bjpowernode.UsbKingFactory")).getDeclaredMethods())[0];
CGLIB$sale$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Integer;)D", "sale", "CGLIB$sale$0");
}
//实现在父类的sale方法
public final double sale(Integer var1) throws Exception {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
//调用了第二步对象中的intercept方法
//CGLIB$sale$0$Method为通过反射获取的父类中的sale方法
Object var2 = var10000.intercept(this, CGLIB$sale$0$Method, new Object[]{var1}, CGLIB$sale$0$Proxy);
return var2 == null ? 0.0D : ((Number)var2).doubleValue();
} else {
return super.sale(var1);
}
}
}
四、JDK动态代理实现自动生成接口的实现类
1. 创建一个接口
public interface HelloService {
void sayHello(String name);
}
2. 生成代理类
public class Test {
public static void main(String[] args) {
HelloService helloService = (HelloService) Proxy.newProxyInstance(
HelloService.class.getClassLoader(),
//不能这么写HelloService.class.getInterfaces(),
new Class[]{HelloService.class}, //注意这里的写法,将这个接口转为数组
((proxy, method, args1) -> {
System.out.println("Hello " + Arrays.toString(args1));
return null;
})
);
helloService.sayHello("World");
}
}