-
Aspect Oriented Programming: 面向切面編程
-
OOP: 面向對象編程: 數據封裝, 繼承和多態
-
把權限作爲切面(Aspect), 把日誌, 事務也視爲切面, 某種自動化的方式, 把切面植入到核心邏輯中, 實現Proxy模式
-
以AOP的視角來編寫上述業務:
- 核心邏輯: BookService;
- 切面邏輯:
- 權限檢查邏輯的Aspect;
- 日誌的Aspect;
- 事務的Aspect;
-
讓框架把上述3個Aspect以Proxy的方式"織入"到"BookService".
AOP原理
-
三種方式AOP的織入:
- 編譯期: 在編譯時, 由編譯器把切面調用編譯進字節碼, 這種方式需要定義新的關鍵字並擴展編譯器.
- 類加載器: 在目標被裝載到JVM時, 通過一個特殊的類加載器, 對目標類的字節碼重新"增強".
- 運行期: 目標對象和切面都是普通的Java類, 通過JVM的動態代理功能或者第三方庫實現運行期動態織入.
-
Spring的AOP實現是基於JVM的動態代理. 由於JVM的動態代理要求必須實現接口. 如果一個普通類沒有業務接口, 就需要通過CGLB或者javassist第三方庫實現.
裝配AOP
- 主要概念
- Aspect: 切面, 橫跨多個核心邏輯的功能, 或稱爲系統關注點
- Joinpoint: 連接點, 定義在應用程序的何處插入切面的執行
- Pointcut: 切入點: 一組連接點的集合
- Advice: 增強, 特定連接點上執行的動作
- Introduction: 引介, 爲一個已有的Java對象動態的增加新的接口
- Weaving: 織入, 將切面整合到程序的執行流中
- Interceptror: 攔截器, 一種實現增強的方式
- Target Object: 目標對象, 真正執行業務的核心邏輯對象
- AOP Proxy: AOP代理, 客戶端持有的增強後的對象引用
@Aspect
@Component
public class LoggingAspect {
// 在UserService的每個方法前執行
@Before("execution(public * com.zhangrh.spring.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}
// 在MailService的每個方法前後執行
@Around("execution(public * com.zhangrh.spring.service.MailService.*(..))")
public Object doLooging(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.out.println("[Around] done" + pjp.getSignature());
return retVal;
}
}
-
Spring容器啓動時, 自動爲我們創建的注入了Aspect的子類, 取代了原始的
UserService
.- 使用AcpectJ解析註解
- 通過CGLIB實現代理類
-
使用AOP:
- 定義執行方法, 並在方法上通過AspectJ的註解告訴Spring應該在何處調用此方法
- 標記
@Component
和@Aspect
- 在
@Configuration
類上標記@EnableAspectJAutoProxy
-
攔截器類型:
- @Before: 先執行攔截代碼, 再執行目標代碼. 如果攔截器拋出異常, 那目標代碼就不執行了
- @After: 先執行目標代碼, 再執行攔截器代碼, 無論目標是否異常, 攔截器代碼都會執行
- @AfterRunning: 只有當目標代碼正常返回時, 纔會執行
- @AfterThrowing: 只有當目標代碼拋出異常時, 纔會執行
- @Around: 能完全控制目標代碼是否執行, 並可在執行前後, 拋出異常前後, 任意攔截代碼, 是上面的合集.
使用註解裝配AOP
@Component
@Transactional // 所有的public都被安排
public class UserService {
// 有事務
@Transactional
public User createUser(String name) {
// ...
}
// 無事務
public boolean isValidName(String name) {
// ...
}
}
- 使用
@Around("execution...")
殺傷力太大
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MetricTime {
String value();
}
@Component
@Aspect
public class MetricAspect {
@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
String name = metricTime.value();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long t = System.currentTimeMillis() - start;
System.out.println("[Metrics] " + name + ": " + t + "ms");
}
}
}
@MetricTime("login")
public User Login(String email, String password) {
// ...
}
AOP避坑指南
- 無論使用AspectJ語法, 還是配合Annotation, AOP的本質都是一個代理模式
- 使得調用方無感知的調用指定方法
- Spring通過CGLB創建的代理類, 不會初始化類自身繼承的任何成員變量, 包括final類型的成員變量
- AOP避坑指南:
- 訪問被注入的Bean時, 總是調用方法而非直接訪問字段;
- 編寫Bean時, 如果可能被代理, 就不要編寫
public final
方法