代理模式
1、代理模式
代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。
例如:
原来的访问关系:
通过代理的访问关系:
有 A,B,C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许A 类调用其方法,但 B 类可以调用 C 类的方法。A 类通过 B 类调用 C 类的方法。这里 B 是 C的代理。 A 通过代理 B 访问 C。
1.1、代理模式的作用
1.1.1、控制访问
代理类不让客户类访问目标类。例如商家(代理类)不让用户(客户类)访问厂家(目标类)。
1.1.2、增强功能
在原有功能的基础上,增加额外的功能。 新增加的功能,叫做增强功能。
1.2、代理模式的分类
可以将代理模式分为静态代理和动态代理。
2、静态代理
静态代理是指,代理类在程序运行前就已经定义好.java 源文件,其与目标类的关系在程序运行前就已经确立。在程序运行前代理类已经编译为.class 文件。
2.1、静态代理的使用
(1)定义目标接口
package com.scut.service;
// 表示功能的,厂家,商家都要完成的功能
public interface UsbSell {
//定义方法 参数 amount:表示一次购买的数量,暂时不用
//返回值表示一个u盘的价格。
float sell(int amount);
}
(2)定义目标类
package com.scut.factory;
import com.scut.service.UsbSell;
//目标类: 闪迪厂家, 不接受用户的单独购买。
public class UsbSanFactory implements UsbSell {
@Override
public float sell(int amount) {
System.out.println("闪迪 目标类中的方法调用 , UsbSanFactory 中的sell ");
//一个128G的u盘是 85元。
//后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75
return 85.0f;
}
}
(3)定义代理类
package com.scut.shangjian;
import com.scut.factory.UsbKingFactory;
import com.scut.factory.UsbSanFactory;
import com.scut.service.UsbSell;
//taobao是一个商家
public class TaoBaoSanDi implements UsbSell {
//声明商家代理的厂家具体是谁
private UsbSanFactory factory = new UsbSanFactory();
@Override
//实现销售u盘功能
public float sell(int amount) {
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(amount);
price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。
//在目标类的方法调用后
System.out.println("淘宝商家,给你返一个优惠券,或者红包");
//增加的价格
return price;
}
}
(4)定义调用类
package com.scut;
import com.scut.shangjian.TaoBao;
import com.scut.shangjian.WeiShang;
public class ShopMain {
public static void main(String[] args) {
//创建代理类对象
TaoBaoSanDi taoBao = new TaoBaoSanDi();
//调用代理类对象的方法
float price = taoBao.sell(1);
System.out.println("购买u盘单价:"+price);
}
}
2.2、静态代理的缺点
(1)代码复杂,难于管理
代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
(2)代理类依赖目标类,代理类过多
代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。
3、动态代理
动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。动态代理其实就是JDK运行期间,动态创建 class 字节码并加载到 JVM。动态代理的实现方式常用的有两种:使用 JDK 代理代理,与通过 CGLIB 动态代理。
3.1、JDK动态代理
JDK动态代理是基于 Java的反射机制实现的。使用JDK中接口和类实现代理对象的动态创建。JDK 的动态要求目标对象必须实现接口,这是 Java设计上的要求。 从 JDK1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method和 InovcationHandler。
3.1.1、JDK动态代理的使用
(1)定义目标接口
package com.scut.service;
//目标接口
public interface UsbSell {
float sell(int amount);
void print();
}
(2)定义目标类
package com.scut.factory;
import com.scut.service.UsbSell;
//目标类
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
//目标方法
System.out.println("目标类中,执行sell目标方法");
return 85.0f;
}
@Override
public void print() {
}
}
(3)定义InvocationHandler接口实现类
package com.scut.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//必须实现InvocationHandler接口,完成代理类要做的功能(1.调用目标方法,2.功能增强)
public class MySellHandler implements InvocationHandler {
private Object target = null;
//动态代理:目标对象是活动的,不是固定的,需要传入进来。
//传入是谁,就给谁创建代理。
public MySellHandler(Object target) {
//给目标对象赋值
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
//float price = factory.sell(amount); //厂家的价格。
res = method.invoke(target,args); //执行目标方法
//商家 需要加价, 也就是代理要增加价格。
//price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。
if( res != null ){
Float price = (Float)res;
price = price + 25;
res = price;
}
//在目标类的方法调用后,你做的其它功能,都是增强的意思。
System.out.println("淘宝商家,给你返一个优惠券,或者红包");
//记录数据库
//增加的价格
return res;
}
}
(4)创建动态代理对象
package com.scut;
import com.scut.factory.UsbKingFactory;
import com.scut.handler.MySellHandler;
import com.scut.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MainShop {
public static void main(String[] args) {
//创建代理对象,使用Proxy
//1. 创建目标对象
// UsbKingFacotry factory = new UsbKingFactory();
UsbSell factory = new UsbKingFactory();
//2.创建InvocationHandler调用处理器对象
InvocationHandler handler = new MySellHandler(factory);
//3.创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
//com.sun.proxy.$Proxy0 : 这是jdk动态代理创建的对象类型。
System.out.println("proxy:"+proxy.getClass().getName());
//4.通过代理执行方法
float price = proxy.sell(1);
System.out.println("通过动态代理对象,调用方法:"+price);
}
}
3.2、CDLIB动态代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。
使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。
CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。
CGLIB 经常被应用在框架中,例如 Spring ,Hibernate 等。CGLIB 的代理效率高于 JDK。对于CGLIB 一般的开发中并不使用。简单了解即可。