細(xì)說SpringAOP的核心用法和原理解析

汪偉俊 | 作者
Java技術(shù)迷 | 出品
初識AOP
AOP:Aspect Oriented Programing,意為面向切面編程,對于習(xí)慣了傳統(tǒng)OOP思想的同學(xué)來說,AOP在一開始確實難以接受,但它能夠幫助我們在一些特定的業(yè)務(wù)上提供非常大的幫助。
來看一個例子:
public class Cacluate {
public int add(int num1,int num2){
return num1 + num2;
}
public int subtract(int num1,int num2){
return num1 - num2;
}
public int multiply(int num1,int num2){
return num1 * num2;
}
public int divide(int num1,int num2){
return num1 / num2;
}
}
這是一個做加減乘除運算的工具類,可以通過調(diào)用該類的方法計算兩個數(shù)的運算值:
public static void main(String[] args) {
Cacluate cacluate = new Cacluate();
int addResult = cacluate.add(10,2);
int subtractResult = cacluate.subtract(10,2);
int multiplyResult = cacluate.multiply(10,2);
int divideResult = cacluate.divide(10,2);
System.out.println(addResult + "--" + subtractResult + "--" + multiplyResult + "--" + divideResult);
}
運行結(jié)果:
12--8--20--5
現(xiàn)在需求來了,若是想在每次做運算之前輸出一個日志信息,記錄當(dāng)前系統(tǒng)的運行情況,我們應(yīng)該如何實現(xiàn)呢?
public class Cacluate {
public int add(int num1,int num2){
System.out.println("輸出日志信息,方法名:add,參數(shù):" + num1 + ";" + num2);
return num1 + num2;
}
public int subtract(int num1,int num2){
System.out.println("輸出日志信息,方法名:subtract,參數(shù):" + num1 + ";" + num2);
return num1 - num2;
}
public int multiply(int num1,int num2){
System.out.println("輸出日志信息,方法名:multiply,參數(shù):" + num1 + ";" + num2);
return num1 * num2;
}
public int divide(int num1,int num2){
System.out.println("輸出日志信息,方法名:divide,參數(shù):" + num1 + ";" + num2);
return num1 / num2;
}
}
這樣雖然實現(xiàn)了日志功能,但也能體會到這種方式的愚蠢,你需要在每個方法中添加相同重復(fù)的代碼,當(dāng)日志信息需要修改時,所有出現(xiàn)了日志的地方都需要修改,大大增加了維護(hù)成本。
為此,AOP思想誕生了,它通過橫向抽取機(jī)制為這類無法通過縱向繼承體系進(jìn)行抽象的重復(fù)性代碼提供了解決方案,AOP將日志操作橫向抽取出來,再將日志操作融合到業(yè)務(wù)邏輯中實現(xiàn)功能。
AOP的相關(guān)概念
在具體講解AOP之前,我們需要來了解一下AOP的有關(guān)概念。
JoinPoint——連接點
何為連接點?連接點表示的是程序執(zhí)行的某個特定的位置,比如你要在項目中加入日志的操作,你可以將其設(shè)置在類加載前、類加載后、方法執(zhí)行前、方法執(zhí)行后,而Spring只支持方法的連接點,即你只能在方法執(zhí)行前后來設(shè)置你需要進(jìn)行的操作。
PointCut——切點
切點又是什么呢?我們知道,對于方法執(zhí)行前后的位置我們稱之為連接點,但是,我們需要將日志操作設(shè)置在哪些類的哪些方法執(zhí)行前后再執(zhí)行呢?這一定位稱為切點,通常需要通過切點表達(dá)式進(jìn)行過濾。
Advice——通知
通知,也叫增強(qiáng),當(dāng)我們通過切點表達(dá)式指定了需要切入的位置后,Spring就會在每個切點的位置增強(qiáng)該方法,例如添加上你的日志操作。
Aspect——切面
切面包括橫切邏輯的定義,也包括連接點的定義,Spring AOP將切面所定義的橫切邏輯切入到切面所指定的連接點中,即它的作用就是將切點和通知結(jié)合定位到連接點上。
動態(tài)代理
需要了解的是,Spring AOP使用動態(tài)代理在運行期切入增強(qiáng)的代碼,所以我們需要掌握動態(tài)代理的相關(guān)知識。
動態(tài)代理是反射的高級應(yīng)用,JDK為我們提供了Proxy和InvocationHandler接口,通過該類和該接口生成代理對象實現(xiàn)方法的增強(qiáng)。
看一個例子:
public interface ICar {
void use();
}
public class Car implements ICar {
@Override
public void use() {
System.out.println("汽車使用汽油");
}
}
定義一個接口和接口的實現(xiàn)類,表示使用汽車需要汽油,但是汽車并不一定只能使用汽油,還能使用電力驅(qū)動,為此,可以使用動態(tài)代理增強(qiáng)該use()方法:
public class CarInvocatioHandler<T> implements InvocationHandler {
private T t;
public CarInvocatioHandler(T t){
this.t = t;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//若執(zhí)行的是use()方法,則對該方法進(jìn)行增強(qiáng)
if(method.getName().equals("use")){
//調(diào)用原方法,保持原有的功能
method.invoke(t,args);
//增強(qiáng)方法
System.out.println("汽車使用電力");
}
return null;
}
}
首先需要實現(xiàn)InvocationHandler接口的invoke方法,并調(diào)用invoke()方法,保證原有的功能不被破壞,然后再編寫需要增強(qiáng)的邏輯,返回值是目標(biāo)代理方法的返回值,沒有就返回null即可,有了該接口的實現(xiàn)類后,接下來:
public static void main(String[] args) {
ICar car = new Car();
CarInvocatioHandler invocatioHandler = new CarInvocatioHandler(car);
ICar proxy = (ICar) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), invocatioHandler);
proxy.use();
}
通過Proxy類創(chuàng)建代理對象,并按照CarInvocatioHandler實現(xiàn)類的規(guī)則進(jìn)行增強(qiáng),運行結(jié)果:
汽車使用汽油
汽車使用電力
我們將這一過程類比到剛才的場景中去,要對四種計算方法設(shè)置日志操作,只需要分別對這四個方法進(jìn)行動態(tài)代理,增強(qiáng)它們即可,然而動態(tài)代理有一個缺陷,就是只能針對接口做代理,所以我們需要對計算方法做一個處理:
public interface CacluateDao {
int add(int num1,int num2);
int subtract(int num1,int num2);
int multiply(int num1,int num2);
int divide(int num1,int num2);
}
public class Cacluate implements CacluateDao{
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int subtract(int num1, int num2) {
return num1 - num2;
}
@Override
public int multiply(int num1, int num2) {
return num1 * num2;
}
@Override
public int divide(int num1, int num2) {
return num1 / num2;
}
}
這樣我們就可以增強(qiáng)這些方法了:
public class CacluateInvocationHandler<T> implements InvocationHandler {
private T t;
public CacluateInvocationHandler(T t){
this.t = t;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int result = 0;
if(method.getName().equals("add")){
//增強(qiáng)方法
System.out.println("輸出日志信息,方法名:" + method.getName() + ",參數(shù):" + Arrays.asList(args));
//保持原有功能
method.invoke(t,args);
result = Integer.valueOf(args[0] + "") + Integer.valueOf(args[1] + "");
}
return result;
}
}
然后編寫動態(tài)代理:
public static void main(String[] args) {
CacluateDao cacluate = new Cacluate();
CacluateInvocationHandler invocationHandler = new CacluateInvocationHandler(cacluate);
CacluateDao proxy = (CacluateDao) Proxy.newProxyInstance(cacluate.getClass().getClassLoader(), cacluate.getClass().getInterfaces(), invocationHandler);
int result = proxy.add(3, 4);
System.out.println(result);
}
運行結(jié)果:
輸出日志信息,方法名:add,參數(shù):[3, 4]
7
其它方法的增強(qiáng)方式也是類似的,這里就不重復(fù)舉例了。
剛才也說了,Java的動態(tài)代理只支持接口代理,所以是有一定的缺陷的,對此,Spring并不僅僅采用動態(tài)代理來實現(xiàn)方法的增強(qiáng),還使用了CGLib技術(shù),它可以為一個類創(chuàng)建子類,然后通過子類中的攔截方法攔截所有父類的方法并進(jìn)行增強(qiáng)。
通知介紹
Advice,確切地說它應(yīng)該被理解為增強(qiáng),前面也一直在強(qiáng)調(diào)方法的增強(qiáng),那么接下來我們來看看在Spring AOP中是如何去實現(xiàn)方法的增強(qiáng)的。
前置通知
public class Cacluate{
public int add(int num1, int num2) {
return num1 + num2;
}
public int subtract(int num1, int num2) {
return num1 - num2;
}
public int multiply(int num1, int num2) {
return num1 * num2;
}
public int divide(int num1, int num2) {
return num1 / num2;
}
}
首先我們從實現(xiàn)接口的束縛中脫離出來,然后使用Spring AOP進(jìn)行增強(qiáng),比如需要在計算方法之前輸出日志信息,你就可以這樣做:
@Component
@Aspect
public class LoggingAspect {
@Before("execution(* com.wwj.aop.util.*.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法前的日志信息,方法參數(shù)為:" + list);
}
}
定義一個日志切面,并編寫一個方法,在該方法上添加@Before注解,然后編寫切面表達(dá)式,即指定該方法切入到哪個類的哪些方法,這里的execution(* com.wwj.aop.util.*.*(..))則表示將該方法切入到com.wwj.aop.util包下的所有類的所有方法,此時執(zhí)行計算方法:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Cacluate cacluate = (Cacluate) context.getBean("cacluate");
int addResult = cacluate.add(3, 5);
System.out.println(addResult);
}
運行結(jié)果:
執(zhí)行在add方法前的日志信息,方法參數(shù)為:[3, 5]
8
后置通知
學(xué)會了前置通知,那么后面的內(nèi)容就會非常簡單了,比如后置通知,只是簡單地修改一下注解名就可以了:
@After("execution(* com.wwj.aop.util.*.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list);
}
執(zhí)行測試代碼:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 2]
5
需要注意的是后置通知不管程序是否發(fā)生錯誤都會被執(zhí)行,比如:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Cacluate cacluate = (Cacluate) context.getBean("cacluate");
int addResult = cacluate.divide(10,0);
System.out.println(addResult);
}
在數(shù)學(xué)中,0作為除數(shù)是不被允許的,程序在進(jìn)行計算時肯定會產(chǎn)生異常,然而此時后置通知仍然會被執(zhí)行:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 0]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 0]
Exception in thread "main" java.lang.ArithmeticException: / by zero
返回通知
返回通知與后置通知類似,區(qū)別在于,返回通知需要在程序正確執(zhí)行后才會執(zhí)行,若程序發(fā)生異常,則返回通知不會執(zhí)行:
@AfterReturning(value = "execution(* com.wwj.aop.util.*.*(..))",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list + ",運行結(jié)果為:" + result);
}
運行結(jié)果:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 5]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 5]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 5],運行結(jié)果為:2
2
返回通知是能夠獲取到方法的執(zhí)行結(jié)果的,具體做法是在@AfterReturning中指定returning屬性值,然后在方法的入?yún)⒅卸x一個與其相同的變量即可。
異常通知
異常通知,顧名思義,只有當(dāng)程序發(fā)生異常時才會執(zhí)行,異常通知能夠獲取到方法發(fā)生了什么異常:
@AfterThrowing(value = "execution(* com.wwj.aop.util.*.*(..))",throwing = "except")
public void afterThrowingMethod(JoinPoint joinPoint,Exception except){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list + ",異常信息為:" + except);
}
獲取異常信息的方式與返回通知獲取結(jié)果值的方式是一樣的,看運行結(jié)果:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 0]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 0]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 0],異常信息為:java.lang.ArithmeticException: / by zero
環(huán)繞通知
環(huán)繞通知的功能比較強(qiáng)大,它能夠通過一個方法實現(xiàn)之前的所有通知效果,直接看代碼:
@Around("execution(* com.wwj.aop.util.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
try{
//前置通知
System.out.println("執(zhí)行在" + methodName + "方法前的日志信息,方法參數(shù)為:" + args);
//執(zhí)行目標(biāo)方法,保持原有功能
result = joinPoint.proceed();
//返回通知
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + args + ",運行結(jié)果為:" + result);
}catch (Throwable e){
//異常通知
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + args + ",異常信息為:" + e);
}
//后置通知
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + args);
return result;
}
運行結(jié)果:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 2],運行結(jié)果為:5
執(zhí)行在divide方法后的日志信息,方法參數(shù)為:[10, 2]
5
從環(huán)繞通知應(yīng)該不難理解,為什么后置通知無論什么情況都會執(zhí)行,且只有返回通知能夠獲取到方法執(zhí)行結(jié)果,異常通知如何能夠獲取到異常信息,一目了然。
需要注意,環(huán)繞通知必須攜帶ProceedingJoinPoint參數(shù)并且必須有返回值。
xml配置實現(xiàn)AOP
前面介紹了五種通知的用途和注意事項,順帶著還貼出了注解實現(xiàn)這五種通知的方法,在Spring中,通知還能通過xml文件進(jìn)行配置實現(xiàn),下面來看一看。
先來看看前置通知的配置,在這之前,先將注解全部去除:
@Component
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法前的日志信息,方法參數(shù)為:" + list);
}
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list);
}
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list + ",運行結(jié)果為:" + result);
}
public void afterThrowingMethod(JoinPoint joinPoint,Exception except){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("執(zhí)行在" + methodName + "方法后的日志信息,方法參數(shù)為:" + list + ",異常信息為:" + except);
}
}
然后進(jìn)行配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.wwj"/>
<aop:config proxy-target-class="true">
<!-- 聲明切點表達(dá)式 -->
<aop:pointcut id="pointCut" expression="execution(* com.wwj.aop.util.*.*(..))"/>
<!-- 配置切面,指定loggingAspect為需要切入的邏輯 -->
<aop:aspect ref="loggingAspect">
<!-- 前置通知,指定beforeMethod為前置通知,并指定將其切入到pointCut中 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
這樣前置通知就完成了,運行結(jié)果:
執(zhí)行在divide方法前的日志信息,方法參數(shù)為:[10, 2]
5
接下來配置后置通知:
<aop:config proxy-target-class="true">
<!-- 聲明切點表達(dá)式 -->
<aop:pointcut id="pointCut" expression="execution(* com.wwj.aop.util.*.*(..))"/>
<!-- 配置切面,指定loggingAspect為需要切入的邏輯 -->
<aop:aspect ref="loggingAspect">
<!-- 前置通知,指定beforeMethod為前置通知,并指定將其切入到pointCut中 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
運行結(jié)果:
執(zhí)行在add方法前的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2]
12
然后是返回通知、異常通知,由于配置方式與其相同,這里就直接一起配置了:
<aop:config proxy-target-class="true">
<!-- 聲明切點表達(dá)式 -->
<aop:pointcut id="pointCut" expression="execution(* com.wwj.aop.util.*.*(..))"/>
<!-- 配置切面,指定loggingAspect為需要切入的邏輯 -->
<aop:aspect ref="loggingAspect">
<!-- 前置通知,指定beforeMethod為前置通知,并指定將其切入到pointCut中 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointCut"/>
<!-- 返回通知 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="result"/>
<!-- 異常通知 -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="except"/>
</aop:aspect>
</aop:config>
其實用法和注解配置是完全一樣的。
運行結(jié)果:
執(zhí)行在add方法前的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2],運行結(jié)果為:12
12
切面優(yōu)先級
這里再強(qiáng)調(diào)一個切面優(yōu)先級的問題,比如現(xiàn)在又需要在項目中添加一個輸出當(dāng)前日期的操作,我們就需要再編寫一個切面類:
@Component
public class TimeAspect {
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "當(dāng)前執(zhí)行時間為:" + LocalDateTime.now());
}
}
接下來配置一下:
<aop:config proxy-target-class="true">
<aop:pointcut id="pointCut" expression="execution(* com.wwj.aop.util.*.*(..))"/>
<aop:aspect ref="loggingAspect">
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
<aop:after method="afterMethod" pointcut-ref="pointCut"/>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="result"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="except"/>
</aop:aspect>
<!-- 配置時間切面 -->
<aop:aspect ref="timeAspect">
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
你也可以使用注解進(jìn)行配置,這里就以xml配置方式舉例了,執(zhí)行以下測試方法:
執(zhí)行在add方法前的日志信息,方法參數(shù)為:[10, 2]
add當(dāng)前執(zhí)行時間為:2020-10-03T17:54:08.297
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2],運行結(jié)果為:12
12
需求來了,我若是想讓時間操作總是在日志操作執(zhí)行被執(zhí)行,該怎么辦呢?
為此,Spring為我們提供了切面的優(yōu)先級,能夠非常靈活地解決切面的執(zhí)行順序問題,比如這里的需求,就可以這樣配置:
<aop:config proxy-target-class="true">
<!-- 聲明切點表達(dá)式 -->
<aop:pointcut id="pointCut" expression="execution(* com.wwj.aop.util.*.*(..))"/>
<!-- 配置日志切面,指定loggingAspect為需要切入的邏輯 -->
<aop:aspect ref="loggingAspect" order="2">
<!-- 前置通知,指定beforeMethod為前置通知,并指定將其切入到pointCut中 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointCut"/>
<!-- 返回通知 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="result"/>
<!-- 異常通知 -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="except"/>
</aop:aspect>
<!-- 配置時間切面 -->
<aop:aspect ref="timeAspect" order="1">
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
這里將日志切面的order設(shè)置為2,將時間切面的order設(shè)置為1,因為order總是值越小,優(yōu)先級越高,所以時間切面的優(yōu)先級就會高于日志切面,看運行結(jié)果:
add當(dāng)前執(zhí)行時間為:2020-10-03T17:57:36.779
執(zhí)行在add方法前的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2]
執(zhí)行在add方法后的日志信息,方法參數(shù)為:[10, 2],運行結(jié)果為:12
12
事實證明確實如此。
而對于注解實現(xiàn),order是這樣配置的:
@Component
@Aspect
@Order(1)
public class TimeAspect {
@Before("execution(* com.wwj.aop.util.*.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "當(dāng)前執(zhí)行時間為:" + LocalDateTime.now());
}
}
直接通過@Order注解并設(shè)置value屬性值即可。
本文作者:汪偉俊 為Java技術(shù)迷專欄作者 投稿,未經(jīng)允許請勿轉(zhuǎn)載。

往 期 推 薦
1、Intellij IDEA這樣 配置注釋模板,讓你瞬間高出一個逼格! 2、基于SpringBoot的迷你商城系統(tǒng),附源碼! 3、最牛逼的 Java 日志框架,性能無敵,橫掃所有對手! 4、把Redis當(dāng)作隊列來用,真的合適嗎? 5、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎? 6、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會? 7、Spring中毒太深,離開Spring我居然連最基本的接口都不會寫了

點分享

點收藏

點點贊

點在看

