我們在業務開發中,使用得最多的是面向對象編程(OOP),因為它的代碼邏輯直觀,從上往下就能查看完整的執行鏈路。
在這個基礎上延伸,出現了面向切面編程(AOP),將可以重復性的橫切邏輯抽取到統一的模塊中。
例如日志打印、安全監測,如果按照 OOP 的思想,在每個方法的前后都要加上重復的代碼,之后要修改的話,更改的地方就會太多,導致不好維護。所以出現了 AOP 編程, AOP 所關注的方向是橫向的,不同于 OOP 的縱向。
所以接下來一起來學習 AOP 是如何使用以及 Spring 容器里面的處理邏輯~
創建用于攔截的 bean
public class TestAopBean {
private String testStr = "testStr";
public void testAop() {
// 被攔截的方法,簡單打印
System.out.println("I am the true aop bean");
}
}
創建 Advisor
@Aspectpublic class AspectJTest {
public class AspectJTest {
@Pointcut("execution(* *.testAop(..))")
public void test() {
}
@Before("test()")
public void beforeTest() {
System.out.println("before Test");
}
@After("test()")
public void afterTest() {
System.out.println("after Test");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint joinPoint) {
System.out.println("around Before");
Object o = null;
try {
// 調用切面的方法
o = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around After");
return o;
}
}
首先類打上了 @Aspect 注解,讓 Spring 認識到這個是一個切面 bean,在方法打上 @Pointcut("execution(* *.testAop(..))"),表示這是一個切點方法,execution() 內部的表達式指明被攔截的方法,Before 、After、Around 分別表示在被攔截方法的前、后已經環繞執行。
創建配置文件 aop.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy /> <bean id="aopTestBean" class="aop.TestAopBean"/> <bean class="aop.AspectJTest" /></beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy />
<bean id="aopTestBean" class="aop.TestAopBean"/>
<bean class="aop.AspectJTest" />
</beans>
測試 Demo
public class AopTestBootstrap {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
TestAopBean bean = (TestAopBean) context.getBean("aopTestBean");
bean.testAop();
// 輸出內容 看輸出順序,了解到增強方法的執行順序 :
// Around proceed 之前 -> Before -> Around proceed 之后 -> After
//around Before
//before Test
//I am the true aop bean
//around After
//after Test
}
}
根據上面的啟動例子,發現在自己寫的核心業務方法 testAop() 上,明明只是簡單打印了 I am the true aop bean,但執行結果輸出了其它內容,說明這個類被增強了,在不修改核心業務方法上,我們對它進行了擴展。證明了 AOP 可以使輔助功能獨立于核心業務之外,方便了程序的擴展和解耦。
使用起來很方便,接下來一起來看看 Spring 是如何實現 AOP 功能的吧~
動態 AOP 自定義標簽
之前在介紹自定義標簽時,提到了 AOP 的實現也借助了自定義注解,根據自定義標簽的思想:每個自定義的標簽,都有對應的解析器,然后借助強大的開發工具 IDEA 定位功能,找到解析器注冊的地方:
640?wx_fmt=png
按住 ctrl
,定位標簽對應的 xsd
文件
根據命名文件,在 META-INF
目錄下找到了 spring.handlers
文件
在處理器文件中發現了處理器 AopNamespaceHandler
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
// 注釋 8.1 自定義注解,注冊解析器,元素名是 aspectj-autoproxy
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
處理器繼承自 NamespaceHandlerSupport,在加載過程中,將會執行 init 初始化方法,在這里,會注冊 aspectj-autoproxy 類型的解析器 AspectJAutoProxyBeanDefinitionParser
如何注冊自定義解析器之前也了解過了,所以接下來直接來看看,遇到 aspectj-autoproxy 類型的 bean,程序是如何解析的。
注冊 AnnotationAwareAspectJAutoProxyCreator
來看下解析時,它的入口方法如下:
public BeanDefinition parse(Element element, ParserContext parserContext) {
// aop 注解的解析入口,注冊 AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 對注解中子類的處理
extendBeanDefinition(element, parserContext);
return null;
}
入口方法一如既往的簡潔,交代了要做的事情,然后具體復雜邏輯再交給工具類或者子類繼續實現,所以接下來要看的是如何注冊 AnnotationAwareAspectJAutoProxyCreator。
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) {
// 通過工具類,注冊或升級 AspectJAnnotationAutoProxyCreator
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 處理 proxy-target-class 以及 expose-proxy 屬性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 注冊組件并通知,讓監聽器進行處理
registerComponentIfNecessary(beanDefinition, parserContext);
}
可以看到這個方法內部有三個處理邏輯,所以我們來一個一個去分析了解:
注冊或者升級 AnnotationAwareAspectJAutoProxyCreator
對于 AOP 的實現,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成,它可以根據 @Point 注解定義的切點來自動代理相匹配的 bean。
由于 Spring 替我們做了很多工作,所以開發 AOP 業務時才可以這么簡單,連配置也簡化了許多,所以來看下 Spring 是如何使用自定義配置來幫助我們自動注冊 AnnotationAwareAspectJAutoProxyCreator。
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) {
// 實際注冊的 bean 類型是 AnnotationAwareAspectJAutoProxyCreator
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// 如果在 registry 已經存在自動代理創建器,并且傳入的代理器類型與注冊的不一致,根據優先級判斷是否需要修改
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// 根據優先級選擇使用哪一個
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
// 傳進來的參數優先級更大,修改注冊的 beanName,使用傳進來的代理創建器
apcDefinition.setBeanClassName(cls.getName());
}
}
// 因為已經存在代理器,不需要之后的默認設置,直接返回
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
// 默認的是最小優先級
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 自動代理創建器的注冊名字永遠是 org.springframework.aop.config.internalAutoProxyCreator
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
這個步驟中,實現了自動注冊 AnnotationAwareAspectJAutoProxyCreator 類,同時能看到涉及到優先級的概念和注冊名一直都是 AUTO_PROXY_CREATOR_BEAN_NAME。
處理 proxy-target-class 以及 expose-proxy 屬性
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
// 這方法作用挺簡單的,就是解析下面兩個屬性,如果是 true,將它們加入代理注冊器的屬性列表中
// definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE)
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
// 處理 proxy-target-class 屬性
// 與代碼生成方式有關,在之后步驟中決定使用 jdk 動態代理 或 cglib
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
// 處理 expose-proxy 屬性
// 擴展增強,有時候目標對象內部的自我調用無法實施切面中的增強,通過這個屬性可以同時對兩個方法進行增強
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
關于 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); 方法,它是一個屬性設置的過程,如果解析到的屬性為 true,將它們加入代理注冊器的屬性列表中,這里不細說下去。
將這兩個屬性分開熟悉:
proxy-target-class
Spring AOP 部分使用 JDK 動態代理 (Proxy + InvocationHandler),或者 CGLIB (Code Generation LIB)來為目標對象創建代理。書中提到,推薦使用的是 JDK 動態代理。
如果被代理的目標對象實現了至少一個接口,則會使用 JDK 動態代理。所有該目標類型實現的接口都將被代理。
若該目標對象沒有實現任何接口,則創建一個 CGLIB 代理。如果希望代理目標對象的所有方法,而不只是實現自接口的方法,可以通過該屬性 proxy-target-class 開啟強制使用 CGLIB 代理。
但是強制開啟 CGLIB 會有以下兩個問題:
如果考慮好上面兩個方面,那就可以通過以下兩個地方來強制開啟 CGLIB 代理:
<aop:config proxy-target-class="true">...</aop:config><aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:config proxy-target-class="true">...</aop:config>
<aop:aspectj-autoproxy proxy-target-class="true"/>
其中有關 CGLIB 代理,這位老哥講得很透徹,建議大家可以去了解一下~ Cglib及其基本使用
expose-proxy
有時候目標對象內部的自我調用將無法實施切面中的增強。
例如兩個方法都加上了事務注解 @Transactional 但是事務類型不一樣:
public interface TestService {
void a();
void b();
}
public class TestServiceImpl implements TestService {
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
System.out.println("Hello world");
}
}
此處的 this 指向了目標對象, this.b() 方法將不會執行 b 事務的切面,即不會執行事務增強。
為了解決這個問題,使 a() 和 b() 方法同時增強,可以通過 expose-proxy 來實現:
<aop:config expose-proxy="true">...</aop:config><aop:aspectj-autoproxy expose-proxy="true"/>
<aop:config expose-proxy="true">...</aop:config>
<aop:aspectj-autoproxy expose-proxy="true"/>
注冊組件并通知
emmmm,這個方法內部邏輯如名字一樣清晰,所以不細說啦。
創建 AOP 代理
前面主要圍繞著自動代理器 AnnotationAwareAspectJAutoProxyCreator 的注冊流程來講解,接下來看自動代理器做了什么來完成 AOP 的操作。
下面是 AnnotationAwareAspectJAutoProxyCreator 的繼承體系:
640?wx_fmt=png
在圖片右上角,發現它實現了 BeanPostProcessor 接口,之前文章提到過,它是一個后處理器,可以在 bean 實例化前后進行擴展。查看了實現了該接口的兩個方法,postProcessBeforeInitialization 沒有做處理,直接返回該對象。
實際進行處理的是 postProcessAfterInitialization 方法,在 bean 實例化之后的處理,在這一步中進行里代理增強,所以來看下這個方法:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// 組裝 key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 如果適合被代理,則需要封裝指定的 bean
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 如果已經處理過
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 不需增強
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 給定的 bean 類是否代表一個基礎設施類,基礎設置類不應代理 || 配置了指定 bean 不需要代理
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 如果存在增強方法則創建代理
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
// 增強方法不為空
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 創建代理
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
來提取一下核心流程:
獲取增強方法或者增強器
我們剛才寫的 @Before 、@After 之類的,就是增強方法,AOP 處理時,要先找出這些增強方法。
根據獲取的增強進行代理
找到增強方法后,需要對這些增強方法進行增強代理,實際上這個 bean 已經不完全是原來的類型了,會變成代理后的類型。
獲取增強方法或者增強器
入口方法在這里:
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
// 尋找符合的切面
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 從 beanFactory 中獲取聲明為 AspectJ 注解的類,對并這些類進行增強器的提取
// 委派給子類實現 org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.extendAdvisors
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 尋找匹配的增強器
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
對于指定 bean 的增強方法的獲取包含這兩個步驟,獲取所有的增強以及尋找所有增強中適用于 bean 的增強并應用。對應于 findCandidateAdvisors 和 findAdvisorsThatCanApply 這兩個方法。如果沒找到對應的增強器,那就返回 DO_NOT_PROXY ,表示不需要進行增強。
由于邏輯太多,所以接下來貼的代碼不會太多,主要來了解它的大致流程,有需要的可以跟著源碼工程的注釋跟蹤完整的流程~:
尋找對應的增強器 findCandidateAdvisors
protected List<Advisor> findCandidateAdvisors() {
List<Advisor> advisors = super.findCandidateAdvisors();
if (this.aspectJAdvisorsBuilder != null) {
// 注釋 8.3 實際調用的是 org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
實際來看,關鍵是這個方法 this.aspectJAdvisorsBuilder.buildAspectJAdvisors() 這個方法看起來簡單,但是實際處理的邏輯很多,代碼深度也很多,所以為了避免太多代碼,我羅列了主要流程,和關鍵的處理方法做了什么
主要流程如下:
獲取所有 beanName,會將之前在 beanFactory 中注冊的 bean 都提取出來。
遍歷前一步驟提取出來的 bean 列表,找出打上 @AspectJ 注解的類,進行進一步處理
繼續對前一步提取的 @AspectJ 注解的類進行增強器的提取
將提取結果加入緩存中
可以查詢代碼中的注釋,從 [注釋 8.3] 到 [注釋 8.8 根據切點信息生成增強器] 都是這個方法的處理邏輯
※※在這個流程的最后一步中,會將識別到的切點信息(PointCut)和增強方法(Advice)進行封裝,具體是由 Advisor 的實現類 InstantiationModelAwarePointcutAdvisorImpl 進行統一封裝。
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
// 簡單賦值
this.declaredPointcut = declaredPointcut;
...
if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
this.pointcut = new PerTargetInstantiationModelPointcut(
this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
this.lazy = true;
}
else {
// A singleton aspect.
this.pointcut = this.declaredPointcut;
this.lazy = false;
// 初始化增強器
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
}
封裝體前半部分邏輯只是簡單賦值。關鍵是這個方法 instantiateAdvice(this.declaredPointcut),在這一步中,對不同的增強(Before/After/Around)實現的邏輯是不一樣的。在 ReflectiveAspectJAdvisorFactory#getAdvice 方法中區別實現了根據不同的注解類型封裝不同的增強器。
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
...
// 注釋 8.7 根據不同的注解類型封裝不同的增強器
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut:
}
return null;
case AtAround:
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
default:
}
}
最后切點方法通過解析和封裝成 Advisor,提取到的結果加入到緩存中。細心的你可能會發現除了普通的增強器外,還有另外兩種增強器:同步實例化增強器和引介增強器。由于用的比較少,所以我看到源碼中這兩個分支處理沒有深入去學習,感興趣的同學請繼續深入學習這兩種增強器~
獲取匹配的增強器 findAdvisorsThatCanApply
在前面流程中,已經完成了所有增強器的解析,但是對于前面解析到的增強器,并不一定都適用于當前處理的 bean,所以還需要通過一個方法來挑選出合適的增強器。
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
// 在這一步中進行過濾增強器
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
可以看到,具體實現過濾操作的是工具類方法 AopUtils.findAdvisorsThatCanApply:
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
// 遍歷所有增強器
for (Advisor candidate : candidateAdvisors) {
// 首先處理引介增強
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
// 前面處理過了,跳過
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
// 處理普通增強器類型
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
具體判斷邏輯在 canApply() 方法中,如果判斷符合條件的,加入到 eligibleAdvisors 中,最后返回對于這個 bean 適合的增強器列表。
創建代理
通過前面的流程,獲取到了所有對應 bean 的增強器后,可以開始代理的創建。
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
ProxyFactory proxyFactory = new ProxyFactory();
// 拷貝,獲取當前類中的相關屬性
proxyFactory.copyFrom(this);
// 決定對于給定 bean 是否應該使用 targetClass 而不是他的接口代理
if (!proxyFactory.isProxyTargetClass()) {
// 檢查 proxyTargetClass 設置以及 preserveTargetClass 屬性
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
// 添加代理接口
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 這一步中,主要將攔截器封裝為增強器
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
// 定制代理
customizeProxyFactory(proxyFactory);
// 用來控制代理工廠被配置之后,是否含允許修改通知
// 缺省值為 false,不允許修改代理的配置
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 生成代理,委托給了 ProxyFactory 去處理。
return proxyFactory.getProxy(getProxyClassLoader());
}
對于代理類的創建和處理, Spring 委托給了 ProxyFactory 去處理,在上面貼出的函數主要是對 ProxyFactory 的初始化操作,進而對真正的創建代理做準備,主要流程如下:
獲取當前類的屬性
添加代理接口
封裝 Advisor 并加入到 ProxyFactory
設置要代理的類
為子類提供定制的函數 customizeProxyFactory,子類通過該方法對 ProxyFactory 進行進一步的封裝
進行獲取代理操作
比較關鍵的是第三個步驟和第六個步驟,其中在第三個步驟中,進行的是攔截器包裝,詳細代碼流程請查 [注釋 8.9 為給定的bean創建AOP代理] 和 [注釋 8.10 包裝攔截器,封裝成 Advisor]。
接著,完成了所有增強器的封裝過程,到了解析的最后一步,進行代理的創建和獲取。
public Object getProxy(@Nullable ClassLoader classLoader) { return createAopProxy().getProxy(classLoader);}
return createAopProxy().getProxy(classLoader);
}
創建代理 createAopProxy()
定位到創建代理的代碼:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
從上面代碼中能看出,根據了幾個關鍵屬性,判斷創建的是哪種類型的 AopProxy,一種是 JDK 動態代理,另一種是 CGLIB 動態代理。
前面提到過的 proxy-target-class 屬性和 targetClass 屬性,在這里判斷了應該創建哪一個代理。
獲取代理 getProxy()
640?wx_fmt=png
觀察圖片以及前面分析,可以知道有兩種代理方式:[JDK 動態代理] 和 [CGLIB 動態代理]
同時先說下動態代理的含義:抽象類在編譯期間是未確定具體實現子類,在運行時才生成最終對象。
JDK 動態代理
JDK 代理是默認推薦的代理方式,使用的是 Proxy + InvocationHandler。
可以通過以下方式實現:定義一個接口、實現類,和一個處理器繼承于 InvocationHandler,然后重載處理器中的 invoke 方法,對代理對象進行增強。
JdkDynamicAopProxy.java
public Object getProxy(@Nullable ClassLoader classLoader) {
// 注釋 8.11 JDK 動態代理
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
獲取代理的核心步驟在 Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),第三個參數是 JdkDynamicAopProxy 本身,而且它實現了 InvocationHandler 接口,重載了 invoke 方法。
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 注釋 8.12 jdk 動態代理重載的 invoke 方法
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
// 獲取此方法的攔截鏈
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 檢查我們是否有任何切面邏輯。如果我們不這樣做,我們可以回退直接反射調用目標,并避免創建 MethodInvocation。
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 將攔截器封裝在 ReflectiveMethodInvocation,便于使用 proceed 執行攔截器
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 執行攔截器鏈
retVal = invocation.proceed();
}
...
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
創建 JDK 代理過程中,主要的工作時創建了一個攔截器鏈,并使用 ReflectiveMethodInvocation 類進行封裝,封裝之后,逐一調用它的 proceed 方法, 用來實現在目標方法的前置增強和后置增強。
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
public Object proceed() throws Throwable {
// 執行完所有增強器后執行切點方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 獲取下一個要執行的攔截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 動態匹配
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// 匹配失敗,跳過攔截器,直接返回
return proceed();
}
}
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
具體代碼和注釋請定位到該方法查看。關于 JDK 動態代理,深入學習的話也可以單獨拎出來,所以推薦看這篇資料 小豹子帶你看源碼:JDK 動態代理,進行了和學習
CGLIB 動態代理
CGLIB[Code Generation LIB] 是一個強大的高性能的代碼生成包。它廣泛應用于許多 AOP 框架。
再次推薦參考資料一,這位老哥將 CGLIB 代理, 詳細介紹了 CGLIB 在什么場景使用,以及被它增強后代碼處理順序,Cglib及其基本使用。
希望看完這篇文章,能過了解到 CGLIB 代碼生成包具體是如何對類進行增強。
代理增強結果
通過前面一系列步驟,解析標簽、屬性、增強方法,到最后獲取 CGLIB 代理,通過代理創建 bean
來看下最后被代理的 bean 內部:
640?wx_fmt=png
從圖中可以看到,最終創建的是被修飾后的 bean,內部很明顯是 CGGLIB 代理生成的代碼,我們在不修改業務代碼的情況下,實現了方法增強。
靜態 AOP
既然有動態代理,那么也會有靜態代理。
使用靜態 AOP 的時候,需要用到 LTW (Load-Time Weaving 加載時織入),指的是在虛擬機載入字節碼文件時動態織入 AspectJ 切面。
AOP 的靜態代理主要是在虛擬機啟動時通過改變目標對象字節碼的方式來完成對目標對象的增強,它與動態代理相比具有更高的效率,因為在動態代理調用的過程中,還需要一個動態創建代理類并代理目標對象的步驟,而靜態代理則是在啟動時便完成了字節碼增減,當系統再次調用目標類時,與調動正常的類并無區別,所以在效率上會相對高些。
關于靜態 AOP 的使用和學習,可以參考這篇文章:從代理機制到Spring AOP
總結
動態 AOP 使用起來很簡單,對于如何實現,總結起來就兩點:
動態解析 AOP
標簽
創建 AOP
代理
但在 Spring 底層實現邏輯卻是復雜到不行,從 Spring 框架中可以看到這是良好的代碼設計思路,頂層入口盡量簡單,使用者很容易就能掌握該功能,復雜實現邏輯都被隱藏了。
寫這一篇 AOP 學習總結,花了將近一周,先看了一遍書籍, 下班后花了一晚,將大致流程理了一遍,第二天晚上走讀代碼,發現有些地方還存在疑惑,例如 JDK 和 cglib 動態代理是怎么回事,翻閱查詢資料,弄懂后又過了一天。
將代碼注釋加上,分析動態代理每一個步驟做的事情,結合之前學的后處理器 BeanPostProcessor 知識和自定義標簽解析知識一起又梳理一遍。零零散散,終于整理完成。
在靜態 AOP 知識點,按照我的理解,越往系統底層深入,它的執行效率越高,所以減少了動態創建代理類和代理目標對象的步驟,靜態代理的速度會得到提升。同時由于接近底層后,代碼編寫的復雜度同樣會增加,所以我在權衡高頻率使用場景(動態代理),本次學習沒有詳細去了解,留下這個坑,以后有機會再填吧~
轉自:https://blog.csdn.net/javageektech/article/details/96937647