此文章只作為筆記記錄,不作為講解文章。
1. SpringAop簡介
傳統的OOP開發中的代碼邏輯是自上而下的,而這些過程會產生一些橫切性問題,這些橫切性的問題和我們的主業務邏輯關系不大,這些橫切性問題不會影響到主邏輯實現的,但是會散落到代碼的各個部分,難以維護。AOP是處理一些橫切性問題,AOP的編程思想就是把這些問題和主業務邏輯分開,達到與主業務邏輯解耦的目的。
- 日志記錄
- 權限驗證
- 效率檢查
- 事務管理
- exception
2. 依賴包引入
//SpringBoot項目引入Aop依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.5.RELEASE</version> </dependency> //Spring項目引入Aop依賴 <!-- springAop依賴包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.4.RELEASE</version> </dependency> <!-- springAop依賴Aspect的語法標准包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.7.RELEASE</version> </dependency>
3. Aop實現示例
項目結構
3.1 定義依賴注入掃描器
AppConfig配置類
package com.java.study.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; //掃描注解 @ComponentScan("com.java.study") //開啟Aop,默認為false(JDK代理模式) true(Cglib模式)
// (this) JDK代理時,指向接口和代理類proxy,cglib代理時 指向接口和子類(不使用proxy) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { }
3.2 自定義service方法
TestService測試方法類
package com.java.study.service; import org.springframework.stereotype.Component; @Component("testService") public class TestService { public void Test1(){ System.out.println("這是測試方法 test1 ......"); } }
3.3 定義切面類
TestAdvice切面類
package com.java.study.aspect; import com.java.study.custom.KthLog; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.logging.Logger; @Component @Aspect public class TestAdvice { private final static Logger logger = Logger.getLogger("TestAdvice.class"); // @annotation匹配的是自定義注解所標注的方法 @Pointcut("@annotation(com.java.study.custom.KthLog)") public void loggerMother(){} @Pointcut("execution(* com.java.study..*())") public void loggerMother2(){} @Pointcut("execution(* com.java.study.*(java.lang.String))") public void loggerMother3(){} @Before("loggerMother() && @within(log)") public void Before(JoinPoint pointcut, KthLog log){ System.out.println(" 方法名:"+ pointcut.getSignature().getName() +"日志輸出:"+log.value()); } @After("loggerMother2()") public void Before(){ System.out.println(" 測試增強方法 。。。。。。"); } @Around("loggerMother2()&&!loggerMother3()") public Object arround(ProceedingJoinPoint joinPoint) { logger.info("方法環繞start....."); try { Object o = joinPoint.proceed(); logger.info("方法環繞proceed,結果是 :" + o); return o; } catch (Throwable e) { return null; } } }
//execution表達式 (用於匹配方法) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 問號 ? 表示當前項可以有也可以沒有,其中各項的語義如下: modifiers-pattern:方法的可見性,如public,protected;(private不能被代理) ret-type-pattern:方法的返回值類型,如int,void等; declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect; name-pattern:方法名類型,如buisinessService(); param-pattern:方法的參數類型,如java.lang.String; throws-pattern:方法拋出的異常類型,如java.lang.Exception;
.. : 表示當前包及其子包
//within表達式 (用於匹配類) within(declaring-type-pattern) declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
//args表達式 (args匹配的是運行時傳遞給方法的參數類型) 與 execution 不同 args(java.io.Serializable) //匹配運行時傳遞的參數類型為指定類型的、且參數個數和順序匹配
3.4 定義啟動類
TestApp啟動類
package com.java.study; import com.java.study.config.AppConfig; import com.java.study.service.TestService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestApp { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); TestService testService = (TestService) ac.getBean("testService"); testService.Test1(); } }
4. 自定義注解
定義 KthLog注解類
package com.java.study.custom; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;
//元注解 @Retention @Retention(RetentionPolicy.RUNTIME) public @interface KthLog { String value() default ""; }
元注解講解
java.lang.annotation 提供了四種元注解,專門注解其他的注解(在自定義注解的時候,需要使用到元注解)
@Retention – 什么時候使用該注解
@Target – 注解用於什么地方
@Documented – 注解是否將包含在JavaDoc中
@Inherited – 是否允許子類繼承該注解
1.)@Retention – 定義該注解的生命周期
● RetentionPolicy.SOURCE : 在編譯階段丟棄。這些注解在編譯結束之后就不再有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬於這類注解。
● RetentionPolicy.CLASS : 在類加載的時候丟棄。在字節碼文件的處理中有用。注解默認使用這種方式
● RetentionPolicy.RUNTIME : 始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。我們自定義的注解通常使用這種方式。
2.)@Target – 表示該注解用於什么地方。默認值為任何元素,表示該注解用於什么地方。可用的ElementType 參數包括
● ElementType.CONSTRUCTOR: 用於描述構造器
● ElementType.FIELD: 成員變量、對象、屬性(包括enum實例)
● ElementType.LOCAL_VARIABLE: 用於描述局部變量
● ElementType.METHOD: 用於描述方法
● ElementType.PACKAGE: 用於描述包
● ElementType.PARAMETER: 用於描述參數
● ElementType.TYPE: 用於描述類、接口(包括注解類型) 或enum聲明
3.)@Documented – 一個簡單的Annotations 標記注解,表示是否將注解信息添加在java 文檔中。
4.)@Inherited – 定義該注釋和子類的關系
● @Inherited 元注解是一個標記注解,@Inherited 闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited 修飾的annotation 類型被用於一個class,則這個annotation 將被用於該class 的子類。
5. 配合Aop增強類使用
修改TestService測試方法類
package com.java.study.service; import com.java.study.custom.KthLog; import org.springframework.stereotype.Component; @Component("testService") public class TestService { @KthLog("這是TestService類中******") public void Test1(){ System.out.println("這是測試方法 test1 ......"); } }
之后使用TestAdvice切面類的loggerMother方法即可。