作者:小傅哥
博客: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 中進行完整的實現,否則在使用這些功能的時候就會遇到各種問題。