Java设计模式:Proxy(代理)模式
概念定义
代理模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。
使用代理模式的原因有:
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理对象可以在客户类和委托对象之间起到中介的作用(代理类和委托类实现相同的接口)。以现实生活为例,经纪人就是明星的代理,外界可以通过联系经纪人来间接与明星沟通。
- 开放封闭原则:可以通过给代理类增加额外的功能来扩展委托类的功能,这样只需要修改代理类而不需要再修改委托类,符合开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。使用代理模式,可以在调用委托类业务功能的前后加入一些公共的服务(例如鉴权、计时、缓存、日志、事务处理等),甚至修改委托类的业务功能。
代理可以分为静态代理和动态代理,前者更接近代理模式的本质。
- 静态代理是由程序员编写代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就已确定。
- 动态代理是代理类的源码是在程序运行期间由编译器动态的生成(如JVM根据反射等机制生成代理类)。代理类和委托类的关系在程序运行时确定。
应用场景
- 需要修改或屏蔽一个或若干类的部分功能,复用另外一部分功能,可使用静态代理。
- 需要拦截一批类中的某些方法,在方法的前后插入一些公共的操作,可使用动态代理。
示例代码
本节根据场景和实现的不同,依次介绍静态代理、JDK动态代理、Cglib动态代理(也称子类代理)以及Spring AOP代理。
静态代理
静态代理:在程序运行前就已经存在代理类的字节码文件。
示例代码如下:
// 共同接口 //
public interface ISubject {
void doAction();
void byebye();
}
// 真实对象(委托类) //
public class RealSubject implements ISubject {
@Override
public void doAction() { System.out.println("Real Action Here!"); }
@Override
public void byebye() { System.out.println("Wave goodbye!"); }
}
// 代理对象(代理类) //
public class SubjectProxy implements ISubject {
private ISubject subject;
public SubjectProxy() {
// RealSubject实例可根据环境变量、配置等创建不同类型的实例(多态)
subject = new RealSubject(); // 此处仅简单地new实例
}
@Override
public void doAction() {
System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
subject.doAction();
System.out.println("doWhatever end <<"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
}
@Override
public void byebye() {
System.out.println("Say goodbye"); // 改变委托类行为(例如实现数据库连接池时避免close关闭连接)
}
}
// 验证代码 //
public class StaticProxyDemo {
public static void main(String[] args) {
SubjectProxy subject = new SubjectProxy();
subject.doAction();
subject.byebye();
}
}
执行结果如下:
>> doWhatever start
Real Action Here!
doWhatever end <<
Say goodbye
静态代理的特点如下:
- 使用静态代理时,通常客户类不需要感知RealSubject。
- 静态代理的缺点:代理对象需要与目标对象实现一样的接口,因此接口较多时需要定义和维护大量的代理类代码。
- 与适配器的差异:适配器通常考虑改变接口形态,而代理则不会也不能改变接口形态。
- 与装饰器的差异:被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象则由客户端创建并传给装饰对象,可以层层嵌套,层层装饰。代理模式常用于控制被代理对象的访问,而装饰模式是增加被装饰者的功能。
JDK动态代理
JDK动态代理所用到的代理对象,在程序运行阶段调用到代理类对象时才由JVM真正创建。JVM根据传进来的业务实现类对象及方法名,在内存中动态地创建一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。具体而言,每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现。当使用者调用代理对象所代理的接口中的方法时,这个调用的信息会被传递给InvocationHandler的invoke方法。
示例代码如下:
// 业务接口 //
public interface ISubject {
void doAction();
}
// 业务实现类 //
public class RealSubject implements ISubject {
@Override
public void doAction() { System.out.println("Real Action Here!"); }
}
public class RealSubject2 implements ISubject {
@Override
public void doAction() { System.out.println("Real Action2 Here!"); }
}
// 动态代理类 //
public class SubjectJdkProxyHandler implements InvocationHandler {
private Object realSubject;
public SubjectJdkProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
Object result = method.invoke(realSubject, args); // 执行目标对象方法
System.out.println("doWhatever end <<"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
return result;
}
}
// 验证代码 //
public class JdkProxyDemo {
public static void main(String[] args) {
ISubject subject = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {ISubject.class}, // 或RealSubject.class.getInterfaces()
new SubjectJdkProxyHandler(new RealSubject())); // RealSubject必须实现Subject接口,否则无法强转后调用业务方法
subject.doAction();
// 使用同一个SubjectProxyHandler类,可代理不同的类型
ISubject subject2 = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {ISubject.class}, new SubjectJdkProxyHandler(new RealSubject2())); // 可使用工厂模式创建代理对象
subject2.doAction();
}
}
执行结果如下:
>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action2 Here!
doWhatever end <<
JDK动态代理的特点如下:
- 通过实现InvocationHandler接口完成代理逻辑。
- 通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
- 代理类必须实现接口。
Cglib动态代理
Cglib(Code Generation Library)是个功能强大、高性能、开源的代码生成包,它可以为没有实现接口的类提供代理。具体而言,Cglib继承被代理的类,覆写其业务方法来实现代理。因为采用继承机制,所以不能对final修饰的类进行代理。
示例代码如下:
// 业务实现类 //
public class RealSubject {
public void doAction() { System.out.println("Real Action Here!"); }
}
// 动态代理类(实现方法拦截器接口) //
// MethodInterceptor接口来自net.sf.cglib.proxy.MethodInterceptor或org.springframework.cglib.proxy.MethodInterceptor
public class SubjectMethodInterceptor implements MethodInterceptor {
public Object createCglibProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer(); // 创建增强器,用来创建动态代理类
enhancer.setSuperclass(targetClass); // 为增强器指定要代理的业务类,即为生成的代理类指定父类
enhancer.setCallback(this); // 设置回调(对于代理类上所有方法的调用,都会调用CallBack)
return enhancer.create(); // 创建动态代理类对象并返回
// 以上语句可简化为:return Enhancer.create(targetClass, this); //
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("doWhatever end <<"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
return result;
}
}
// 验证代码 //
public class CglibProxyDemo {
// 还可使用CGLib + JDK InvocationHandler接口实现动态代理
public static Object createCglibProxyWithHandler(Class<?> targetClass) {
MethodInterceptor interceptor = null;
try {
InvocationHandler invocationHandler = new SubjectJdkProxyHandler(targetClass.newInstance());
interceptor = (Object o, Method method, Object[] objects,
MethodProxy methodProxy) -> invocationHandler.invoke(o, method, objects);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return Enhancer.create(targetClass, interceptor);
}
public static void main(String[] args) {
RealSubject subject = (RealSubject) new SubjectMethodInterceptor().createCglibProxy(RealSubject.class);
subject.doAction();
RealSubject subject2 = (RealSubject) createCglibProxyWithHandler(RealSubject.class);
subject2.doAction();
}
}
执行结果如下:
>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action Here!
doWhatever end <<
Cglib动态代理的特点如下:
- 需要引入Cglib的jar文件,但Spring的核心包内已包含Cglib功能,所以直接引入spring-core-xxx.jar即可。
- Cglib动态代理虽然不需要接口信息,但是它拦截并包装被代理类的所有方法。
- 委托类不能为final,否则报错java.lang.IllegalArgumentException: Cannot subclass final class xxx。
- 不会拦截委托类中无法重载的final/static方法,而是跳过此类方法只代理其他方法。
Spring AOP动态代理
示例代码如下:
public interface ISubject {
void doAction();
}
public class RealSubject implements ISubject {
@Override
public void doAction() { System.out.println("Real Action Here!"); }
}
public class SubjectSpringAopInvoker implements MethodInterceptor { // 来自org.aopalliance.intercept.MethodInterceptor
private RealSubject target;
public SubjectSpringAopInvoker(RealSubject realSubject) {
this.target = realSubject;
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
Object result = methodInvocation.getMethod().invoke(this.target, methodInvocation.getArguments());
System.out.println("doWhatever end <<"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
return result;
}
}
public class SpringAopProxyDemo {
public static void main(String[] args) {
ISubject proxy = ProxyFactory.getProxy(ISubject.class, new SubjectSpringAopInvoker(new RealSubject()));
proxy.doAction();
}
}
执行结果如下:
>> doWhatever start
Real Action Here!
doWhatever end <<
关于Spring实现动态代理的详细介绍,可参考《Spring学习总结(二)——静态代理、JDK与CGLIB动态代理、AOP+IoC》一文。
业界实践
- org.apache.ibatis.binding.MapperProxy(mybatis-3.4.6.jar)
- org.springframework.aop.framework.ProxyFactoryBean
Java设计模式:Proxy(代理)模式的更多相关文章
- Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景
我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...
- java设计模式6——代理模式
java设计模式6--代理模式 1.代理模式介绍: 1.1.为什么要学习代理模式?因为这就是Spring Aop的底层!(SpringAop 和 SpringMvc) 1.2.代理模式的分类: 静态代 ...
- 夜话JAVA设计模式之代理模式(Proxy)
代理模式定义:为另一个对象提供一个替身或者占位符以控制对这个对象的访问.---<Head First 设计模式> 代理模式换句话说就是给某一个对象创建一个代理对象,由这个代理对象控制对原对 ...
- java设计模式之八代理模式(Proxy)
其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你 ...
- Java设计模式:代理模式(转)
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一 ...
- C++设计模式-Proxy代理模式
Proxy代理模式 作用:为其他对象提供一种代理以控制对这个对象的访问. 代理的种类: 如果按照使用目的来划分,代理有以下几种: 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代 ...
- Java设计模式之-------->"代理模式"
01.什么是代理模式? 解析:代理(Proxy):代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不想或者不能直接引用另一个对象, 而代理对象可以在客户端和目标 ...
- Java设计模式之代理模式(Proxy)
前言: 最近在研究Retrofit开源框架的时候,其主要核心代码是通过注解标示参数,动态代理模式实现具体接口,反射机制进行参数解析,最终实现发送请求.其实之前在学习Xutils源码的时候,Xutils ...
- Java 设计模式_代理模式(2016-08-19)
概念: 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 就是一个人或者机构代表另一个人或者机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一 ...
- JAVA设计模式:代理模式&& 装饰模式区别
在前面学习了代理模式和装饰模式后,发现对两者之间有时候会混淆,因此对两者进行了区别和理解: 装饰模式你可以这样理解,就像糖一样,卖的时候商家大多要在外面包一层糖纸,其实原本还是糖. public in ...
随机推荐
- PC上装VM上装虚拟机
1.虚拟机网卡选择桥接模式 2.查看本PC机的网络 3.到/etc/sysconfig/network-scripts,修改网卡,vi ifcfg-ens33 4.重新/etc/init.d/netw ...
- jvaa之初始化块
1.初始化块的作用:对java对象进行初始化: 2.程序的执行顺序:声明成员变量的默认值-->显示初始化,多个初始化块依次被执行(同级别下按先后顺序执行)-->构造器在对成员进行赋值操作. ...
- 【RTOS】基于V7开发板的uCOS-III,uCOS-II,RTX4,RTX5,FreeRTOS原版和带CMSIS-RTOS V2封装层版全部集齐
RTOS模板制作好后,后面堆各种中间件就方便了. 1.基于V7开发板的最新版uCOS-II V2.92.16程序模板,含MDK和IAR,支持uC/Probe https://www.cnblogs.c ...
- 关于java中三种初始化块的执行顺序
许多小伙伴对于java中的三种初始化块的执行顺序一直感到头疼,接下来我们就来分析一下这三种初始化块到底是怎么运行的.有些公司也会将这个问题作为笔试题目. 下面通过一段代码来看看创建对象时这么初始化块是 ...
- Linux下shell脚本实现mongodb定时自动备份
MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功 ...
- 关于 Sublime Text 3 中 input 无法输入的问题
在新装的 Sublime Text 3 中,Ctrl + B 运行时会发现无法正常输入,我记录了一下我的解决方法. 相关原文可参照 https://blog.csdn.net/weixin_42116 ...
- Comprehensive Tutorial 综合教程(MainDemo应用程序)
Follow this tutorial to create a simple application used to store contacts and other related objects ...
- vue-cli 3.0按需引入element-ui
vue-cli 3.0脚手架初始化项目成功后,如果需要按需引入element-ui,按照官网的教程,会报如下错误: 这个是由于babel编译插件版本问题,需要安装最新的babel编译插件“@babel ...
- Dynamics CRM - js中用webapi基于fetchxml查询遇到的问题 -- Invalid URI: The Uri scheme is too long.
最近用WebApi做基于Fetchxml的查询的时候,遇到一个很蛋疼的报错:Invalid URI: The Uri scheme is too long. 检查了整个URL,也没发现有什么问题. - ...
- 设置API:wx.openSetting,wx.getSetting使用说明(示例:地图授权与取消授权后的重新授权)
这个API解决了过去一个长久以来无法解决的问题,如何让用户重复授权: 打开小程序的设置界面:就是主动调取授权 目前资料极少,但是已经可以让大家先看看了: 官方文档地址:https://mp.weixi ...