Spring之AOP(面向切面編程)
1、AOP的基本介紹
AOP是Aspect Oriented Programming,即面向切面編程。AOP是OOP(面向對象編程)的延續,利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。OOP作為面向對象編程的模式,獲得了巨大的成功,OOP的主要功能是數據封裝、繼承和多態。而AOP是一種新的編程方式,它和OOP不同,OOP把系統看作多個對象的交互,AOP把系統分解為不同的關注點,或者稱之為切面(Aspect)。
AOP 要達到的效果是,保證開發者在不修改源代碼的前提下,去為系統中的業務組件添加某種通用功能。AOP 的本質是由 AOP 框架修改業務組件的多個方法的源代碼。
AOP技術看上去比較神秘,但實際上,它本質就是一個動態代理。
1.1、spring AOP的代理機制
按照 AOP 框架修改源代碼的時機,可以將其分為兩類:
- 靜態 AOP 實現:AOP 框架在編譯階段對程序源代碼進行修改,生成了靜態的 AOP 代理類。此時生成的 *.class 文件已經被改掉了,需要使用特定的編譯器,比如 AspectJ。
- 動態 AOP 實現:AOP 框架在運行階段對動態生成代理對象,在內存中以 JDK 動態代理,或 CGlib 動態地生成 AOP 代理類,如 SpringAOP。目標對象和切面都是普通Java類,通過JVM的動態代理功能或者第三方庫實現運行期動態織入。
最簡單的方式就是動態 AOP 實現,Spring的AOP實現就是基于JVM的動態代理。JVM的動態代理要求必須實現接口,所以如果一個普通類并沒有實現任何借口,那么就需要通過CGLIB或者Javassist這些第三方庫來實現 AOP。
如果要被代理的對象是個實現類,Spring 會自動使用JDK動態代理來完成操作(Spirng默認采用JDK動態代理實現機制);如果要被代理的對象是個普通類,即不是實現類,那么 Spring 會強制使用 CGLib 來實現動態代理。
通過配置Spring的中<aop:config>標簽可以顯式地指定使用什么代理機制,proxy-target-class=true 表示使用CGLib代理,如果為 false 就是默認使用JDK動態代理:

1.2、AOP相關術語
AOP 領域中的特性術語:
- 通知(Advice,增強): 通知描述了切面何時執行以及如何執行增強處理。(比如給類中的某個方法進行了添加了一些額外的操作,這些額外操作就是增強)
- 連接點(join point): 應用執行過程中能夠插入切面的一個點,這個點可以是方法的調用、異常的拋出。在 Spring AOP 中,連接點總是方法的調用,即哪些方法可以被增強,這些方法就可以稱之為一個連接點。
- 切入點(PointCut): 可以插入增強處理的連接點。實際被真正增強了的方法,稱為切入點。(連接點都可被增強,但實際應用可能只增強了類中的某個方法,則該方法就被稱為切入點)
- 切面(Aspect): 切面是通知和切點的結合。把通知(增強)應用到切入點的過程就稱為切面。
- 引入(Introduction):引入允許我們向現有的類添加新的方法或者屬性。
- 織入(Weaving): 將增強處理添加到目標對象中,并創建一個被增強的對象,這個過程就是織入。
1.3、通知的五種類型(@Before、@After、@AfterReturning、@AfterThrowing、@Around)
通知(增強)有五種類型:
- 前置通知(@Before):在目標方法運行之前運行。目標代碼有異常也會執行,但如果攔截器拋異常,那么目標代碼就不執行了;
- 后置通知(@After):在目標方法運行結束之后運行。無論目標代碼是否拋異常,攔截器代碼都會執行;
- 返回通知(@AfterReturning):在目標方法正常返回之后運行。和@After不同的是,只有當目標代碼正常返回時,才執行攔截器代碼,并且這個通知執行順序在 @After 之后
- 異常通知(@AfterThrowing):在目標方法出現異常之后運行。和@After不同的是,只有當目標代碼拋出了異常時,才執行攔截器代碼;
- 環繞通知(@Around):增強的方法會將目標方法封裝起來,能控制是否執行目標代碼,并可以在執行前后、拋異常后執行任意攔截代碼,可以說是包含了上面所有功能。
2、AOP的基本使用
2.1、切入點表達式
切入點表達式是 spring 用表達式來對指定的方法進行攔截。
示例如下:
execution([權限修飾符] [返回類型(可省略)] [完整類名].[方法名](參數列表)) //示例如下,權限修飾符可用*表示任意權限,參數列表可用 .. 表示方法中的參數 execution(public int com.demo.Test.*(..)) //作用于Test類中的所有方法 execution(* com.demo.Test.add(..)) //作用于Test類中的add方法 execution(* com.demo.*.*(..)) //作用于demo包下的所有類的所有方法
2.2、AOP的使用
spring 框架一般都是基于 AspectJ 實現 AOP 操作,AspectJ 不是 spring 的組成部分,一般把 AspectJ 和 spring 框架一起使用,進行 AOP 操作。
基于 AspectJ 實現 AOP 操作有兩種方式:
- 基于XML配置文件實現
- 基于注解方式實現
先導入依賴包,spring 實現AOP不僅僅需要自己的jar包,還需要第三方的jar,將這三個jar包放入項目中就可以spring的aop編程了,如下所示:

使用示例:
先創建一個類(被增強的類):
package webPackage; import org.springframework.stereotype.Component; //需要被增強的類 @Component public class User { public void add() { System.out.println("user add..."); } }
然后創建增強類(切面類,編寫增強邏輯):
package webPackage; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect //生成代理對象 public class UserProxy { //前置通知 @Before("execution(* webPackage.User.add(..))") public void beforeHandler() { System.out.println("前置處理。。。"); } }
在 xml 配置文件中引入命名變量,并且開啟組件注解掃描和 aspectj 切面支持:
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--開啟組件掃描--> <context:component-scan base-package="testMain, service, dao, webPackage"></context:component-scan> <!-- 開啟aspectj切面支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
驗證代碼:
package testMain; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import webPackage.User; public class Test01 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); User user = (User) context.getBean("user"); user.add(); //將輸出 前置處理。。。 user add... } }
如果 User 類實現了某個接口,即是實現類,上面的用法可能會報錯: com.sun.proxy.$Proxy11 cannot be cast to web.User。報錯是因為不能用接口的實現類(Target)來轉換Proxy的實現類,它們是同級,應該用共同的接口來轉換。將獲得Bean的接收類型改成接口類型即可。
此時可以這么用:
package test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import web.User; import web.UserInter; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); UserInter user = (UserInter) context.getBean("user"); user.add(); //將輸出 前置處理。。。 user add... } }
上面實現的是前置通知,其他類型通知如下:
package webPackage; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect //生成代理對象 public class UserProxy { //前置通知 @Before("execution(* webPackage.User.add(..))") public void beforeHandler() { System.out.println("前置處理。。。"); } //后置通知 @After("execution(* webPackage.User.add(..))") public void afterHandler() { System.out.println("后置處理。。。"); } //返回通知 @AfterReturning("execution(* webPackage.User.add(..))") public void afterReturnHandler() { System.out.println("返回處理。。。"); } //異常通知 @AfterThrowing("execution(* webPackage.User.add(..))") public void afterThrowReturnHandler() { System.out.println("異常處理。。。"); } //環繞通知 @Around("execution(* webPackage.User.add(..))") public void aroundThrowReturnHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("環繞之前。。。"); proceedingJoinPoint.proceed(); //執行目標方法 System.out.println("環繞之后。。。"); } }
執行結果:環繞之前。。。 前置處理。。。 user add... 環繞之后。。。 后置處理。。。 返回處理。。。
異常通知沒有執行,因為目標方法并沒有拋出異常,如果拋出異常,異常通知才會執行。
2.3、抽取相同切入點(@Pointcut)
使用多個通知時,可能需要重復寫切入點表達式,此時我們可以通過 @Pointcut 注解來將切入點抽取出來進行復用:
package webPackage; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect //生成代理對象 public class UserProxy { @Pointcut("execution(* webPackage.User.add(..))") public void pointDemo() {} //前置通知 @Before("pointDemo()") public void beforeHandler() { System.out.println("前置處理。。。"); } //后置通知 @After("pointDemo()") public void afterHandler() { System.out.println("后置處理。。。"); } }
2.4、多個切面(增強類)優先級
如果有多個切面對同一個類的方法都進行了增強,我們可以用 @Order(num) 來定義各個切面的優先級,num越小,優先級越高。
比如 UserProxy 和 UserProxy02 都對 User 的 add 方法進行了增強,我們就可以通過 @Order(num) 來指定哪個增強類先執行:
UserProxy 代碼:
package webPackage; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Aspect @Order(2) public class UserProxy { @Pointcut("execution(* webPackage.User.add(..))") public void pointDemo() {} //前置通知 @Before("pointDemo()") public void beforeHandler() { System.out.println("UserProxy 的前置處理。。。"); } }
UserProxy02 代碼:
package webPackage; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Aspect //生成代理對象 @Order(1) public class UserProxy02 { @Pointcut("execution(* webPackage.User.add(..))") public void pointDemo() {} //前置通知 @Before("pointDemo()") public void beforeHandler() { System.out.println("UserProxy02 的前置處理。。。"); } }
驗證代碼:
package testMain; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import webPackage.User; public class Test01 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); User user = (User) context.getBean("user"); user.add(); //將輸出:UserProxy02 的前置處理。。。 UserProxy 的前置處理。。。 user add... } }
2.5、完全注解開發(不需要xml配置文件)
我們可以通過配置類來替代 xml 配置文件,實現完全注解開發:
照著上面的例子,先建一個 User 類和一個增強類 UserProxy,然后用配置類替代 xml 配置文件。配置類如下:
package webPackage; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan(basePackages = {"testMain", "service", "dao", "webPackage"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { }
Spring的 IOC 容器看到 @EnableAspectJAutoProxy 注解,就會自動查找帶有@Aspect的Bean,然后根據每個方法的@Before、@Around等注解把AOP注入到特定的Bean中。
使用 bean 跟用配置文件方式不太一樣,代碼如下:
package testMain; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import webPackage.AopConfig; import webPackage.User; public class Test01 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); User user = (User) context.getBean("user"); user.add(); } }
2.6、直接通過配置文件實現AOP(一般不用)
先創建一個類:

然后創建增強類:

配置前置通知:


浙公網安備 33010602011771號