当前位置: 首页 > news >正文

设计模式二:代理模式

1、什么是动态代理

可能很多小伙伴首次接触动态代理这个名词的时候,或者是在面试过程中被问到动态代理的时候,不能很好的描述出来,动态代理到底是个什么高大上的技术。不方,其实动态代理的使用非常广泛,例如我们平常使用的 Spring中的 @Transactional注解,其依赖于 AOP,而 AOP的底层实现便是动态代理,看到这里,是不是更有兴趣去了解动态代理了呢?

动态代理:可以分解为"动态"+“代理”。

  • 代理:"代理"一词,在我们的生活中也是随处可见的,例如房屋中介,其是对房主的一种代理,房主需要出租其房屋,但是可能没时间去接待租客,给租客介绍房屋信息,带领租客看房,但是房屋中介可以为租客提供这些服务,所以,代理其是对被代理对象的一个功能增强
  • 动态:"动态"通常与"静态"相比较,"静态"描述的是事物是固定存在的,"动态"则描述的是事物是随着需求而动态生成的。

所以,静态代理存在一定的局限性,不能很好的满足需求的千变万化,动态代理的出现,就是为了解决这些局限性。

我们先来看看静态代理。

2.、静态代理

在开发中,通常需要为方法添加日志打印,能够记录程序的执行过程,以便后续出现异常问题的时候,能更好的排查定位。

假设我们现在已经完成了系统用户的增加、删除、修改等功能,这些功能在类 UserServiceImpl中已经实现。

代码示例:

public class UserServiceImpl {public void add() {System.out.println("添加用户");}public void update() {System.out.println("修改用户");}public void delete() {System.out.println("删除用户");}
}

现在,我们需要在UserServiceImpl类中的方法添加日志功能,那么怎么才能更好地去实现这个需求呢?

1)直接在目标方法前后添加日志代码

代码示例:

public class UserServiceImpl {public void add() {System.out.println("====== add方法开始 ======");System.out.println("添加用户");System.out.println("====== add方法结束 ======");}public void update() {System.out.println("====== update方法开始 ======");System.out.println("修改用户");System.out.println("====== update方法结束 ======");}public void delete() {System.out.println("====== delete方法开始 ======");System.out.println("删除用户");System.out.println("====== delete方法结束 ======");}
}

观察上述代码,这种方式的缺点在于:

2)静态代理方式实现

静态代理需要我们将目标类的方法抽取到接口中,代理类和目标类实现同一个接口,既然要实现代理,代理类自然需要在其内部维护目标对象的引用,并通过构造函数为其赋值,然后在代理类的方法中调用目标对象的同名方法,并在调用前后完成功能的增强。

实现步骤:

  • 抽取UserService接口
  • 创建目标类UserServiceImpl实现UserService接口
  • 创建代理类UserServiceProxy实现UserService接口
  • 代理类中完成功能的增强

代码实现:

// 目标接口
public interface UserService {void add();void update();void delete();
}
// 目标类
public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("添加用户");}@Overridepublic void update() {System.out.println("修改用户");}@Overridepublic void delete() {System.out.println("删除用户");}
}
// 代理类
public class UserServiceProxy implements UserService {private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic void add() {System.out.println("====== add方法开始 ======");userService.add();System.out.println("====== add方法结束 ======");}@Overridepublic void update() {System.out.println("====== update方法开始 ======");userService.update();System.out.println("====== update方法结束 ======");}@Overridepublic void delete() {System.out.println("====== delete方法开始 ======");userService.delete();System.out.println("====== delete方法结束 ======");}
}

观察上述代码,静态代理遵循开闭原则,在不修改目标类的前提下,完成了功能的增强,但是依然存在大量重复的代码,且一个代理类只能代理一个目标类,如果有n个目标类需要被代理,就需要同比增加n个代理类。

那么,有没有办法可以使得我们不需要去定义这么多的代理类,就可以实现对目标类功能的增强?答案是有的:动态代理

3、JDK动态代理

前面,我们提到静态代理的实现方式:代理类和目标类都实现同一个接口,在代理类中维护目标类对象,并完成对目标类对象方法的增强,这种方式虽然遵循开闭原则,但是代理类和目标类至少是"一对一"的绑定关系,如果需要被代理的目标类个数越多,代理类就会越多,会产生大量重复的代码,也不利于后期的维护。

从静态代理中,我们知道代理类也是接口的一个实现类,代理对象的类型也是属于接口类型,我们来验证一下。

public class Test {public static void main(String[] args) {UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());System.out.println(userServiceProxy instanceof UserService);}
}
// 打印结果:true

那么,能不能动态生成这些代理对象呢?我们知道类是构造对象的模板,代理类都还不存在,怎么去构造代理对象呢?

除了不存在代理类,还剩下 UserService接口和 UserServiceImpl目标类,JDK动态代理的目的就是通过接口来生成代理类以及代理类的对象,我们知道接口是不能直接通过new关键字创建对象的。

那么JDK动态代理是怎么创建出代理类以及代理类对象的呢?

我们先来看看通过 new关键字创建对象的过程。

UserServiceImpl userService = new UserServiceImpl();
/*
创建对象的过程:1.执行new指令,如果类未加载,先执行类加载过程。1.加载:JVM通过ClassLoader将UserServiceImpl.class文件加载到方法区(Method Area),在堆内存中创建代表该类的Class对象。2.验证3.准备:为静态变量分配内存并设置类型初始值。4.解析5.初始化:为静态变量赋值、执行静态代码块2.为对象分配内存,将对象的实例字段初始化类型零值。3.执行构造方法,对对象进行初始化
*/

追踪上述过程,我们得知创建对象,需要先得到该类的Class对象,通过Class对象去创建实例对象。为了验证这一点,我们不妨来看看通过反射的方式创建对象的过程。

public class Test {@SneakyThrowspublic static void main(String[] args) {// 获取Class对象Class<UserServiceImpl> userServiceClass = UserServiceImpl.class;// 获取构造器Constructor<?>[] constructors = userServiceClass.getConstructors();for (Constructor<?> constructor : constructors) {// 通过构造器创建实例对象System.out.println(constructor.newInstance());}}
}

现在,问题回归到接口不能直接new,也没有构造方法,并且不存在代理类的class文件,怎么获得Class对象了。

动态代理关键类

我们先来看看JDK动态代理的实战代码:

  • 需要自定义个 CustomInvocationHandler实现 InvocationHandler接口。
  • 利用 Proxy.newProxyInstance构建实例对象。
// UserService接口
public interface UserService {void add();void update();void delete();
}// 目标类
public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("添加用户");}@Overridepublic void update() {System.out.println("修改用户");}@Overridepublic void delete() {System.out.println("删除用户");}
}// CustomInvocationHandler
public class CustomInvocationHandler implements InvocationHandler {// 目标对象private Object target;public CustomInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}
}public class Test {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();// 关键代码UserService service = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new CustomInvocationHandler(userService));service.add();}
}

从测试代码可以看出,Proxy类是关键。我们来看看Proxy为我们提供的方法:

Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)虽然被标注为过时方法,但是从名字上可以得知,其目的是为了获得代理类的Class对象。话不多说,我们来调用一下。

public class Test {public static void main(String[] args) {Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());System.out.println(proxyClass.getName());for (Method method : proxyClass.getDeclaredMethods()) {System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");}System.out.println(Arrays.toString(proxyClass.getConstructors()));}
}

可以看到:

  • 获得的Class对象的名称为 $Proxy0
  • 定义了我们需要的 add();update();delete()方法。
  • 定义了一个有参构造方法 $Proxy0(InvocationHandler handler)

虽然没有无参构造方法,我们还是得尝试一下调用一下这个有参的构造方法,需要我们传入一个 java.lang.reflect.InvocationHandler对象

public class Test {@SneakyThrowspublic static void main(String[] args) {System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());// 获取$Proxy0(InvocationHandler handler)构造方法Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService = (UserService) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy.getClass());System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");return null;}});userService.add();}
}

看的出来,**当我们获得代理对象之后,通过代理对象来调用接口方法,都会回调构造时传进来的 InvocationHandler对象的 invoke(Object proxy, Method method, Object[] args)方法,**该方法有3个参数:

  • Object proxy:代表的是代理对象本身。
  • Method method:代表的是被调用的方法的Method对象。
  • Object[] args:代表的是被调用方法的参数。

可以猜测,JDK动态代理生成的代理类中,维护了InvocationHandler类的对象变量,并且在实现接口方法时,通过InvocationHandler对象调用了 invoke(Object proxy, Method method, Object[] args)方法。

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

不知道大家没有看到这行代码哈,当添加了这行代码之后,可以将在项目目录下保存动态创建的class文件, com/sun/proxy/$Proxy0.class

可以看到生成的代理类 $Proxy0继承自 Proxy类,并实现了 UserService接口,并且在 add()方法中通过其父类 Proxy中维护的 InvocationHandler对象调用 invoke()方法,这也就成功的解释了前面调用 userService.add()方法,会回调到invoke()方法。

这时候我们再把代码改造一下,如下:

public class CustomInvocationHandler implements InvocationHandler {// 目标对象private Object target;public CustomInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}
}public class Test {@SneakyThrowspublic static void main(String[] args) {UserServiceImpl target = new UserServiceImpl();Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService = (UserService) constructor.newInstance(new CustomInvocationHandler(target));userService.add();}
}

这样就完成了对目标对象功能的增强,前面我们提到过 Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

已经被标注为过时,推荐我们使用 Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法。

动态代理设计思想

好的,到这里,我们来总结一下JDK动态的设计思想:

使用 JDK动态代理,使得我们免去编写代理类,只需要将增强功能编写在 InvocationHandlerinvoke方法中。

4、CGLib动态代理

CGLib代理的目标对象不需要事先任何接口,它是通过动态集成目标对象实现动态代理的。CGLib代理执行代理方法的效率之所以比JDK高,是因为CGLib采用了FastClass机制:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int类型);这个index当作一个入参,FastClass 就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。FastClass并不是跟代理类一起生成的,而是在第一次执行MethodProxy的invoke()或invokeSuper()方法时产生并放在缓存中的。

5、CGLib和JDK动态代理对比

  • JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象。
  • JDK动态代理和CGLib代理在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架(字节码操控框架)写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低。
  • JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高

6、Spring中的代理选择原则

  • 当Bean有实现接口时,Spring就会用JDK动态代理
  • 当Bean没有实现接口时,Spring会选择CGLib代理
  • Spring可以通过配置强制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas=“true”>
http://www.lryc.cn/news/302630.html

相关文章:

  • php使用get_browser()函数将移动端和pc端分开
  • 更改WordPress作者存档链接author和Slug插件Edit Author Slug
  • RISCV——Spinlock锁的理解
  • 阿里云幻兽帕鲁Windows 服务器怎么下载存档?
  • ArcGIS API for JavaScript 4.X 本地部署(js,字体)
  • 202427读书笔记|《猫的自信:治愈系生活哲学绘本》——吸猫指南书,感受猫咪的柔软慵懒与治愈
  • 数据结构链表力扣例题AC(2)——代码以及思路记录
  • C++面试宝典第30题:分发饼干
  • 文件包含+文件上传漏洞(图片马绕过)
  • 华为配置旁挂二层组网隧道转发示例
  • Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease
  • 电脑文件误删除如何恢复?2024最新三种恢复方法
  • Netty应用——Google Protobuf强化篇(二十)
  • SpringAMQP开启“可靠性”机制
  • 戴尔Dell R740服务器开机冒烟亮黄灯故障维修
  • 【阅读笔记】空域保边降噪《Side Window Filtering》
  • vue3前端excel导出;组件表格,自定义表格导出;Vue3 + xlsx + xlsx-style
  • npm install一直卡在 sill idealTree buildDeps
  • spring boot rabbitmq常用配置
  • MySQL学习记录——십삼 视图及用户、权限管理
  • PyCharm 自动添加文件头注释
  • 用HTML Canvas和JavaScript创建美丽的花朵动画效果
  • java----js常用的api
  • unity 使用VS Code 开发,VS Code配置注意事项
  • 领域驱动设计(Domain Driven Design)
  • CF778A String Game 题解
  • 【工具插件类教学】Unity运行时监控变量,属性,事件等的值和调用Runtime Monitoring
  • 实际生产中的一次非典型的基于jmeter的接口自动化实践
  • 新能源汽车软件开发设计规范
  • Linux:grep进阶(11)