明天想和小伙伴们聊一下咱们在应用 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 编译之后的后果:
@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 自带的能力:
- 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 应用更多。