代理模式(Proxy Pattern)

代理模式(Proxy Pattern)
概述
代理是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
分类
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
应用实例
- Windows 里面的快捷方式
- 猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类
- 买火车票不一定在火车站买,也可以去代售点
- 代购
- 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制
- 信用卡是银行账户的代理,银行账户则是一大捆现金的代理。它们都实现了同样的接口,均可用于进行支付。消费者会非常满意,因为不必随身携带大量现金;商店老板同样会十分高兴,因为交易收入能以电子化的方式进入商店的银行账户中,无需担心存款时出现现金丢失或被抢劫的情况
- Spring AOP
优点
-
职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
-
高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
-
智能化
基本结构

- 服务接口(Service Interface)声明了服务接口。代理必须遵循该接口才能伪装成服务对象。
- 服务(Service)类提供了一些实用的业务逻辑。
- 代理(Proxy)类包含一个指向服务对象的引用成员变量。代理完成其任务(例如延迟初始化、记录日志、访问控制和缓存等)后会将请求传递给服务对象。通常情况下,代理会对其服务对象的整个生命周期进行管理。
- 客户端(Client) 能通过同一接口与服务或代理进行交互,所以你可在一切需要服务对象的代码中使用代理。
静态代理【重点】
概述
静态代理是指预先确定了代理与被代理者的关系,即代理类与被代理类的依赖关系在编译期间就确定了。
静态代理又分为强制代理和普通代理。普通代理的要求就是客户端只能访问代理角色,而不能访问真实角色;强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。
案例一
描述
永强的公司老板突然在发工资的前一天带着小姨子跑路了,可怜的永强一身房贷,被迫提起劳动仲裁,劳动局就会为其指派一位代理律师全权负责永强的仲裁事宜。那这里面就是使用了代理模式,因为在劳动仲裁这个活动中,代理律师会全权代理永强。
案例代码
Interface
// 诉讼接口
public interface LawSuit {
// 提起诉讼
void submit();
// 法庭辩护
void defend();
}
Service
// 诉讼当事人(原告)
public class Parties implements LawSuit {
@Override
public void submit() {
System.out.println("老板欠薪跑路,证据确凿!!!");
}
@Override
public void defend() {
System.out.println("铁证如山,欠债还钱");
}
}
Proxy
// 代理律师类,全权负责案件的代理
public class Lawyer implements LawSuit {
// 包含一个被代理的对象
private final LawSuit lawSuit;
// 通过构造方法传递被代理对象
public Lawyer(LawSuit lawSuit) {
this.lawSuit = lawSuit;
}
@Override
public void submit() {
// 搜集证据
this.gatherEvidence();
// 提起诉讼
this.lawSuit.submit();
}
@Override
public void defend() {
// 法庭辩护
this.lawSuit.defend();
// 交换意见
this.compareNotes();
}
// 搜集证据(属于增强的方法)
private void gatherEvidence() {
System.out.println("搜集证据");
}
// 交换意见(属于增强的方法)
private void compareNotes() {
System.out.println("与审判长、审判员交换意见,被告如果不服,代其上诉或申诉");
}
}
Client
// 开庭
public class Court {
@Test
public void testLawSuit() {
// 当事人安排上
Parties parties = new Parties();
// 当事人的代理律师安排上
Lawyer lawyer = new Lawyer(parties);
// 代理律师进行提起诉讼以及法庭辩护
lawyer.submit();
lawyer.defend();
}
}
案例二
描述
玉田是个游戏死忠粉,每天最大的乐趣就是在游戏里大杀四方,但是游戏打时间长了,腰酸背痛、眼睛干涩、手臂酸麻,等等,也就是网络成瘾综合症都出来了。玉田想玩游戏,但又不想碰触到游戏中的烦恼,如何解决呢?有办法,现在游戏代练的公司非常多,于是玉田把自己的账号交给代练人员,由他们去帮着升级,去打怪,非常好的想法。游戏代练者,它也不能有作弊的方法(开挂),也只能手动打怪,这个时候,玉田的代练者就上着他的号,扮演着他的角色在游戏里继续大杀四方。
案例代码
Interface
// 游戏接口
public interface GamePlay {
// 登录
void login();
// 打怪
void killBoss();
// 升级
void upgrade();
}
Service
// 游戏玩家(被代理对象)
public class GamePlayer implements GamePlay {
// 用户名
private final String username;
// 通过构造方法进行传递参数
public GamePlayer(String username) {
this.username = username;
}
// 登录
@Override
public void login() {
System.out.println(username + "登录成功>_<");
}
// 打怪
@Override
public void killBoss() {
System.out.println(username + "正在打怪...");
}
// 升级
@Override
public void upgrade() {
System.out.println(username + "已升级!!!");
}
}
Proxy
// 代练,也要实现游戏接口
public class GamePlayProxy implements GamePlay {
// 包含了一个游戏玩家
private final GamePlay gamePlayer;
// 通过构造方法对其进行赋值
public GamePlayProxy(GamePlayer gamePlay) {
this.gamePlayer = gamePlay;
}
// 登录
@Override
public void login() {
this.gamePlayer.login();
}
// 打怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
// 升级
@Override
public void upgrade() {
this.gamePlayer.killBoss();
// 计算时间
this.count();
}
// 计算时间
public void count() {
System.out.println("本次共花费了150金币");
}
}
Client
// 游戏开始
public class Client {
@Test
public void playByProxy() {
// 网瘾少年上号
GamePlayer player = new GamePlayer("易烊千玺");
// 代练上号
GamePlayProxy proxy = new GamePlayProxy(player);
// 执行方法
proxy.login();
proxy.killBoss();
proxy.upgrade();
}
}
案例三
描述
刘英是个刚毕业的大学生,找到一份离家较远的工作,没办法回家住,于是她决定在单位附近15分钟路程的小区租房住,但是苦于人生地不熟,马上就要上班,自己在附近并没有熟人可以给她提供房源,于是她打开了手机上的58同城...
案例代码
Interface
// 租房接口
public interface Rent {
// 租房
void rent();
}
Service
// 房东(被代理对象)
public class Lord implements Rent {
// 租房
@Override
public void rent() {
System.out.println("房东租房");
}
}
Proxy
// 中介
public class Agent implements Rent {
// 包含一个房东对象
private final Lord lord;
// 通过构造方法进行传参
public Agent(Lord lord) {
this.lord = lord;
}
// 租房
@Override
public void rent() {
lord.rent();
}
}
Client
// 租客
public class Renter {
@Test
public void rent() {
// 房东安排上
Lord lord = new Lord();
// 中介安排上
Agent agent = new Agent(lord);
// 中介出租了方法
agent.rent();
}
}
动态代理
动态代理本质上仍然是代理,只是代理与被代理人的关系是动态确定的。映射到编程领域为这个关系是在运行时确定的。
JDK 动态代理
概述
自 Java 1.3 以后,Java 提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了 Spring 的很多地方。JDK 动态代理主要涉及 java.lang.reflect 包下边的两个类:Proxy 和 InvocationHandler。其中,InvocationHandler 是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。
【注意】要实现JDK动态代理的首要条件是:被代理类必须实现一个接口
原理
JDK 动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个 InvocationHandler,该 InvocationHandler 包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。
核心 API
// 由代理对象的调用处理程序实现的接口,在代理对象上调用方法时,该方法调用将被编码并分派到其调用处理程序的invoke方法。
interface InvocationHandler {
// 处理代理对象上的方法调用并返回结果
// 参数:
// proxy ——> 代理对象
// method --> 被代理类要执行的方法
// args --> 被代理类要执行的方法的参数
// 返回值:被代理类要执行的方法的返回值
public Object invoke(Object proxy, Method method, Object[] args);
}
// 提供了用于创建动态代理类和实例的静态方法
class Proxy {
// 返回指定接口的代理类对象,该对象将方法调用分派到指定的调用处理程序
// 参数:
// loader ——> 被代理类的类加载器
// interfaces --> 被代理类要实现的所有接口
// h --> 将方法调用分派给指定的执行处理器
// 返回值:代理对象
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
}
案例一
Interface
// 诉讼接口
public interface LawSuit {
// 提起诉讼
void submit();
// 法庭辩护
void defend();
}
Service
// 诉讼当事人(原告)
public class Parties implements LawSuit {
@Override
public void submit() {
System.out.println("老板欠薪跑路,证据确凿!!!");
}
@Override
public void defend() {
System.out.println("铁证如山,欠债还钱");
}
}
InvocationHandler
// 律师事务所,实现JDK动态代理的核心接口InvocationHandler
public class LawOffice implements InvocationHandler {
// 包含一个动态的被代理对象(当事人)
private final Object target;
// 通过构造方法进行传参
public LawOffice(Object obj) {
this.target = obj;
}
/**
* 此方法用于代理对象调用被代理对象的操作
*
* @param proxy 代理对象(律师)
* @param method 被代理对象的方法
* @param args 被代理对象的方法所需要的参数
* @return 被代理对象的方法执行后的返回值
* @throws Throwable 非法参数异常、安全异常、空指针异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取被代理对象方法的方法名
System.out.println("案情进展:" + method.getName());
// 执行被代理对象的方法
return method.invoke(target, args);
}
}
Client
// 开庭
public class Court {
@Test
public void testLawSuit() {
// 安排一个当事人(被代理对象)
Parties parties = new Parties();
// 安排一个律师(代理对象)
InvocationHandler handler = new LawOffice(parties);
// 根据被代理对象的类加载器、类的所有对象以及InvocationHandler接口实现类来获取代理对象(代理律师)
LawSuit lawSuit = (LawSuit) Proxy.newProxyInstance(parties.getClass().getClassLoader(), parties.getClass().getInterfaces(), handler);
// 代理对象执行方法
lawSuit.submit();
lawSuit.defend();
}
}
案例二
Interface
// 游戏接口
public interface GamePlay {
// 登录
void login();
// 打怪
void killBoss();
// 升级
void upgrade();
}
Service
// 游戏玩家(被代理对象)
public class GamePlayer implements GamePlay {
// 用户名
private final String username;
// 通过构造方法进行传递参数
public GamePlayer(String username) {
this.username = username;
}
// 登录
@Override
public void login() {
System.out.println(username + "登录成功>_<");
}
// 打怪
@Override
public void killBoss() {
System.out.println(username + "正在打怪...");
}
// 升级
@Override
public void upgrade() {
System.out.println(username + "已升级!!!");
}
}
InvocationHandler
// 游戏工作室
public class GameOffice implements InvocationHandler {
// 声明一个被代理对象
private final Object object;
// 通过构造方法进行传递参数
public GameOffice(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被代理对象的方法之前执行
if (method.getName().equals("login")) {
System.out.println("当前用户正在登录");
}
// 执行被代理对象的方法
Object result = method.invoke(object, args);
// 被代理对象的方法之后执行
if (method.getName().equals("upgrade")) {
System.out.println("耗时3天3夜,花费300金币升了一级");
}
return result;
}
}
Client
// 游戏开始
public class Client {
@Test
public void play() {
// 网瘾少年出场
GamePlayer gamePlayer = new GamePlayer("易烊千玺");
// 安排一个游戏工作室
InvocationHandler handler = new GameOffice(gamePlayer);
// 获取一个游戏代练
GamePlay gamePlay = (GamePlay) Proxy.newProxyInstance(gamePlayer.getClass().getClassLoader(), gamePlayer.getClass().getInterfaces(), handler);
// 执行对应的方法
gamePlay.login();
gamePlay.killBoss();
gamePlay.upgrade();
}
}
案例三
Interface
// 租房接口
public interface Rent {
// 租房
void rent();
}
Service
// 房东(被代理对象)
public class Lord implements Rent {
// 租房
@Override
public void rent() {
System.out.println("房东租房");
}
}
InvocationHandler
// 中介公司
public class Agent implements InvocationHandler {
// 包含一个被代理的对象
private final Object object;
// 通过构造方法进行赋值
public Agent(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介看房");
// 执行被代理对象的方法
Object result = method.invoke(object, args);
System.out.println("签合同");
return result;
}
}
Client
// 租客租房
public class Renter {
@Test
public void rent() {
// 房东安排上
Lord lord = new Lord();
// 中介公司安排上
InvocationHandler handler = new Agent(lord);
// 获取代理对象
Rent rent = (Rent) Proxy.newProxyInstance(lord.getClass().getClassLoader(), lord.getClass().getInterfaces(), handler);
// 执行方法
rent.rent();
}
}
CGLib 动态代理
概述
由于 JDK 只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以 cgLib 横空出世!CGLib(Code Generation Library)是一个强大、高性能的 Code 生成类库,它可以在程序运行期间动态扩展类或接口,AOP,测试,数据访问框架使用它来生成动态代理对象并拦截字段访问。它的底层是使用 java 字节码操作框架 ASM 实现。
原理
动态生成一个要代理类的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它在运行的时候比使用 Java 反射的 JDK 动态代理要快,但是在创建代理对象的时候要慢。
核心 API
// 方法增强回调器,用来拦截代理对象的方法并对其增强
interface MethodInterceptor extends Callback {
// 所有的代理对象都将执行此方法而不是原始方法
// 参数:
// obj --> 被代理对象
// method --> 被代理对象的方法
// args --> 被代理对象的方法参数
// proxy --> 代理对象
// 返回值:被代理对象的方法返回值
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
// 代理类
class MethodProxy {
// 调用相同类型且不同对象的原始方法,此方法可能会导致递归,进而引发栈内存溢出,慎用
Object invoke(Object obj, Object[] args)
// 调用指定对象的原始方法
Object invokeSuper(Object obj, Object[] args);
}
// 生成动态代理对象以启用方法拦截,该类允许代理除了实现接口外,扩展一个具体的基类
class Enhancer {
// 设置被代理类
// 参数:
// superclass --> 被代理类,必须具有可访问的构造方法
void setSuperclass(Class superclass);
// 设置被代理接口
void setInterfaces(Class[] interfaces);
// 设置回调函数
// 参数:
// callback:传入MethodInterceptor的接口实现类
void setCallback(final Callback callback);
// 根据无参构造创建一个代理对象
Object create();
// 根据有参构造创建一个代理对象
Object create(Class[] argumentTypes, Object[] arguments);
}
案例一
pom.xml
<dependencies>
<!--cglib动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
Service
// 被代理对象
public class Parties {
// 提起诉讼
public void submit() {
System.out.println("老板欠薪跑路,证据确凿!!!");
}
// 法庭辩护
public void defend() {
System.out.println("铁证如山,欠债还钱");
}
}
MethodInterceptor
// 律师事务所,实现CGLib动态代理的核心接口MethodInterceptor
public class LawOffice implements MethodInterceptor {
/**
* 所有生成的代理方法都调用此方法,而不是原始方法
*
* @param o 被代理对象
* @param method 被代理对象的方法
* @param objects 被代理对象的方法参数
* @param methodProxy 代理对象,用来调用未被拦截的方法,可多次调用
* @return 被代理对象的方法返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("案件进度:" + method.getName());
// 执行被代理对象中的方法
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
Client
// 开庭
public class Court {
@Test
public void test() {
// 安排一个当事人
Parties parties = new Parties();
// cgLib动态代理核心类,用于生成代理对象
Enhancer enhancer = new Enhancer();
// 指定被代理对象
enhancer.setSuperclass(parties.getClass());
// 设置回调拦截
enhancer.setCallback(new LawOffice());
// 创建代理对象
Parties lawyer = (Parties) enhancer.create();
// 执行方法
lawyer.submit();
lawyer.defend();
}
}
案例二
pom.xml
<dependencies>
<!--cglib动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
Service
// 游戏玩家(被代理对象)
public class GamePlayer implements GamePlay {
// 用户名
private final String username;
// 有参构造
public GamePlayer(String username) {
this.username = username;
}
// 登录
@Override
public void login() {
System.out.println(username + "登录成功>_<");
}
// 打怪
@Override
public void killBoss() {
System.out.println(username + "正在打怪...");
}
// 升级
@Override
public void upgrade() {
System.out.println(username + "已升级!!!");
}
}
MethodInterceptor
// 代练工作室
public class GameOffice implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 被代理对象的方法执行前
if (method.getName().equals("login")) {
System.out.println("代练正在登录...");
}
// 执行被代理对象的方法
Object result = proxy.invokeSuper(obj, args);
// 被代理对象的方法执行后
if (method.getName().equals("upgrade")) {
System.out.println("此次升级花费3天3夜...");
}
return result;
}
}
Client
// 游戏开始
public class Client {
@Test
public void play() {
// 网瘾少年上线
GamePlayer gamePlayer = new GamePlayer("易烊千玺");
// 准备CGLib的核心类对象
Enhancer enhancer = new Enhancer();
// 设置被代理对象
enhancer.setSuperclass(gamePlayer.getClass());
// 设置回调
enhancer.setCallback(new GameOffice());
// 根据有参方法创建对象
GamePlayer player = (GamePlayer) enhancer.create(new Class[]{String.class}, new Object[]{"易烊千玺"});
// 执行方法
player.login();
player.killBoss();
player.upgrade();
}
}
案例三
pom.xml
<dependencies>
<!--cglib动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
Service
// 房东(被代理对象)
public class Lord implements Rent {
// 租房
@Override
public void rent() {
System.out.println("房东租房");
}
}
MethodInterceptor
// 中介公司
public class Agent implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("带租客看房");
// 执行被代理对象的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("签合同");
return result;
}
}
Client
// 租客
public class Rent {
@Test
public void rent() {
// 房东安排上
Lord lord = new Lord();
// 获取CGLib核心类对象
Enhancer enhancer = new Enhancer();
// 设置被代理对象
enhancer.setSuperclass(lord.getClass());
// 设置回调
enhancer.setCallback(new Agent());
// 获取代理对象
Lord proxy = (Lord) enhancer.create();
// 租房
proxy.rent();
}
}
案例场景
概述
模拟实现 mybatis 中代理类生成部分,即 Mybatis 的使用中只需要定义接口不需要写实现类就可以完成增删改查操作。
案例代码
自定义注解
// 自定义注解
@Documented
// 作用域
@Retention(RetentionPolicy.RUNTIME)
// 作用位置
@Target({ElementType.METHOD})
public @interface Select {
// sql语句
String value() default "";
}
InvocationHandler
// 自定义InvocationHandler接口实现类
public class Handler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法上的注解
Select select = method.getAnnotation(Select.class);
// 展示
System.out.println("sql:" + select.value().replace("#{id}", args[0].toString()));
// 连接数据库获取受影响的行数
int affectedRows = 1;
return affectedRows;
}
}
代理工厂类
// 代理工厂类
public class MapperFactoryBean<T> {
// 声明传入的接口类型(被代理接口)
private final Class<T> mapperInterface;
// 通过构造方法进行赋值
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 获取代理对象
public T getObject() {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, new Handler());
}
}
dao 层接口
// dao层接口
public interface UserDao {
@Select("select name from user where id = #{id}")
int findById(String id);
}
测试类
public class TestUserDao {
@Test
public void test() {
// 获取mapper工厂
MapperFactoryBean<UserDao> factoryBean = new MapperFactoryBean<>(UserDao.class);
// 获取代理对象
UserDao userDao = factoryBean.getObject();
// 执行sql语句获取受影响的行数
int affectedRows = userDao.findById("1"); // sql:select name from user where id = 1
System.out.println("受影响的行数:" + affectedRows);
}
}更多推荐
所有评论(0)