Spring代理

Spring代理

概述

  • 在生活中,常见到的房产中介,会负责代理业务的房产进行售卖、招租,撮合买卖双方达成交易,减少买家和卖家的时间成本和交易成本,俗称代理模式(图)

  • 在软件开发中,也存在这样的机制,就是常说的设计模式中的代理模式

  • Spring及相关的框架也广泛应用了代理技术

  • Spring代理主要分为静态代理(如AspectJ)和动态代理(如Spring AOP)

静态代理

概述

  • 静态代理使用代理模式实现,针对每个需要代理的方法,都需要进行代理包装处理,相对比较繁琐

  • 静态代理需要在编译期确定代理类

  • 静态代理主要适用场景

    • 日志记录:静态代理可以在方法执行前后添加日志记录的功能,记录方法的输入参数、返回值以及执行时间等信息。

    • 性能统计:静态代理可以用于在方法执行前后统计方法的执行时间,用于性能监控和优化。

    • 安全控制:静态代理可以在方法执行前进行安全校验,判断用户是否有权限执行该方法,从而增加安全性。

    • 事务管理:静态代理可以在方法执行前开启事务,在方法执行后提交事务或回滚事务,用于实现事务的一致性和隔离性。

    • 数据校验:静态代理可以在方法执行前对输入参数进行校验,确保数据的合法性和完整性。

    • 远程方法调用:静态代理可以通过网络调用远程方法,对外隐藏底层的网络通信细节,简化客户端的调用过程。

    • 访问控制:静态代理可以用于实现访问控制,根据不同的用户权限对方法进行过滤或拦截。

    • 缓存:静态代理可以在方法执行前判断缓存中是否存在计算的结果,如果存在则直接返回缓存结果,提高系统的响应速度

  • 优点

    • 通过代码在开发时实现,直观、简单、灵活

  • 缺点

    • 被代理类的每个方法都要有相应的代理方法,繁琐,不方便扩展

应用实例

  • 静态代理实例,用于代理一个消息发送与接收的服务,该服务具备父接口

    • 服务接口

      package com.demo.proxy.service;
      ​
      /**
       * 消息服务接口
       */
      public interface IMessageService {
          /**
           * 发送消息
           * @param messageContent 消息内容
           * @return 发送结果
           */
          String send(String messageContent);
      ​
          /**
           * 接收消息
           * @param messageContent 消息内容
           * @return 接收结果
           */
          String receive(String messageContent);
      }
    • 服务实现

      package com.demo.proxy.service;
      ​
      /**
       * 消息服务实现类
       */
      public class MessageServiceImpl implements IMessageService {
          /**
           * 发送消息
           *
           * @param messageContent 消息内容
           * @return 发送结果
           */
          public String send(String messageContent) {
              String result = "发送消息:" + messageContent;
      ​
              System.out.println(result);
      ​
              return result;
          }
      ​
          /**
           * 接收消息
           *
           * @param messageContent 消息内容
           * @return 接收结果
           */
          public String receive(String messageContent) {
              String result = "接收消息:" + messageContent;
      ​
              System.out.println(result);
      ​
              return result;
          }
      }
    • 静态代理

      package com.demo.proxy.staticproxy;
      ​
      import com.demo.proxy.service.IMessageService;
      ​
      /**
       * 静态代理类,继承于消息服务接口,并持有被代理的消息服务实现
       */
      public class MessageServiceProxy implements IMessageService {
          //被代理的消息服务
          private IMessageService messageService;
      ​
          /**
           * 通过构造方法初始化被代理的消息服务
           * @param messageService
           */
          public MessageServiceProxy(IMessageService messageService) {
              this.messageService = messageService;
          }
      ​
          /**
           * 发送消息
           *
           * @param messageContent 消息内容
           * @return 返回结果
           */
          public String send(String messageContent) {
              System.out.println("【静态代理开始...】");
      ​
              String result = messageService.send(messageContent);
      ​
              System.out.println("【静态代理结束。】");
      ​
              return result;
          }
      ​
          /**
           * 接收消息
           * @param messageContent 消息内容
           * @return 返回结果
           */
          public String receive(String messageContent) {
              System.out.println("【静态代理开始...】");
      ​
              String result = messageService.receive(messageContent);
      ​
              System.out.println("【静态代理结束。】");
      ​
              return result;
          }
      }
    • 使用静态代理

      package com.demo.proxy;
      ​
      import com.demo.proxy.service.IMessageService;
      import com.demo.proxy.service.MessageServiceImpl;
      import com.demo.proxy.staticproxy.MessageServiceProxy;
      ​
      /**
       * 使用静态代理
       */
      public class ProxyApplication {
          public static void main(String[] args){
              System.out.println("---------------静态代理---------------");
              //定义被代理服务
              IMessageService messageService = new MessageServiceImpl();
              //定义静态代理
              MessageServiceProxy messageServiceProxy = new MessageServiceProxy(messageService);
              //通过静态代理调用服务,并实现代理拦截
              System.out.println("------发送消息方法------");
              messageServiceProxy.send("你好!");
              System.out.println("------接收消息方法------");
              messageServiceProxy.receive("感谢调用。");
          }
      }
    • 运行结果

动态代理

概述

  • 动态代理主要用于在方法执行前后需要进行拦截的业务中

  • 动态代理是在运行时确定代理类

  • 动态代理主要适用场景

    • AOP:动态代理可以在方法执行前后添加额外的逻辑,比如日志记录、性能统计等

    • 事务管理:通过动态代理可以在方法执行前开启事务,在方法执行后提交事务或回滚事务

    • 远程调用:动态代理可以用于创建远程对象的代理,通过网络传输来远程调用方法

    • 缓存:动态代理可以拦截对方法的调用,并判断是否已经在缓存中存在该方法的结果,如果存在则直接返回缓存结果,避免重复计算

    • 权限控制:动态代理可以在方法执行前判断用户的权限,如果没有权限则抛出异常或者拒绝执行

    • 延迟加载:动态代理可以用于实现延迟加载的功能,只有当真正需要访问某个对象的属性或调用其方法时才会触发对象的加载

  • 动态代理主要分为JDK代理CGLIB代理

JDK代理

概述

  • 基于接口

  • 利用反射机制生成一个包含被代理对象的所有接口的代理类,并覆盖接口中的所有方法,实现对目标对象进行代理

  • 优点

    • 无需要使用第三方类库,基于JVM就可以完成

    • 简单、快捷

  • 缺点

    • 只能基于接口,并对接口的子类进行代理,对普通类无能为力

应用实例

  • JDK代理实例,用于代理一个消息发送与接收的服务,该服务具备父接口

    • 服务接口

      package com.demo.proxy.service;
      ​
      /**
       * 消息服务接口
       */
      public interface IMessageService {
          /**
           * 发送消息
           * @param messageContent 消息内容
           * @return 发送结果
           */
          String send(String messageContent);
      ​
          /**
           * 接收消息
           * @param messageContent 消息内容
           * @return 接收结果
           */
          String receive(String messageContent);
      }
    • 服务实现

      package com.demo.proxy.service;
      ​
      /**
       * 消息服务实现类
       */
      public class MessageServiceImpl implements IMessageService {
          /**
           * 发送消息
           *
           * @param messageContent 消息内容
           * @return 发送结果
           */
          public String send(String messageContent) {
              String result = "发送消息:" + messageContent;
      ​
              System.out.println(result);
      ​
              return result;
          }
      ​
          /**
           * 接收消息
           *
           * @param messageContent 消息内容
           * @return 接收结果
           */
          public String receive(String messageContent) {
              String result = "接收消息:" + messageContent;
      ​
              System.out.println(result);
      ​
              return result;
          }
      }
    • JDK代理类

      package com.demo.proxy.jdkproxy;
      ​
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;
      ​
      /**
       * JDK代理类
       * 1、继承于消息服务接口,并持有被代理的消息服务接口对象
       * 2、需要实现InvocationHandler接口,并覆盖invoke方法
       */
      public class MessageInvocationHandler implements InvocationHandler {
          /**
           * 代理类中的真实对象,一般在构造时赋值,后续不能进行修改
           */
          private final Object target;
      ​
          /**
           * 构造方法,传入被代理的真实对象
           *
           * @param target
           */
          public MessageInvocationHandler(Object target) {
              this.target = target;
          }
      ​
          /**
           * 调用代理方法
           *
           * @param proxy  代理对象
           * @param method 拦截方法元信息
           * @param args   方法参数列表
           * @return
           * @throws InvocationTargetException
           * @throws IllegalAccessException
           */
          public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
              System.out.println("【JDK代理开始代理...】代理方法:" + method.getName());
              Object result = method.invoke(this.target, args);
              System.out.println("【JDK代理结束代理。】代理方法:" + method.getName());
              return result;
          }
      }
    • JDK代理类工厂类

      package com.demo.proxy.jdkproxy;
      ​
      import java.lang.reflect.Proxy;
      ​
      /**
       * JDK代理工厂
       */
      public class JdkProxyFactory {
          /**
           * 获取代理对象
           * @param target
           * @return
           */
          public static Object getProxy(Object target) {
              //创建代理实例
              return Proxy.newProxyInstance(
                      //被代理对象的类加载器
                      target.getClass().getClassLoader(),
                      //被代理对象的类所属接口
                      target.getClass().getInterfaces(),
                      //JDK代理类对象,需要实现InvocationHandler接口,并重写其invoke方法
                      new MessageInvocationHandler(target)
              );
          }
      }
    • 使用JDK代理

      package com.demo.proxy;
      ​
      import com.demo.proxy.jdkproxy.JdkProxyFactory;
      import com.demo.proxy.service.IMessageService;
      import com.demo.proxy.service.MessageServiceImpl;
      ​
      /**
       * 使用JDK代理
       */
      public class ProxyApplication {
          public static void main(String[] args){
              System.out.println("---------------JDK代理---------------");
              //定义被代理服务
              IMessageService messageService = new MessageServiceImpl();
              //使用JDK代理对服务进行代码,服务作为参数传递
              IMessageService messageServiceProxy = (IMessageService) JdkProxyFactory.getProxy(messageService);
              //通过JDK代理调用服务,实现代理拦截
              System.out.println("------发送消息方法------");
              messageServiceProxy.send("你好!");
              System.out.println("------接收消息方法------");
              messageServiceProxy.receive("感谢调用。");
          }
      }
    • 运行结果

CGLIB代理

概述

  • CGLIB(Code Generation Library),采用动态生成字节码的方式,在被代理类加载之前就完成了代理类的创建并缓存到内存中,以后每次调用时,都直接使用缓存的代理类

  • 相比JDK代理,更适合于大规模的方法拦截和增强等场景

  • 需要使用第三方cglib库,cglib库基于ASM

  • ASM是一个Java字节码操控框架,能够以二进制形式修改已有类或动态生成类,可直接生成二进制的.class字节码文件,也可在类加载进JVM之前动态改变类的行为

  • CGLIB代理过程中,可先用ASM将代理对象类的.class字节码文件加载进来,通过修改其字节码动态生成子类,在子类中采用方法拦截技术拦截所有父类方法的调用并织入自己的代理逻辑;并借助FastClass机制加速代理效率

  • 优点

    • 无需继承于接口

    • 性能更好

  • 缺点

    • 需要使用第三方cglib类库

应用实例

  • CGLIB代理应用实例,用于代理一个消息发送与接收的服务

    • 在Maven配置中引入cglib库依赖

      <!--cglib依赖-->
      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.3.0</version>
      </dependency>
    • 独立服务类,无需具备父接口

      package com.demo.proxy.service;
      ​
      /**
       * 我的消息发送类,无任何继承关系
       */
      public class MyMessageService {
          /**
           * 发送消息方法
           * @param message
           * @return
           */
          public String send(String message){
              String result = "发送消息:"+message;
              System.out.println(result);
              return result;
          }
      ​
          public String receive(String message){
              String result = "接收消息:"+message;
              System.out.println(result);
              return result;
          }
      }
    • CGLIB代理类

      package com.demo.proxy.cglib;
      ​
      import net.sf.cglib.proxy.MethodInterceptor;
      import net.sf.cglib.proxy.MethodProxy;
      ​
      import java.lang.reflect.Method;
      ​
      /**
       * CGLIB代理类
       * 需要实现MethodInterceptor接口,并覆盖intercept方法
       */
      public class MessageMethodInterceptor implements MethodInterceptor {
          /**
           * 代理方法
           *
           * @param obj         被代理对象
           * @param method      被拦截的方法元数据
           * @param args        方法参数列表
           * @param methodProxy 原始方法
           */
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
              //调用方法之前,可以添加自己的操作
              System.out.println("【CGLIB代理开始代理...】代理方法:" + method.getName());
              Object object = methodProxy.invokeSuper(obj, args);
              //调用方法之后,同样可以添加自己的操作
              System.out.println("【CGLIB代理结束代理。】代理方法:" + method.getName());
              return object;
          }
      }
    • CGLIB代理类工厂类

      package com.demo.proxy.cglib;
      ​
      import net.sf.cglib.proxy.Enhancer;
      ​
      /**
       * CGLIB代理类工厂类
       */
      public class CglibProxyFactory {
          /**
           * 获取代理类
           *
           * @param clazz
           * @return
           */
          public static Object getProxy(Class <?> clazz) {
              //创建动态代理增强类
              Enhancer enhancer = new Enhancer();
              //设置类加载器
              enhancer.setClassLoader(clazz.getClassLoader());
              //设置被代理类元信息
              enhancer.setSuperclass(clazz);
              //设置方法拦截器
              enhancer.setCallback(new MessageMethodInterceptor());
              //创建代理对象
              Object result = enhancer.create();
      ​
              return result;
          }
      }
    • 使用CGLIB代理

      package com.demo.proxy;
      ​
      import com.demo.proxy.cglib.CglibProxyFactory;
      import com.demo.proxy.jdkproxy.JdkProxyFactory;
      import com.demo.proxy.service.IMessageService;
      import com.demo.proxy.service.MessageServiceImpl;
      import com.demo.proxy.service.MyMessageService;
      ​
      /**
       * 使用CGLIB代理
       */
      public class ProxyApplication {
          public static void main(String[] args) {
              System.out.println("---------------CGLIB代理---------------");
              MyMessageService messageServiceOfCglib = (MyMessageService) CglibProxyFactory.getProxy(MyMessageService.class);
              messageServiceOfCglib.send("你好!");
              messageServiceOfCglib.receive("感谢调用。");
          }
      }
    • 运行结果

    • 注意,JDK17或以上,使用cglib库会报错,需要添加启动的VM options,值为:--add-opens java.base/java.lang=ALL-UNNAMED,即可成功运行;具体步骤如下(针对IDEA 2023)

      1. 点击菜单Run->Edit Configurations...进入运行配置页面,如下图

      2. 然后点击Add VM options,如下图

      3. 最后添加配置选项:--add-opens java.base/java.lang=ALL-UNNAMED,如下图

注意点

  • final方法

    • JDK代理,是基于接口的,不存在final方法

    • CGLIB代理,final修改的方法不会被拦截

  • static方法

    • JDK代理,有方法体,且不能被子类覆盖,会编译错误

    • CGLIB代理,子类不能覆盖,不会报错,但也不会被拦截

  • 非public方法

    • JDK代理,接口的方法都是public的,不存在此问题

    • CGLIB代理,private的不能被代理并拦截,其他的都可以

Spring中的动态代理使用

  • Spring中会根据需要代理的对象是否有父接口来确定使用JDK代理还是CGLIB代理

  • 如MyBatis中

    • 针对XXXMapper接口,使用的就是JDK代理,结合XXXMapper.xml,动态生成代理类进行高用

    • 针对延迟加载,通过lazyLoadingEnabled=true|false配置,利用CGLIB代理,针对association对象,如果为空,使用代理拦截,然后通过SQL查询加载数据

  • ...

代码

网盘地址:链接:https://pan.baidu.com/s/15ukcxYf-1-eHSvMMDRmaww?pwd=8888 提取码:8888

posted @ 2024-01-14 15:21  拐子  阅读(118)  评论(0编辑  收藏  举报