作者:小傅哥 博客:https://
? 沉淀、分享、成長,讓自己和他人都能有所收獲!??
? 目錄 一、前言
二、目標
三、方案
四、實現(xiàn)
五、測試
1. 事先準備
2. 自定義攔截方法
3. spring.xml 配置 AOP
4. 單元測試
六、總結(jié)
七、系列推薦
一、前言 嘎小子,這片代碼水太深你把握不?。?/code>
在電視劇《楚漢傳奇》中有這么一段劉邦與韓信的飲酒對話,劉邦問韓信我那個曹參
讀過書見過世面能帶多少兵,韓信說能帶一萬五,又補充說一萬五都吃力。劉邦又一一說出樊噲
、盧綰
、周勃
,韓信笑著說不足2萬,腦子不行。這時候劉邦有點掛不住臉了,問:那我呢,我能帶多少兵。韓信說,你能帶十萬。劉邦一看比他們都多,啊,還行。轉(zhuǎn)頭一想就問韓信那你呢,你能帶多少兵。韓信喝多了,說啊,我,我多多益善。這時候劉邦惱了領(lǐng)導(dǎo)勁上來了,問:那我為什么能管著你,你給我說,說呀!
這像不像你領(lǐng)導(dǎo)問你,你能寫多少代碼、搭多少框架、接多少項目。可能很大一部分沒經(jīng)歷太多的新人碼農(nóng),僅僅是能完成一些簡單的功能模塊開發(fā),而沒有辦法駕馭整個項目的涉及到的所有工程,也不能為項目提煉出一些可復(fù)用的通用性組件模塊。在初級碼農(nóng)的心里,接一點需求還好,但沒有人帶的時候完全接一個較大型項目就會比較慌了,不知道這里有沒有坑,自己也把握住不。這些代碼一塊塊的帶著能寫,但是都弄到一塊,就太難了!
在代碼開發(fā)成長的這條路上,要經(jīng)歷CRUD、ERP查數(shù)據(jù)、接口包裝、功能開發(fā)、服務(wù)整合、系統(tǒng)建設(shè)等,一直到獨立帶人承擔(dān)較大型項目的搭建。這一過程需要你能有大量的編寫代碼經(jīng)驗積累和復(fù)雜問題的處理手段,之后才能一段段的把看似獨立的模塊后者代碼片段組裝成一個較大型能跑起來的項目。就像 Spring 的開發(fā)過程一樣,我們總是不斷在添加新的功能片段,最后又把技術(shù)實現(xiàn)與Spring 容器整合,讓使用方可以更簡單的運用 Spring 提供的能力。
二、目標 在上一章節(jié)我們通過基于 Proxy.newProxyInstance 代理操作中處理方法匹配和方法攔截,對匹配的對象進行自定義的處理操作。并把這樣的技術(shù)核心內(nèi)容拆解到 Spring 中,用于實現(xiàn) AOP 部分,通過拆分后基本可以明確各個類的職責(zé),包括你的代理目標對象屬性、攔截器屬性、方法匹配屬性,以及兩種不同的代理操作 JDK 和 CGlib 的方式。
再有了一個 AOP 核心功能的實現(xiàn)后,我們可以通過單元測試的方式進行驗證切面功能對方法進行攔截,但如果這是一個面向用戶使用的功能,就不太可能讓用戶這么復(fù)雜且沒有與 Spring 結(jié)合的方式單獨使用 AOP,雖然可以滿足需求,但使用上還是過去分散。
因此我們需要在本章節(jié)完成 AOP 核心功能與 Spring 框架的整合,最終能通過在 Spring 配置的方式完成切面的操作。
三、方案 其實在有了AOP的核心功能實現(xiàn)后,把這部分功能服務(wù)融入到 Spring 其實也不難,只不過要解決幾個問題,包括:怎么借著 BeanPostProcessor 把動態(tài)代理融入到 Bean 的生命周期中,以及如何組裝各項切點、攔截、前置的功能和適配對應(yīng)的代理器。整體設(shè)計結(jié)構(gòu)如下圖:
為了可以讓對象創(chuàng)建過程中,能把xml中配置的代理對象也就是切面的一些類對象實例化,就需要用到 BeanPostProcessor 提供的方法,因為這個類的中的方法可以分別作用與 Bean 對象執(zhí)行初始化前后修改 Bean 的對象的擴展信息。但這里需要集合于 BeanPostProcessor 實現(xiàn)新的接口和實現(xiàn)類,這樣才能定向獲取對應(yīng)的類信息。 但因為創(chuàng)建的是代理對象不是之前流程里的普通對象,所以我們需要前置于其他對象的創(chuàng)建,所以在實際開發(fā)的過程中,需要在 AbstractAutowireCapableBeanFactory#createBean 優(yōu)先完成 Bean 對象的判斷,是否需要代理,有則直接返回代理對象。在Spring的源碼中會有 createBean 和 doCreateBean 的方法拆分 這里還包括要解決方法攔截器的具體功能,提供一些 BeforeAdvice、AfterAdvice 的實現(xiàn),讓用戶可以更簡化的使用切面功能。除此之外還包括需要包裝切面表達式以及攔截方法的整合,以及提供不同類型的代理方式的代理工廠,來包裝我們的切面服務(wù)。 四、實現(xiàn) 1. 工程結(jié)構(gòu)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蟲洞?!梗貜?fù):Spring 專欄,獲取完整源碼
AOP 動態(tài)代理融入到Bean的生命周期中類關(guān)系,如圖 13-2
圖 13-2 整個類關(guān)系圖中可以看到,在以 BeanPostProcessor 接口實現(xiàn)繼承的 InstantiationAwareBeanPostProcessor 接口后,做了一個自動代理創(chuàng)建的類 DefaultAdvisorAutoProxyCreator,這個類的就是用于處理整個 AOP 代理融入到 Bean 生命周期中的核心類。 DefaultAdvisorAutoProxyCreator 會依賴于攔截器、代理工廠和Pointcut與Advisor的包裝服務(wù) 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 實現(xiàn)的。環(huán)繞 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 承擔(dān)了 Pointcut 和 Advice 的組合,Pointcut 用于獲取 JoinPoint,而 Advice 決定于 JoinPoint 執(zhí)行什么操作。 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 實現(xiàn)了 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 實現(xiàn)了 MethodInterceptor 接口,在 invoke 方法中調(diào)用 advice 中的 before 方法,傳入對應(yīng)的參數(shù)信息。 而這個 advice.before 則是用于自己實現(xiàn) MethodBeforeAdvice 接口后做的相應(yīng)處理。其實可以看到具體的 MethodInterceptor 實現(xiàn)類,其實和我們之前做的測試是一樣的,只不過現(xiàn)在交給了 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); } }
其實這個代理工廠主要解決的是關(guān)于 JDK 和 Cglib 兩種代理的選擇問題,有了代理工廠就可以按照不同的創(chuàng)建需求進行控制。 6. 融入Bean生命周期的自動代理創(chuàng)建者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 類的主要核心實現(xiàn)在于 postProcessBeforeInstantiation 方法中,從通過 beanFactory.getBeansOfType 獲取 AspectJExpressionPointcutAdvisor 開始。 獲取了 advisors 以后就可以遍歷相應(yīng)的 AspectJExpressionPointcutAdvisor 填充對應(yīng)的屬性信息,包括:目標對象、攔截方法、匹配器,之后返回代理對象即可。 那么現(xiàn)在調(diào)用方獲取到的這個 Bean 對象就是一個已經(jīng)被切面注入的對象了,當(dāng)調(diào)用方法的時候,則會被按需攔截,處理用戶需要的信息。 五、測試 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個不同方法,另外你還可以增加新的類來加入測試。后面我們的測試過程,會給這個兩個方法添加我們的攔截處理,打印方法執(zhí)行耗時。 2. 自定義攔截方法public class UserServiceBeforeAdvice implements MethodBeforeAdvice { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("攔截方法:" + method.getName()); } }
與上一章節(jié)的攔截方法相比,我們不在是實現(xiàn) MethodInterceptor 接口,而是實現(xiàn) MethodBeforeAdvice 環(huán)繞攔截。在這個方法中我們可以獲取到方法的一些信息,如果還開發(fā)了它的 MethodAfterAdvice 則可以兩個接口一起實現(xiàn)。 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 中配置即可。因為我們已經(jīng)把 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("測試結(jié)果:" + userService.queryUserInfo()); }
在單元測試中你只需要按照正常獲取和使用 Bean 對象即可,不過這個時候如果被切面攔截了,那么其實你獲取到的就是對應(yīng)的代理對象里面的處理操作了。 測試結(jié)果
攔截方法:queryUserInfo 測試結(jié)果:小傅哥,100001 ,深圳 Process finished with exit code 0
通過測試結(jié)果可以看到,我們已經(jīng)讓攔截方法生效了,也不需要自己手動處理切面、攔截方法等內(nèi)容。截圖上可以看到,這個時候的 IUserService 就是一個代理對象 六、總結(jié) 本章節(jié)實現(xiàn) AOP 功能的外在體現(xiàn)主要是把以前自己在單元測試中的切面攔截,交給 Spring 的 xml 配置了,也就不需要自己手動處理了。那么這里有一個非常重要的知識點,就是把相應(yīng)的功能如何與 Spring 的 Bean 生命周期結(jié)合起來,本章節(jié)用到的 BeanPostProcessor,因為它可以解決在 Bean 對象執(zhí)行初始化方法之前,用于修改新實例化 Bean 對象的擴展點,所以我們也就可以處理自己的 AOP 代理對象邏輯了。 一個功能的實現(xiàn)往往包括核心部分、組裝部分、鏈接部分,為了這些各自職責(zé)的分工,則需要創(chuàng)建接口和類,由不同關(guān)系的繼承、實現(xiàn)進行組裝。只有明確了各個職責(zé)分工,才好靈活的擴展相應(yīng)的功能邏輯,否則很難駕馭大型系統(tǒng)的開發(fā)和建設(shè),也就是那種不好把握的感覺。 目前我們實現(xiàn)的 AOP 與 Spring 源碼中的核心邏輯是類似的,但更會偏簡單一些,也不會考慮更多的復(fù)雜場景遇到的問題,包括是否有構(gòu)造函數(shù)、是否為代理中的切面等。其實也可以看出只要是 Java 中的一些特性,都需要在真實使用的 Spring 中進行完整的實現(xiàn),否則在使用這些功能的時候就會遇到各種問題。 七、系列推薦