1、什么是代理模式

Provide a surrogate or placeholder for another object to control access to it.

Proxy Pattern:为其他对象提供一种代理以控制对这个对象的访问。

说人话:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能,比如Spring AOP。

2、代理模式定义

①、Subject

抽象主题角色,可以是抽象类,可以是接口,是一个最普通的业务类定义,无特殊要求。

②、RealSubject

真实主题角色,也叫被代理角色,是业务逻辑的具体执行者。

③、Proxy

代理主题角色,也叫代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。

通用代码如下:

/**
* 抽象主题类
*/
public interface Subject {
void doSomething();
}
/**
* 真实主题角色
*/
public class RealSubject implements Subject{ @Override
public void doSomething() {
//TODO 具体执行的事
}
}
/**
* 代理主题角色
*/
public class Proxy implements Subject{
//要代理的具体实现类
private Subject realSubject; public Proxy(Subject realSubject){
this.realSubject = realSubject;
}
@Override
public void doSomething() {
this.before();
realSubject.doSomething();
this.after();
} // 预处理
private void before(){
// TODO
} // 善后处理
private void after(){
// TODO
}
}

3、代理模式的两种实现

比如用代理模式实现统计某个接口的耗时。

3.1 静态代理

①、基于接口编程

抽象主题类:

public interface IUserController {
// 登录
String login(String username,String password);
// 注册
String register(String username,String password);
}

具体主题类:

public class UserController implements IUserController{
@Override
public String login(String username, String password) {
// TODO 登录逻辑
return null;
} @Override
public String register(String username, String password) {
// TODO 注册逻辑
return null;
}
}

代理主题类:

public class UserControllerProxy implements IUserController{
private IUserController userController; public UserControllerProxy(IUserController userController){
this.userController = userController;
}
@Override
public String login(String username, String password) {
long startTime = System.currentTimeMillis();
// 登录逻辑
userController.login("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return null;
} @Override
public String register(String username, String password) {
long startTime = System.currentTimeMillis();
// 注册逻辑
userController.register("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return null;
}
}

测试:

因为原始类 UserController 和代理类 UserControllerProxy 实现相同的接口,是基于接口而非实现编程,将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码

public class StaticProxyTest {
public static void main(String[] args) {
IUserController userController = new UserControllerProxy(new UserController());
userController.login("username","password");
userController.register("username","password");
}
}

在上面的代码中,代理类和具体主题类需要实现相同的接口,假如具体主题类没有实现接口,并且不是我们开发维护的(比如来自第三方接口),我们要统计这个第三方接口的耗时,那应该如何实现代理模式呢?

②、基于继承

继承具体主题类,然后扩展其方法即可,直接看代码。

public class UserControllerProxy extends UserController {
@Override
public String login(String username, String password) {
long startTime = System.currentTimeMillis();
// 登录逻辑
super.login("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return null;
} @Override
public String register(String username, String password) {
long startTime = System.currentTimeMillis();
// 注册逻辑
super.register("username","password");
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return null;
}
}

3.2 动态代理

在上面的例子中,有两个问题:

①、我们需要在代理类中,将具体主题类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑,如果方法很多,重复代码也会很多。

②、如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

那该如何解决上面的问题呢?答案就是动态代理(Dynamic Proxy)。

动态代理:不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

JDK动态代理:

public class DynamicProxyHandler implements InvocationHandler {

    private Object target;
public DynamicProxyHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(this.target, args);
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return result;
}
}

测试:

public class DynamicProxyTest {
public static void main(String[] args) {
// 1、创建具体主题类
IUserController userController = new UserController();
// 2、创建 Handler
DynamicProxyHandler proxyHandler = new DynamicProxyHandler(userController);
// 3、动态产生代理类
IUserController o = (IUserController)Proxy.newProxyInstance(userController.getClass().getClassLoader(),
userController.getClass().getInterfaces(),
proxyHandler);
o.login("username","password");
o.register("username","password");
}
}

这是 JDK 动态代理,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,代理对象是在程序运行时产生的,而不是编译期,要求是具体主题类必须实现接口。

另外一种方式是 Cglib 动态代理。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,也就是通过修改字节码生成子类来处理。

Cglib 动态代理:

public class UserController{
public String login(String username, String password) {
// TODO 登录逻辑
System.out.println("登录");
return null;
} public String register(String username, String password) {
// TODO 注册逻辑
System.out.println("注册");
return null;
}
}

注意:真实主题类是没有实现接口的。

public class CglibDynamicProxy implements MethodInterceptor {
private Object target; public CglibDynamicProxy(Object target){
this.target = target;
} // 给目标创建代理对象
public Object newProxyInstance(){
// 1.工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3.设置回调函数
enhancer.setCallback(this);
// 4.创建子类(代理对象)
return enhancer.create();
} @Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(this.target, args);
long endTime = System.currentTimeMillis();
long responseTime = endTime - startTime;
System.out.println("接口响应时间:"+responseTime);
return result;
}
}

测试:

public class CglibDynamicProxyTest {
public static void main(String[] args) {
UserController userController = new UserController();
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(userController);
UserController o = (UserController)cglibDynamicProxy.newProxyInstance();
o.login("username","password");
o.register("username","password");
}
}

4、代理模式优点

①、职责清晰

真实的角色就是实现实际的业务逻辑, 不用关心其他非本职责的事务, 通过后期的代理完成一件事务, 附带的结果就是编程简洁清晰。

②、高扩展性

具体主题角色是随时都会发生变化的, 只要它实现了接口, 甭管它如何变化,代理类完全都可以在不做任何修改的情况下使用。

5、代理模式应用场景

①、业务系统的非功能性需求开发

这是最常用的一个场景。比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。

典型例子就是 SpringAOP。

②、RPC

RPC(远程代理) 框架也可以看作一种代理模式,通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

Java设计模式之(五)——代理模式的更多相关文章

  1. java设计模式之Proxy(代理模式)

    java设计模式之Proxy(代理模式) 2008-03-25 20:30 227人阅读 评论(0) 收藏 举报 设计模式javaauthorizationpermissionsstringclass ...

  2. Java设计模式之《代理模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6525527.html 代理模式算是我接触较早的模式,代理就是中介,中间人.法律上也有代理, ...

  3. 重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不 ...

  4. Java设计模式学习记录-代理模式

    代理模式 代理模式是常见设计模式的一种,代理模式的定义是:为其他对象提供一种代理以控制对这个对象的访问. 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起 ...

  5. Java设计模式:Proxy(代理)模式

    概念定义 代理模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式. 使用代理模式的原因有: 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象, ...

  6. java设计模式-----11、代理模式

    Proxy模式又叫做代理模式,是构造型的设计模式之一,它可以为其他对象提供一种代理(Proxy)以控制对这个对象的访问. 所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代 ...

  7. 《Java设计模式》之代理模式 -Java动态代理(InvocationHandler) -简单实现

    如题 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式可细分为如下, 本文不做多余解释 远程代理 虚拟代理 缓冲代理 保护代理 借鉴文章 ht ...

  8. Java设计模式—Proxy动态代理模式

    代理:设计模式 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 图 1. 代 ...

  9. Java设计模式9:代理模式

    代理模式 代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式的结构 有些情况下,一个客户不想活着不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间 ...

  10. 阿里架构师浅析Java设计模式之虚拟代理模式

    虚拟代理模式(Virtual Proxy)是一种节省内存的技术,它建议创建那些占用大量内存或处理复杂的对象时,把创建这类对象推迟到使用它的时候.在特定的应用中,不同部分的功能由不同的对象组成,应用启动 ...

随机推荐

  1. nginx负载均衡部署

    1 系统版本 CentOS Linux release 6.0.1708 (Core) 2 编译安装前所需要的准备: 1.GCC编译器 首先检查GCC是否安装,命令:gcc -v ,如果显示有相关版本 ...

  2. SudokuSolver 2.0:用C++实现的数独解题程序 【一】

    SudokuSolver 2.0 实现效果 H:\Read\num\Release>sudoku.exe Order please: Sudoku Solver 2.0 2021/10/2 by ...

  3. iNeuOS工业互联网操作系统,设备振动状态监测、预警和分析应用案例

    目       录 1.      概述... 2 2.      系统部署结构... 2 3.      系统应用介绍... 4 4.      专业分析人员... 8 5.      应用案例分享 ...

  4. GAN实战笔记——第一章GAN简介

    GAN简介 一.什么是GAN GAN是一类由两个同时训练的模型组成的机器学习技术:一个是生成器,训练其生成伪数据:另一个是鉴别器,训练其从真实数据中识别伪数据. 生成(generative)一词预示着 ...

  5. Dapr-服务调用

    前言 上一篇对Dapr进行了了解,并搭建了Dapr环境.接下来就对Dapr的各个构建块类型的了解.应用实际案例. 一.服务调用: 在许多具有多个需要相互通信的服务的环境中,都会面临着很多问题. 如: ...

  6. Redis:学习笔记-01

    Redis:学习笔记-01 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 1. Redis入门 2.1 ...

  7. 是兄弟就来摸鱼 Scrum Meeting 博客汇总

    是兄弟就来摸鱼 Scrum Meeting 博客汇总 一.Alpha阶段 第一次Scrum meeting 第二次Scrum meeting 第三次Scrum meeting 第四次Scrum mee ...

  8. Spring Security Resource Server的使用

    Spring Security Resource Server的使用 一.背景 二.需求 三.分析 四.资源服务器认证流程 五.实现资源服务器 1.引入jar包 2.资源服务器配置 3.资源 六.测试 ...

  9. linux系统上国际化失败

    文章目录 一.需求: 二.出现的问题 三.代码结构 1.配置文件中的配置 2.java代码中的使用 四.解决方案 一.需求: 最近项目中有这么一个需求,当用户当前的语言环境是 中文时,导出的 exce ...

  10. Noip模拟10 2021.6.27

    T1 入阵曲 好了,又一个考试败笔题. 也就是在那个时候,小 F 学会了矩阵乘法.让两个矩阵乘几次就能算出斐波那契数, 真是奇妙无比呢. 不过, 小 F 现在可不想手算矩阵乘法--他觉得好麻烦.取而代 ...