系列文章目录
创建型模式 - 单例模式(一)
创建型模式 - 工厂模式(二)
创建型模式 - 原型模式(三)
创建型模式 - 建造者模式(四)
结构型模式 - 适配器模式(一)
结构型模式 - 桥接模式(二)
结构型模式 - 装饰器模式(三)
结构型模式 - 组合模式(四)
结构型模式 - 外观模式(五)
结构型模式 - 享元模式(六)
结构型模式 - 代理模式(七)
行为型模式 - 模板方法模式(一)
行为型模式 - 命令模式(二)
行为型模式 - 访问者模式(三)
行为型模式 - 迭代器模式(四)
行为型模式 - 观察者模式(五)
行为型模式 - 中介者模式(六)
行为型模式 - 备忘录模式(七)
行为型模式 - 解释器模式(八)
行为型模式 - 状态模式(九)
行为型模式 - 策略模式(十)
行为型模式 - 责任链模式(十一)
文章目录
前言
一、代理模式
1.1 代理模式介绍
- 代理模式:
- 由于某些原因需要给某些对象提供一个代理以控制对该对象的访问。这时,
访问对象不适合或不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
;- 被代理的对象可以是
远程对象
、创建开销大的对象
或需要安全控制的对象
;
1.2 代理模式结构
- 抽象主题(Subject)角色:
- 它声明了真实主题(Real Subject)和代理主题(Proxy)的共同接口,这样一来在任何使用真实主题(Real Subject)的地方都可以使用代理主题(Proxy),客户端通常需要针对抽象主题角色进行编程;
- 代理主题(Proxy)角色:
- 提供了与真实主题(Real Subject)角色相同的接口,其内部含有对真实主题(Real Subject)角色的引用,它可以访问、控制或扩展真实主题角色(Real Subject)的功能;
- 包含对真实主题(Real Subject)的引用,从而可以在任何时候操作真实主题(Real Subject)对象;
- 在代理角色中提供一个与真实主题(Real Subject)角色相同的接口,以便在任何时候都可以替代真实主题(Real Subject);
- 代理主题(Proxy)角色还可以控制对真实主题(Real Subject)的使用,负责在需要的时候创建和删除真实主题(Real Subject)对象,并对真实主题对象的使用加以约束;
- 真实主题(Real Subject)角色:
- 定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作;
1.3 代理分类
- 代理模式有不同的形式,主要有:
静态代理
;动态代理
(JDK代理、接口代理);Cglib代理
(可以在内存动态创建对象,而不需要实现接口,它是属于动态代理的范畴);
二、实现
例子:
- 张三: 李四啊,我直播打游戏很久了,打游戏打的想吐,你帮我直播几天吧?
- 李四:好,我试试。
- 李四登录了张三的直播号,开始了直播:大家好,我是李四,张三回家生猴子了,我代她直播两天。。
2.1 静态代理实现
- 首先创建一个接口(JDK代理都是面向接口),然后创建具体的实现类实现这一接口,再创建一个代理类同样实现这个接口;
- 具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层;
package com.dozezz.designpattern.proxy.staticproxy;
/**
* 抽象主题角色:被代理角色能干啥
*/
public interface ILiveTelecast {
void liveTelecast();
}
package com.dozezz.designpattern.proxy.staticproxy;
/**
* 代理主题角色
* 代理一般否是和被代理对象属于同一个接口
* 代理的东西不一样,每一种不同的被代理类都需要创建不同的代理类
*/
public class LiveTelecastProxy implements ILiveTelecast {
// 被代理对象
private ILiveTelecast liveTelecast;
public LiveTelecastProxy(ILiveTelecast liveTelecast) {
this.liveTelecast = liveTelecast;
}
@Override
public void liveTelecast() {
//可以增强功能
System.out.println("开始代理 完成某些操作。。。。。 ");
System.out.println("李四开始直播。。。");
// liveTelecast.liveTelecast();
System.out.println("结束代理 完成某些操作。。。。。 ");
}
}
package com.dozezz.designpattern.proxy.staticproxy;
/**
* 真实主题角色
*/
public class ZhangsanLiveTelecast implements ILiveTelecast{
@Override
public void liveTelecast() {
System.out.println("张三的直播生涯开始。。。");
}
}
package com.dozezz.designpattern.proxy;
import com.dozezz.designpattern.proxy.staticproxy.LiveTelecastProxy;
import com.dozezz.designpattern.proxy.staticproxy.ZhangsanLiveTelecast;
/**
* 代理测试类
*/
public class ClientTest {
public static void main(String[] args) {
LiveTelecastProxy liveTelecastProxy = new LiveTelecastProxy(new ZhangsanLiveTelecast());
liveTelecastProxy.liveTelecast();
}
}
优缺点:
- 在不修改目标对象的功能前提下,能够通过代理对象对目标功能扩展;
- 我们得为每一个服务都创建代理类,工作量大,不易管理。同时接口一旦发生改变,代理类也得相应修改;
2.2 JDK动态代理实现
- 在动态代理中我们不再需要手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建;
package com.dozezz.designpattern.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理对象
*/
public class DynamicProxyHandler<T> implements InvocationHandler {
// 被代理对象
private T targetObject;
public DynamicProxyHandler(T targetObject) {
this.targetObject = targetObject;
}
public static <T> T getProxyInstance(T t) {
/**
* 第一个参数指定当前目标对象使用的类加载器,获取加载器的方法固定
* 第二个参数是目标对象实现的接口类型,使用泛型方法确定类型【数组】
* 第三个参数事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入
*/
return (T) Proxy.newProxyInstance(
t.getClass().getClassLoader(),
t.getClass().getInterfaces(), //必须接口,不是接口会报类型转换异常,代理对象都创建不出来,可以试下
new DynamicProxyHandler(t));
}
/**
* 定义目标方法的拦截逻辑;每个方法都会进来的
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK 动态代理开始。。可以前置处理点东西");
// 反射执行
Object invoke = method.invoke(targetObject, args);
System.out.println("JDK 动态代理结束。。可以后置处理点东西");
return null;
}
}
- 动态代理只能代理一个接口么?尝试下
在这里插入图片描述
package com.dozezz.designpattern.proxy.dynamic;
/**
* 直播卖货
*/
public interface ISellLiveTelecast {
void sell();
}
package com.dozezz.designpattern.proxy.staticproxy;
import com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast;
/**
* 真实主题角色
*/
public class ZhangsanLiveTelecast implements ILiveTelecast, ISellLiveTelecast {
@Override
public void liveTelecast() {
System.out.println("张三的直播生涯开始。。。");
}
@Override
public void sell() {
System.out.println("张三直播卖货,只要998,让你爽到不能呼吸。。");
}
}
package com.dozezz.designpattern.proxy;
import com.dozezz.designpattern.proxy.dynamic.DynamicProxyHandler;
import com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.ILiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.LiveTelecastProxy;
import com.dozezz.designpattern.proxy.staticproxy.ZhangsanLiveTelecast;
import java.util.Arrays;
/**
* @Description: 代理测试类
* @Author: dozezz
* @Date: 2021/7/23 10:53
* @Version: 1.0
*/
public class ClientTest {
public static void main(String[] args) {
//本地生成展示代理类,项目根目录com.sun.proxy目录下,Jdk 1.8
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
ISellLiveTelecast proxyInstance1 = DynamicProxyHandler.getProxyInstance(new ZhangsanLiveTelecast());
proxyInstance1.sell();
System.out.println(Arrays.asList(proxyInstance1.getClass().getInterfaces()));
}
}
- 能不能代理被代理对象本类的方法?
package com.dozezz.designpattern.proxy.staticproxy;
import com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast;
/**
* 真实主题角色
*/
public class ZhangsanLiveTelecast implements ILiveTelecast, ISellLiveTelecast {
@Override
public void liveTelecast() {
System.out.println("张三的直播生涯开始。。。");
}
@Override
public void sell() {
System.out.println("张三直播卖货,只要998,让你爽到不能呼吸。。");
}
public void selfMethod() {
System.out.println("自己方法能代理吗?");
}
}
package com.dozezz.designpattern.proxy;
import com.dozezz.designpattern.proxy.dynamic.DynamicProxyHandler;
import com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.ILiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.LiveTelecastProxy;
import com.dozezz.designpattern.proxy.staticproxy.ZhangsanLiveTelecast;
import java.util.Arrays;
/**
* @Description: 代理测试类
* @Author: dozezz
* @Date: 2021/7/23 10:53
* @Version: 1.0
*/
public class ClientTest {
public static void main(String[] args) {
//本地生成展示代理类,项目根目录com.sun.proxy目录下,Jdk 1.8
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
ISellLiveTelecast proxyInstance1 = DynamicProxyHandler.getProxyInstance(new ZhangsanLiveTelecast());
proxyInstance1.sell();
System.out.println(Arrays.asList(proxyInstance1.getClass().getInterfaces()));
//能不能代理被代理对象本类的方法? proxy只能转换成接口类
ZhangsanLiveTelecast proxyInstance2 = DynamicProxyHandler.getProxyInstance(new ZhangsanLiveTelecast());
proxyInstance2.sell();
// [interface com.dozezz.designpattern.proxy.staticproxy.ILiveTelecast, interface com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast]
// Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.dozezz.designpattern.proxy.staticproxy.ZhangsanLiveTelecast
// at com.dozezz.designpattern.proxy.ClientTest.main(ClientTest.java:37)
}
}
优缺点:
- 虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度,但是它始终无法摆脱仅支持 Interface 约束;
- JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有借口的类,如何实现动态代理呢? CGLIB 代理
2.3 CGLIB 代理实现
- CGLIB采用了非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑;
- 使用继承,所以不能对 final 修饰的类进行代理‘
- JDK动态代理和CGLIB动态代理均是SpringAOP的基础;
package com.dozezz.designpattern.proxy.cglib;
public class ZhangsanLiveTelecast {
public void liveTelecast(){
System.out.println("张三开始了直播生涯。。");
}
}
package com.dozezz.designpattern.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 CglibProxy {
public static <T> T createProxy(T t){
// 创建一个增强器
Enhancer enhancer = new Enhancer();
// 设置要增强那个类的功能,增强器为这个了类创建一个子类
enhancer.setSuperclass(t.getClass());
// 设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o,
Method method,//为了获取到原方法的一些元数据信息
Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB 代理执行一些功能。。");
// 这样调用会炸的;
// method.invoke()
// methodProxy.invoke()
// 编写拦截逻辑,目标方法执行
Object invoke = methodProxy.invokeSuper(o ,objects);
return invoke;
}
});
return (T) enhancer.create();
}
}
package com.dozezz.designpattern.proxy;
import com.dozezz.designpattern.proxy.cglib.CglibProxy;
import com.dozezz.designpattern.proxy.dynamic.DynamicProxyHandler;
import com.dozezz.designpattern.proxy.dynamic.ISellLiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.ILiveTelecast;
import com.dozezz.designpattern.proxy.staticproxy.LiveTelecastProxy;
import com.dozezz.designpattern.proxy.staticproxy.ZhangsanLiveTelecast;
import net.sf.cglib.proxy.Enhancer;
import java.util.Arrays;
/**
* @Description: 代理测试类
* @Author: dozezz
* @Date: 2021/7/23 10:53
* @Version: 1.0
*/
public class ClientTest {
public static void main(String[] args) {
com.dozezz.designpattern.proxy.cglib.ZhangsanLiveTelecast proxy = CglibProxy.createProxy(new com.dozezz.designpattern.proxy.cglib.ZhangsanLiveTelecast());
proxy.liveTelecast();
}
}
优缺点:
- CGLIB 创建动态代理对象比JDK创建的动态代理对象性能更高,但是花费的时间要多些;
- 对于单例对象,因为无需频繁创建,用CGLIB合适,反之JDK更合适;
- CGLIB 采用动态创建子类的方法,对于 final 修饰的方法无法进行代理;
三、代理模式总结
3.1 代理模式应用场景
- 远程代理:这种方式通常为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问;
- 虚拟代理:创建的目标对象开销很大,例如下载一个很大的文件需要很长时间;
- 安全代理:控制不同种类客户对真实对象的访问权限;
- 智能指引:用于调用目标对象时,代理附加一些额外的处理功能;
- 延迟加载:为了提高系统的性能,延迟对目标的加载;
四、参考文献
- http://c.biancheng.net/view/1354.html
- https://www.bilibili.com/video/BV1G4411c7N4?p=54&spm_id_from=pageDriver
- https://baiyp.ren/JAVA%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-07%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F.html