15 代理模式
15.1 代理模式简介
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处
是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。 - 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、理 动态代理 (JDK 代理、接口代理)和 Cglib 理 代理 (可以在内存
动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
代理模式UML类图:
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
15.2 静态代理
15.2.1 静态代理基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
15.2.2 静态代理案例实现
需求:
模拟用户购买iPhone手机的例子。
用户买iPhone手机,去专卖店购买。
具体步骤:
- 定义手机的规则,不能你说是手机就是手机。
/**
* 接口的出现是为了定义规则用的
*/
public interface Phone {
//打电话
public void call();
//玩游戏
public void play();
//拍照片
public void photo();
}
- 定义商店的规则,不可能谁都可以卖iPhone,要进行授权
public interface Store {
public Phone selliPhone();
}
- 具体手机实现类,iPhone
/**
* 这个类用来描述手机
* 就是自己的一些描述
* 这些是电话的一些功能描述----方法
*/
public class iPhone implements Phone{
//描述属性
private String name;
private String color;
//构造方法
public iPhone(){}
public iPhone(String name,String color){
this.name=name;
this.color=color;
}
//打电话
public void call(){
System.out.println("真的iPhone 打电话很流畅");
}
//玩游戏
public void play(){
System.out.println("真的iPhone 玩游戏不卡顿");
}
//拍照片
public void photo(){
System.out.println("真的iPhone 拍照片很清晰");
}
}
- 具体iPhone专卖店
/**
* 这个类是苹果专卖店
* 理解为苹果的工厂-----苹果专卖店销售
* 前店后厂
*/
public class AppleStore implements Store{
//这是一个工厂店-----手机 关系 依赖
//依赖关系体现 一个类的方法中使用到了另一个类的对象(传参数 方法内创建)
//生产真机
// 做事情之前是否需要提供什么条件---参数
// 做事情之后是否需要留下什么结果---返回值
private iPhone productPhone(){//可以理解为这个方法是工厂店自己的
//创建一台手机
iPhone iPhone = new iPhone("iPhone7","black");
//将生出来的手机返回出去
return iPhone;
}
//卖手机
// 做事情之前是否需要条件---参数
// 做事情之后是否需要结果---返回值
public Phone selliPhone(){
//找自己的工厂生产一台手机
iPhone iPhone = this.productPhone();
//将工厂方法生产的手机返回出去
return iPhone;
}
}
- 提供具体代理类帮我们干活,苹果专卖店可能太多人了,我找人帮我卖
- 代理类不仅能去专卖店拿手机卖而且还能造假货。
/**
* 可以理解为这是一个代理商-------在中间转手一下
*
* 代理模式
*/
public class ProxyStore implements Store{
//聚合关系 代理店--工厂店
private AppleStore store = new AppleStore();
//如果想要造假 自己在山沟沟里造一个山寨工厂
private lPhone productlPhone(){
lPhone lPhone = new lPhone();
return lPhone;
}
//代购 代理------卖手机
public Phone selliPhone(){//可以在真正用户不知情的情况下修改执行的过程
//1.有一台手机???? 真机 找工厂店
Phone iPhone = this.store.selliPhone();
//2.有一个山寨工厂 山寨手机
Phone lPhone = this.productlPhone();
//3.手机卖出去
// return iPhone;
return iPhone;
}
}
- 客户端测试
public class TestMain {
public static void main(String[] args) {
//1.有一个代理店
Store store = new ProxyStore();
//2.代理店卖手机
Phone iPhone = store.selliPhone();
//3.用户使用
iPhone.call();
iPhone.play();
iPhone.photo();
}
}
测试结果:
真的iPhone 打电话很流畅
真的iPhone 玩游戏不卡顿
真的iPhone 拍照片很清晰
15.2.3 静态代理的优缺点
1、主要优点
- 被代理对象只要和代理类实现了同一接口即可,代理类无须知道被代理对象具体是什么类、怎么做的,而客户端只需知道代理即可,实现了类之间的解耦合。
2、主要缺点
- 代理类和被代理类实现了相同的接口,代理类通过被代理类实现了相同的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
- 每个代理类只能为一个接口服务,如果程序开发中要使用代理的接口很多的话,必然会产生许多的代理类,造成类膨胀。
15.3 JDK动态代理
15.3.1 jdk动态代理基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是由java内部的反射机制来实现的。
15.3.2 jdk动态代理案例实现
需求:
同样是用户进行手机购买,但是此时是去代工厂购买,代工厂卖各种手机
实现步骤:
- 定义手机规则
/**
* 这个类用来描述一个手机
* 有一些功能(方法)
* 打电话 玩游戏 拍照片
*/
public interface Phone {
//描述一下手机的功能
void call();
void play();
void photo();
}
- 手机具体实现类
// iPhone手机
public class iPhone implements Phone {
@Override
public void call() {
System.out.println("iPhone12打电话真好用");
}
@Override
public void play() {
System.out.println("iPhone12玩王者真好用");
}
@Override
public void photo() {
System.out.println("iPhone12照相真好用");
}
}
// 华为手机
public class Mate40 implements Phone {
@Override
public void call() {
System.out.println("华为Mate40打电话带劲");
}
@Override
public void play() {
System.out.println("华为Mate40打吃鸡带劲");
}
@Override
public void photo() {
System.out.println("华为Mate40照相带劲");
}
}
- 定义生产线规则
// 华为生产线
public interface HuaweiStore {
Mate40 salePhone();
}
// 苹果生产线
public interface AppleStore{
// 苹果专卖店只负责卖,找代工厂负责生产
iPhone salePhone();
}
- 创建代理工厂对象
public class ProxyStore {
//描述一个方法 通过Proxy这个类获取一个代理对象(代工商店)
// 1.方法是一件事情 做事情之前是否需要条件---参数 代理谁? AppleStore
// 2.方法是一件事情 做事情之后是否需要结果---返回值 代理对象--利用泛型
public static <T> T getStore(Class clazz) {
// //分析创建proxy代理对象需要的条件
// //0.类加载器---目的是把需要代理的AppleStore这个类加载进来
ClassLoader classLoader = clazz.getClassLoader();
// //1.代理谁 AppleStore(传递的参数clazz) 下面第二个问号需要数组
Class[] classes = new Class[]{clazz};
// //2.代理他之后 具体该做什么事情---原来真正店做的事情 卖手机
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// //内部类中方法的三个参数含义
// //1.proxy代理对象 2.method代理的那个方法 3.args代理方法传递的参数
// 想让这个工厂根据传递的参数,代工不同的产品
// 1、获取需要被代工的类
Class clazz = method.getDeclaringClass();
// 2、判断clazz从而执行不同的代工
if (clazz == AppleStore.class) {
return new iPhone();
} else if (clazz == HuaweiStore.class) {
return new Mate40();
} else {
return null;
}
}
};
// //1.通过Proxy类帮我们创建一个代理对象
T proxy = (T) Proxy.newProxyInstance(classLoader, classes, handler);
// //2.将这个proxy代理对象返回
return proxy;
}
}
- 客户端测试
public class TestMain {
//动态代理
// 可以理解为是一个代工厂
// 富士康公司(代工 什么品牌都代工 好多生产线 做苹果 做华硕 做索尼)
// 可以理解为 可以出了苹果之外还能卖别的品牌手机
// 由于这个对象什么都可以做 我们不自己创建---找人帮忙(创建一个这个代理商店)
// Proxy类 Java给我们提供的 Math String ArrayList
// Proxy类可以帮我们创建一个代理对象(这个对象就是动态代理的对象 什么都能做)
public static void main(String[] args) {
//1.需要有一个店----可以找代理店(代理店不是new 是通过自己写的类方法创建出来)
AppleStore store = ProxyStore.getStore(AppleStore.class);
// HuaweiStore store = ProxyStore.getStore(HuaweiStore.class);
//2.商店做事情----卖手机
Phone phone = store.salePhone();
// Phone phone = store.saleiPhone();
//3.拿去使用
phone.call();
phone.play();
phone.photo();
}
}
测试结果:
iPhone12打电话真好用
iPhone12玩王者真好用
iPhone12照相真好用
注意:
这里我们如果想要华为手机,只需要HuaweiStore store = ProxyStore.getStore(HuaweiStore.class);
15.4 Cglib动态代理
15.4.1 Cglib动态代理简介
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个 单独的对象,并没 有实
现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理 - Cglib代理也叫作 子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代
理归属到动态代理。 - Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的
框架使用,例如 SpringAOP,实现方法拦截 - 在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程
低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代
理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
15.4.2 Cglib动态代理实现步骤
- 需要引入cglib的jar文件
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
代码实现:
- 明星类
public class Star {
private String name;
public Star() {
}
public Star(String name) {
this.name = name;
}
public void Sing(String singName) {
System.out.println(singName + ": 唱歌中...");
}
public void Dance(String name) {
System.out.println(name + "跳舞中...");
}
final public void rap(String name) {
System.out.println(name + "说唱中...");
}
}
- 代理类
public class CglibProxyFactory<T> implements MethodInterceptor {
//目标对象
private T target;
public CglibProxyFactory(T target) {
this.target = target;
}
public Object getProxyInstance() {
//工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(target.getClass());
设置enhancer的回调对象,实现了MethodInterceptor接口的类的对象
enhancer.setCallback(this);
//创建子类代理对象
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("表演前签合同1~");
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("表演后收取费用2~");
return obj;
}
}
- 客户端测试
public class Client {
public static void main(String[] args) {
Star star = new Star();
CglibProxyFactory<Star> proxyFactory = new CglibProxyFactory<Star>(star);
Star proxyInstance = (Star) proxyFactory.getProxyInstance();
proxyInstance.Dance("刘德华");
proxyInstance.Sing("忘情水");
proxyInstance.rap("刘德华");
}
}
测试结果:
表演前签合同1~
刘德华跳舞中...
表演后收取费用2~
表演前签合同1~
忘情水: 唱歌中...
表演后收取费用2~
表演前签合同1~
刘德华说唱中...
表演后收取费用2~
小结:
- CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。
- 所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
- 同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。