Java | 动态代理及作用
作者:Mars酱
声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。
转载:欢迎转载,转载前先请联系我!
什么是代理
代理实际上是一种处理问题的方式。在现实世界中,你登录不同的社交app去撩漂亮小姐姐,经过长时间的努力和你撩力值的提升,最终有一位小姐姐的心被你捕获,也终于有天,你们决定线下见面完成线下###活动。于是,你俩一次又一次的完成线下###活动。久而久之,你和小姐姐产生了深厚的情感,你俩觉得很舒服很开心,应该持续下去,于是她决定成为你的女友,一起奋(tong)斗(ju)。既然要奋(tong)斗(ju)了,那你俩得有住处,于是你积极的找房,终于在跑断双腿之前看中了倒数第二套房,房子的隔音效果比较好啊,也正是你俩想要的,可没想到有几个和你相同经历的朋友和你抢着看房:
原来他们也是和你一样撩到妹之后,决定和妹子共同奋(tong)斗(ju)。但可惜,你们反复过去看房,搞得房东接待不过来,最后房东决定不租了!你只好重新去找房子,这次你学聪明了,不再去满大街跑,你把自己的租房要求告诉给了房屋中介,毕竟中介也是过来人,了解了你的需求,为了满足你和女友打(tong)拼(ju)生活,中介给你提供了隔音效果好的房子,正好这套房的房东年迈已高,就想躺着收租、和自己老婆看着小电影,于是,中介带着你去看房,房子隔音果然好啊,还有24小时热水,完全满足你的实际需求。中介带着你看房,还给你各种推销吹嘘,你看完之后,你和中介加了好友,中介告诉你其他租客也要找他看:
最后,你为了和你爱的女友的未来,你果断决定租下了这套房子!房东也很开心,总算实现了躺床上和老婆看着小电影,手机每个月收到房租到账短信的理想生活。
由此可见,代理是日常生活中常见的模式,故事中的中介就是一个代理,他代理房东和租客之间的需求。
在软件开发中,代理模式是常用的结构型设计模式中的一种,特征是代理类与委托类有同样的接口,代理类主要负责提交请求给委托类的前后进行事件处理,比如:预处理消息、过滤消息,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并没有真正实现,而是通过调用委托类的对象的相关方法,来提供特定的服务。
代理模式结构
以上故事在软件开发中结构如下:
你带着女友,把上面结构用编码语言重新记录了下来,首先你创建了一个房东接口LandlordInterface,房东可以出租,也可以接待:
package com.mars;
/**
* Created by Mars酱
*/
public interface LandlordInterface {
/** 出租房屋 */
void rentHouse();
/** 接待看房 */
void reception();
}
然后创建了一个房东的具体实现类 LandlordImpl,同时实现 LandlordInterface 接口:
package com.mars;
/**
* Created by Mars酱
*/
public class LandlordImpl implements LandlordInterface {
/** 出租房屋 */
@Override
public void rentHouse(){
System.out.println("出租:房子已租出。");
}
/** 接待看房 */
@Override
public void reception(){
System.out.println("接待:欢迎参观");
}
}
你租下的那套房,房东是给中介代理了接待和出租,所以,在接待和出租前后都加上中介的服务,如下:
package com.mars;
/**
* Created by Mars酱
*/
public class LandlordProxy implements LandlordInterface {
private LandlordInterface impl;
public LandlordProxy(LandlordInterface impl) {
this.impl = impl;
}
/** 出租房屋 */
@Override
public void rentHouse(){
System.out.println("出租前:这是房子钥匙,请您拿好!");
impl.rentHouse();
System.out.println("出租后:祝您和女友过上自己想要的生活!");
}
/** 接待看房 */
@Override
public void reception(){
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
impl.reception();
System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
}
}
最后 ,你去看房,而且决定租下来了。
/**
* Created by Mars酱
*/
public class Renter {
LandlordInterface landlordProxy = new LandlordProxy(new LandlordImpl());
public void seeHouse(){
// 中介接待你
landlordProxy.reception();
}
public void iRentThisHouse(){
// 房子出租
landlordProxy.rentHouse();
}
}
写上main函数:
/**
* Created by Mars酱
*/
public static void main(String[] args) {
Renter you = new Renter();
you.seeHouse();
System.out.println(">> 看完房子,回去和女友商量了一番,如是决定了!");
you.iRentThisHouse();
}
运行结果:
静态代理
以上故事的代码实现,其实就是静态代理的实现过程,中介持有房东需求,然后通过房东的基本需求,在接待和出租前后加上自己的服务实现静态代理。 但这个代理方式存在着问题:
- 当房东接口增加了需求,那么房东实现都要增加这个需求的实现。与此同时,中介代理也必须实现新增的房东接口的需求代理,这样对于房东和中介都不是很友好。
- 中介代理目前只是对1个房东出租代理,假设添加一个厂房房东的实现,那么就要再增加对应的厂房中介代理,这样对代理类的维护也变得非常繁琐。
上面的问题在动态代理中就得到了比较好的解决。
动态代理
先看下网上搜索出来的定义:
动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。
其实动态代理与静态代理的本质一样,最终程序运行时都需要生成一个代理对象实例,通过它来完成相关增强以及业务逻辑,只不过静态代理需要硬编码的方式指定,而动态代理则是以动态方式生成代理(有编译时操作字节码生成的方式、运行时通过反射、字节码生成的式)。
由此可见,动态的好处很明显,代理逻辑与业务逻辑是互相独立的,没有耦合,代理1个类或100个类要做的事情没有任何区别。
在java中,常见的动态代理有两种:一种是JDK自带的动态代理,一种是CGLib的动态代理。
JDK动态代理
JDK动态代理是由JDK官方提供的代理方式,主要有java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
两个类完成。
java.lang.reflect.Proxy
:通过java反射,创建出代理类;
java.lang.reflect.InvocationHandler
:负责接口方法的调用;
LandlordInterface、LandlordImpl都不用变,动态代理是改变中介和租客部分的逻辑,上面故事使用JDK动态代理的代码实现:
/** Created by Mars酱 */
/**
* 代理服务
*
* @param landlordInterface LandlordInterface接口
* @return InvocationHandler对象
*/
private InvocationHandler proxyService(LandlordInterface landlordInterface) {
return (proxy, method, args1) -> {
// 如果是接待服务
if (method.getName().equalsIgnoreCase("reception")) {
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
method.invoke(landlordInterface, args1);
System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
}
return null;
};
}
/**
* 动态创建对象
*
* @param invocationHandler InvocationHandler对象
* @return LandlordInterface对象
*/
private LandlordInterface getLandlord(InvocationHandler invocationHandler) {
return (LandlordInterface) Proxy.newProxyInstance(LandlordInterface.class.getClassLoader(), new Class[]{LandlordInterface.class}, invocationHandler);
}
再创建一个只有单间的房东对象:
/**
* Created by Mars酱
*/
public class LandlordSingleRoomImpl implements LandlordInterface {
@Override
public void rentHouse() {
System.out.println("出租:单间房子已租出。");
}
@Override
public void reception() {
System.out.println("接待:欢迎参观,我们这里是单间,隔音效果很好");
}
}
写上main函数:
/**
* Created by Mars酱
*/
public static void main(String[] args) {
RenterUseJDKProxy renterJDKProxy = new RenterUseJDKProxy();
// 1. 代理看普通房东
LandlordInterface normalLandlord = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordImpl()));
normalLandlord.reception();
System.out.println(">> 看完房子: 房子不太好,换一家");
System.out.println();
// 2. 代理看单间房东,听说隔音不错
LandlordInterface singleRoomLandlord = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordSingleRoomImpl()));
singleRoomLandlord.reception();
System.out.println(">> 看完房子: 这房子不错,回去和女友商量下");
}
执行结果:
由此,用到JDK动态代理,需要实现的是InvocationHandler接口。那么,如果去实现InvocationHandler接口,是不是可以代理成功呢?试试看,我们创建一个不需要实现接口的房东类:
/**
* Created by Mars酱
*/
public class LandlordImpl2 {
public void rentHouse() {
System.out.println("出租2:房子已租出。");
}
public void reception() {
System.out.println("接待2:欢迎参观");
}
}
再创建一个租客,再通过Proxy去调用:
/**
* Created by Mars酱
*/
public class Renter2UseJDKProxy {
/**
* 代理服务
*
* @param landlordInterface LandlordInterface接口
* @return 调用程序
*/
private InvocationHandler proxyService(LandlordImpl2 landlordInterface) {
return (proxy, method, args1) -> {
// 如果是接待服务
if (method.getName().equalsIgnoreCase("reception")) {
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
method.invoke(landlordInterface, args1);
System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
}
return null;
};
}
/**
* 动态创建中介对象
*
* @param invocationHandler
* @return
*/
private LandlordImpl2 getLandlord(InvocationHandler invocationHandler) {
return (LandlordImpl2) Proxy.newProxyInstance(LandlordImpl2.class.getClassLoader(),
new Class[]{LandlordImpl2.class},
invocationHandler);
}
public static void main(String[] args) {
Renter2UseJDKProxy renterJDKProxy = new Renter2UseJDKProxy();
// 1. 代理看普通房东
LandlordImpl2 landlord2 = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordImpl2()));
landlord2.reception();
System.out.println(">> 看完房子: 这房子不错,回去和女友商量下");
}
}
运行main函数:
错误提示说我们的这个房东不是一个接口,那么看来使用JDK动态代理是必须要实现业务接口的。这样看来就不是太灵活了,有些房东想着轻松,会把房子代理给中介出租,有些人就想自己出租不喜欢中介,JDK动态代理就无法满足这种情况了。
CGLib动态代理
CGLib的定义,先看网上搜到的说明:
CGLib (Code Generation Library) 是一个开源项目。是一个强大的、高性能、高质量的 Code 生成类库。它可以在运行期扩展 Java 类与实现 Java 接口。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。
CGLib 的底层是 Java 字节码操作框架:ASM
CGLib的优势它自己写了,根据官方说明,只需要用到两个类:net.sf.cglib.proxy.MethodInterceptor
和 net.sf.cglib.proxy.Enhancer
,前者用来在被代理业务的前后添加自己的逻辑,后者负责生成动态代理类。那么我们来试下吧!兄弟们,上车~
/**
* Created by Mars酱
*/
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class RenterUseCglibProxy {
public static void main(String[] args) {
LandlordInterface normalLandlord = (LandlordInterface) cglibProxy(new LandlordImpl()).create();
normalLandlord.reception();
System.out.println(">>> 房子一般,我再看看!");
System.out.println();
LandlordInterface singleLandlord = (LandlordInterface) cglibProxy(new LandlordSingleRoomImpl()).create();
singleLandlord.reception();
}
private static Enhancer cglibProxy(LandlordInterface landlordInterface) {
Enhancer enhancer = new Enhancer();
// 1.设置类加载
enhancer.setClassLoader(landlordInterface.getClass().getClassLoader());
// 2.设置被代理的类
enhancer.setSuperclass(landlordInterface.getClass());
// 3.设置好回调
enhancer.setCallback((MethodInterceptor) (obj, method, params, methodProxy) -> {
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
Object result = methodProxy.invokeSuper(obj, params);
System.out.println("接待后:希望这套房子您能满意。");
return result;
});
// 4.创建代理类
return enhancer;
}
}
运行之后的结果:
效果符合。但是这里cglibProxy()传递的是LandlordInterface接口,那么cglib不是说好的可以代理普通类吗?前面的LandlordImpl2怎么处理呢?我们把cglib这里改造下吧
/**
* Created by Mars酱
*/
public class RenterUseCglibProxy {
public static void main(String[] args) {
LandlordInterface normalLandlord = (LandlordInterface) cglibProxy(LandlordImpl.class).create();
normalLandlord.reception();
System.out.println(">>> 房子一般,我再看看!");
System.out.println();
LandlordInterface singleLandlord = (LandlordInterface) cglibProxy(LandlordSingleRoomImpl.class).create();
singleLandlord.reception();
System.out.println(">>> 房子不错,但是我想再看看!");
System.out.println();
// 自己房子的房东返回的就是自己
LandlordImpl2 singleLandlordSelf = (LandlordImpl2) cglibProxy(LandlordImpl2.class).create();
// 然后自己做接待
singleLandlordSelf.reception();
System.out.println(">>> 房子不错,我想想再决定!");
}
/**
* cglibProxy函数接受Class对象
*/
private static Enhancer cglibProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(clazz.getClassLoader());
enhancer.setSuperclass(clazz);
enhancer.setCallback((MethodInterceptor) (obj, method, params, methodProxy) -> {
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
Object result = methodProxy.invokeSuper(obj, params);
System.out.println("接待后:希望这套房子您能满意。");
return result;
});
return enhancer;
}
}
运行之后的结果:
细心的朋友发现,最后那个接待语怎么还是用的中介的呢?那是因为自住房房东的接待方法和中介的一致,为了区别,我们可以再改造一下,cglib中有个回调过滤器net.sf.cglib.proxy.CallbackFilter
,我们用这个过滤器处理一下自住房的房东接待语
/**
* Created by Mars酱
*/
private static Enhancer cglibProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(clazz.getClassLoader());
enhancer.setSuperclass(clazz);
// 1. 创建两个回调,一个用来适用于中介,一个适用于自住房,数组索引从0开始
enhancer.setCallbacks(new Callback[]{(MethodInterceptor) (obj, method, params, methodProxy) -> {
System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
Object result = methodProxy.invokeSuper(obj, params);
System.out.println("接待后:希望这套房子您能满意。");
return result;
}, (MethodInterceptor) (obj, method, params, methodProxy) -> {
System.out.println("接待前:您好,我不是中介。今天我带你去我自己房子里面看看。");
Object result = methodProxy.invokeSuper(obj, params);
System.out.println("接待后:你什么时候有空就给我个答复。");
return result;
}});
// 2. 创建函数过滤器,如果是自己房子的函数,使用第二个回调函数,回调数组中索引为1
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return (method.getName().equalsIgnoreCase("selfReception")) ? 1 : 0;
}
@Override
public boolean equals(Object o) {
return false;
}
});
return enhancer;
}
同样的,自住房的房东接待函数名也改个名为:
/**
* Created by Mars酱
*/
// 原函数名为:reception()
public void selfReception() {
System.out.println("接待2:欢迎参观,这是我自己的房子,没有中介。");
}
执行之后的结果:
好了,cglib确实可以代理接口对象和普通对象。
总结
Java动态代理,介绍了两种方式:JDK动态代理和开源Cglib动态代理,它们的实现机制不同:
- JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。
- CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作。
共同的部分,底层其实都是用到了通过反射完成,
总的来说,JDK 动态代理利用接口实现代理,Cglib 动态代理利用继承的方式实现代理。
而动态代理的作用就在于:
做方法的增强,在不改变源代码的情况下给原来的方法前后增加一些其他方法。比如:日志记录,事务管理等等
动态代理在 Java 开发中是非常常见的,在日志,监控,事务,技术框架中都有着广泛使用。动态代理的底层原理下次再讲。
转载自:https://juejin.cn/post/7215963267490627644