明天想和小伙伴们聊一下咱们在应用 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 的定位:
- 基于 Java 语言的面向切面编程语言。
- 兼容 Java。
- 易学易用。
应用 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(这个是针对以后我的项目的设置,所以能够释怀批改):
有如下几个须要批改的点:
- 首先批改编译器为 ajc。
- 将应用的 Java 版本改为 8,这个一共有两个中央须要批改。
- 设置 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 编译之后的后果:
@Aspectpublic 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 自带的能力:
- Proxy.newProxyInstance 办法示意要生成一个动静代理对象。
- newProxyInstance 办法有三个参数,第一个是一个类加载器,第二个参数是一个被代理的对象所实现的接口,第三个则是具体的代理逻辑。
- 在 InvocationHandler 中,有一个 invoke 办法,该办法有三个参数,别离示意以后代理对象,被拦挡下来的办法以及办法的参数,咱们在该办法中能够统计被拦挡办法的执行工夫,通过形式执行被拦挡下来的指标办法。
- 最终,第一步的办法返回了一个代理对象,执行该代理对象,就有代理的成果了。
下面这个案例就是一个 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 的几个劣势吧。
- Spring AOP 因为要生成动静代理类,因而,对于一些 static 或者 final 润饰的办法,是无奈代理的,因为这些办法是无奈被重写的,final 润饰的类也无奈被继承。然而,AspectJ 因为不须要动静生成代理类,一切都是编译时实现的,因而,这个问题在 AspectJ 中人造的就被解决了。
- Spring AOP 有一个局限性,就是只能用到被 Spring 容器治理的 Bean 上,其余的类则无奈应用,AspectJ 则无此限度(话说回来,Java 我的项目 Spring 基本上都是标配了,所以这点其实到也不重要)。
- Spring AOP 只能在运行时加强,而 AspectJ 则反对编译时加强,编译后加强以及运行时加强。
- Spring AOP 反对办法的加强,然而 AspectJ 反对办法、属性、结构器、动态对象、final 类/办法等的加强。
- AspectJ 因为是编译时加强,因而运行效率也要高于 Spring AOP。
- 。。。
尽管 AspectJ 有这么多劣势,然而 Spring AOP 却有另外一个制胜法宝,那就是简略易用!
所以,咱们日常开发中,还是 Spring AOP 应用更多。