概述

Hutool-aop概述

AOP模块主要针对JDK中动态代理进行封装,抽象动态代理为切面类Aspect,通过ProxyUtil代理工具类将切面对象与被代理对象融合,产生一个代理对象,从而可以针对每个方法执行前后做通用的功能。
在aop模块中,默认实现可以下两个切面对象:

  1. SimpleAspect 简单切面对象,不做任何操作,继承此对象可重写需要的方法即可,不必实现所有方法
  2. TimeIntervalAspect 执行时间切面对象,用于简单计算方法执行时间,然后通过日志打印方法执行时间

    由于AOP模块封装JDK的代理,故被代理对象必须实现接口。

切面代理工具-ProxyUtil

使用

使用JDK的动态代理实现切面

  1. 我们定义一个接口:

    1. public interface Animal{
    2. void eat();
    3. }
  2. 定义一个实现类:

    1. public class Cat implements Animal{
    2. @Override
    3. public void eat() {
    4. Console.log("猫吃鱼");
    5. }
    6. }
  3. 我们使用TimeIntervalAspect这个切面代理上述对象,来统计猫吃鱼的执行时间:

    1. Animal cat = ProxyUtil.proxy(new Cat(), TimeIntervalAspect.class);
    2. cat.eat();

    TimeIntervalAspect位于cn.hutool.aop.aspects包,继承自SimpleAspect,代码如下:

    1. public class TimeIntervalAspect extends SimpleAspect{
    2. //TimeInterval为Hutool实现的一个计时器
    3. private TimeInterval interval = new TimeInterval();
    4. @Override
    5. public boolean before(Object target, Method method, Object[] args) {
    6. interval.start();
    7. return true;
    8. }
    9. @Override
    10. public boolean after(Object target, Method method, Object[] args) {
    11. Console.log("Method [{}.{}] execute spend [{}]ms", target.getClass().getName(), method.getName(), interval.intervalMs());
    12. return true;
    13. }
    14. }

    执行结果为:

    1. 猫吃鱼
    2. Method [cn.hutool.aop.test.AopTest$Cat.eat] execute spend [16]ms

    在调用proxy方法后,IDE自动补全返回对象为Cat,因为JDK机制的原因,我们的返回值必须是被代理类实现的接口,因此需要手动将返回值改为Animal,否则会报类型转换失败。

使用Cglib实现切面

使用Cglib的好处是无需定义接口即可对对象直接实现切面,使用方式完全一致:

  1. 引入Cglib依赖

    1. <dependency>
    2. <groupId>cglib</groupId>
    3. <artifactId>cglib</artifactId>
    4. <version>3.2.7</version>
    5. </dependency>
  2. 定义一个无接口类(此类有无接口都可以)

    1. public class Dog {
    2. public String eat() {
    3. Console.log("狗吃肉");
    4. }
    5. }
    1. Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class);
    2. String result = dog.eat();

    执行结果为:

    1. 狗吃肉
    2. Method [cn.hutool.aop.test.AopTest$Dog.eat] execute spend [13]ms

    其它方法

    ProxyUtil中还提供了一些便捷的Proxy方法封装,例如newProxyInstance封装了Proxy.newProxyInstance方法,提供泛型返回值,并提供更多参数类型支持。

    原理

    动态代理对象的创建原理是假设创建的代理对象名为 $Proxy0:

  3. 根据传入的interfaces动态生成一个类,实现interfaces中的接口

  4. 通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load
  5. 调用$Proxy0的$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,并生成实现方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法。
  6. 将$Proxy0的实例返回给客户端。
  7. 当调用代理类的相应方法时,相当于调用 InvocationHandler.invoke(Object, Method, Object []) 方法。