AOP通俗地來講就是指在程序運行期間動態的將某段代碼切入到指定方法的指定位置進行運行的編程方式,
這次我將會來對基於註解式的SpringAOP做源碼解析
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>review-java</artifactId> <groupId>review</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>review-spring</artifactId> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
本次aop要實現的目標是在方法div運行的時候將日誌進行打印(在方法之前、方法運行結束、方法出現異常等運行位置進行日誌打印)
package aop.annotation.service; public class MathCalculator { /** * 除法 * * @param i * @param j * @return */ public int div(int i, int j) { System.out.println("MathCalculator.div方法被調用"); return i / j; } }
切面類裏面的方法需要動態感知MathCalculator.div運行到哪裏,然後執行相應的通知方法
給切面類的目標方法標註何時何地運行(即以下通知註解)
LogAspects 裏面有通知方法:
1)前置通知:(@Before) logStart:在目標方法div運行之前
2)後置通知:(@Aftre) logEnd:在目標方法div運行結束之後 (無論方法正常結束還是異常結束)
3)返回通知:(@AfterReturning) logReturn:在目標方法div正常返回之後運行
4)異常通知:(@AfterThrowing) logException:在目標方法div出現異常之後運行
5)環繞通知:(@Around) 動態代理,手動推進目標方法運行(joinPoint.proceed())
package aop.annotation.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import java.util.Arrays; /** * @Aspect 此註解告訴spring這是切面 */ @Aspect public class LogAspects { /** * 抽取公共的切入點表達式 * 1、如果在本類引用 直接在註解加方法名 如@Before(commonPointCut()) * 2、如果不是在本類引用 (比如其他的切面要引用這個切入點) 則要用全類名 */ @Pointcut("execution(public int aop.annotation.service.MathCalculator.div(int, int))") public void commonPointCut() { } //@Before在目標方法之前切入;切入點表達式(指定在哪個方法切入) //@Before("public int aop.annotation.service.MathCalculator.div(int, int)") /** * @param joinPoint 可以通過此參數獲取方法相關信息 如方法名和參數信息,此參數必須放在第一個參數 */ @Before("commonPointCut()") public void logStart(JoinPoint joinPoint) { System.out.println("" + joinPoint.getSignature().getName() + "除法運行,@Before,參數列表是:{" + Arrays.asList(joinPoint.getArgs()) + "}"); } @After("commonPointCut()") public void logEnd(JoinPoint joinPoint) { System.out.println("除法結束,@After"); } /** * 用returning來接收返回值 * * @param result */ @AfterReturning(value = "commonPointCut()", returning = "result") public void logReturn(Object result) { System.out.println("除法正常返回,@AfterReturning 運行結果:{" + result + "}"); } /** * 用throwing來接收異常 * * @param joinPoint 此參數必須放在第一位 * @param exception */ @AfterThrowing(value = "commonPointCut()", throwing = "exception") public void logException(JoinPoint joinPoint, Exception exception) { System.out.println("" + joinPoint.getSignature().getName() + "除法異常,@AfterException 異常信息:{" + exception.getStackTrace() + "}"); } }
告訴spring哪個是切面類,(在切面類LogAspects加一個註解@Aspect)
在配置類中加 @EnableAspectJAutoProxy 開啓基於註解的aop模式
package aop.annotation.config; import aop.annotation.aop.LogAspects; import aop.annotation.service.MathCalculator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 三步: * 1)、將業務邏輯組件和切面類都加入到容器中;告訴spring哪個是切面類(@Aspect) * 2)、在切面類的每一個通知方法上標註通知註解,告訴spring何時何地運行(切入點表達式) * 3)、開啓基於註解的aop模式 @EnableAspectJAutoProxy */ @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { @Bean public MathCalculator calculator() { return new MathCalculator(); } @Bean public LogAspects logAspects() { return new LogAspects(); } }
package aop.annotation.test; import aop.annotation.config.MainConfigOfAOP; import aop.annotation.service.MathCalculator; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class IOCTest_AOP { /** * AOP單元測試 */ @Test public void testAop() { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); MathCalculator mathCalculator = annotationConfigApplicationContext.getBean(MathCalculator.class); mathCalculator.div(1, 0); annotationConfigApplicationContext.close(); } }
本次測試1除以0的異常情況:可見在div運行之前,運行之後,以及出現異常時都打印出相關的信息。
其他情況,大家可以自行測試
aop的效果就是這樣,在div運行前後的各個位置進行了處理,把日誌的代碼與業務邏輯的代碼進行解耦。
下一篇將開始進行源碼解析,將首先通過源碼解析註解@EnableAspectJAutoProxy的作用