手寫Spring---AOP面向切面程式設計(4)

說出你的願望吧~發表於2019-05-10

接上一篇《手寫Spring---AOP面向切面程式設計(3)》繼續更新

補充:擴充套件的Advisor

在上一篇中已經講到了Advisor包下的Advisor.java,也就是一個通知者和一個基於AspectJ語法實現的Advisor,由於通知者也可分為許多不同種類的通知者,所以我們在進行擴充套件的時候,需要通過不同的實現方式定義不同的通知者。

現在看看這兩種不同的設計方式

手寫Spring---AOP面向切面程式設計(4)

第二種相比第一種多了一層抽象,把相同的屬性方法抽離掉然後減少了冗餘程式碼, 此時pointcut的生成必須由抽象類AbstractPointcutAdvisor的子類去實現生成不同的 pointcut,是AspectJ的生成AspectJ的,是正則Reg的生成正則的。

一、織入Weaving的實現

① 織入要完成什麼?

將使用者提供的增強功能加到指定的方法上
複製程式碼

② 在什麼時候進行織入?

建立bean例項,在bean初始化後進行增強
複製程式碼

③ 如何確定bean需要增強

對bean類及方法進行遍歷,匹配使用者指定的切面
複製程式碼

④ 如何進行織入

代理!
複製程式碼

二、織入Weaving的設計

此時我們回顧一下AOP的使用流程

使用者---> 配置並註冊切面
我們---> 織入流程:初始化bean---判斷是否需要增強 ? 代理增強---返回例項 : 返回例項
複製程式碼

① 使用者到哪裡去註冊切面?

② 判斷匹配,織入邏輯寫在哪裡

③ 此時我們是否需要在bean建立的過程中加一層處理?

還記得我們之前實現的DefaultBeanFactory嗎?
在它的doGetBean方法中,初始化Bean前增加一個增強邏輯即可
複製程式碼

後續我們在bean建立過程中還會再加入更多不同的處理,如果直接在BeanFactory中實現,會導致BeanFactory程式碼爆炸性增長,而且也不容易進行擴充套件,此時我們該如何處理呢?

此時我們再回顧一下3周前的那篇《手寫Spring---IOC容器(1)》中講到的Bean的產出過程

1.建立Bean定義---① 2.註冊Bean定義②---③ 3.建立Bean例項④---⑤ 4.初始化Bean例項⑥

在此4個節點中,每一項過程的前後階段都可能增加各種各樣的處理邏輯
為了讓我們更加靈活,設計好BeanFactory後不需要修改程式碼,
我們需要在各個節點中加入擴充套件點(使用了 **① ~ ⑥** 標識,
比如**①**的意思為註冊bean定義前,**②**為註冊Bean定義後)和序號產生器制
複製程式碼

此時我們需要會想到觀察者模式(監聽模式),需要六個擴充套件點,也就是六個觀察者


三、應用觀察者模式加入我們的AOP織入

此時我們不推薦僅定義一個介面去完成所有擴充套件點的監聽,因為它們的用途不一樣且實現的時候需要一下子實現6個功能,此時我們定義一個監聽介面BeanPostProcessor來監聽Bean初始化前後過程,具體流程參照下圖:

① 監聽者模式實現AOP流程圖

手寫Spring---AOP面向切面程式設計(4)

② 程式碼實現

1.BeanPostProcessor.java

這裡預設什麼都不幹,定義的是default預設方法,這裡是為了方便選擇只進行某一個的擴充套件點的處理,比如我們之後就只選擇進行初始化後的處理

public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean,String beanName) throws Throwable{
        return bean;
    }
    default Object postProcessAfterInitialization(Object bean,String beanName) throws Throwable{
        return bean;
    }
}
複製程式碼

2.AdvisorRegistry.java

提供對於Advisor的註冊和獲取

public interface AdvisorRegistry {
    public void registAdvisor(Advisor ad);
    public List<Advisor> getAdvisors();
}
複製程式碼

3.BeanFactory.java

此時我們建立好例項之後需要放入Bean工廠裡面,還需要在BeanFactory.java中加入註冊監聽的方法

public interface BeanFactory {
    Object getBean(String name) throws Throwable;
    //新加入的註冊方法
    void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor);
}
複製程式碼

4.DefaultBeanFactory.java

這裡是BeanFactory的預設實現,在此提供registerBeanPostProcessor的實現

(1)定義一個集合

private List<BeanPostProcessor> beanPostProcessors = Collections.synchronizedList(new ArrayList<>());
複製程式碼

(2)註冊方法(僅僅為新增進執行緒同步集合beanPostProcessors中)

@Override
public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
    this.beanPostProcessors.add(beanPostProcessor);
    if (beanPostProcessor instanceof BeanFactoryAware){
        ((BeanFactoryAware) beanPostProcessor).setBeanFactory(this);
    }
}
複製程式碼

5.Aware和BeanFactoryAware

如何去使用觀察者模式,還必須提供喚醒的功能

(1)此時在Bean初始化前和初始化後加入處理(DefaultBeanFactory中的doGetBean方法)

// 應用bean初始化前的處理
    instance = this.applyPostProcessBeforeInitialization(instance, beanName);

    // 執行初始化方法
    this.doInit(bd, instance);

    // 應用bean初始化後的處理
    instance = this.applyPostProcessAfterInitialization(instance, beanName);
複製程式碼

(2)applyPostProcessBeforeInitialization()和applyPostProcessAfterInitialization()方法其實就是遍歷你的所有註冊進來的processor,然後一個一個呼叫它們的方法,按照這個規範來擴充套件多少功能都可以

// 應用bean初始化前的處理
private Object applyPostProcessBeforeInitialization(Object bean, String beanName) throws Throwable {
    for (BeanPostProcessor bpp : this.beanPostProcessors) {
        bean = bpp.postProcessBeforeInitialization(bean, beanName);
    }
    return bean;
}

// 應用bean初始化後的處理
private Object applyPostProcessAfterInitialization(Object bean, String beanName) throws Throwable {
    for (BeanPostProcessor bpp : this.beanPostProcessors) {
        bean = bpp.postProcessAfterInitialization(bean, beanName);
    }
    return bean;
}
複製程式碼

6.AdvisorAutoProxyCreator的實現

AOP 織入功能 applyPostProcessAfterInitialization 肯定在此實現

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Throwable {

}
複製程式碼

③ 實現織入:判斷bean是否需要增強

回憶這個過程: 織入流程:初始化bean---判斷是否需要增強 ? 代理增強---返回例項 : 返回例項

1. 如何判斷bean例項是否要增強?

1.獲取bean的類及所有方法(通過反射getMethods()和getDeclareMethods())
2.遍歷Advisor,取advisor中的Pointcut來匹配類和方法
Pointcut中的兩個匹配方法:
    boolean matchClass(Class<?> targetClass);
    boolean matchMethod(Method method,Class<?> targetClass);
    
即是用前面通過反射獲得的方法和matchMethod()來比較
複製程式碼

2. 此時我們需要確定getMethods()和getDeclareMethods()可以取到所有的方法嗎?

如果閱讀過原始碼中的說明,就會發現getMethods()方法返回所有的公開方法,
無法取得私有和保護型別的方法,getDeclareMethods()無法取得父類
的繼承方法(第一小段最後一句but excluding inherited methods)

所以這兩個方法仍然不夠,我們到時候會直接使用spring裡面內建的方法
複製程式碼

3. 回到AdvisorAutoProxyCreator.java中的postProcessAfterInitialization()方法

// 第一步:在此判斷bean是否需要進行切面增強
    List<Advisor> matchAdvisors = getMatchedAdvisors(bean, beanName);

// 第二步:如需要就進行增強,建立代理物件,進行代理增強。再返回增強的物件。
    if (CollectionUtils.isNotEmpty(matchAdvisors)) {
        bean = this.createProxy(bean, beanName, matchAdvisors);
    }
複製程式碼

4. 編寫(3)提到的getMatchedAdvisors()方法

private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
    if (CollectionUtils.isEmpty(advisors)) {
        return null;
    }

    // 得到類、所有的方法
    Class<?> beanClass = bean.getClass();
    List<Method> allMethods = this.getAllMethodForClass(beanClass);

    // 存放匹配的Advisor的list
    List<Advisor> matchAdvisors = new ArrayList<>();
    // 遍歷Advisor來找匹配的
    for (Advisor ad : this.advisors) {
        if (ad instanceof PointcutAdvisor) {
            if (isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) {
                matchAdvisors.add(ad);
            }
        }
    }

    return matchAdvisors;
}
複製程式碼

5. 在(4)中得到所有方法的getAllMethodForClass()

此時我們需要用到Spring自帶的ClassUtils和ReflectionUtils,ps:可以直接在maven中新增SpringBoot的依賴之後無須再進行導包。

這裡的邏輯為建立一個集合,使用ClassUtils自帶的遍歷方法getAllInterfacesForClassAsSet()遍歷所有介面,之後把自身beanClass也一起丟進這個集合中,之後來一個迴圈把介面中的所有方法依次遍歷出來

private List<Method> getAllMethodForClass(Class<?> beanClass) {
    List<Method> allMethods = new LinkedList<>();
    Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
    classes.add(beanClass);
    for (Class<?> clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method m : methods) {
            allMethods.add(m);
        }
    }

    return allMethods;
}
複製程式碼

6. 在getMatchedAdvisors中匹配Advisor和bean的所有方法

private boolean isPointcutMatchBean(PointcutAdvisor pa, Class<?> beanClass, List<Method> methods) {
    Pointcut p = pa.getPointcut();

    // 首先判斷類是否匹配
    // 注意之前說過的AspectJ情況下這個匹配是不可靠的,需要通過方法來匹配
    //這裡的判斷僅僅起到過濾作用,類不匹配的前提下直接跳過
    if (!p.matchClass(beanClass)) {
        return false;
    }

    // 再判斷是否有方法匹配
    for (Method method : methods) {
        if (p.matchMethod(method, beanClass)) {
            return true;
        }
    }
    return false;
}
複製程式碼

7.postProcessAfterInitialization()方法的實現

此時我們已經得到了匹配上的方法,可以對這些方法進行代理增強操作了

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Throwable {

    // 在此判斷bean是否需要進行切面增強
    List<Advisor> matchAdvisors = getMatchedAdvisors(bean, beanName);
    // 如需要就進行增強,再返回增強的物件。
    if (CollectionUtils.isNotEmpty(matchAdvisors)) {
        bean = this.createProxy(bean, beanName, matchAdvisors);
    }
    return bean;
}
複製程式碼

8.建立代理物件

private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) throws Throwable {
    // 通過AopProxyFactory工廠去完成選擇、和建立代理物件的工作。
    return AopProxyFactory.getDefaultAopProxyFactory().createAopProxy(bean, beanName, matchAdvisors, beanFactory)
            .getProxy();
}
複製程式碼

④ 代理增強的實現

這部分涉及的內容比較多,建議先理清思路然後看懂邏輯即可,因為我們的主要出發點在於以後能夠理解原始碼而不是真的要自己完整實現一個spring

1. 代理增強的邏輯是怎樣的?

代理可分為jdk動態代理(使用invoke方法增強)和cglib動態代理(使用intercept方法增強),判斷代理生成方式後返回相應代理物件即可
複製程式碼

2. jdk動態代理和cglib動態代理的兩者抽象

我們知道,要實現jdk動態代理,就要實現InvocationHandler介面,要實現cglib動態代理,就要實現MethodInteceptor介面,我們可以對這兩者進行抽象,抽成一個AopProxy介面,裡面提供獲取代理物件的方法即可

public interface AopProxy {
    Object getProxy();
    Object getProxy(ClassLoader classLoader);
}
複製程式碼

3. jdk動態代理的invoke和cglib動態代理intercept邏輯一樣嗎?

一樣,都是使用Advice進行增強
---> 從Advisor中找出對當前方法進行增強的Advice
---> 是否有匹配的Advice?責任鏈式應用Advice增強:執行被代理物件的方法
---> 返回結果

jdk要生成代理物件,完成織入增強,需要以下資料
    要實現的介面,目標物件,匹配的Advisors,可能還需要BeanFactory
cglib需要的資料:
    要繼承的類,實現的介面,構造引數型別,構造引數,目標物件,
    匹配的Advisor和BeanFactory
    
    為何需要構造引數和構造引數型別,因為擴充套件目標類如果沒提供無參構造方法,那它的子類建立時必須呼叫父類的有參構造方法
複製程式碼

4. JdkDynamicAopProxy和CglibDynamicAopProxy的設計

AopProxyUtils

先不看JdkDynamicAopProxy和CglibDynamicAopProxy的具體實現,我們先看它們的結構,因為前面我們也提到了,它們倆做的事情,功能其實是一樣的,所以可以提取相同的功能實現部分為一個通用方法applyAdvices()

我們可以直接定義一個工具類AopProxyUtils來儲存這樣的通用方法

public static Object applyAdvices(Object target, Method method, Object[] args, 
    List<Advisor> matchAdvisors,Object proxy, BeanFactory beanFactory) throws Throwable {
    // 這裡要做什麼?
    // 1、獲取要對當前方法進行增強的advice
    List<Object> advices = AopProxyUtils.getShouldApplyAdvices(target.getClass(), method, matchAdvisors,
            beanFactory);
    // 2、如有增強的advice,責任鏈式增強執行
    if (CollectionUtils.isEmpty(advices)) {
        return method.invoke(target, args);
    } else {
        // 責任鏈式執行增強
        AopAdviceChainInvocation chain = new AopAdviceChainInvocation(proxy, target, method, args, advices);
        return chain.invoke();
    }
}

public static List<Object> getShouldApplyAdvices(Class<?> beanClass, Method method, List<Advisor> matchAdvisors,
    BeanFactory beanFactory) throws Throwable {
        if (CollectionUtils.isEmpty(matchAdvisors)) {
            return null;
        }
        List<Object> advices = new ArrayList<>();
        for (Advisor ad : matchAdvisors) {
            if (ad instanceof PointcutAdvisor) {
                if (((PointcutAdvisor) ad).getPointcut().matchMethod(method, beanClass)) {
                    advices.add(beanFactory.getBean(ad.getAdviceBeanName()));
                }
            }
        }

        return advices;
}
複製程式碼

如何來進行責任鏈的增強,注意我們必須定義一個責任鏈記錄索引,比如下方程式碼中我們就使用i作為索引,在invoke()方法中,先判斷i是否小於advices的size,如果小於,那就是還有增強未執行,之後的判斷邏輯很簡單,如果這個增強是前置增強,那就是直接執行即可,

如果是環繞增強return ((MethodSurroudAdvice) advice).invoke(invokeMethod, null, this),參考MethodSurroudAdvice中的invoke()的引數,此時我們需要理解,這裡的invoke方法中的invokeMethod物件取得的就是public Object invoke() throws Throwable這個方法本身,null表示無引數,this表示自身

後置增強是返回結果後增強,所以先去呼叫invoke---Object returnValue = this.invoke();,然後再增強

如果i<advices.size()不成立,那就執行目標方法return method.invoke(target, args);

public class AopAdviceChainInvocation {

    private static Method invokeMethod;
    static {
        try {
            invokeMethod = AopAdviceChainInvocation.class.getMethod("invoke", null);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }

    private Object proxy;
    private Object target;
    private Method method;
    private Object[] args;
    private List<Object> advices;

    public AopAdviceChainInvocation(Object proxy, Object target, Method method, Object[] args, List<Object> advices) {
        super();
        this.proxy = proxy;
        this.target = target;
        this.method = method;
        this.args = args;
        this.advices = advices;
    }

    // 責任鏈執行記錄索引號
    private int i = 0;

    public Object invoke() throws Throwable {
        if (i < this.advices.size()) {
            Object advice = this.advices.get(i++);
            if (advice instanceof MethodBeforeAdvice) {
                // 執行前置增強
                ((MethodBeforeAdvice) advice).before(method, args, target);
            } else if (advice instanceof MethodSurroudAdvice) {
                // 執行環繞增強和異常處理增強。注意這裡給入的method 和 物件 是invoke方法和鏈物件
                return ((MethodSurroudAdvice) advice).invoke(invokeMethod, null, this);
            } else if (advice instanceof AfterReturningAdvice) {
                // 當是後置增強時,先得得到結果,再執行後置增強邏輯
                Object returnValue = this.invoke();
                ((AfterReturningAdvice) advice).afterReturning(returnValue, method, args, target);
                return returnValue;
            }
            return this.invoke();
        } else {
            return method.invoke(target, args);
        }
    }
}
複製程式碼
JdkDynamicAopProxy和CglibDynamicAopProxy的程式碼實現

jdk:

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);

    private String beanName;
    private Object target;
    private List<Advisor> matchAdvisors;

    private BeanFactory beanFactory;

    public JdkDynamicAopProxy(String beanName, Object target, List<Advisor> matchAdvisors, BeanFactory beanFactory) {
        super();
        this.beanName = beanName;
        this.target = target;
        this.matchAdvisors = matchAdvisors;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
    }

    @Override
    public Object getProxy() {
        return this.getProxy(target.getClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("為" + target + "建立代理。");
        }
        return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
    }

}
複製程式碼

cglib:

public class CglibDynamicAopProxy implements AopProxy, MethodInterceptor {

    private static final Log logger = LogFactory.getLog(CglibDynamicAopProxy.class);
    private static Enhancer enhancer = new Enhancer();

    private String beanName;
    private Object target;

    private List<Advisor> matchAdvisors;

    private BeanFactory beanFactory;

    public CglibDynamicAopProxy(String beanName, Object target, List<Advisor> matchAdvisors, BeanFactory beanFactory) {
        super();
        this.beanName = beanName;
        this.target = target;
        this.matchAdvisors = matchAdvisors;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getProxy() {
        return this.getProxy(target.getClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("為" + target + "建立cglib代理。");
        }
        Class<?> superClass = this.target.getClass();
        enhancer.setSuperclass(superClass);
        enhancer.setInterfaces(this.getClass().getInterfaces());
        enhancer.setCallback(this);
        Constructor<?> constructor = null;
        try {
            constructor = superClass.getConstructor(new Class<?>[] {});
        } catch (NoSuchMethodException | SecurityException e) {

        }
        if (constructor != null) {
            return enhancer.create();
        } else {
            BeanDefinition bd = ((DefaultBeanFactory) beanFactory).getBeanDefinition(beanName);
            return enhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues());
        }
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
    }

}
複製程式碼

兩個動態代理也只是用了它們本身的一些api而已,我們只需要看懂我們的邏輯即可,注意的是cglib的動態代理中我們必須做到傳遞構造引數型別和構造引數,此時我們又有了新的問題

如何傳遞建立bean例項時獲得的資料到初始化後的aop中

建立時,是在defaultBeanFactory來進行操作
增強時,經過了AdvisorAutoProxyCreator
使用時,又在CglibDynamicAopProxy

此時我們考慮將引數放在bean定義中使用ThreadLocal持有引數值,
因為BeanDefinition是同一個類的多個例項。
特別是prototype的時候,是共享的,
我們需要保證每一個建立流程中都是使用不同執行緒的資料
複製程式碼

我們在BeanDefinition中加入了兩個方法:因為我們本身在BeanDefiniion中就已經有了構造器的快取(public Constructor getConstructor(); public void setConstructor(Constructor constructor);),引數型別我們可以通過它們來獲得

List<?> getConstructorArgumentValues();
public Object[] getConstructorArgumentRealValues();
複製程式碼

我們可以通過CglibDynamicAopProxy中的getProxy方法來更好理解

else {
    BeanDefinition bd = ((DefaultBeanFactory) beanFactory).getBeanDefinition(beanName);
    return enhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues());
}
複製程式碼

這裡就是使用了getConstructor()來取得構造器,然後呼叫 getParameterTypes()來取得了構造引數的型別

⑤ 如何在Creator中使用AopProxy

應用工廠模式把選擇的邏輯交給工廠

public interface AopProxyFactory {
    AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
            throws Throwable;

    /**
     * 獲得預設的AopProxyFactory例項
     *
     * @return AopProxyFactory
     */
    static AopProxyFactory getDefaultAopProxyFactory() {
        return (AopProxyFactory) new DefaultAopProxyFactory();
    }
}
複製程式碼

之後我們實現了一個預設的工廠,這裡做了判斷是使用jdk還是cglib,我們省略了判斷而直接使用了cglib,spring原始碼裡面的判斷也是如此,但是原始碼還另外存在某些機制來判斷,在這裡我們也不再過多闡述了,在以後原始碼解讀的時候會再展開

public class DefaultAopProxyFactory implements AopProxyFactory{
    @Override
    public AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
            throws Throwable {
        // 是該用jdk動態代理還是cglib?
        if (shouldUseJDKDynamicProxy(bean, beanName)) {
            return new JdkDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
        } else {
            return new CglibDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
        }
    }

    private boolean shouldUseJDKDynamicProxy(Object bean, String beanName) {
        // 如何判斷?有實現介面就用JDK,沒有就用cglib?
        return false;
    }
}
複製程式碼

四、下一篇依舊是Spring的配置,之後會是原始碼

測試程式碼相對還是較多,就不貼上出來了,AOP這一篇的程式碼很多,而且比較凌亂(自己都這麼覺得),需要多花時間去整理總結,有必要的話在之後會再一次進行一次更加詳細點的總結篇,把一些過程再講述清楚點。有問題或者是建議可在留言區提出讓我改進。共勉,謝謝。

相關文章