1000行代碼讀懂Spring(二)- 在Spring中實現AOP

關於AOP

AOP是Spring核心功能之一。今天就用tiny-spring來實現一個AOP。具體功能會包括:java

  1. 讀取AspectJ格式的Pointcut描述。
  2. 使用JDK動態代理以及CGLib兩種方式進行AOP織入。

AOP分爲配置(Pointcut,Advice),織入(Weave)兩部分工做,固然還有一部分是將AOP整合到整個容器的生命週期中。git

AOP相關概念較多,我不會一一列舉,可是會在每一步對概念作一點解釋。github

7.step7-使用JDK動態代理實現AOP織入

git checkout step-7-method-interceptor-by-jdk-dynamic-proxy

織入(weave)相對簡單,咱們先從它開始。Spring AOP的織入點是AopProxy,它包含一個方法Object getProxy()來獲取代理後的對象。spring

在Spring AOP中,我以爲最重要的兩個角色,就是咱們熟悉的MethodInterceptorMethodInvocation(這兩個角色都是AOP聯盟的標準),它們分別對應AOP中兩個基本角色:AdviceJoinpoint。Advice定義了在切點指定的邏輯,而Joinpoint則表明切點。express

public interface MethodInterceptor extends Interceptor {
	
    Object invoke(MethodInvocation invocation) throws Throwable;
}

Spring的AOP只支持方法級別的調用,因此其實在AopProxy裏,咱們只須要將MethodInterceptor放入對象的方法調用便可。app

咱們稱被代理對象爲TargetSource,而AdvisedSupport就是保存TargetSource和MethodInterceptor的元數據對象。這一步咱們先實現一個基於JDK動態代理的JdkDynamicAopProxy,它能夠對接口進行代理。因而咱們就有了基本的織入功能。代理

@Test
	public void testInterceptor() throws Exception {
		// --------- helloWorldService without AOP
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
		HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
		helloWorldService.helloWorld();

		// --------- helloWorldService with AOP
		// 1. 設置被代理對象(Joinpoint)
		AdvisedSupport advisedSupport = new AdvisedSupport();
		TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class,
				HelloWorldService.class);
		advisedSupport.setTargetSource(targetSource);

		// 2. 設置攔截器(Advice)
		TimerInterceptor timerInterceptor = new TimerInterceptor();
		advisedSupport.setMethodInterceptor(timerInterceptor);

		// 3. 建立代理(Proxy)
		JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
		HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy();

		// 4. 基於AOP的調用
		helloWorldServiceProxy.helloWorld();

	}

8.step8-使用AspectJ管理切面

git checkout step-8-invite-pointcut-and-aspectj

完成了織入以後,咱們要考慮另一個問題:對什麼類以及什麼方法進行AOP?對於「在哪切」這一問題的定義,咱們又叫作「Pointcut」。Spring中關於Pointcut包含兩個角色:ClassFilterMethodMatcher,分別是對類和方法作匹配。Pointcut有不少種定義方法,例如類名匹配、正則匹配等,可是應用比較普遍的應該是和AspectJ表達式的方式。code

AspectJ是一個「對Java的AOP加強」。它最先是實際上是一門語言,咱們跟寫Java代碼同樣寫它,而後靜態編譯以後,就有了AOP的功能。下面是一段AspectJ代碼:server

aspect PointObserving {
    private Vector Point.observers = new Vector();

    public static void addObserver(Point p, Screen s) {
        p.observers.add(s);
    }
    public static void removeObserver(Point p, Screen s) {
        p.observers.remove(s);
    }
    ...
}

這種方式無疑過重了,爲了AOP,還要適應一種語言?因此如今使用也很少,可是它的Pointcut表達式被Spring借鑑了過來。因而咱們實現了一個AspectJExpressionPointcutxml

@Test
    public void testMethodInterceptor() throws Exception {
        String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression(expression);
        boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
        Assert.assertTrue(matches);
    }

9.step9-將AOP融入Bean的建立過程

git checkout step-9-auto-create-aop-proxy

萬事俱備,只欠東風!如今咱們有了Pointcut和Weave技術,一個AOP已經算是完成了,可是它尚未結合到Spring中去。怎麼進行結合呢?Spring給了一個巧妙的答案:使用BeanPostProcessor

BeanPostProcessor是BeanFactory提供的,在Bean初始化過程當中進行擴展的接口。只要你的Bean實現了BeanPostProcessor接口,那麼Spring在初始化時,會優先找到它們,而且在Bean的初始化過程當中,調用這個接口,從而實現對BeanFactory核心無侵入的擴展。

那麼咱們的AOP是怎麼實現的呢?咱們知道,在AOP的xml配置中,咱們會寫這樣一句話:

<aop:aspectj-autoproxy/>

它其實至關於:

<bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>

AspectJAwareAdvisorAutoProxyCreator就是AspectJ方式實現織入的核心。它實際上是一個BeanPostProcessor。在這裏它會掃描全部Pointcut,並對bean作織入。

爲了簡化xml配置,我在tiny-spring中直接使用Bean的方式,而不是用aop前綴進行配置:

<bean id="autoProxyCreator" class="us.codecraft.tinyioc.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>

    <bean id="timeInterceptor" class="us.codecraft.tinyioc.aop.TimerInterceptor"></bean>

    <bean id="aspectjAspect" class="us.codecraft.tinyioc.aop.AspectJExpressionPointcutAdvisor">
        <property name="advice" ref="timeInterceptor"></property>
        <property name="expression" value="execution(* us.codecraft.tinyioc.*.*(..))"></property>
    </bean>

TimerInterceptor實現了MethodInterceptor(實際上Spring中還有Advice這樣一個角色,爲了簡單,就直接用MethodInterceptor了)。

至此,一個AOP基本完工。

10.step10-使用CGLib進行類的織入

git checkout step-10-invite-cglib-and-aopproxy-factory

前面的JDK動態代理只能對接口進行代理,對於類則無能爲力。這裏咱們須要一些字節碼操做技術。這方面大概有幾種選擇:ASMCGLibjavassist,後二者是對ASM的封裝。Spring中使用了CGLib。

在這一步,咱們還要定義一個工廠類ProxyFactory,用於根據TargetSource類型自動建立代理,這樣就須要在調用者代碼中去進行判斷。

另外咱們實現了Cglib2AopProxy,使用方式和JdkDynamicAopProxy是徹底相同的。

有一個細節是CGLib建立的代理是沒有注入屬性的, Spring的解決方式是:CGLib僅做代理,任何屬性都保存在TargetSource中,使用MethodInterceptor=>TargetSource的方式進行調用。

至此,AOP部分完工,Spring的核心也基本成型。除去import語句,main下面一共是1026行。下篇博文會對Spring進行一個總體的分析。

項目地址

依然附上項目地址:https://github.com/code4craft/tiny-spring

相關文章
相關標籤/搜索