乐趣区

关于java:似懂非懂的-AspectJ

明天想和小伙伴们聊一下咱们在应用 Spring AOP 时,一个十分常见的概念 AspectJ。

1. 对于代理

小伙伴们晓得,Java 23 种设计模式中有一种模式叫做代理模式,这种代理咱们能够将之称为动态代理,Spring AOP 咱们常说是一种动静代理,那么这两种代理的区别在哪里呢?

1.1 动态代理

这种代理在咱们日常生活中其实十分常见,例如房屋中介就相当于是一个代理,当房东须要出租房子的时候,须要公布广告、寻找客户、清理房间。。。因为比拟麻烦,因而房东能够将租房子这件事件委托给两头代理去做。这就是一个动态代理。

我通过一个简略的代码来演示一下,首先咱们有一个租房的接口,如下:

public interface Rent {void rent();
}

房东实现了该接口,示意想要出租屋宇:

public class Landlord implements Rent{
    @Override
    public void rent() {System.out.println("房屋出租");
    }
}

中介作为两头代理,也实现了该接口,同时代理了房东,如下:

public class HouseAgent implements Rent {
    private Landlord landlord;

    public HouseAgent(Landlord landlord) {this.landlord = landlord;}

    public HouseAgent() {}

    @Override
    public void rent() {publishAd();
        landlord.rent();
        agencyFee();}

    public void publishAd() {System.out.println("公布招租广告");
    }

    public void agencyFee() {System.out.println("收取中介费");
    }
}

能够看到,中介的 rent 办法中,除了调用房东的 rent 办法之外,还调用了 publishAd 和 agencyFee 两个办法。

接下来客户租房,只须要和代理打交道就能够了,如下:

public class Client {public static void main(String[] args) {Landlord landlord = new Landlord();
        HouseAgent houseAgent = new HouseAgent(landlord);
        houseAgent.rent();}
}

这就是一个简略的代理模式。无论大家是否有接触过 Java 23 种设计模式,下面这段代码应该都很好了解。

这是动态代理。

1.2 动静代理

动静代理考究在不扭转原类原办法的状况下,加强指标办法的性能,例如,大家平时应用的 Spring 事务性能,在不扭转指标办法的状况下,就能够通过动静代理为办法增加事务处理能力。再比方松哥在 TienChin 我的项目中所讲的日志解决、接口幂等性解决、多数据源解决等,都是动静代理能力的体现:

从实现原理上,咱们又能够将动静代理划分为两大类:

  • 编译时加强。
  • 运行时加强。

1.2.1 编译时加强

编译时加强,这种有点相似于 Lombok 的感觉,就是在编译阶段就间接生成了代理类,未来运行的时候,就间接运行这个编译生成的代理类,AspectJ 就是这样一种编译时加强的工具。

AspectJ 全称是 Eclipse AspectJ,其官网地址是:http://www.eclipse.org/aspectj,截止到本文写作时,目前最新版本为:1.9.7。

从官网咱们能够看到 AspectJ 的定位:

  1. 基于 Java 语言的面向切面编程语言。
  2. 兼容 Java。
  3. 易学易用。

应用 AspectJ 时须要应用专门的编译器 ajc。

1.2.2 运行时加强

运行时加强则是指借助于 JDK 动静代理或者 CGLIB 动静代理等,在内存中长期生成 AOP 动静代理类,咱们在 Spring AOP 中常说的动静代理,个别是指这种运行时加强。

咱们素日开发写的 Spring AOP,基本上都是属于这一类。

2. AspectJ 和 Spring AOP

通过后面的介绍,置信大家曾经明确了 AspectJ 其实也是 AOP 的一种实现,只不过它是编译时加强。

接下来,松哥再通过三个具体的案例,来和小伙伴们演示编译时加强和运行时加强。

2.1 AspectJ

首先,在 IDEA 中想要运行 AspectJ,须要先装置 AspectJ 插件,就是上面这个:

装置好之后,咱们须要在 IDEA 中配置一下,应用 ajc 编译器代替 javac(这个是针对以后我的项目的设置,所以能够释怀批改):

有如下几个须要批改的点:

  1. 首先批改编译器为 ajc。
  2. 将应用的 Java 版本改为 8,这个一共有两个中央须要批改。
  3. 设置 aspectjtools.jar 的地位,这个 jar 包须要本人提前准备好,能够从 Maven 官网下载,而后在这里配置 jar 的门路,配置实现之后,点击 test 按钮进行测试,测试胜利就会弹出来图中的弹框。

对于第 3 步所须要的 jar,也能够在我的项目的 Maven 中增加如下依赖,主动下载,下载到本地仓库之后,再删除掉 pom.xml 中的配置即可:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.9.7.M3</version>
</dependency>

这样,开发环境就筹备好了。

接下来,假如我有一个银行转帐的办法:

public class MoneyService {public void transferMoney() {System.out.println("转账操作");
    }
}

我想给这个办法增加事务,那么我就新建一个 Aspect,如下:

public aspect TxAspect {void around():call(void MoneyService.transferMoney()){System.out.println("开启事务");
        try {proceed();
            System.out.println("提交事务事务");
        } catch (Exception e) {System.out.println("回滚事务");
        }
    }
}

这就是 AspectJ 的语法,跟 Java 有点像,然而不太一样。须要留神的是,这个 TxAspect 不是一个 Java 类,它的后缀是 .aj

proceed 示意继续执行指标办法,前后逻辑比较简单,我就不多说了。

最初,咱们去运行转账服务:

public class Demo01 {public static void main(String[] args) {MoneyService moneyService = new MoneyService();
        moneyService.transferMoney();}
}

运行后果如下:

这就是一个动态代理。

为什么这么说呢?咱们通过 IDEA 来查看一下 TxAspect 编译之后的后果:

@Aspect
public class TxAspect {
    static {
        try {ajc$postClinit();
        } catch (Throwable var1) {ajc$initFailureCause = var1;}

    }

    public TxAspect() {}

    @Around(value = "call(void MoneyService.transferMoney())",
        argNames = "ajc$aroundClosure"
    )
    public void ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afea(AroundClosure ajc$aroundClosure) {System.out.println("开启事务");

        try {ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afeaproceed(ajc$aroundClosure);
            System.out.println("提交事务事务");
        } catch (Exception var2) {System.out.println("回滚事务");
        }

    }

    public static TxAspect aspectOf() {if (ajc$perSingletonInstance == null) {throw new NoAspectBoundException("org_javaboy_demo_p2_TxAspect", ajc$initFailureCause);
        } else {return ajc$perSingletonInstance;}
    }

    public static boolean hasAspect() {return ajc$perSingletonInstance != null;}
}

再看一下编译之后的启动类:

public class Demo01 {public Demo01() { }

    public static void main(String[] args) {MoneyService moneyService = new MoneyService();
        transferMoney_aroundBody1$advice(moneyService, TxAspect.aspectOf(), (AroundClosure)null);
    }
}

能够看到,都是批改后的内容了。

所以说 AspectJ 的作用就有点相似于 Lombok,间接在编译期间将咱们的代码改了,这就是编译时加强。

2.2 Spring AOP

Spring AOP 在开发的时候,其实也应用了 AspectJ 中的注解,像咱们平时应用的 @Aspect、@Around、@Pointcut 等,都是 AspectJ 里边提供的,然而 Spring AOP 并未借鉴 AspectJ 的编译时加强,Spring AOP 没有应用 AspectJ 的编译器和织入器,Spring AOP 还是应用了运行时加强。

运行时加强能够利用 JDK 动静代理或者 CGLIB 动静代理来实现。我别离来演示。

2.2.1 JDK 动静代理

JDK 动静代理有一个要求,就是被代理的对象须要有接口,没有接口不行,CGLIB 动静代理则无此要求。

假如我当初有一个计算器接口:

public interface ICalculator {int add(int a, int b);
}

这个接口有一个实现类:

public class CalculatorImpl implements ICalculator {
    @Override
    public int add(int a, int b) {System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
}

当初,我想通过动静代理实现统计该接口的执行工夫性能,JDK 动静代理如下:

public class Demo02 {public static void main(String[] args) {CalculatorImpl calculator = new CalculatorImpl();
        ICalculator proxyInstance = (ICalculator) Proxy.newProxyInstance(Demo02.class.getClassLoader(), new Class[]{ICalculator.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime = System.currentTimeMillis();
                Object invoke = method.invoke(calculator, args);
                long endTime = System.currentTimeMillis();
                System.out.println(method.getName() + "办法执行耗时" + (endTime - startTime) + "毫秒");
                return invoke;
            }
        });
        proxyInstance.add(3, 4);
    }
}

不须要任何额定依赖,都是 JDK 自带的能力:

  1. Proxy.newProxyInstance 办法示意要生成一个动静代理对象。
  2. newProxyInstance 办法有三个参数,第一个是一个类加载器,第二个参数是一个被代理的对象所实现的接口,第三个则是具体的代理逻辑。
  3. 在 InvocationHandler 中,有一个 invoke 办法,该办法有三个参数,别离示意以后代理对象,被拦挡下来的办法以及办法的参数,咱们在该办法中能够统计被拦挡办法的执行工夫,通过形式执行被拦挡下来的指标办法。
  4. 最终,第一步的办法返回了一个代理对象,执行该代理对象,就有代理的成果了。

下面这个案例就是一个 JDK 动静代理。这是一种运行时加强,在编译阶段并未批改咱们的代码。

2.2.2 CGLIB 动静代理

从 SpringBoot2 开始,AOP 默认应用的动静代理就是 CGLIB 动静代理了,相比于 JDK 动静代理,CGLIB 动静代理反对代理一个类。

应用 CGLIB 动静代理,须要首先增加依赖,如下:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

假如我有一个计算器,如下:

public class Calculator {public int add(int a, int b) {System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
}

大家留神,这个计算器就是一个实现类,没有接口。

当初,我想统计这个计算器办法的执行工夫,首先,我增加一个办法执行的拦截器:

public class CalculatorInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {long startTime = System.currentTimeMillis();
        Object result = methodProxy.invokeSuper(o, objects);
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + "办法执行耗时" + (endTime - startTime) + "毫秒");
        return result;
    }
}

当把代理办法拦挡下来之后,额定要做的事件就在 intercept 办法中实现。通过执行 methodProxy.invokeSuper 能够调用到代理办法。

最初,配置 CGLIB,为办法配置加强:

public class Demo03 {public static void main(String[] args) {Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Calculator.class);
        enhancer.setCallback(new CalculatorInterceptor());
        Calculator calculator = (Calculator) enhancer.create();
        calculator.add(4, 5);
    }
}

这里其实就是创立了字节增强器,为生成的代理对象配置 superClass,而后设置拦挡下来之后的回调函数就行了,最初通过 create 办法获取到一个代理对象。

这就是 CGLIB 动静代理。

3. 小结

通过下面的介绍,当初大家应该搞明确了动态代理、编译时加强的动静代理和运行时加强的动静代理了吧~

那么咱们在我的项目中到底该如何抉择呢?

先来说 AspectJ 的几个劣势吧。

  1. Spring AOP 因为要生成动静代理类,因而,对于一些 static 或者 final 润饰的办法,是无奈代理的,因为这些办法是无奈被重写的,final 润饰的类也无奈被继承。然而,AspectJ 因为不须要动静生成代理类,一切都是编译时实现的,因而,这个问题在 AspectJ 中人造的就被解决了。
  2. Spring AOP 有一个局限性,就是只能用到被 Spring 容器治理的 Bean 上,其余的类则无奈应用,AspectJ 则无此限度(话说回来,Java 我的项目 Spring 基本上都是标配了,所以这点其实到也不重要)。
  3. Spring AOP 只能在运行时加强,而 AspectJ 则反对编译时加强,编译后加强以及运行时加强。
  4. Spring AOP 反对办法的加强,然而 AspectJ 反对办法、属性、结构器、动态对象、final 类 / 办法等的加强。
  5. AspectJ 因为是编译时加强,因而运行效率也要高于 Spring AOP。
  6. 。。。

尽管 AspectJ 有这么多劣势,然而 Spring AOP 却有另外一个制胜法宝,那就是 简略易用

所以,咱们日常开发中,还是 Spring AOP 应用更多。

退出移动版