SpringBoot 通過自定義註解實現AOP切面程式設計例項
阿新 • • 發佈:2018-11-11
一直心心念的想寫一篇關於AOP切面例項的博文,拖更了許久之後,今天終於著手下筆將其完成。
基礎概念
1、切面(Aspect)
首先要理解‘切’字,需要把物件想象成一個立方體,傳統的面向物件變成思維,類定義完成之後(封裝)。每次例項化一個物件,對類定義中的成員變數賦值,就相當於對這個立方體進行了一個定義,定義完成之後,那個物件就在那裡,不卑不亢,不悲不喜,等著被使用,等著被回收。
面向切面程式設計則是指,對於一個我們已經封裝好的類,我們可以在編譯期間或在執行期間,對其進行切割,把立方體切開,在原有的方法裡面新增(織入)一些新的程式碼,對原有的方法程式碼進行一次增強處理。而那些增強部分的程式碼,就被稱之為切面,如下面程式碼例項中的通用日誌處理程式碼,常見的還有事務處理、許可權認證等等。
2、切入點(PointCut)
要對哪些類中的哪些方法進行增強,進行切割,指的是被增強的方法。即要切哪些東西。
3、連線點(JoinPoint)
我們知道了要切哪些方法後,剩下的就是什麼時候切,在原方法的哪一個執行階段加入增加程式碼,這個就是連線點。如方法呼叫前,方法呼叫後,發生異常時等等。
4、通知(Advice)
通知被織入方法,改如何被增強。定義切面的具體實現。那麼這裡面就涉及到一個問題,空間(切哪裡)和時間(什麼時候切,在何時加入增加程式碼),空間我們已經知道了就是切入點中定義的方法,而什麼時候切,則是連線點的概念,如下面例項中,通用日誌處理(切面),@Pointcut規則中指明的方法即為切入點,@Before、@After是連線點,而下面的程式碼就是對應通知。
@Before("cutMethod()") public void begin() { System.out.println("[email protected]== lingyejun blog logger : begin"); }
5、目標物件(Target Object)
被一個或多個切面所通知的物件,即為目標物件。
6、AOP代理物件(AOP Proxy Object)
AOP代理是AOP框架所生成的物件,該物件是目標物件的代理物件。代理物件能夠在目標物件的基礎上,在相應的連線點上呼叫通知。
7、織入(Weaving)
將切面切入到目標方法之中,使目標方法得到增強的過程被稱之為織入。
例項程式碼
相關依賴包
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>1.3.8.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.8.RELEASE</version> <scope>test</scope> </dependency> </dependencies>
定義和實現日誌切面
package com.lingyejun.annotation; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @Author: Lingye * @Date: 2018/11/11 * @Describe: * 定義日誌切面 * @Lazy 註解:容器一般都會在啟動的時候例項化所有單例項 bean,如果我們想要 Spring 在啟動的時候延遲載入 bean,需要用到這個註解 * value為true、false 預設為true,即延遲載入,@Lazy(false)表示物件會在初始化的時候建立 * * @Modified By: */ @Aspect @Component @Lazy(false) public class LoggerAspect { /** * 定義切入點:對要攔截的方法進行定義與限制,如包、類 * * 1、execution(public * *(..)) 任意的公共方法 * 2、execution(* set*(..)) 以set開頭的所有的方法 * 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply這個類裡的所有的方法 * 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的類的所有的方法 * 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的類的所有的方法 * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的類的有三個引數,第一個引數為String型別,第二個引數為任意型別,第三個引數為Long型別的方法 * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun)) */ @Pointcut("@annotation(com.lingyejun.annotation.Lingyejun)") private void cutMethod() { } /** * 前置通知:在目標方法執行前呼叫 */ @Before("cutMethod()") public void begin() { System.out.println("[email protected]== lingyejun blog logger : begin"); } /** * 後置通知:在目標方法執行後呼叫,若目標方法出現異常,則不執行 */ @AfterReturning("cutMethod()") public void afterReturning() { System.out.println("[email protected]== lingyejun blog logger : after returning"); } /** * 後置/最終通知:無論目標方法在執行過程中出現一場都會在它之後呼叫 */ @After("cutMethod()") public void after() { System.out.println("[email protected]== lingyejun blog logger : finally returning"); } /** * 異常通知:目標方法丟擲異常時執行 */ @AfterThrowing("cutMethod()") public void afterThrowing() { System.out.println("[email protected]== lingyejun blog logger : after throwing"); } /** * 環繞通知:靈活自由的在目標方法中切入程式碼 */ @Around("cutMethod()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取目標方法的名稱 String methodName = joinPoint.getSignature().getName(); // 獲取方法傳入引數 Object[] params = joinPoint.getArgs(); Lingyejun lingyejun = getDeclaredAnnotation(joinPoint); System.out.println("[email protected]== lingyejun blog logger --》 method name " + methodName + " args " + params[0]); // 執行源方法 joinPoint.proceed(); // 模擬進行驗證 if (params != null && params.length > 0 && params[0].equals("Blog Home")) { System.out.println("[email protected]== lingyejun blog logger --》 " + lingyejun.module() + " auth success"); } else { System.out.println("[email protected]== lingyejun blog logger --》 " + lingyejun.module() + " auth failed"); } } /** * 獲取方法中宣告的註解 * * @param joinPoint * @return * @throws NoSuchMethodException */ public Lingyejun getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { // 獲取方法名 String methodName = joinPoint.getSignature().getName(); // 反射獲取目標類 Class<?> targetClass = joinPoint.getTarget().getClass(); // 拿到方法對應的引數型別 Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); // 根據類、方法、引數型別(過載)獲取到方法的具體資訊 Method objMethod = targetClass.getMethod(methodName, parameterTypes); // 拿到方法定義的註解資訊 Lingyejun annotation = objMethod.getDeclaredAnnotation(Lingyejun.class); // 返回 return annotation; } }
自定義一個註解
package com.lingyejun.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author: Lingye * @Date: 2018/11/11 * @Describe: * @Modified By: */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Lingyejun { /** * 何種場景下的通用日誌列印 * * @return */ String module(); }
呼叫切面類
package com.lingyejun.annotation; import org.springframework.stereotype.Component; /** * @Author: Lingye * @Date: 2018/11/11 * @Describe: * @Modified By: */ @Component public class LoggerApply { @Lingyejun(module = "http://www.cnblogs.com/lingyejun/") public void lingLogger(String event) throws Exception { System.out.println("lingLogger(String event) : lingyejun will auth by blog address"); throw new Exception(); } }
測試程式碼
package com.lingyejun.annotation; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) public class AnnotationTest { @Autowired private LoggerApply loggerApply; @Test public void testAnnotationLogger() { try { loggerApply.lingLogger("Blog Home"); } catch (Exception e) { System.out.println("a exception be there"); } } }
效果展示