动态代理与Spring Aop的两种实现

在说Spring Aop的动态代理之前,首先我们得先了解什么是动态代理

要了解动态代理就不得不先说一下一种常用的设计模式--代理模式了(有基础的小伙伴可以直接看重点)

在博主的理解中,代理充当着在客户和目标之间一个中介作用的身份,根据创建代理类的时间点,又可以分为静态代理和动态代理

静态代理的组成一般为代理类、被代理类、被代理类接口(包含<需要被代理的方法:下面简称proxy方法>),代理类与被代理类同时实现被代理类接口,被代理类对proxy方法进行了真正的实现,而代理类对proxy方法的实现不是真正的实现,代理类对proxy方法的实现仅在包装了一些其他的操作之后去调用了被代理类实现的proxy方法,最终完成了proxy方法的实现

有人可能会问,为什么不直接调用目标方法,反而通过调用代理方法间接的去调用目标方法,如此绕一大圈的意义何在,其实很简单,代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后处理消息等,代理模式就是在访问目标对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。举个栗子,目标类A和目标类B同时实现了方法X,此时假如目标类A和目标类B都需要在方法X执行之前打印一段相同的文字(不要问为什么打印文字,为了简单),在没有代理的情况下,需要同时去修改目标类A和目标类B,如果目标类不只A、B呢,所以,如果有了代理类的存在,一切都会很简单,我们只需要在代理类的方法X调用目标类A和目标类B的方法X之前打印这段文字即可,即在代理过程中切入了一些其他操作,同时代理模式还隐藏了目标类的真正实现,客户无需关心目标类的具体实现,实现了目标类与客户类之间的解耦

ok,有了前面的基础,今天的重点来了

什么是动态代理<1>,不,确切的说,为什么使用动态代理<2>

<1>上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”运用反射机制动态创建而成,其实动态代理就是又一种的静态代理模式,使用动态代理时,我们需要定义一个位于代理类与目标类之间的中介类(以下简称调用处理器类),生成的代理类所有的方法调用都会转到调用处理器中同一个方法去执行,而调用处理器又根据传入参数将方法调用转到目标类中去执行,代理类对调用处理器类进行了代理,而调用处理器对目标类进行了代理,也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理

<2>相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数,举个栗子,如果我们有很多代理类,每个代理类有很多代理方法,而我们要在每个代理类的每个代理方法之前打印一段相同的文字,此刻,是不是要修改每个代理类的每个代理方法,然而如果使用了动态代理,我们只需要修改一处,其次如果目标类的需要被代理的方法发生了变化(增加或减少),代理类也要相应的做出修改,而动态代理则无需考虑这些问题

ok,继续接入正题

AOP(面向切面编程)

面向切面编程,是一种通过预编译方式运行期间动态实现在不修改源代码的情况下给程序动态统一添加功能的一种技术

Spring Aop是一种运行时进行动态增强的 AOP 框架,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理,与之相对的还有一种编译时增强的 AOP 框架AspectJ (静态代理),编译时增强的 AOP 框架在性能上更有优势——因为运行时动态增强的 AOP 框架需要每次运行时都进行动态增强

由此可见静态代理与动态代理各有千秋,如何抉择自行斟酌

那么Spring Aop是怎么使用的呢,下面插入一段配置

<aop:config>
        <!-- 切入点表达式  确定目标类 -->
        <aop:pointcut expression="execution(* 项目名.包名.DaoImpl.*(..))" id="切入点id"/>
    
        <!-- ref指向对象就是切面 -->
        <aop:aspect ref="transaction">
            <aop:before method="beginTransaction" pointcut-ref="切入点id"/>
            <aop:after-returning method="commit" pointcut-ref="切入点id"/>
        </aop:aspect>
</aop:config>

从这段代码可以猜测,Spring Aop的原理如下

1.当spring容器对配置文件解析到<aop:config>的时候,把切入点表达式解析出来,按照切入点表达式匹配spring容器内容的bean
2.如果匹配成功,则为该bean创建代理对象
3.当客户端利用context.getBean获取一个对象时,如果该对象有代理对象,则返回代理对象,如果没有代理对象,则返回对象本身(在Spring应用很深哦)

那么Spring Aop的代理机制是怎么实现的呢

spring AOP代理机制

1、若目标对象实现了若干接口,spring使用JDK的动态代理

      优点:因为有接口,所以使系统更加松耦合

      缺点:为每一个目标类创建接口

2、若目标对象没有实现任何接口,spring使用CGLIB库的动态代理

       优点:因为代理类与目标类是继承关系,所以不需要有接口的存在

       缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好

下面介绍一下Spring Aop代理机制的两种动态代理的使用与实现原理

  • jdk的动态代理的使用与实现原理

先来介绍一下InvocationHandler接口
在使用jdk的动态代理时,我们需要定义一个调用处理器类,这个调用处理器类被要求实现InvocationHandler接口,这个接口的定义如下

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

}

当我们调用代理类对象的方法时,这个“调用”就会被转送到invoke方法中

仔细看方法参数,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法(代理类被调用的那个方法)的参数

我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理,但一般在开发环境下是不靠谱的,如果方法很多的话,需要写很多的if语句)

使用jdk的动态代理的步骤是

(1)目标类的定义
jdk的动态代理方式下,要求目标类必须实现某个接口。目标类的定义如下

public class Duck implements Brid { 
  
  public void say() {  
    System.out.println("呱呱"); 
  }  
} 

(2)调用处理器类的定义
上面提过,调用处理器类必须实现InvocationHandler接口。调用处理器类的定义如下

public class CustomProxy implements InvocationHandler { 
  
  /**
   * 目标类
   */
  private Object obj;  
  
  public CustomProxy(Object obj) { 
    this.obj = obj; 
  } 
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    System.out.println("打印文字"); 
    Object result = method.invoke(obj, args); 
    return result;  
  }   
}

(3)动态生成代理类
动态生成代理类如下

public class Main { 
  
  public static void main(String[] args) { 
    //创建调用处理器
    CustomProxy inter = new CustomProxy(new Duck());
    //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 
    //获得代理实例,其实就是实现了Brid接口的类
    Duck duck = (Duck)Proxy.newProxyInstance(Duck.class.getClassLoader(), new Class[] 
    {Brid.class}, inter);  
    //通过代理实例调用代理方法,实际上会转到invoke方法调用
    duck.say(); 
  }   
}

在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器,调用处理器再通过method.invoke(obj, args)调用了目标类的相应方法,这就是动态代理的整个流程了,画一个大概的流程吧,代理实例.say()调用 调用处理器实例.invoke(),调用处理器实例.invoke()又通过传入参数调用目标实例.say(),并在目标实例.say()方法前后织入横切逻辑,思路是不是越来越清晰了

大头来了,代理类实例是如何产生,又是如何与调用处理器关联的呢

newProxyInstance方法的声明如下

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        throws IllegalArgumentException

方法的三个参数含义分别如下
loader:定义了代理类的ClassLoder(类加载器)
interfaces:代理类实现的接口列表
h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例

方法的实现如下

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
        throws IllegalArgumentException
{
  Objects.requireNonNull(h);
  /**
   * 拿到要实现的接口
   */
  final Class<?>[] intfs = interfaces.clone();
  final SecurityManager sm = System.getSecurityManager();
  if (sm != null) {
    checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  }
  /**
   * 得到代理类类型
   * 传入了类加载器和要实现的接口,通过该类加载器加载该类并为其实现需要实现的接口,
   * 并附加上接口实现逻辑(猜测是调用 调用处理器类的invoke方法)
   */
  Class<?> cl = getProxyClass0(loader, intfs);
  try {
    if (sm != null) {
      checkNewProxyPermission(Reflection.getCallerClass(), cl);
    }
    /**
     * 得到代理类构造器
     */
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    if (!Modifier.isPublic(cl.getModifiers())) {
      AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
          cons.setAccessible(true);
          return null;
        }
      });
    }
    /**
     * 通过构造函数得到代理类
     * 从这里可以看出参数是InvocationHandler接口类
     */
    return cons.newInstance(new Object[]{h});
  } catch (IllegalAccessException|InstantiationException e) {
    throw new InternalError(e.toString(), e);
  } catch (InvocationTargetException e) {
    Throwable t = e.getCause();
    if (t instanceof RuntimeException) {
      throw (RuntimeException) t;
    } else {
      throw new InternalError(t.toString(), t);
    }
  } catch (NoSuchMethodException e) {
    throw new InternalError(e.toString(), e);
  }
}

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
  if (interfaces.length > 65535) {
    throw new IllegalArgumentException("interface limit exceeded");
  }
  /**
   * 如果定义代理类的给定的类加载器loader要实现的给定的接口interfaces存在,这将简单地返回缓存的副本;
   * 否则,它将通过ProxyClassFactory创建代理类
   */
   return proxyClassCache.get(loader, interfaces);
}

newProxyInstance方法中相关的主要部分做了注释,其他的未注释部分从名字就能看出来,是在反射过程中加入了一些安全校验,由以上代码可以看出,这个方法的实现流程为,拿到需要实现的接口,生成一个实现了这些接口的代理类类型(怎么生成代理类类型涉及到更深层面的反射机制,感兴趣的小伙伴可以自行查阅),这个代理类类型有一个参数是InvocationHandler接口类(调用处理器)的构造函数,由此可以猜测生成的这个代理类类型还包含了一个调用处理器的引用,那么可以想象,生成的代理类类型的接口实现逻辑必然包含了对调用处理器实例.invoke()方法的调用,那么代理类与调用处理器,调用处理器与目标类之间的关联关系是不是一下就清楚了呢

下面是假想的通过Proxy.newProxyInstance(Duck.class.getClassLoader(), new Class[] {Brid.class}, inter)生成的代理类逻辑,当然实际情况会比这个更复杂一些

public class DuckProxy implements Brid { 
  private InvocationHandler h;
  public DuckProxy(InvocationHandler h){
    this.h = h;
  }
  public void say() {  
     h.invoke(传入参数包含了调用的方法信息);
  }  
} 
  • CGLIB库的动态代理的使用与实现原理

CGLIB库的动态代理原理与jdk的动态代理原理基本类似,不过与通过接口生成代理类的方式不同,CGLib库采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的方式拦截所有父类方法的调用,并顺势织入横切逻辑,创建的这个子类即动态代理中动态生成的代理类

下面简单说一下CGLIB库动态代理的使用流程

(1)目标类的定义:与jdk动态代理不同的是,CGLIB库动态代理的目标类定义无需必须实现某个接口

public class Duck { 
  
  public void say() {  
    System.out.println("呱呱"); 
  }  
} 

(2)调用处理器类的定义:与jdk动态代理类似,CGLIB库动态代理的调用处理器定义需要实现MethodInterceptor接口,CGLIB库动态代理的调用处理器的intercept方法相当于jdk动态代理的调用处理器的invoke方法

public class CustomProxy implements InvocationHandler { 
  
  private Enhancer enhancer = new Enhancer();
 
  /**
   * 通过字节码技术动态创建子类实例
   */
  public Object getProxy(Class clazz) {
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    return enhancer.create();
  } 
  
  
  @Override
  public Object intercept(Object arg0, Method arg1, Object[] arg2,MethodProxy arg3) 
  throws Throwable { 
    System.out.println("打印文字"); 
    Object result = arg3.invokeSuper(arg0, arg2); 
    return result;  
  }   
}

(3)动态生成代理类

public class Main { 
  
  public static void main(String[] args) { 
    //创建调用处理器
    CustomProxy inter = new CustomProxy();
    //获得代理实例,其实就是Duck的子类
    Duck duck = (Duck)inter.getProxy(Duck.class);  
    //通过代理实例调用代理方法,实际上会转到intercept方法调用
    duck.say(); 
  }   
}

CGLIB库动态代理与jdk动态代理的调用流程基本类似,CGLIB库动态代理与jdk动态代理的代理类的产生过程和原理也基本类似,就不详解了,感兴趣的回去自行实践
最后,各位看了觉得有帮助的小可爱们请点个赞哟

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值