漫漫面試路之Spring框架學習:tiny-spring

面試必問的Spring框架,項目來源於黃億華大佬的<<1000行代碼讀懂Spring(一)- 實現一個基本的IoC容器>>

概念的講解

參考

IOC

什麼是IOC?

知乎

Inversion of Control,即控制反轉,是一種設計思想。

傳統Java SE程序設計中,我們直接通過new創建對象;IOC則有一個專門的容器(IoC容器)來創建及注入依賴對象,而不是由對象主動去找。

什麼是DI?

Dependency Injection,即依賴注入。組件之間的依賴關係由容器在運行期決定,也就是說由容器動態的將某個依賴關係注入到組件之中。DI的目的是爲了提升組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。

舉個栗子

IOC

找女朋友一般情況就是到處去看哪裏有符合自己審美的,然後打聽她們的興趣愛好、電話等等。傳統程序開發也是如此,在一個對象中,如果要使用其他的對象,我們需要手動new一個。

Ioc相當於一個婚戀平臺,你只需要告訴它們你的要求,然後它們會根據要求給你提供一個女朋友,整個過程都是由平臺來控制。Spring的開發方式也是如此。所有的類都會在容器中登記,告訴spring它是什麼,需要什麼,然後spring會在運行到適當的時候把你要的東西給你,同時也把你給其他需要你的東西。所有類的創建、銷燬都有spring控制。以前是對象控制其他對象,現在是spring控制所有對象,所以叫控制反轉。

DI

IoC的一個重點就是在系統運行中,動態的向某個對象提供它所需要的其他對象,這就是通過DI實現的。

比如對象A需要操作數據庫,以前我們是自己編碼獲取一個Connction對象,現在我們只需要告訴spring,A中需要一個Connection,至於Connection怎麼構造、何時構造我們都不需要知道。spring會在適當的時候向A注射一個Connection,這就是依賴注入。

tiny-spring分析

克隆項目到本地

git clone https://github.com/code4craft/tiny-spring.git

我在原來項目的基礎上添加了一些中文註釋,github項目地址:https://github.com/zcsherrydc/zcs-spring.git

每一步的命令需要更改爲對應的序號,如第一步就是step1,第二步就是step2

git checkout origin/step1

step1-最基本的容器

git checkout step-1-container-register-and-get

IoC最基本的角色有兩個:容器(BeanFactory)和Bean本身。BeanDefinition封裝了Bean對象。

在這裏插入圖片描述

step2-在工廠中創建bean

git checkout step-2-abstract-beanfactory-and-do-bean-initilizing-in-it

step1中的bean是初始化好之後再set進去的,實際使用中我們希望容器來管理bean的創建。爲了方便擴展,抽象出BeanFactory接口和AbstractBeanFactory類。

在這裏插入圖片描述

AbstractBeanFactory裏面最重要的是抽象方法doCreateBean()。通過反射獲取bean的實例,放入BeanDefinition中,並註冊到BeanFactory。

public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        Object bean = doCreateBean(beanDefinition);
        beanDefinition.setBean(bean);
        beanDefinitionMap.put(name, beanDefinition);
}

測試

    @Test
    public void test() {
//        初始化
        BeanFactory beanFactory = new AutowireCapable();

//        注入bean
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setBeanClassName("zcs.ioc.HelloWorldService");
        beanFactory.registerBeanDefinition("helloWorldService",beanDefinition);

//        獲取bean
        HelloWorldService helloWorldService= (HelloWorldService) beanFactory.getBean("helloWorldService");
        helloWorldService.helloWorld();
    }

我們需要給BeanFactory傳入完整類名實例名稱。這些都可以通過xml文件獲取,在step4中完成。還有一個問題就是現在是通過無參構造函數創建的對象,內部成員變量都是null,所以下一步是對成員變量賦值。

step3-爲bean注入屬性

git checkout step-3-inject-bean-with-property

創建兩個類,PropertyValuePropertyValues,前者保存一個字段的名稱和對應值,後者有一個集合保存了所有的PropertyValue。每個BeanDefinition有一個PropertyValues

AutowireCapable中新增屬性注入方法。Spring使用setter進行注入,這裏爲了簡單使用Field。

protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
        for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
            Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
            declaredField.setAccessible(true);
            declaredField.set(bean, propertyValue.getValue());
        }
    }

HelloWorldService新增text字段。

step4-讀取xml來初始化bean

git checkout step-4-config-beanfactory-with-xml

看一下文件結構:

Resource

類名 說明
Resource 接口,通過getInputStream()獲取資源的輸入流
UrlResource 實現Resource接口,通過URL獲取資源
ResourceLoader 資源加載類,通過getResource()獲取一個Resource對象
類名 說明
BeanDefinition Bean的包裝類,包括Bean的名字、類型、屬性鍵值對
BeanDefinitionReader 解析 BeanDefinition 的接口。通過 loadBeanDefinitions(String) 從一個地址來加載類定義。
AbstractBeanDefinitionReader 實現 BeanDefinitionReader 接口的抽象類
XmlBeanDefinitionReader 具體實現了 loadBeanDefinitions() 方法,從 XML 文件中讀取類定義

XmlBeanDefinitionReader負責讀取xml,把標籤封裝成BeanDefinition註冊到registry,之後把registry都註冊到AutowireCapableBeanFactory中
在這裏插入圖片描述

整個讀取流程:
在這裏插入圖片描述

  1. new一個ResourceLoader放入XmlBeanDefinition中,通過loadBeanDefinition傳入xml文件地址
  • 獲取xml文件的輸入流
  • 通過DocumentBuilder解析出Document。先獲取所有的bean,然後根據bean名稱和類名構建空實例,對當前的bean繼續解析,根據property實現對bean屬性的賦值
  1. 將解析好的bean註冊到BeanFactory

step5-爲bean注入bean

git checkout step-5-inject-bean-to-bean

目前爲止,我們只是處理了bean之間沒有依賴的情況,還無法處理bean之間的依賴。

我們定義一個BeanReference來表示這個屬性是對另一個Bean的引用。在XMLBeanDefinitionprocessProperty方法中加入判斷。如果是ref,就new一個BeanReference封裝到PropertyValue.

    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                String name = propertyEle.getAttribute("name");
                String value = propertyEle.getAttribute("value");
                //---------------新加入代碼----------------------
//                name對應的是value
                if (value!=null && value.length()>0) {
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                }else{
//                    name對應的是ref
                    String ref=propertyEle.getAttribute("ref");
                    if (ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                + name + "' must specify a ref or value");
                    }
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }

在這裏插入圖片描述

解析完成之後,BeanFactory中只有<String, BeanDefinition>信息,並沒有bean實例,即bean爲null。

同時爲了解決循環依賴的問題,我們使用懶加載的方式,即在需要使用的時候才創建。如果屬性對應的bean找不到,那麼就先創建。因爲總是先創建後注入,所以不會存在循環依賴的問題。

//        獲取bean
        HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
        helloWorldService.helloWorld();				

xml中的配置

  <bean name="helloWorldService" class="zcs.ioc.HelloWorldService">
        <property name="text" value="Hello World!"></property>
        <property name="outputService" ref="outputService"></property>
    </bean>

    <bean name="outputService" class="zcs.ioc.OutputService">
        <property name="helloWorldService" ref="helloWorldService"></property>
    </bean>

step6-ApplicationContext登場

git checkout step-6-invite-application-context

現在功能差不多齊全了,但是使用起來比較麻煩,於是引入ApplicationContext接口,並在refresh()方法下進行bean的初始化。
在這裏插入圖片描述

public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
        super(beanFactory);
        this.configLocation = configLocation;
        refresh();
    }

所以使用的時候只需要傳入xml地址就可以了

public void test() throws Exception{
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("tinyioc.xml");
        HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
        helloWorldService.helloWorld();
    }

至此爲止,tiny-spring的IoC部分基本結束。

AOP

什麼是AOP

即面向切面編程,將分散個各個業務邏輯代碼中相同的代碼通過橫向切割的方式提取到一個獨立的模塊中

比如日誌記錄、安全控制、事務處理、異常處理等
在這裏插入圖片描述

Spring Aop主要是通過動態代理實現的,先看一下代理的相關內容

什麼是代理

給目標對象提供一個代理對象,並由代理對象控制對目標對象的引用。

使用代理的最主要原因就是在不改變目標對象的情況下對方法進行增強。比如,增加日誌等。

舉個栗子

有一個接口,只有hello的方法

public interface IHello{
    void hello();
}

有一個實現類

public class HelloImpl implements IHello{
    @Override
    public void hello(){
        System.out.println("hello");
    }
}

現在想在方法被調用的時候添加日誌。如果直接在實現類上改,那麼所有的實現類都需要改,顯而易見,這是非常繁瑣的一件事。當新增實現類時,也需要做這種繁瑣重複的事情,代碼可維護性極低。

那麼該怎麼才能一次性完成這項工作呢?這就需要代理了。

靜態代理

舉個栗子理解一下

有一排瓶子放在那不動,任務是給每個瓶子貼上標籤。

方法一:每次需要改標籤你要自己一個個去貼。

方法二:現在有了流水線,你把之前貼標籤的任務交給了流水線上的機器來做,只要往機器裏輸入標籤內容,把需要改的瓶子放入流水線,出來的就是改過標籤的瓶子了,這樣就方便多了。

這裏的瓶子就相當於實現類,靜態代理就相當於方法二,代理類相當於流水線的機器,在代理類中進行方法的修改(比如添加日誌)。只要將需要修改的實現類(委託類)傳給代理類即可。

target是需要修改的實現類,通過構造函數傳入。

public class HelloProxy implements IHello {
    private IHello target;

    public HelloProxy(IHello target) {
        this.target = target;
    }

    @Override
    public void hello() {
        System.out.println("start");
        target.hello();
        System.out.println("end");
    }
}
 	@Test
	public void test(){
        IHello hello=new HelloImpl();
        HelloProxy helloProxy=new HelloProxy(hello);
        helloProxy.hello();
    }

輸出結果如下:
在這裏插入圖片描述

優點

客戶端不需要知道實現類是什麼,只需知道代理類即可

缺點
  • 代理類和實現類(委託類)實現了相同的接口,接口類每增加一個方法,代理類和實現類都需要實現,增加了維護的複雜度。例如想在每個方法都添加日誌,那麼代理類的每個實現方法也都要添加
  • 一個代理類只對應一種接口,如果想對兩個接口都添加日誌,那麼就需要兩個代理類。靜態代理只能爲特定的接口服務

動態代理

在程序開發中,靜態代理會產生許多代理類,所以就需要動態代理了。

動態代理是通過反射機制實現的,能夠代理各種類型的對象。

實現動態代理需要兩個類:

  • java.lang.reflect.InvocationHandler
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

該接口只有一個方法,代理類需要實現這個方法來實現方法的調用

  • java.lang.reflect.Proxy

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
    

    通過該方法得到一個代理類對象

代理類

public class DynamicProxy{
    private Object target;

    //傳入實現類的實例
    public DynamicProxy(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler(){
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("start");
                        Object ret = method.invoke(target, args);
                        System.out.println("end");
                        return ret;
                    }
                });
    }  
}

測試

@Test
    public void test(){
//        動態代理
        DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
        IHello hello1 = (IHello) dynamicProxy.getProxy();
        hello1.hello();
    }

在這裏插入圖片描述

缺點:委託類一定要有接口

CGLIB

肯定不可能所有的類都實現了接口,所以動態代理並不適用所有類

這時候就需要CGLIB登場,先看代碼

寫一個國際通用入門類

public class HelloWorld {
    public void hello(){
        System.out.println("hello world");
    }
}

寫CGLIB代理類,代理類需要實現MethodInterceptor接口

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLibTest implements MethodInterceptor {
	//固定寫法 直接寫就完事了
    public Object getInstance(Class<?> target){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("開始");
        Object invoke = methodProxy.invokeSuper(o, objects);
        System.out.println("結束");
        return invoke;
    }
}
	@Test
    public void test() throws Exception {
        HelloWorld helloWorld= (HelloWorld) new CGLibTest().getInstance(HelloWorld.class);
        helloWorld.hello();
    }

在這裏插入圖片描述

注意事項:

  • 代理類不能是final
  • 方法如果是final/static,不會被攔截

原理

底層原理是動態代理,使用了兩種:

  • JDK動態代理,默認,實現接口的類,創建速度快
  • CGLib動態代理,適用沒有實現接口的類,運行速度比JDK快

如果是單例,最好使用CGLib代理

相關概念

Join point(連接點)

能夠被攔截的地方,每個成員方法都可以是連接點

Pointcut(切點)

具體定位的連接點(方法)就是切點

Advice(通知/增強)

表示在切點上具體需要執行的操作 ,提供了前置、後置、環繞、異常、返回這5種操作

Aspect(切面)

類似Java中的類聲明,由切點和相應的Advice結合而成

Weaving(織入)

把切面的代碼代入連接點的過程

Spring對AOP的支持

  • 基於代理:現在基本不使用
    首先看一下增強接口的繼承關係圖
    在這裏插入圖片描述

    分爲5類:
    在這裏插入圖片描述

  • xml配置

  • 註解

tiny-spring分析

繼續之前的代碼分析

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

我們知道,想要成功代理的話需要委託實例和委託實例的接口

這是一個完整的動態代理結構圖,我們一點一點分析

在這裏插入圖片描述

我們把實例和Class(其實是Interface)封裝進TargetSource,把TargetSource和MethodInterceptor封裝進AdvisedSupport,那麼MethodInterceptor是個什麼東西呢?

AOP中有兩個重要的角色:MethodInterceptor和MethodInvocation,它們分別對應上文提到的Advice和Join Point兩個概念,我們知道AOP的作用就是在哪幹,幹什麼,Join Point表示在哪個方法上執行,而Advice就是要幹什麼

//對應advice
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation var1) throws Throwable;
}
//對應joined point
public interface MethodInvocation extends Invocation {
    Method getMethod();
}

所以我們需要做的事情都要寫在MethodInterceptor中,而且在上一部分中我們瞭解到它代表環繞增強,表示在目標方法前後實施增強

讓我們來寫一個小代碼練練手,還是用之前的HelloWorldService接口

1、先寫一個MethodInterceptor實現類,表示我們需要做什麼
這個接口只有一個方法,重寫即可,invocation.proceed()代表執行原方法,前後的代碼是我們新增的非業務邏輯代碼,這樣就寫好了要做什麼

public class TimerInterceptor implements MethodInterceptor {

   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      long time = System.nanoTime();
      System.out.println("Invocation of Method " + invocation.getMethod().getName() + " start!");
      Object proceed = invocation.proceed();
      System.out.println("Invocation of Method " + invocation.getMethod().getName() + " end! takes " + (System.nanoTime() - time)
            + " nanoseconds.");
      return proceed;
   }
}

2、接下來寫要在哪做,通過前面的分析我們知道,需要實現MethodInvocation接口
這是MethodInvocation的繼承關係圖,可以看到,頂層接口是Joinpoint,也就是連接點
在這裏插入圖片描述

新建一個實現類,需要重寫上述接口的所有方法

public class ReflectiveMethodInvocation implements MethodInvocation {

    private Object target;

    private Method method;

    private Object[] args;

    public ReflectiveMethodInvocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return args;
    }

    @Override
    public Object proceed() throws Throwable {
        return method.invoke(target, args);
    }

    @Override
    public Object getThis() {
        return target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return method;
    }
}

3、新建一個動態代理類,通過getProxy反射獲取一個實例對象,然後調用invoke方法實現動態代理

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

   private AdvisedSupport advised;

   public JdkDynamicAopProxy(AdvisedSupport advised) {
      this.advised = advised;
   }

    @Override
   public Object getProxy() {
      return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { advised.getTargetSource().getTargetClass() }, this);
   }

   @Override
   public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
      MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
      return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method,
            args));
   }
}

4、測試

		// --------- 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, 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();

step8-使用AspectJ管理切面

在完成了織入之後,需要考慮的問題就是對什麼類以及什麼方法進行織入,也就是切點。Spring中ClassFilterMethodMatcher分別表示類和方法做匹配,怎麼匹配呢?

AspectJ是對java的aop增強的一門語言,有單獨的編譯器,Spring借鑑了AspectJ風格的切點表達式,新建AspectJExpressionPointcut實現上述兩個接口,通過切點解析器將AspectJ風格的表達式解析成切點表達式

pointcutParser就是切點解析器,supportedPrimitives是一個set集合,表示AspectJ語言的關鍵字

pointcutParser將String類型的expression解析包裝成PointcutExpression,然後可以就可以匹配類和方法了

public AspectJExpressionPointcut(Set<PointcutPrimitive> supportedPrimitives) {
        pointcutParser = PointcutParser
      .getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
    }

	protected void checkReadyToMatch() {
        if (pointcutExpression == null) {
            pointcutExpression = buildPointcutExpression();
        }
    }
	//    字符串轉切點表達式
    private PointcutExpression buildPointcutExpression() {
        return pointcutParser.parsePointcutExpression(expression);
    }
 	// 將表達式和類做匹配,返回匹配結果
    @Override
    public boolean matches(Class<?> targetClass) {
        checkReadyToMatch();
        return pointcutExpression.couldMatchJoinPointsInType(targetClass);
    }

    // 將表達式和方法做匹配,返回匹配結果
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        checkReadyToMatch();
        ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
        if (shadowMatch.alwaysMatches()) {
            return true;
        } else if (shadowMatch.neverMatches()) {
            return false;
        }
        // TODO:其他情況不判斷了!見org.springframework.aop.aspectj.RuntimeTestWalker
        return false;
    }

AspectJ包幫我們做好了匹配的事情,只不過我們使用動態代理而不是它的編譯器實現了織入

step9-將AOP融入Bean的創建過程中

現在我們已經解決了在哪做,做什麼的問題,接下來就要把AOP融入IOC容器的Bean中,我們首先要解決在IOC容器的哪裏織入AOP,然後是要爲哪些對象織入

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

postProcessorAfterInitialization 方法中,使用動態代理的方式,返回一個對象的代理對象。解決了 在 IOC 容器的何處植入 AOP 的問題。

public interface BeanPostProcessor {
	//初始化方法之前調用
    Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception;
	//初始化方法之後調用
    Object postProcessAfterInitialization(Object bean, String beanName) throws Exception;
}

AspectJAwareAdvisorAutoProxyCreator是實現AOP織入的關鍵類,它實現了BeanPostProcessor接口,它會掃描所有的切點(AspectJExpressionPointcutAdvisor包裝了Pointcut和AspectJExpressionPointcut),並對bean做織入
在這裏插入圖片描述

public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
    //提供代理pointcut與advice的AspectJExpressionPointcutAdvisor不需要處理
    if (bean instanceof AspectJExpressionPointcutAdvisor)
        return bean;

    if (bean instanceof MethodInterceptor)
        return bean;

    List<AspectJExpressionPointcutAdvisor> advisors = beanFactory
            .getBeansForType(AspectJExpressionPointcutAdvisor.class);
    for (AspectJExpressionPointcutAdvisor advisor : advisors) {
        if (advisor.getPointcut().getClassFilter().matches(bean.getClass())) {
            AdvisedSupport advisedSupport = new AdvisedSupport();
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());

            TargetSource targetSource = new TargetSource(bean, bean.getClass().getInterfaces());
            advisedSupport.setTargetSource(targetSource);

            return new JdkDynamicAopProxy(advisedSupport).getProxy();
        }
    }
    return bean;
}

動態代理類的invoke

public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
      MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
      // 判斷是否爲要攔截的方法,是則執行 Advice 邏輯 否則正常執行
if (advised.getMethodMatcher() != null
      && advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
   return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),
         method, args));
} else {
   return method.invoke(advised.getTargetSource().getTarget(), args);
}
  }

xml

<bean id="autoProxyCreator" class="zcs.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>

<bean id="timeInterceptor" class="zcs.aop.TimerInterceptor"></bean>

<!-- Creator 將對 Advisor 類型的 bean 進行掃描和處理 -->
<bean id="aspectjAspect" class="zcs.aop.AspectJExpressionPointcutAdvisor">
    <!-- 調用對應的 setter 方法進行 Property 的注入 -->
    <property name="advice" ref="timeInterceptor"></property>
    <property name="expression" value="execution(* zcs.*.*(..))"></property>
</bean>

整個流程如下

  1. AutoProxyCreator(實現了BeanPostProcessor接口)最先被實例化
  2. 其他Bean開始實例化,AutoProxyCreator加載BeanFactory中所有的PointcutAdvisor(這保證了PointcutAdvisor優先於普通Bean),然後依次使用PointcutAdvisor內置的ClassFilter判斷當前對象需不需要攔截
  3. 如果是,生成AdvisedSupport交給AopProxy生成代理對象
  4. AopProxy實現了InvocationHandler接口,在invoke函數中,首先使用advised.getMethodMatcher()判斷是不是要攔截的方法,如果是交給Advice(自定義的MethodInterceptor)執行,如果不是,則直接交給 TargetSource 的原始對象來執行。

step10-使用CGLib進行類的織入

前面說到,JDK動態代理只能對接口實現代理,如果類沒有實現接口,那麼必須要用CGLib來實現。

我們需要新建一個ProxyFactory根據TargetSource類型來自動創建代理

public class ProxyFactory extends AdvisedSupport implements AopProxy {
    // 在 creator 中,生成相應的動態代理的時候就可以使用工廠類的 getProxy()
    @Override
    public Object getProxy() {
        return createAopProxy().getProxy();
    }

    protected final AopProxy createAopProxy() {
        return new Cglib2AopProxy(this);
    }

    //...可以根據 TargetSource 決定使用不同的動態代理
    protected final AopProxy createAopProxy(TargetSource targetSource) {
        return new JdkDynamicAopProxy(this);
    }
}

TargetSource修改變量

	public class TargetSource {

        private Class<?> targetClass;

        private Class<?>[] interfaces;

        private Object target;
    }

至此結束,歡迎交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章