秒懂 Java 的三种代理模式
前言
代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。
代理模式大致有三种角色:
Real Subject
:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;Proxy
:代理类,将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;Subject
:定义 RealSubject 和 Proxy 角色都应该实现的接口。
代理模式有三种类型,静态代理,动态代理(JDK代理,接口代理)、Cglib代理(在内存中动态的创建目标对象的子类)
正文
静态代理
静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
。
可以看见,代理类无非是在调用委托类方法的前后增加了一些操作。委托类的不同,也就导致代理类的不同。
某公司生产电视机,在当地销售需要找到一个代理销售商。那么客户需要购买电视机的时候,就直接通过代理商购买就可以。
代码示例:
电视机:
public class TV {
private String name;//名称
private String address;//生产地
public TV(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "TV{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
创建公司接口:
public interface TVCompany {
/**
* 生产电视机
* @return 电视机
*/
public TV produceTV();
}
公司的工厂生产电视机:
public class TVFactory implements TVCompany {
@Override
public TV produceTV() {
System.out.println("TV factory produce TV...");
return new TV("小米电视机","合肥");
}
}
代理商去下单拿货(静态代理类):
public class TVProxy implements TVCompany{
private TVCompany tvCompany;
public TVProxy(){
}
@Override
public TV produceTV() {
System.out.println("TV proxy get order .... ");
System.out.println("TV proxy start produce .... ");
if(Objects.isNull(tvCompany)){
System.out.println("machine proxy find factory .... ");
tvCompany = new TVFactory();
}
return tvCompany.produceTV();
}
}
消费者通过代理商拿货(代理类的使用):
public class TVConsumer {
public static void main(String[] args) {
TVProxy tvProxy = new TVProxy();
TV tv = tvProxy.produceTV();
System.out.println(tv);
}
}
输出结果:
TV proxy get order ....
TV proxy start produce ....
machine proxy find factory ....
TV factory produce TV...
TV{name='小米电视机', address='合肥'}
Process finished with exit code 0
小结:
优点:
静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
缺点:
静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
动态代理
动态代理具有如下特点:
JDK动态代理对象不需要实现接口,只有目标对象需要实现接口。
实现基于接口的动态代理需要利用JDK中的API,在JVM内存中动态的构建
Proxy对象
。需要使用到
java.lang.reflect.Proxy
,和其newProxyInstance
方法,但是该方法需要接收三个参数。
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的。Class<?>[] interfaces
:目标对象实现的接口的类型,使用泛型方式确认类型。InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
有一天公司增加了业务,出售的商品越来越多,售后也需要更上。但是公司发现原来的代理商,还要再培训才能完成全部的业务,于是就找了另外的
动态代理商B
。代理商B
承诺无缝对接公司所有的业务,不管新增什么业务,均不需要额外的培训即可完成。
代码示例:
公司增加了维修业务:
public interface TVCompany {
/**
* 生产电视机
* @return 电视机
*/
public TV produceTV();
/**
* 维修电视机
* @param tv 电视机
* @return 电视机
*/
public TV repair(TV tv);
}
工厂也得把维修业务搞起来:
public class TVFactory implements TVCompany {
@Override
public TV produceTV() {
System.out.println("TV factory produce TV...");
return new TV("小米电视机","合肥");
}
@Override
public TV repair(TV tv) {
System.out.println("tv is repair finished...");
return new TV("小米电视机","合肥");
}
}
B代理商 全面代理公司所有的业务。使用Proxy.newProxyInstance
方法生成代理对象,实现InvocationHandler
中的 invoke
方法,在invoke
方法中通过反射调用代理类的方法,并提供增强方法。
public class TVProxyFactory {
private Object target;
public TVProxyFactory(Object o){
this.target = o;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("TV proxy find factory for tv.... ");
Object invoke = method.invoke(target, args);
return invoke;
}
});
}
}
购买、维修这两个业务 B代理
就可以直接搞定了。后面公司再增加业务,B代理也可以一样搞定。
public class TVConsumer {
public static void main(String[] args) {
TVCompany target = new TVFactory();
TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
TV tv = tvCompany.produceTV();
tvCompany.repair(tv);
}
}
输出结果:
TV proxy find factory for tv....
TV factory produce TV...
TV proxy find factory for tv....
tv is repair finished...
Process finished with exit code 0
小结:
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
动态代理的方式中,所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。
JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类,并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。
怎么解决这个问题呢?我们可以用 CGLIB 动态代理机制。
Cglib代理
静态代理和JDK代理都需要某个对象实现一个接口,有时候代理对象只是一个单独对象,此时可以使用Cglib代理。
Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。
C代理商不仅想代理公司,而且还想代理多个工厂的产品。
Cglib通过Enhancer
来生成代理类,通过实现MethodInterceptor
接口,并实现其中的intercept
方法,在此方法中可以添加增强方法,并可以利用反射Method
或者MethodProxy
继承类 来调用原方法。
看到
B代理商
承接了公司(接口)的多种业务,那么此时C代理商
又从中发现新的商机, B 只能代理某个公司的产品,而我不仅想要代理公司产品,而且对接不同的工厂,拿货渠道更广,赚钱更爽快。于是Cglib就用上了。
代码示例:
public class TVProxyCglib implements MethodInterceptor {
//给目标对象创建一个代理对象
public Object getProxyInstance(Class c){
//1.工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(c);
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类(代理对象)
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("TVProxyFactory enhancement.....");
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
}
新代理的B工厂
public class TVFactoryB {
public TV produceTVB() {
System.out.println("tv factory B producing tv.... ");
return new TV("华为电视机", "南京");
}
public TV repairB(TV tv) {
System.out.println("tv B is repair finished.... ");
return tv;
}
}
C代理
可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。
public class TVConsumer {
public static void main(String[] args) {
TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
TV tv = tvCompany.produceTV();
tvCompany.repair(tv);
System.out.println("==============================");
TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
TV tv = tvFactoryB.produceTVB();
tvFactoryB.repairB(tv);
}
}
输出结果:
TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv....
TVProxyFactory enhancement.....
tv B is repair finished....
Process finished with exit code 0
Spring中AOP使用代理
Spring中AOP的实现有JDK和Cglib两种,如下图:
如果目标对象需要实现接口,则使用JDK代理。
如果目标对象不需要实现接口,则使用Cglib代理。
总结
静态代理
:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能。JDK动态代理
:需要代理类实现某个接口,使用Proxy.newProxyInstance
方法生成代理类,并实现InvocationHandler
中的invoke
方法,实现增强功能。Cglib动态代理
:无需代理类实现接口,使用Cblib
中的Enhancer
来生成代理对象子类,并实现MethodInterceptor
中的intercept
方法,在此方法中可以实现增强功能。
最后
我是一个正在被打击还在努力前进的码农。如果文章对你有帮助,记得点赞、关注哟,谢谢!
秒懂 Java 的三种代理模式的更多相关文章
- Java的三种代理模式
Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...
- Java的三种代理模式简述
本文着重讲述三种代理模式在java代码中如何写出,为保证文章的针对性,暂且不讨论底层实现原理,具体的原理将在下一篇博文中讲述. 代理模式是什么 代理模式是一种设计模式,简单说即是在不改变源码的情况下, ...
- 理解java的三种代理模式
代理模式是什么 代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展. 比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing(). 1 public class ...
- Java的三种代理模式(Spring动态代理对象)
Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...
- Java的三种代理模式&完整源码分析
Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...
- 转!!Java的三种代理模式
转自 http://www.cnblogs.com/cenyu/p/6289209.html 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象 ...
- Java中三种代理模式
代理模式 代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能. 这就 ...
- Java的三种代理模式(Proxy,CGLib)
1.静态代理,这种不用说最不靠谱.每个类一个代理,代码量很大. 2.JDK代理.使用java.lang.reflect.Proxy进行代理,但是被代理的类必须要实现接口. 3.Cglib代理.不用实现 ...
- Java的三种代理模式:静态代理/JDK动态代理/Cglib动态代理
1.静态代理:需要定义接口或者父类,目标对象与代理对象均实现同一接口或继承同一父类. 2.JDK动态代理:需要目标对象实现一个接口,通过动态反射的机制,生成代理对象,实现同一个接口 3.Cglib动态 ...
随机推荐
- jvm调优的几种场景
假定你已经了解了运行时的数据区域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器. 一.cpu占用过高 cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束 ...
- 简单的Java面向对象程序
上一篇随笔Java静态方法和实例方法的区别以及this的用法,老师看了以后说我还是面向过程的编程,不是面向对象的编程,经过修改以后,整了一个面向对象的出来: /** * 3 延续任务2, 定义表示圆形 ...
- Python OpenCV图片转视频 工具贴(三)
Python OpenCV图片转视频 粘贴即用,注意使用时最好把自己的文件按照数字顺序命名.按照引导输入操作. # 一键傻瓜式引导图片串成视频 # 注意使用前最好把文件命名为数字顺序格式 import ...
- java笔试题(二)
1.写出一维数组初始化的两种方式 int[] arr={1,2,3}; String[] str=new String[2]; str[1]="23"; 2.写出二维数组初始化的两 ...
- 这应该是把Java内存区域讲的最清楚的一篇文章
基本问题: 介绍下 Java 内存区域(运行时数据区) Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么) 对象的访问定位的两种方式(句柄和直接指针两种方式) 拓展问题: ...
- 「模拟8.13」任(liu_runda的神题,性质分析)
考场时没有发现性质,用了个前缀和优化暴力,结果写WA了 我们发现其实联通块的个数就是点的个数-边的个数 然后我们需要维护横向上和纵向上的边的前缀和 前缀和的查询形式稍改一下 暴力 1 #include ...
- 【codeforces841A】Generous Kefa
原题 A. Generous Kefatime limit per test:2 secondsmemory limit per test:256 megabytes input:standard i ...
- Scala语言笔记 - 第二篇
目录 1 Map的基础操作 2 Map生成view和transform解析 最近研究了下scala语言,这个语言最强大的就是它强大的函数式编程(Function Programming)能力,记录 ...
- hive学习笔记之三:内部表和外部表
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- python用random模块模拟抽奖逻辑(print修改end参数使打印结果不分行)
import random #引入random模块,运用random函数list_one=["10081","10082","10083" ...