Spring AOP的核心應(yīng)用與原理解析

概述
AOP(Aspect Oriented Programming) 面向切面編程,實質(zhì)上是對我們的對象進行增強,并且提供良好的管理機制。
對于對象增強,可以有以下幾種方法
?
1.? 裝飾器模式,比如JDK中的I/O流
InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));
2.? 靜態(tài)代理模式
3.? 適配器模式
4.? 動態(tài)代理,包括 ?Proxy 和 CGLIB 等方法進行動態(tài)代理
?
SpringAOP 是一套 AOP 的解決方案,他比傳統(tǒng)的對象增強更容易管理和擴展。
在對象增強上,它的規(guī)則是:基于動態(tài)代理來實現(xiàn)。默認地,如果使用接口方法的,用 JDK 提供的動態(tài)代理實現(xiàn),如果沒有接口,使用 CGLIB 實現(xiàn)。SpringAOP 基于 IOC 容器,動態(tài)代理之后,會把原對象替換成動態(tài)代理的對象。
?
?
概念
Advisor 是 AOP 的一個概念,他是 保存AOP配置 的一個單位
?
·????? Advice:方法攔截邏輯,可控制該方法執(zhí)行前和方法執(zhí)行后或者出現(xiàn)異常時候的執(zhí)行的邏輯
·????? PointCut: 在哪些地方的方法應(yīng)用攔截
·????? Advisor: 里面有且只有一個 Advice,可以對多個 PointCut 執(zhí)行 Advice 方法。
?
?
注解配置 Spring AOP
?
SpringAOP 和 AspectJ 沒多大關(guān)系,而僅僅是使用了 AspectJ 中的概念,包括使用的注解也是直接來自于 AspectJ 的包(有點迷)。
?
依賴
<dependency>
??? <groupId>org.aspectj</groupId>
??? <artifactId>aspectjweaver</artifactId>
??? <version>1.8.11</version>
</dependency>
或者在 SpringBoot
<dependency>
?? <groupId>org.springframework.boot</groupId>
?? <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
?
注解
注解開啟,在Main 類上面標上這個注解
@EnableAspectJAutoProxy
?
在 配置文件Bean 上 打上 @Aspect
@Aspect
?
配置 PointCut
@Aspect
public class SystemArchitecture {
??? @Pointcut("execution(* transfer(..))")// the pointcut expression
??? private void anyOldTransfer() {}// the pointcut signature
}
?
一些配置規(guī)則
@Pointcut("execution(* transfer(..))")
// 方法簽名
@Pointcut("within(com.javadoop.springaoplearning.service..*)")
// 包下所有類的所有方法
@Pointcut("execution( .*(..))
@annotation(com.javadoop.annotation.Subscribe)")
// 指定注解的所有方法
@Pointcut("bean(*Service)")
// 指定 bean 名的所有方法
// 通常 "." 代表一個包名,".." 代表包及其子包,方法參數(shù)任意匹配使用兩個點 ".."
?
?
一些實踐中的配置
@Aspect
public class SystemArchitecture {
?
??? // web 層
??? @Pointcut("within(com.javadoop.web..*)")
??? public void inWebLayer() {}
?
??? // service 層
??? @Pointcut("within(com.javadoop.service..*)")
??? public void inServiceLayer() {}
?
??? // dao 層
??? @Pointcut("within(com.javadoop.dao..*)")
??? public void inDataAccessLayer() {}
?
??? // service 實現(xiàn),注意這里指的是方法實現(xiàn),其實通常也可以使用 bean(*ServiceImpl)
??? @Pointcut("execution(* com.javadoop..service.*.*(..))")
??? public void businessService() {}
?
??? // dao 實現(xiàn)
??? @Pointcut("execution(* com.javadoop.dao.*.*(..))")
??? public void dataAccessOperation() {}
}
?
配置 Advice
@Aspect
public class AdviceExample {
?
??? // 這里會用到我們前面說的 SystemArchitecture
??? // 下面方法就是寫攔截 "dao層實現(xiàn)"
??? @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
??? public void doAccessCheck() {
??????? // ... 實現(xiàn)代碼
??? }
?
??? @Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
??? public void logArgs(JoinPoint joinPoint) {
??????? System.out.println("方法執(zhí)行前,打印入?yún)ⅲ?#34; + Arrays.toString(joinPoint.getArgs()));
??? }
?
??? @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
??? public void doAccessCheck() {
??????? // ...
??? }
?
??? @AfterReturning(
??????? pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
??????? returning="retVal")
??? public void doAccessCheck(Object retVal) {
??????? // 這樣,進來這個方法的處理時候,retVal 就是相應(yīng)方法的返回值,是不是非常方便
??????? //? ... 實現(xiàn)代碼
??? }
?
??? // 異常返回
??? @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
??? public void doRecoveryActions() {
??????? // ... 實現(xiàn)代碼
??? }
?
??? @AfterThrowing(
??????? pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
??????? throwing="ex")
??? public void doRecoveryActions(DataAccessException ex) {
??????? // ... 實現(xiàn)代碼
??? }
?
??? // 注意理解它和 @AfterReturning 之間的區(qū)別,這里會攔截正常返回和異常的情況
??? @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
??? public void doReleaseLock() {
??????? // 通常就像 finally 塊一樣使用,用來釋放資源。
??????? // 無論正常返回還是異常退出,都會被攔截到
??? }
?
??? // 感覺這個很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
??? @Around("com.javadoop.aop.SystemArchitecture.businessService()")
??? public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
??????? // start stopwatch
??????? Object retVal = pjp.proceed();
??????? // stop stopwatch
??????? return retVal;
??? }
?
}
?
advisor會由Spring給我們生成。
?
原理解析
?
在 Spring中,Bean 初始化結(jié)束后,會對每一個 bean 調(diào)用一次實現(xiàn) BeanPostProcessor 接口的 Bean postProcessAfterInitialization() ?方法。
?
當我們通過注解配置 AOP (XML差不多),Spring 會 給我們注冊一個 AnnotationAwareAspectJAutoProxyCreator 的 Bean,這個 Bean 就實現(xiàn)了 BeanPostProcessor 接口 的 postProcessAfterInitialization() 方法。
?
這個方法傳入 原始Bean,返回處理過的 Bean ,我們在這里就可以對 這個 Bean 偷梁換柱,換成 Proxy 對象。實現(xiàn)我們代理的目的。
?
在 ProxyCreater 中實現(xiàn)了這個方法:
?
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
?? if (bean != null) {
????? Object cacheKey = getCacheKey(bean.getClass(), beanName);
????? if (!this.earlyProxyReferences.contains(cacheKey)) {
???????? return wrapIfNecessary(bean, beanName, cacheKey);
????? }
?? }
?? return bean;
}
?
這個方法干了這些事情:
1.? 把這個 bean 匹配的AOP增強配置打包成 advisor

?
2.? 保存這個Bean的接口方法
3.? 根據(jù)接口方法和配置決定使用 JDK Proxy 代理生成器(JdkDynamicAopProxy) 還是 CGLIB 代理生成器(ObjenesisCglibAopProxy) 來生成代理對象,一般情況下:
a.? 如果被代理的目標類實現(xiàn)了一個或多個自定義的接口,那么就會使用 JDK 動態(tài)代理
b.? 如果沒有實現(xiàn)任何接口,會使用 CGLIB 實現(xiàn)代理。
4.? 生成代理對象后,把這個代理對象替換掉原來的 Bean
?
簡單講一下創(chuàng)建 JDK Proxy 代理,CGLIB類似,但是操縱比較復(fù)雜。
Proxy 使用方法見 Java 動態(tài)代理機制 (一) JDK Proxy 詳解
JdkDynamicAopProxy -> getProxy
public Object getProxy(ClassLoader classLoader) {
?? if (logger.isDebugEnabled()) {
????? logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
?? }
??
?? Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
?? findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
?? // 獲取這個Bean的所有接口,調(diào)用 JDK 的 Proxy 生成 Proxy
?? // 第三個是? InvocationHandler,代理類實現(xiàn)了? InvocationHandler 接口,有 invoke 方法
?? return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
?
當我們調(diào)用 被 Proxy 代理的類的時候,都會調(diào)用到 JdkDynamicAopProxy 的 invoke 方法,invoke 方法 在我們原方法的周圍做一些增強(從advisor中獲取我們寫好的增強函數(shù))。剩下的就交給 JDK 來處理了。
?