爐火純青,基于JDK和Cglib動(dòng)態(tài)代理,實(shí)現(xiàn)AOP核心功能
本文轉(zhuǎn)載自微信公眾號(hào)「bugstack蟲洞棧」,作者小傅哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系bugstack蟲洞棧公眾號(hào)。
目錄
- 一、前言
- 二、目標(biāo)
- 三、方案
- 四、實(shí)現(xiàn)
- 1. 工程結(jié)構(gòu)
- 2. 代理方法案例
- 3. 切點(diǎn)表達(dá)式
- 4. 包裝切面通知信息
- 5. 代理抽象實(shí)現(xiàn)(JDK&Cglib)
- 五、測(cè)試
- 1. 事先準(zhǔn)備
- 2. 自定義攔截方法
- 3. 單元測(cè)試
- 六、總結(jié)
一、前言
為什么,你的代碼總是糊到豬圈上?
怎么辦,知道你在互聯(lián)網(wǎng),不知道你在哪個(gè)大廠。知道你在加班,不知道你在和哪個(gè)產(chǎn)品爭(zhēng)辯。知道你在偷懶,不知道你要摸魚到幾點(diǎn)。知道你在搬磚,不知道你在蓋哪個(gè)豬圈。
當(dāng)你特別辛苦夜以繼日的完成著,每天、每周、每月重復(fù)性的工作時(shí),你能獲得的成長是最小,得到的回報(bào)也是少的。留著最多的汗、拿著最少的錢
可能你一激動(dòng)開始看源碼,但不知道看完的源碼能用到什么地方??丛O(shè)計(jì)模式,看的時(shí)候懂,但改自己的代碼又下不去手。其實(shí)一方面是本身技術(shù)棧的知識(shí)面不足,另外一方面是自己儲(chǔ)備的代碼也不夠。最終也就導(dǎo)致根本沒法把一些列的知識(shí)串聯(lián)起來,就像你看了 HashMap,但也聯(lián)想不到分庫分表組件中的數(shù)據(jù)散列也會(huì)用到了 HashMap 中的擾動(dòng)函數(shù)思想和泊松分布驗(yàn)證、看了Spring 源碼,也讀不出來 Mybatis 是如何解決只定義 Dao 接口就能使用配置或者注解對(duì)數(shù)據(jù)庫進(jìn)行 CRUD 操作、看來 JDK 的動(dòng)態(tài)代理,也想不到 AOP 是如何設(shè)計(jì)的。所以成體系學(xué)習(xí),加強(qiáng)技術(shù)棧知識(shí)的完整性,才能更好的用上這些學(xué)習(xí)到的編碼能力。
二、目標(biāo)
到本章節(jié)我們將要從 IOC 的實(shí)現(xiàn),轉(zhuǎn)入到關(guān)于 AOP(Aspect Oriented Programming) 內(nèi)容的開發(fā)。在軟件行業(yè),AOP 意為:面向切面編程,通過預(yù)編譯的方式和運(yùn)行期間動(dòng)態(tài)代理實(shí)現(xiàn)程序功能功能的統(tǒng)一維護(hù)。其實(shí) AOP 也是 OOP 的延續(xù),在 Spring 框架中是一個(gè)非常重要的內(nèi)容,使用 AOP 可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使各模塊間的業(yè)務(wù)邏輯耦合度降低,提高代碼的可復(fù)用性,同時(shí)也能提高開發(fā)效率。
關(guān)于 AOP 的核心技術(shù)實(shí)現(xiàn)主要是動(dòng)態(tài)代理的使用,就像你可以給一個(gè)接口的實(shí)現(xiàn)類,使用代理的方式替換掉這個(gè)實(shí)現(xiàn)類,使用代理類來處理你需要的邏輯。比如:
- @Test
- public void test_proxy_class() {
- IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!");
- String result = userService.queryUserInfo();
- System.out.println("測(cè)試結(jié)果:" + result);
- }
代理類的實(shí)現(xiàn)基本都大家都見過,那么有了一個(gè)基本的思路后,接下來就需要考慮下怎么給方法做代理呢,而不是代理類。另外怎么去代理所有符合某些規(guī)則的所有類中方法呢。如果可以代理掉所有類的方法,就可以做一個(gè)方法攔截器,給所有被代理的方法添加上一些自定義處理,比如打印日志、記錄耗時(shí)、監(jiān)控異常等。
三、方案
在把 AOP 整個(gè)切面設(shè)計(jì)融合到 Spring 前,我們需要解決兩個(gè)問題,包括:如何給符合規(guī)則的方法做代理,以及做完代理方法的案例后,把類的職責(zé)拆分出來。而這兩個(gè)功能點(diǎn)的實(shí)現(xiàn),都是以切面的思想進(jìn)行設(shè)計(jì)和開發(fā)。如果不是很清楚 AOP 是啥,你可以把切面理解為用刀切韭菜,一根一根切總是有點(diǎn)慢,那么用手(代理)把韭菜捏成一把,用菜刀或者斧頭這樣不同的攔截操作來處理。而程序中其實(shí)也是一樣,只不過韭菜變成了方法,菜刀變成了攔截方法。整體設(shè)計(jì)結(jié)構(gòu)如下圖:
- 就像你在使用 Spring 的 AOP 一樣,只處理一些需要被攔截的方法。在攔截方法后,執(zhí)行你對(duì)方法的擴(kuò)展操作。
- 那么我們就需要先來實(shí)現(xiàn)一個(gè)可以代理方法的 Proxy,其實(shí)代理方法主要是使用到方法攔截器類處理方法的調(diào)用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入?yún)?Method method 進(jìn)行 method.invoke(targetObj, args) 這塊是整個(gè)使用時(shí)的差異。
- 除了以上的核心功能實(shí)現(xiàn),還需要使用到 org.aspectj.weaver.tools.PointcutParser 處理攔截表達(dá)式 "execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))",有了方法代理和處理攔截,我們就可以完成設(shè)計(jì)出一個(gè) AOP 的雛形了。
四、實(shí)現(xiàn)
1. 工程結(jié)構(gòu)
- small-spring-step-11
- └── src
- ├── main
- │ └── java
- │ └── cn.bugstack.springframework
- │ ├── aop
- │ │ ├── aspectj
- │ │ │ └── AspectJExpressionPointcut.java
- │ │ ├── framework
- │ │ │ ├── AopProxy.java
- │ │ │ ├── Cglib2AopProxy.java
- │ │ │ ├── JdkDynamicAopProxy.java
- │ │ │ └── ReflectiveMethodInvocation.java
- │ │ ├── AdvisedSupport.java
- │ │ ├── ClassFilter.java
- │ │ ├── MethodMatcher.java
- │ │ ├── Pointcut.java
- │ │ └── TargetSource.java
- │ ├── beans
- │ │ ├── factory
- │ │ │ ├── config
- │ │ │ │ ├── AutowireCapableBeanFactory.java
- │ │ │ │ ├── BeanDefinition.java
- │ │ │ │ ├── BeanFactoryPostProcessor.java
- │ │ │ │ ├── BeanPostProcessor.java
- │ │ │ │ ├── BeanReference.java
- │ │ │ │ ├── ConfigurableBeanFactory.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
工程源碼:公眾號(hào)「bugstack蟲洞?!梗貜?fù):Spring 專欄,獲取完整源碼
AOP 切點(diǎn)表達(dá)式和使用以及基于 JDK 和 CGLIB 的動(dòng)態(tài)代理類關(guān)系,如圖 12-2
圖 12-2
- 整個(gè)類關(guān)系圖就是 AOP 實(shí)現(xiàn)核心邏輯的地方,上面部分是關(guān)于方法的匹配實(shí)現(xiàn),下面從 AopProxy 開始是關(guān)于方法的代理操作。
- AspectJExpressionPointcut 的核心功能主要依賴于 aspectj 組件并處理 Pointcut、ClassFilter,、MethodMatcher 接口實(shí)現(xiàn),專門用于處理類和方法的匹配過濾操作。
- AopProxy 是代理的抽象對(duì)象,它的實(shí)現(xiàn)主要是基于 JDK 的代理和 Cglib 代理。在前面章節(jié)關(guān)于對(duì)象的實(shí)例化 CglibSubclassingInstantiationStrategy,我們也使用過 Cglib 提供的功能。
2. 代理方法案例
在實(shí)現(xiàn) AOP 的核心功能之前,我們先做一個(gè)代理方法的案例,通過這樣一個(gè)可以概括代理方法的核心全貌,可以讓大家更好的理解后續(xù)拆解各個(gè)方法,設(shè)計(jì)成解耦功能的 AOP 實(shí)現(xiàn)過程。
單元測(cè)試
- @Test
- public void test_proxy_method() {
- // 目標(biāo)對(duì)象(可以替換成任何的目標(biāo)對(duì)象)
- Object targetObj = new UserService();
- // AOP 代理
- IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
- // 方法匹配器
- MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (methodMatcher.matches(method, targetObj.getClass())) {
- // 方法攔截器
- MethodInterceptor methodInterceptor = invocation -> {
- long start = System.currentTimeMillis();
- try {
- return invocation.proceed();
- } finally {
- System.out.println("監(jiān)控 - Begin By AOP");
- System.out.println("方法名稱:" + invocation.getMethod().getName());
- System.out.println("方法耗時(shí):" + (System.currentTimeMillis() - start) + "ms");
- System.out.println("監(jiān)控 - End\r\n");
- }
- };
- // 反射調(diào)用
- return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
- }
- return method.invoke(targetObj, args);
- }
- });
- String result = proxy.queryUserInfo();
- System.out.println("測(cè)試結(jié)果:" + result);
- }
首先整個(gè)案例的目標(biāo)是給一個(gè) UserService 當(dāng)成目標(biāo)對(duì)象,對(duì)類中的所有方法進(jìn)行攔截添加監(jiān)控信息打印處理。
從案例中你可以看到有代理的實(shí)現(xiàn) Proxy.newProxyInstance,有方法的匹配 MethodMatcher,有反射的調(diào)用 invoke(Object proxy, Method method, Object[] args),也用用戶自己攔截方法后的操作。這樣一看其實(shí)和我們使用的 AOP 就非常類似了,只不過你在使用 AOP 的時(shí)候是框架已經(jīng)提供更好的功能,這里是把所有的核心過程給你展示出來了。
測(cè)試結(jié)果
- 監(jiān)控 - Begin By AOP
- 方法名稱:queryUserInfo
- 方法耗時(shí):86ms
- 監(jiān)控 - End
- 測(cè)試結(jié)果:小傅哥,100001,深圳
- Process finished with exit code 0
- 從測(cè)試結(jié)果可以看到我們已經(jīng)對(duì) UserService#queryUserInfo 方法進(jìn)行了攔截監(jiān)控操作,其實(shí)后面我們實(shí)現(xiàn)的 AOP 就是現(xiàn)在體現(xiàn)出的結(jié)果,只不過我們需要把這部分測(cè)試的案例解耦為更具有擴(kuò)展性的各個(gè)模塊實(shí)現(xiàn)。
拆解案例
圖 12-3
- 拆解過程可以參考截圖 12-3,我們需要把代理對(duì)象拆解出來,因?yàn)樗梢允?JDK 的實(shí)現(xiàn)也可以是 Cglib 的處理。
- 方法匹配器操作其實(shí)已經(jīng)是一個(gè)單獨(dú)的實(shí)現(xiàn)類了,不過我們還需要把傳入的目標(biāo)對(duì)象、方法匹配、攔截方法,都進(jìn)行統(tǒng)一的包裝,方便外部調(diào)用時(shí)進(jìn)行一個(gè)入?yún)⑼競(jìng)鳌?/li>
- 最后其實(shí)是 ReflectiveMethodInvocation 的使用,它目前已經(jīng)是實(shí)現(xiàn) MethodInvocation 接口的一個(gè)包裝后的類,參數(shù)信息包括:調(diào)用的對(duì)象、調(diào)用的方法、調(diào)用的入?yún)ⅰ?/li>
3. 切點(diǎn)表達(dá)式
定義接口
cn.bugstack.springframework.aop.Pointcut
- public interface Pointcut {
- /**
- * Return the ClassFilter for this pointcut.
- * @return the ClassFilter (never <code>null</code>)
- */
- ClassFilter getClassFilter();
- /**
- * Return the MethodMatcher for this pointcut.
- * @return the MethodMatcher (never <code>null</code>)
- */
- MethodMatcher getMethodMatcher();
- }
- 切入點(diǎn)接口,定義用于獲取 ClassFilter、MethodMatcher 的兩個(gè)類,這兩個(gè)接口獲取都是切點(diǎn)表達(dá)式提供的內(nèi)容。
cn.bugstack.springframework.aop.ClassFilter
- public interface ClassFilter {
- /**
- * Should the pointcut apply to the given interface or target class?
- * @param clazz the candidate target class
- * @return whether the advice should apply to the given target class
- */
- boolean matches(Class<?> clazz);
- }
- 定義類匹配類,用于切點(diǎn)找到給定的接口和目標(biāo)類。
cn.bugstack.springframework.aop.MethodMatcher
- public interface MethodMatcher {
- /**
- * Perform static checking whether the given method matches. If this
- * @return whether or not this method matches statically
- */
- boolean matches(Method method, Class<?> targetClass);
- }
- 方法匹配,找到表達(dá)式范圍內(nèi)匹配下的目標(biāo)類和方法。在上文的案例中有所體現(xiàn):methodMatcher.matches(method, targetObj.getClass())
實(shí)現(xiàn)切點(diǎn)表達(dá)式類
- public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
- private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
- static {
- SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
- }
- private final PointcutExpression pointcutExpression;
- public AspectJExpressionPointcut(String expression) {
- PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
- pointcutExpression = pointcutParser.parsePointcutExpression(expression);
- }
- @Override
- public boolean matches(Class<?> clazz) {
- return pointcutExpression.couldMatchJoinPointsInType(clazz);
- }
- @Override
- public boolean matches(Method method, Class<?> targetClass) {
- return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
- }
- @Override
- public ClassFilter getClassFilter() {
- return this;
- }
- @Override
- public MethodMatcher getMethodMatcher() {
- return this;
- }
- }
- 切點(diǎn)表達(dá)式實(shí)現(xiàn)了 Pointcut、ClassFilter、MethodMatcher,三個(gè)接口定義方法,同時(shí)這個(gè)類主要是對(duì) aspectj 包提供的表達(dá)式校驗(yàn)方法使用。
- 匹配 matches:pointcutExpression.couldMatchJoinPointsInType(clazz)、pointcutExpression.matchesMethodExecution(method).alwaysMatches(),這部分內(nèi)容可以單獨(dú)測(cè)試驗(yàn)證。
匹配驗(yàn)證
- @Test
- public void test_aop() throws NoSuchMethodException {
- AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..))");
- Class<UserService> clazz = UserService.class;
- Method method = clazz.getDeclaredMethod("queryUserInfo");
- System.out.println(pointcut.matches(clazz));
- System.out.println(pointcut.matches(method, clazz));
- // true、true
- }
這里單獨(dú)提供出來一個(gè)匹配方法的驗(yàn)證測(cè)試,可以看看你攔截的方法與對(duì)應(yīng)的對(duì)象是否匹配。
4. 包裝切面通知信息
cn.bugstack.springframework.aop.AdvisedSupport
- public class AdvisedSupport {
- // 被代理的目標(biāo)對(duì)象
- private TargetSource targetSource;
- // 方法攔截器
- private MethodInterceptor methodInterceptor;
- // 方法匹配器(檢查目標(biāo)方法是否符合通知條件)
- private MethodMatcher methodMatcher;
- // ...get/set
- }
- AdvisedSupport,主要是用于把代理、攔截、匹配的各項(xiàng)屬性包裝到一個(gè)類中,方便在 Proxy 實(shí)現(xiàn)類進(jìn)行使用。這和你的業(yè)務(wù)開發(fā)中包裝入?yún)⑹且粋€(gè)道理
- TargetSource,是一個(gè)目標(biāo)對(duì)象,在目標(biāo)對(duì)象類中提供 Object 入?yún)傩?,以及獲取目標(biāo)類 TargetClass 信息。
- MethodInterceptor,是一個(gè)具體攔截方法實(shí)現(xiàn)類,由用戶自己實(shí)現(xiàn) MethodInterceptor#invoke 方法,做具體的處理。像我們本文的案例中是做方法監(jiān)控處理
- MethodMatcher,是一個(gè)匹配方法的操作,這個(gè)對(duì)象由 AspectJExpressionPointcut 提供服務(wù)。
5. 代理抽象實(shí)現(xiàn)(JDK&Cglib)
定義接口
cn.bugstack.springframework.aop.framework
- public interface AopProxy {
- Object getProxy();
- }
- 定義一個(gè)標(biāo)準(zhǔn)接口,用于獲取代理類。因?yàn)榫唧w實(shí)現(xiàn)代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定義接口會(huì)更加方便管理實(shí)現(xiàn)類。
cn.bugstack.springframework.aop.framework.JdkDynamicAopProxy
- public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
- private final AdvisedSupport advised;
- public JdkDynamicAopProxy(AdvisedSupport advised) {
- this.advised = advised;
- }
- @Override
- public Object getProxy() {
- return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
- MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
- return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
- }
- return method.invoke(advised.getTargetSource().getTarget(), args);
- }
- }
- 基于 JDK 實(shí)現(xiàn)的代理類,需要實(shí)現(xiàn)接口 AopProxy、InvocationHandler,這樣就可以把代理對(duì)象 getProxy 和反射調(diào)用方法 invoke 分開處理了。
- getProxy 方法中的是代理一個(gè)對(duì)象的操作,需要提供入?yún)?ClassLoader、AdvisedSupport、和當(dāng)前這個(gè)類 this,因?yàn)檫@個(gè)類提供了 invoke 方法。
- invoke 方法中主要處理匹配的方法后,使用用戶自己提供的方法攔截實(shí)現(xiàn),做反射調(diào)用 methodInterceptor.invoke 。
- 這里還有一個(gè) ReflectiveMethodInvocation,其他它就是一個(gè)入?yún)⒌陌b信息,提供了入?yún)?duì)象:目標(biāo)對(duì)象、方法、入?yún)ⅰ?/li>
cn.bugstack.springframework.aop.framework.Cglib2AopProxy
- public class Cglib2AopProxy implements AopProxy {
- private final AdvisedSupport advised;
- public Cglib2AopProxy(AdvisedSupport advised) {
- this.advised = advised;
- }
- @Override
- public Object getProxy() {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
- enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
- enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
- return enhancer.create();
- }
- private static class DynamicAdvisedInterceptor implements MethodInterceptor {
- @Override
- public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
- CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
- if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
- return advised.getMethodInterceptor().invoke(methodInvocation);
- }
- return methodInvocation.proceed();
- }
- }
- private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
- @Override
- public Object proceed() throws Throwable {
- return this.methodProxy.invoke(this.target, this.arguments);
- }
- }
- }
基于 Cglib 使用 Enhancer 代理的類可以在運(yùn)行期間為接口使用底層 ASM 字節(jié)碼增強(qiáng)技術(shù)處理對(duì)象的代理對(duì)象生成,因此被代理類不需要實(shí)現(xiàn)任何接口。
關(guān)于擴(kuò)展進(jìn)去的用戶攔截方法,主要是在 Enhancer#setCallback 中處理,用戶自己的新增的攔截處理。這里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相應(yīng)的反射操作。
五、測(cè)試
1. 事先準(zhǔn)備
- 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 "注冊(cè)用戶:" + userName + " success!";
- }
- }
- 在 UserService 中提供了2個(gè)不同方法,另外你還可以增加新的類來加入測(cè)試。后面我們的測(cè)試過程,會(huì)給這個(gè)兩個(gè)方法添加我們的攔截處理,打印方法執(zhí)行耗時(shí)。
2. 自定義攔截方法
- public class UserServiceInterceptor implements MethodInterceptor {
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
- long start = System.currentTimeMillis();
- try {
- return invocation.proceed();
- } finally {
- System.out.println("監(jiān)控 - Begin By AOP");
- System.out.println("方法名稱:" + invocation.getMethod());
- System.out.println("方法耗時(shí):" + (System.currentTimeMillis() - start) + "ms");
- System.out.println("監(jiān)控 - End\r\n");
- }
- }
- }
- 用戶自定義的攔截方法需要實(shí)現(xiàn) MethodInterceptor 接口的 invoke 方法,使用方式與 Spring AOP 非常相似,也是包裝 invocation.proceed() 放行,并在 finally 中添加監(jiān)控信息。
3. 單元測(cè)試
- @Test
- public void test_dynamic() {
- // 目標(biāo)對(duì)象
- IUserService userService = new UserService();
- // 組裝代理信息
- AdvisedSupport advisedSupport = new AdvisedSupport();
- advisedSupport.setTargetSource(new TargetSource(userService));
- advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
- advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));
- // 代理對(duì)象(JdkDynamicAopProxy)
- IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
- // 測(cè)試調(diào)用
- System.out.println("測(cè)試結(jié)果:" + proxy_jdk.queryUserInfo());
- // 代理對(duì)象(Cglib2AopProxy)
- IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
- // 測(cè)試調(diào)用
- System.out.println("測(cè)試結(jié)果:" + proxy_cglib.register("花花"));
- }
- 整個(gè)案例測(cè)試了 AOP 在于 Spring 結(jié)合前的核心代碼,包括什么是目標(biāo)對(duì)象、怎么組裝代理信息、如何調(diào)用代理對(duì)象。
- AdvisedSupport,包裝了目標(biāo)對(duì)象、用戶自己實(shí)現(xiàn)的攔截方法以及方法匹配表達(dá)式。
- 之后就是分別調(diào)用 JdkDynamicAopProxy、Cglib2AopProxy,兩個(gè)不同方式實(shí)現(xiàn)的代理類,看看是否可以成功攔截方法
測(cè)試結(jié)果
- 監(jiān)控 - Begin By AOP
- 方法名稱:public abstract java.lang.String cn.bugstack.springframework.test.bean.IUserService.queryUserInfo()
- 方法耗時(shí):86ms
- 監(jiān)控 - End
- 測(cè)試結(jié)果:小傅哥,100001,深圳
- 監(jiān)控 - Begin By AOP
- 方法名稱:public java.lang.String cn.bugstack.springframework.test.bean.UserService.register(java.lang.String)
- 方法耗時(shí):97ms
- 監(jiān)控 - End
- 測(cè)試結(jié)果:注冊(cè)用戶:花花 success!
- Process finished with exit code 0
如 AOP 功能定義一樣,我們可以通過這樣的代理方式、方法匹配和攔截后,在對(duì)應(yīng)的目標(biāo)方法下,做了攔截操作進(jìn)行監(jiān)控信息打印。
六、總結(jié)
從本文對(duì) Proxy#newProxyInstance、MethodInterceptor#invoke,的使用驗(yàn)證切面核心原理以及再把功能拆解到 Spring 框架實(shí)現(xiàn)中,可以看到一個(gè)貌似復(fù)雜的技術(shù)其實(shí)核心內(nèi)容往往沒有太多,但因?yàn)樾枰獮榱藵M足后續(xù)更多的擴(kuò)展就需要進(jìn)行職責(zé)解耦和包裝,通過這樣設(shè)計(jì)模式的使用,以此讓調(diào)用方能更加簡(jiǎn)化,自身也可以不斷按需擴(kuò)展。
AOP 的功能實(shí)現(xiàn)目前還沒有與 Spring 結(jié)合,只是對(duì)切面技術(shù)的一個(gè)具體實(shí)現(xiàn),你可以先學(xué)習(xí)到如何處理代理對(duì)象、過濾方法、攔截方法,以及使用 Cglib 和 JDK 代理的區(qū)別,其實(shí)這與的技術(shù)不只是在 Spring 框架中有所體現(xiàn),在其他各類需要減少人工硬編碼的場(chǎng)景下,都會(huì)用到。比如RPC、Mybatis、MQ、分布式任務(wù)
一些核心技術(shù)的使用上,都是具有很強(qiáng)的關(guān)聯(lián)性的,它們也不是孤立存在的。而這個(gè)能把整個(gè)技術(shù)棧串聯(lián)起來的過程,需要你來大量的學(xué)習(xí)、積累、由點(diǎn)到面的鋪設(shè),才能在一個(gè)知識(shí)點(diǎn)的學(xué)習(xí)拓展到一個(gè)知識(shí)面和知識(shí)體系的建設(shè)。