• 软件设计模式(五):代理模式


    前言

            代理模式是软件设计模式的重中之重,代理模式在实际应用比较多,比如Spring框架中的AOP。在这篇文章中荔枝将会梳理有关静态代理、动态代理的区别以及两种实现动态代理模式的方式。希望能对有需要的小伙伴有帮助~~~


    文章目录

    前言

    一、静态代理

    二、动态代理

    2.1 基于反射实现动态代理 

    2.2 基于cglib实现动态代理

    总结


    一、静态代理

            静态代理其实比较容易理解,就是我们在扩充一个类方法的功能的时候不想改动原来类的结构,就可以使用代理的方式来实现,这种方式也类似装饰器模式, 代理类还要求和委托类有共同的父类或父接口。 

    我们简单来看看一段demo:

            在这段demo中,定义了一个Movable接口并被两个代理类和一个被代理类实现,在测试类中我们执行代理类和实现类中的move()方法。首先会调用TankLogProxy中的move并传入一个TankTimeProxy类对象,再调用TankTimeProxy.move(),之后会触发调用被代理类的move()方法,之后才会执行完之前代理类中剩下的代理方法。

    1. package com.crj.proxy;
    2. import java.util.Random;
    3. /**
    4. * 静态代理
    5. */
    6. public class Tank implements Movable {
    7. /**
    8. * 模拟坦克移动了一段儿时间
    9. */
    10. @Override
    11. public void move() {
    12. System.out.println("Tank moving claclacla...");
    13. try {
    14. Thread.sleep(new Random().nextInt(10000));
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. public static void main(String[] args) {
    20. new TankLogProxy(
    21. new TankTimeProxy(
    22. new Tank()
    23. )
    24. ).move();
    25. }
    26. }
    27. class TankTimeProxy implements Movable {
    28. Movable m;
    29. public TankTimeProxy(Movable m) {
    30. this.m = m;
    31. }
    32. @Override
    33. public void move() {
    34. long start = System.currentTimeMillis();
    35. m.move();
    36. long end = System.currentTimeMillis();
    37. System.out.println(end - start);
    38. }
    39. }
    40. class TankLogProxy implements Movable {
    41. Movable m;
    42. public TankLogProxy(Movable m) {
    43. this.m = m;
    44. }
    45. @Override
    46. public void move() {
    47. System.out.println("start moving...");
    48. m.move();
    49. long end = System.currentTimeMillis();
    50. System.out.println("stopped!");
    51. }
    52. }
    53. interface Movable {
    54. void move();
    55. }

    这其实就是静态代理的过程,大家其实可以看到代理类的功能是我们提前写死的,对于不同类型的代理就需要不同的代理类了,这样可能会需要很多很多的代理类。所以我们需要通过一种方式来实现代理类的动态生成。


    二、动态代理

            在上文中荔枝提及分离代理行为和代理对象的需求,实现代理类能够代理不同类型的对象,这时候就需要借助动态代理的方式。Java中实现动态代理有两种方式:基于JDK反射机制实现动态代理、借助cglib。

    2.1 基于反射实现动态代理 

    Java JDK实现动态代理的过程我们可以通过这么一张图来表示,在java.lang.reflect中其实提供了一个Proxy类,我们可以借助该类的newInstance()方法实现动态代理。

    我们需要向Proxy.newInstance()方法传入三个参数:类加载对象、接口类和InvocationHandler调用处理对象。Java反射会默认调用InvocationHandler对象中的invoke()方法实现动态代理。

     具体实现动态代理的实例demo如下:

    1. package com.crj.proxy;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. import java.lang.reflect.Proxy;
    5. import java.util.Random;
    6. public class Tank implements Movable {
    7. /**
    8. * 模拟坦克移动了一段儿时间
    9. */
    10. @Override
    11. public void move() {
    12. System.out.println("Tank moving claclacla...");
    13. try {
    14. Thread.sleep(new Random().nextInt(10000));
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. public static void main(String[] args) {
    20. Tank tank = new Tank();
    21. //reflection 通过二进制字节码分析类的属性和方法
    22. Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
    23. new Class[]{Movable.class}, //tank.class.getInterfaces()
    24. new LogHander(tank)
    25. );
    26. m.move();
    27. }
    28. }
    29. class LogHander implements InvocationHandler {
    30. Tank tank;
    31. public LogHander(Tank tank) {
    32. this.tank = tank;
    33. }
    34. //getClass.getMethods[]
    35. @Override
    36. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    37. System.out.println("method " + method.getName() + " start..");
    38. Object o = method.invoke(tank, args);
    39. System.out.println("method " + method.getName() + " end!");
    40. return o;
    41. }
    42. }
    43. interface Movable {
    44. void move();
    45. }

            Java JDK反射机制实现动态代理是基于asm类来实现的,该类主要是通过直接修改字节码文件在类加载后直接将代理方法注入字节码文件中,该机制要求被代理类必须实现接口,这也是这种方法实现动态代理的缺点。 

    2.2 基于cglib实现动态代理

    首先我们需要导入cglib依赖,在项目pom.xml文件中配置

    1. <dependency>
    2. <groupId>cglibgroupId>
    3. <artifactId>cglibartifactId>
    4. <version>3.2.12version>
    5. dependency>

            该方法在Java中提供了一个继承自AbstractClassGenerator类的Enhancer类,在使用动态代理之前必须获得一个Enhancer对象,代理类必须继承一个MethodInterceptor接口并重写intercept方法。在测试类中我们需要设置好被代理对象的父类以及将代理类对象传入setCallback()中,最后调用Enhancer对象的create()方法即可在被代理对象执行方法时实现动态代理。下面给出一个实例demo:

    1. package com.crj.cglib;
    2. import net.sf.cglib.proxy.Enhancer;
    3. import net.sf.cglib.proxy.MethodInterceptor;
    4. import net.sf.cglib.proxy.MethodProxy;
    5. import java.lang.reflect.Method;
    6. import java.util.Random;
    7. /**
    8. * CGLIB实现动态代理不需要接口
    9. */
    10. public class Main {
    11. public static void main(String[] args) {
    12. Enhancer enhancer = new Enhancer();
    13. enhancer.setSuperclass(Tank.class);
    14. enhancer.setCallback(new TimeMethodInterceptor());
    15. Tank tank = (Tank)enhancer.create();
    16. tank.move();
    17. }
    18. }
    19. class TimeMethodInterceptor implements MethodInterceptor {
    20. @Override
    21. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    22. System.out.println(o.getClass().getSuperclass().getName());
    23. System.out.println("before");
    24. Object result = null;
    25. result = methodProxy.invokeSuper(o, objects);
    26. System.out.println("after");
    27. return result;
    28. }
    29. }
    30. class Tank {
    31. public void move() {
    32. System.out.println("Tank moving claclacla...");
    33. try {
    34. Thread.sleep(new Random().nextInt(10000));
    35. } catch (InterruptedException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }

    Spring框架中的面向切面编程也是Java动态代理的一部分。


    总结

            Proxy模式荔枝已经梳理完成,到现在为止软件设计模式中的最常见的模式和最重点的几个模式已经梳理完成了,收获满满(至少水了几篇文章哈哈哈哈)。总之继续加油,争取明天结束软件设计模式的学习~~~

    今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~

  • 相关阅读:
    python requests 配置重试次数
    vue使用wangEditor
    MySQL 开启SSL无效问题
    Javas | DecimalFormat类、BigDecimal类、Random类
    [附源码]计算机毕业设计JAVA在线二手车交易信息管理系统
    react antd table表格点击一行选中数据的方法
    从零开始实现大语言模型(五):缩放点积注意力机制
    常见DDoS攻击
    手把手教你部署nginx+php —— k8s从入门到高并发系列教程 (一)
    【算法竞赛入门练习题】使用 swap() 函数来实现三个数的排序
  • 原文地址:https://blog.csdn.net/qq_62706049/article/details/132776431