作者:小傅哥
部落格:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
嘎小子,這片代碼水太深你把握不住!
在電視劇《楚漢傳奇》中有這麼一段劉邦與韓信的飲酒對話,劉邦問韓信我那個
曹參
讀過書見過世面能帶多少兵,韓信說能帶一萬五,又補充說一萬五都吃力。劉邦又一一說出
樊哙
、
盧绾
周勃
,韓信笑着說不足2萬,腦子不行。這時候劉邦有點挂不住臉了,問:那我呢,我能帶多少兵。韓信說,你能帶十萬。劉邦一看比他們都多,啊,還行。轉頭一想就問韓信那你呢,你能帶多少兵。韓信喝多了,說啊,我,我多多益善。這時候劉邦惱了上司勁上來了,問:那我為什麼能管着你,你給我說,說呀!
這像不像你上司問你,你能寫多少代碼、搭多少架構、接多少項目。可能很大一部分沒經曆太多的新人碼農,僅僅是能完成一些簡單的功能子產品開發,而沒有辦法駕馭整個項目的涉及到的所有工程,也不能為項目提煉出一些可複用的通用性元件子產品。在初級碼農的心裡,接一點需求還好,但沒有人帶的時候完全接一個較大型項目就會比較慌了,不知道這裡有沒有坑,自己也把握住不。這些代碼一塊塊的帶着能寫,但是都弄到一塊,就太難了!
在代碼開發成長的這條路上,要經曆CRUD、ERP查資料、接口包裝、功能開發、服務整合、系統建設等,一直到獨立帶人承擔較大型項目的搭建。這一過程需要你能有大量的編寫代碼經驗積累和複雜問題的處理手段,之後才能一段段的把看似獨立的子產品後者代碼片段組裝成一個較大型能跑起來的項目。就像 Spring 的開發過程一樣,我們總是不斷在添加新的功能片段,最後又把技術實作與Spring 容器整合,讓使用方可以更簡單的運用 Spring 提供的能力。
二、目标
在上一章節我們通過基于 Proxy.newProxyInstance 代理操作中處理方法比對和方法攔截,對比對的對象進行自定義的處理操作。并把這樣的技術核心内容拆解到 Spring 中,用于實作 AOP 部分,通過拆分後基本可以明确各個類的職責,包括你的代理目标對象屬性、攔截器屬性、方法比對屬性,以及兩種不同的代理操作 JDK 和 CGlib 的方式。
再有了一個 AOP 核心功能的實作後,我們可以通過單元測試的方式進行驗證切面功能對方法進行攔截,但如果這是一個面向使用者使用的功能,就不太可能讓使用者這麼複雜且沒有與 Spring 結合的方式單獨使用 AOP,雖然可以滿足需求,但使用上還是過去分散。
是以我們需要在本章節完成 AOP 核心功能與 Spring 架構的整合,最終能通過在 Spring 配置的方式完成切面的操作。
三、方案
- 從 BeanPostProcessor 開始,讓 xml 中的配置加載到 DefaultAdvisorAutoProxyCreator 實作,
- 其實再有了核心功能的開發後,這事也不難。隻不過我們要解決幾個問題,包括:怎麼串聯到Bean的生命周期中、怎麼組裝各項功能、怎麼适配代理,
- 借着 BeanPostProcessor,把動态代理融入到Bean的生命周期
其實在有了AOP的核心功能實作後,把這部分功能服務融入到 Spring 其實也不難,隻不過要解決幾個問題,包括:怎麼借着 BeanPostProcessor 把動态代理融入到 Bean 的生命周期中,以及如何組裝各項切點、攔截、前置的功能和适配對應的代理器。整體設計結構如下圖:
- 為了可以讓對象建立過程中,能把xml中配置的代理對象也就是切面的一些類對象執行個體化,就需要用到 BeanPostProcessor 提供的方法,因為這個類的中的方法可以分别作用與 Bean 對象執行初始化前後修改 Bean 的對象的擴充資訊。但這裡需要集合于 BeanPostProcessor 實作新的接口和實作類,這樣才能定向擷取對應的類資訊。
- 但因為建立的是代理對象不是之前流程裡的普通對象,是以我們需要前置于其他對象的建立,是以在實際開發的過程中,需要在 AbstractAutowireCapableBeanFactory#createBean 優先完成 Bean 對象的判斷,是否需要代理,有則直接傳回代理對象。在Spring的源碼中會有 createBean 和 doCreateBean 的方法拆分
- 這裡還包括要解決方法攔截器的具體功能,提供一些 BeforeAdvice、AfterAdvice 的實作,讓使用者可以更簡化的使用切面功能。除此之外還包括需要包裝切面表達式以及攔截方法的整合,以及提供不同類型的代理方式的代理工廠,來包裝我們的切面服務。
四、實作
1. 工程結構
small-spring-step-12
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ └── AspectJExpressionPointcut.java
│ │ │ └── AspectJExpressionPointcutAdvisor.java
│ │ ├── framework
│ │ │ ├── adapter
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── autoproxy
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├── JdkDynamicAopProxy.java
│ │ │ ├── ProxyFactory.java
│ │ │ └── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java
│ │ ├── Advisor.java
│ │ ├── BeforeAdvice.java
│ │ ├── ClassFilter.java
│ │ ├── MethodBeforeAdvice.java
│ │ ├── MethodMatcher.java
│ │ ├── Pointcut.java
│ │ ├── PointcutAdvisor.java
│ │ └── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ ├── InstantiationAwareBeanPostProcessor.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── IUserService.java
│ ├── UserService.java
│ └── UserServiceInterceptor.java
└── ApiTest.java
工程源碼:
公衆号「bugstack蟲洞棧」,回複:Spring 專欄,擷取完整源碼
AOP 動态代理融入到Bean的生命周期中類關系,如圖 13-2
- 整個類關系圖中可以看到,在以 BeanPostProcessor 接口實作繼承的 InstantiationAwareBeanPostProcessor 接口後,做了一個自動代理建立的類 DefaultAdvisorAutoProxyCreator,這個類的就是用于處理整個 AOP 代理融入到 Bean 生命周期中的核心類。
- DefaultAdvisorAutoProxyCreator 會依賴于攔截器、代理工廠和Pointcut與Advisor的包裝服務 AspectJExpressionPointcutAdvisor,由它提供切面、攔截方法和表達式。
- Spring 的 AOP 把 Advice 細化了 BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice,目前我們做的測試案例中隻用到了 BeforeAdvice,這部分可以對照 Spring 的源碼進行補充測試。
2. 定義Advice攔截器鍊
cn.bugstack.springframework.aop.BeforeAdvice
public interface BeforeAdvice extends Advice {
}
cn.bugstack.springframework.aop.MethodBeforeAdvice
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* Callback before a given method is invoked.
*
* @param method method being invoked
* @param args arguments to the method
* @param target target of the method invocation. May be <code>null</code>.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void before(Method method, Object[] args, Object target) throws Throwable;
}
- 在 Spring 架構中,Advice 都是通過方法攔截器 MethodInterceptor 實作的。環繞 Advice 類似一個攔截器的鍊路,Before Advice、After advice等,不過暫時我們需要那麼多就隻定義了一個 MethodBeforeAdvice 的接口定義。
3. 定義 Advisor 通路者
cn.bugstack.springframework.aop.Advisor
public interface Advisor {
/**
* Return the advice part of this aspect. An advice may be an
* interceptor, a before advice, a throws advice, etc.
* @return the advice that should apply if the pointcut matches
* @see org.aopalliance.intercept.MethodInterceptor
* @see BeforeAdvice
*/
Advice getAdvice();
}
cn.bugstack.springframework.aop.PointcutAdvisor
public interface PointcutAdvisor extends Advisor {
/**
* Get the Pointcut that drives this advisor.
*/
Pointcut getPointcut();
}
- Advisor 承擔了 Pointcut 和 Advice 的組合,Pointcut 用于擷取 JoinPoint,而 Advice 決定于 JoinPoint 執行什麼操作。
cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor
public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {
// 切面
private AspectJExpressionPointcut pointcut;
// 具體的攔截方法
private Advice advice;
// 表達式
private String expression;
public void setExpression(String expression){
this.expression = expression;
}
@Override
public Pointcut getPointcut() {
if (null == pointcut) {
pointcut = new AspectJExpressionPointcut(expression);
}
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice){
this.advice = advice;
}
}
- AspectJExpressionPointcutAdvisor 實作了 PointcutAdvisor 接口,把切面 pointcut、攔截方法 advice 和具體的攔截表達式包裝在一起。這樣就可以在 xml 的配置中定義一個 pointcutAdvisor 切面攔截器了。
4. 方法攔截器
cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {
private MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis());
return methodInvocation.proceed();
}
}
- MethodBeforeAdviceInterceptor 實作了 MethodInterceptor 接口,在 invoke 方法中調用 advice 中的 before 方法,傳入對應的參數資訊。
- 而這個 advice.before 則是用于自己實作 MethodBeforeAdvice 接口後做的相應處理。其實可以看到具體的 MethodInterceptor 實作類,其實和我們之前做的測試是一樣的,隻不過現在交給了 Spring 來處理
5. 代理工廠
cn.bugstack.springframework.aop.framework.ProxyFactory
public class ProxyFactory {
private AdvisedSupport advisedSupport;
public ProxyFactory(AdvisedSupport advisedSupport) {
this.advisedSupport = advisedSupport;
}
public Object getProxy() {
return createAopProxy().getProxy();
}
private AopProxy createAopProxy() {
if (advisedSupport.isProxyTargetClass()) {
return new Cglib2AopProxy(advisedSupport);
}
return new JdkDynamicAopProxy(advisedSupport);
}
}
- 其實這個代理工廠主要解決的是關于 JDK 和 Cglib 兩種代理的選擇問題,有了代理工廠就可以按照不同的建立需求進行控制。
6. 融入Bean生命周期的自動代理建立者
cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if (isInfrastructureClass(beanClass)) return null;
Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();
for (AspectJExpressionPointcutAdvisor advisor : advisors) {
ClassFilter classFilter = advisor.getPointcut().getClassFilter();
if (!classFilter.matches(beanClass)) continue;
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = null;
try {
targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
advisedSupport.setTargetSource(targetSource);
advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
advisedSupport.setProxyTargetClass(false);
return new ProxyFactory(advisedSupport).getProxy();
}
return null;
}
}
- 這個 DefaultAdvisorAutoProxyCreator 類的主要核心實作在于 postProcessBeforeInstantiation 方法中,從通過 beanFactory.getBeansOfType 擷取 AspectJExpressionPointcutAdvisor 開始。
- 擷取了 advisors 以後就可以周遊相應的 AspectJExpressionPointcutAdvisor 填充對應的屬性資訊,包括:目标對象、攔截方法、比對器,之後傳回代理對象即可。
- 那麼現在調用方擷取到的這個 Bean 對象就是一個已經被切面注入的對象了,當調用方法的時候,則會被按需攔截,處理使用者需要的資訊。
五、測試
1. 事先準備
public class UserService implements IUserService {
public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小傅哥,100001,深圳";
}
public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注冊使用者:" + userName + " success!";
}
}
- 在 UserService 中提供了2個不同方法,另外你還可以增加新的類來加入測試。後面我們的測試過程,會給這個兩個方法添加我們的攔截處理,列印方法執行耗時。
2. 自定義攔截方法
public class UserServiceBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("攔截方法:" + method.getName());
}
}
- 與上一章節的攔截方法相比,我們不在是實作 MethodInterceptor 接口,而是實作 MethodBeforeAdvice 環繞攔截。在這個方法中我們可以擷取到方法的一些資訊,如果還開發了它的 MethodAfterAdvice 則可以兩個接口一起實作。
3. spring.xml 配置 AOP
<beans>
<bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"/>
<bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.UserServiceBeforeAdvice"/>
<bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
<property name="advice" ref="beforeAdvice"/>
</bean>
<bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"/>
<property name="advice" ref="methodInterceptor"/>
</bean>
</beans>
- 這回再使用 AOP 就可以像 Spring 中一樣,通過在 xml 中配置即可。因為我們已經把 AOP 的功能融合到 Bean 的生命周期裡去了,你的新增攔截方法都會被自動處理。
4. 單元測試
@Test
public void test_aop() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("測試結果:" + userService.queryUserInfo());
}
- 在單元測試中你隻需要按照正常擷取和使用 Bean 對象即可,不過這個時候如果被切面攔截了,那麼其實你擷取到的就是對應的代理對象裡面的處理操作了。
測試結果
攔截方法:queryUserInfo
測試結果:小傅哥,100001,深圳
Process finished with exit code 0
- 通過測試結果可以看到,我們已經讓攔截方法生效了,也不需要自己手動處理切面、攔截方法等内容。截圖上可以看到,這個時候的 IUserService 就是一個代理對象
六、總結
- 本章節實作 AOP 功能的外在展現主要是把以前自己在單元測試中的切面攔截,交給 Spring 的 xml 配置了,也就不需要自己手動處理了。那麼這裡有一個非常重要的知識點,就是把相應的功能如何與 Spring 的 Bean 生命周期結合起來,本章節用到的 BeanPostProcessor,因為它可以解決在 Bean 對象執行初始化方法之前,用于修改新執行個體化 Bean 對象的擴充點,是以我們也就可以處理自己的 AOP 代理對象邏輯了。
- 一個功能的實作往往包括核心部分、組裝部分、連結部分,為了這些各自職責的分工,則需要建立接口和類,由不同關系的繼承、實作進行組裝。隻有明确了各個職責分工,才好靈活的擴充相應的功能邏輯,否則很難駕馭大型系統的開發和建設,也就是那種不好把握的感覺。
- 目前我們實作的 AOP 與 Spring 源碼中的核心邏輯是類似的,但更會偏簡單一些,也不會考慮更多的複雜場景遇到的問題,包括是否有構造函數、是否為代理中的切面等。其實也可以看出隻要是 Java 中的一些特性,都需要在真實使用的 Spring 中進行完整的實作,否則在使用這些功能的時候就會遇到各種問題。
七、系列推薦
- 關于 Spring 中 getBean 的全流程源碼解析
- 你說,怎麼把Bean塞到Spring容器?
- SpringBoot 中間件設計和開發
- 數學,離一個程式員有多近?
- 半年招聘篩選了400+份履歷,告訴你怎麼寫容易被撩!
公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!