一、開啟注解支援
1、概述
1、Spring AOP如同IoC一樣支援基于XML和基于注解兩種配置方式,基于注解所需的依賴和基于XML所需的依賴一緻,其中spring-context包含了Spring IoC、Spring AOP等核心依賴,而aspectjweaver則是AspectJ架構的依賴,Spring使用該依賴來解析AspectJ的切入點表達式文法,以及AOP的注解支援。
2、開啟AOP注解支援的方式:
- 使用XML配置啟用AOP注解。
- 使用Java配置啟用AOP注解。
2、使用XML方式的配置
1、加入aop命名空間,使用
<aop:aspectj-autoproxy />
标簽即可,Spring将會查找被@Aspect注解标注的Bean,這表明它是一個切面Bean,然後就會進行AOP的自動配置。
2、屬性說明:
:
proxy-target-class
。
是否為被代理類生成CGLIB子類,隻為接口生成代理子類(即:是否使用CGlib動态代理);預設為false,使用的是JDK代理
:
expose-proxy
。
是否将代理的Bean暴露給使用者,如果暴露,就可以通過AopContext類獲得,預設不暴露
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--開啟注解自動配置-->
<aop:aspectj-autoproxy/>
</beans>
3、使用Java方式的配置
1、AOP的注解支援同樣可以使用Java配置方式開啟,進而徹底舍棄XML配置檔案。
2、配置類上添加
@EnableAspectJAutoProxy
,該注解用于開啟Spring AOP的注解自動配置支援。
3、屬性說明:
:
proxy-target-class
。
是否為被代理類生成CGLIB子類,隻為接口生成代理子類(即:是否使用CGlib動态代理);預設為false,使用的是JDK代理
:
expose-proxy
。
是否将代理的Bean暴露給使用者,如果暴露,就可以通過AopContext類獲得,預設不暴露
/**
* @Date: 2023/2/11
* 開啟AOP注解支援配置類
* @Configuration注解:表示該類為一個配置類
* @ComponentScan注解:用于掃描包上的注解将元件加載到IoC容器中
*/
@Configuration
@ComponentScan("com.itan.aop.*")
@EnableAspectJAutoProxy
public class AopConfig {
}
二、切面相關注解
1、@Aspect注解
1、注解對應着XML配置中的
@Aspect
标簽,被該注解标注的類,會被當做切面類并且用于自動配置Spring AOP。如果類僅僅隻标注了該注解,那麼是不會被Spring元件掃描工具自動掃描到的,并且不會加入到IoC容器中,
<aop:aspect />
是以還需要搭配元件注冊相關的注解一起使用(如:@Component)
。
2、切面類和普通類一樣,可以有自己的方法和字段,還可以包含切入點(pointcut)、通知(advice)、聲明(introduction),這些都是通過方法來綁定的。
3、切面類本身是不能成為其他切面通知的目标類,類上面标注了@Aspect注解之後,該類的Bean将從AOP自動配置Bean中排除,是以切面類裡面的方法是不能被代理的。
2、@Pointcut注解
1、注解對應着XML配置中的
@Pointcut
<aop:pointcut />
标簽,用來定義一個切入點,在比對的情況下執行通知,切入點表達式的文法都是一樣的,請參考切入點聲明規則。
2、
。
@Pointcut注解标注在一個切面類的方法上,方法名就是該切入點的名字,在通知中通過名字引用該切入點(XXX(),要帶上括号),也可以将多個切入點組合成一個新的切入點(可以使用&&、||、!等運算符連接配接起來),XML配置中不具備該組合方式
3、通知相關注解
1、advice通知同樣可以使用注解聲明,并且綁定到一個方法上,一共有五種通知注解,分别和XML中的五中通知标簽一一對應。
2、五種通知注解:
:用于定義前置通知,在使用時,通常需要指定一個value屬性值,該屬性值用于指定一個切入點表達式(可以是已有的切入點,也可以直接定義切入點表達式)。
@Before
:用于定義後置通知,在使用時可以指定pointcut或value和returning屬性;其中pointcut/value這兩個屬性的作用一樣,都用于指定切入點表達式;
@AfterReturning
。
returning屬性值是後置通知方法中的參數名,用來将切入點方法的傳回值綁定到該參數上
:用于定義異常通知,在前置通知、切入點方法和後置通知中抛出異常之後可能會執行;在使用時可指定pointcut或value和throwing屬性,其中pointcut/value用于指定切入點表達式;
@AfterThrowing
。
throwing屬性值是異常通知方法中的參數名,用來将前置通知、切入點方法、後置通知執行過程中抛出的異常綁定到該參數上
:用于定義最終通知,不管是否異常,該通知都會執行;使用時需要指定一個value屬性,用于指定一個切入點表達式。
@After
:用于定義環繞通知,使用時需要指定一個value屬性,用于指定一個切入點表達式。
@Around
/**
* @Date: 2023/2/11
* 切面類
* @Aspect:用于定義該類為一個切面類
* @Component:将元件注冊到IoC容器中,否則單單使用@Aspect是不會被元件掃描注解檢測到的
*/
@Aspect
@Component
public class AnnoAspect {
/**
* 切入點,該切入點比對service包下所有類所有方法
*/
@Pointcut("execution(* com.itan.aop.service..*.*(..))")
public void pointCut() {}
/**
* 前置通知
*/
@Before(value = "pointCut()")
public void before() {
System.out.println("前置通知");
}
/**
* 異常通知
*/
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("異常通知,異常為:" + e.getMessage());
}
/**
* 後置通知
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("後置通知,傳回值為:" + result);
}
/**
* 最終通知
*/
@After(value = "pointCut()")
public void afterFinally() {
System.out.println("最終通知");
}
/**
* 環繞通知
* 一定要有ProceedingJoinPoint類型的參數
*/
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("環繞通知 --- 進入方法");
Object[] args = point.getArgs();
System.out.println("外部傳入的參數:" + Arrays.toString(args));
System.out.println(point.toString());
System.out.println(point.toShortString());
System.out.println(point.toLongString());
System.out.println(point.getThis());
System.out.println(point.getTarget());
System.out.println(point.getSignature());
System.out.println(point.getSourceLocation());
System.out.println(point.getKind());
System.out.println(point.getStaticPart());
// proceed方法表示調用切入點方法,否則方法不會執行,args表示參數,proceed就是切入點方法的傳回值
Object proceed = point.proceed(args);
System.out.println("環繞通知 --- 退出方法");
return proceed;
}
}
/**
* @Date: 2023/2/11
* 目标接口實作類
*/
@Service
public class CalculateServiceImpl implements CalculateService {
public Integer add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i + j;
}
public int multiply(int i, int j) {
return i * j;
}
public double divide(int i, int j) {
return i / j;
}
}
@Test
public void test04() {
// 通過配置檔案建立容器對象
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
CalculateService calc = context.getBean(CalculateService.class);
System.out.println(calc.getClass());
calc.add(2,3);
}
/**
* 運作結果:可以看到預設情況下使用JDK代理:class com.sun.proxy.$Proxy28
* class com.sun.proxy.$Proxy28
* 環繞通知 --- 進入方法
* 外部傳入的參數:[2, 3]
* execution(Integer com.itan.aop.service.CalculateService.add(int,int))
* execution(CalculateService.add(..))
* execution(public abstract java.lang.Integer com.itan.aop.service.CalculateService.add(int,int))
* [email protected]
* [email protected]
* Integer com.itan.aop.service.CalculateService.add(int,int)
* org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@7357a011
* method-execution
* execution(Integer com.itan.aop.service.CalculateService.add(int,int))
* 前置通知
* 後置通知,傳回值為:5
* 最終通知
* 環繞通知 --- 退出方法
*/
4、通知順序
1、當同一個連接配接點方法中綁定了多個同一個類型的通知時,有時需要指定通知的執行順序,在XML中通過
<aop:aspect />切面标簽中的order屬性指定執行通知順序
,注解配置中也能實作通知執行順序。
2、實作方式:
通知類實作Ordered接口
通知類标注@Order注解
- 提示:未設定order值時,預設值為Integer.MAX_VALUE;值越小的切面,其内部的前置通知越先執行,後置通知越後執行。
/**
* @Date: 2023/2/11
* 通過@Order注解方式設定順序
*/
@Aspect
@Component
@Order(Integer.MAX_VALUE - 2)
public class AnnoOrderAspect1 {
/**
* 切入點,該切入點比對service包下所有類所有方法
*/
@Pointcut("execution(* com.itan.aop.service..*.multiply(..))")
public void pointCut() {}
/**
* 前置通知
*/
@Before(value = "pointCut()")
public void before() {
System.out.println("AnnoOrderAspect1前置通知");
}
/**
* 後置通知
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("AnnoOrderAspect1後置通知,傳回值為:" + result);
}
/**
* 最終通知
*/
@After(value = "pointCut()")
public void afterFinally() {
System.out.println("AnnoOrderAspect1最終通知");
}
}
/**
* @Date: 2023/2/11
* 通過實作Ordered接口方式設定順序
*/
@Aspect
@Component
public class AnnoOrderAspect2 implements Ordered {
/**
* 切入點,該切入點比對service包下所有類所有方法
*/
@Pointcut("execution(* com.itan.aop.service..*.multiply(..))")
public void pointCut() {}
/**
* 前置通知
*/
@Before(value = "pointCut()")
public void before() {
System.out.println("AnnoOrderAspect2前置通知");
}
/**
* 後置通知
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("AnnoOrderAspect2後置通知,傳回值為:" + result);
}
/**
* 最終通知
*/
@After(value = "pointCut()")
public void afterFinally() {
System.out.println("AnnoOrderAspect2最終通知");
}
/**
* 擷取順序值
* @return
*/
@Override
public int getOrder() {
return Integer.MAX_VALUE - 1;
}
}
@Test
public void test05() {
// 通過配置檔案建立容器對象
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
CalculateService calc = context.getBean(CalculateService.class);
System.out.println(calc.getClass());
calc.multiply(2,3);
}
/**
* 運作結果:值越小的切面的前置通知越先執行,後置通知越後執行
* AnnoOrderAspect1前置通知
* AnnoOrderAspect2前置通知
* AnnoOrderAspect2後置通知,傳回值為:6
* AnnoOrderAspect2最終通知
* AnnoOrderAspect1後置通知,傳回值為:6
* AnnoOrderAspect1最終通知
*/