為了賬號安全,請及時綁定郵箱和手機立即綁定

死磕Spring源碼-AOP分析

2017.07.30 18:00 8797瀏覽

在上一篇Spring源碼的依賴注入總結中,我們可以清楚的知道DI有助于應用對象之間的解耦,這章我們來聊聊另外一種常見且有用的解耦方式,在比較大的系統中的日志管理、事務管理等功能,我們可以使用用面向切面編程的思想。在軟件開發中,像日志這種散布在應用中多處的功能就是橫切關注點,而AOP可以實現橫切關注點與它們所影響的對象之間的解耦。

在面向切面編程中,我們只需在一個地方定義通用的功能,然后以聲明的方式定義這個功能(Advice)何時(Before、After or Around)要以何種方式在何處(Pointcut)應用,并且我們無需改變使用這個通用功能的類。這樣做有兩個好處:第一是把每個關注點集中在一個地方而不是分散到項目的各個地方去;第二是代碼結構極其簡潔,非常好維護,這對于骨子里就有極簡主義的程序猿來說真是一大幸事!不得不佩服Spring團隊(迷弟臉)....

AOP三個基本點

Advice:通知,這定義了切入點的具體要干的事情,分為BeforeAdvice、AfterAdvice以及ThrowsAdvice。

@Around("timeCost()”)//這時的timeCost()就是一個通知

Pointcut:切點,這定義了切入點的位置,將需要增加的地方用某個正則表達式進行表示,或根據某個方法名來匹配。利用MethodMatcher中的matches方法來進行匹配判斷,在JdkRegexpMethodPointcut中,matches方法實際上是通過JDK來實現正則表達式的匹配。

@Pointcut("execution(public
com.intelligentler.shuitu.controller.
.*(..))”)

Advisor:通知器,把具體操作(通知)和待增強的位置(切點)結合起來,定義在哪一個關注點使用哪一個通知。

@Aspect
@Component
@Order(-5)
public class TimeCostAspect {
    …….
}

這里的TimeCostAspect就不單單是一個POJO,而且還是一個切面。

這里有個單例模式的彩蛋:

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
     private Pointcut pointcut = Pointcut.TRUE;
     public DefaultPointcutAdvisor() {
     }
     public DefaultPointcutAdvisor(Advice advice) {
          this(Pointcut.TRUE, advice);
     }
     public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
          this.pointcut = pointcut;
          setAdvice(advice);
     }
     public void setPointcut(Pointcut pointcut) {
          this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
     }
     @Override
     public Pointcut getPointcut() {
          return this.pointcut;
     }
     @Override
     public String toString() {
          return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
     }
}

在DefaultPointcutAdvisor類中Pointcut被設置為Pointcut.TRUE;Pointcut.TRUE在Pointcut的定義如下:

//Pointcut.class
Pointcut TRUE = TruePointcut.INSTANCE;
//TruePointcut.class
class TruePointcut implements Pointcut, Serializable {
     public static final TruePointcut INSTANCE = new TruePointcut();
     private TruePointcut() {
     }
     @Override
     public ClassFilter getClassFilter() {
          return ClassFilter.TRUE;
     }
     @Override
     public MethodMatcher getMethodMatcher() {
          return MethodMatcher.TRUE;
     }
     private Object readResolve() {
          return INSTANCE;
     }
     @Override
     public String toString() {
          return "Pointcut.TRUE";
     }
}

重點來了:這里的TruePointcut INSTANCE是一個單例模式,使用static類變量來持有單例,再使用private私有構造函數來確保單例不會被再次創建和實例化。在這個類中的MethodMatcher.TRUE也是類似的單例實現。

AOP的設計與實現

Spring的AOP采用的是動態代理模式(這一設計模式我在一篇設計模式的博文中有講解),Spring在代理類中包裹切面,在運行期間把切面織入到Bean中,也就是說Spring用代理類封裝了目標類,同時攔截了被通知方法的調用,處理完通知(Advice)后,再把調用轉發給真正的目標Bean,也真因為是動態代理,所以Spring的AOP只支持到方法連接點而無法提供字段和構造器接入點(AspectJ和JBoss可以),所以Spring無法創建細粒度的通知。

根據Spring AOP的動態代理的過程,我們可以把AOP的設計分為兩大塊:第一,需要為目標對象建立代理對象(如何生成代理對象?);第二,需要啟動代理對象的攔截器來完成各種橫切面的織入(如何織入橫切面同時如何攔截對目標對象方法的調用?)。

什么是代理對象--AOP的動態代理

Spring AOP的核心技術是動態代理,對于增強的對象方法,在用戶調用這個對象方法(request)的時候其實是調用Spring AOP提前為其生成好的代理對象(Proxy)的相應方法,這個代理對象的方法實現就包含了preOperation—request—postOperation,通過對對象方法的這種攔截,增強了目標對象的方法操作,這種方式就是代理。

如何生成代理對象?

圖片描述

通過繼承ProxyConfig、AdvisedSupport和ProxyCreatorSupport等基類的功能實現,具體的AOP代理對象的生成,根據不同的需要,分別由AspectJProxyFactory、ProxyFactoryBean和ProxyFactory來完成。對于需要AspectJ的AOP應用,AspectJProxyFactory起到了集成Spring和AspectJ的作用;對于使用Spring AOP的應用,ProxyFactoryBean和ProxyFactory都提供了AOP功能的封裝,其中對于ProxyFactoryBean,可以在IOC容器中完成聲明配置,而對于ProxyFactory則需要編程式地使用Spring AOP的功能。Spring的AspectJProxyFactory、ProxyFactoryBean和ProxyFactory封裝了代理對象AopProxy的生成過程,代理對象的生成實現過程由JDK的Proxy和CGLIB第三方來實現。

下面我們具體分析下ProxyFactoryBean,ProxyFactoryBean是一個FactoryBean,FactoryBean一般是如何生成Bean的呢?在調用getBean方法->doGetBean方法中,我們可以看到無論是直接取單例的bean,還是創建單例、多例、自定義生命周期的bean,都會經過bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);這個方法,深入進去會發現最終會調用FactoryBean的getObject方法生產指定Bean的實例對象(Spring中具體的getObject的實現方法有70多個),也就是說對于FactoryBean而言,調用getBean(String BeanName)得到的是具體某個Bean對象(在getObject中具體實現的)而不是FactoryBean對象本身,如果想得到FactoryBean對象本身,只需要加上&符號即可。在FactoryBean的實現原理中我們可以發現工廠模式和裝飾器模式的具體應用。

對于ProxyFactoryBean來說,生成代理對象,也是通過getObject方法封裝(修飾)了對目標對象增加的增強處理。那么我們具體分析下ProxyFactoryBean中的getObject方法。

@Override
public Object getObject() throws BeansException {
    //初始化通知器鏈
    initializeAdvisorChain();
    //區分singleton和prototype,生成對應的proxy代理對象
    if (isSingleton()) {
        return getSingletonInstance();
    }
    else {
        if (this.targetName == null) {
            logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                    "Enable prototype proxies by setting the 'targetName' property.");
        }
        return newPrototypeInstance();
    }
}

這里的初始化通知器鏈只發生在第一次通過ProxyFactoryBean去獲取代理對象的時候,在initializeAdvisorChain方法里有一個標志位advisorChainInitialized來保證這一點。

生成單例代理對象是在getSingletonInstance方法中完成的,它是生成AopProxy代理對象的調用入口。

private synchronized Object getSingletonInstance() {
    if (this.singletonInstance == null) {
        this.targetSource = freshTargetSource();
        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
            // Rely on AOP infrastructure to tell us what interfaces to proxy.
            Class<?> targetClass = getTargetClass();
            if (targetClass == null) {
                throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
            }
            setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
        }
        // Initialize the shared singleton instance.
        super.setFrozen(this.freezeProxy);
        //通過代理工廠來生成對應的代理對象
        this.singletonInstance = getProxy(createAopProxy());
    }
    return this.singletonInstance;
}

protected Object getProxy(AopProxy aopProxy) {
    return aopProxy.getProxy(this.proxyClassLoader);
}

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    //通過AopProxyFactory工廠類來獲取Aop的代理對象AopProxy,生成什么樣的代理對象由AdivisedSupport決定,AdivisedSupport作為參數傳入createAopProxy方法中,這里表示為this。
    return getAopProxyFactory().createAopProxy(this);
}

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        //從AdvisedSupport中獲取目標對象class
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        //如果targetClass是接口類則使用JDK來生成代理對象,否則使用CGLIB來生成。
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

圖片描述

如何攔截對目標對象方法的調用?

對于JDK的代理對象,攔截使用的是InvocationHandler的invoke回調入口;對于CGLIB的代理對象,攔截是有設置好的回調callback方法(intercept方法)來完成的。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // 獲取目標對象
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // 獲取攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // 如果沒有設定攔截器則直接調用目標對象的對應方法
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool...
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        //獲取AOP通知
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // 如果沒有配置AOP通知,則直接調用目標對象的對應方法
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            // 啟動AOP通知
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

不管用什么方法生成的代理對象,(在invoke或intercept方法中)對攔截器的調用都是通過proceed方法實現的,在proceed方法中完成對目標對象的增強功能,這種實現是通過運行逐個具體的攔截器的攔截方法來完成的。

@Override
public Object proceed() throws Throwable {
    // 從索引為-1的攔截器開始調用,并按序遞增,攔截器方法調用完畢則開始調用目標對象的方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        // 判斷攔截器的攔截方法是否匹配當前的調用方法,匹配則調用攔截器(invoke)方法方法
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 不匹配則遞歸調用proceed
            return proceed();
        }
    }
    else {
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

首先會根據配置來判斷攔截器是否與當前的調用方法相匹配(matches),如果當前的調用方法與配置的攔截器相匹配,那么相應的攔截器就開始發揮作用。這個過程是一個遞歸遍歷的過程,它會遍歷在代理對象中設置的攔截器鏈中所有的攔截器,被匹配的攔截器被逐一調用,直到所有的攔截器都被遍歷完,才是對目標對象的方法調用,這樣就完成了對目標對象方法調用的增強。

應該注意到,Advice通知不是直接對目標對象作用來完成增強的,而是對不同種類的通知通過AdviceAdapter適配器來實現的。

參考文獻:

《Spring實戰》

《Spring技術內幕 深入解析Spring架構與設計原理》

點擊查看更多內容
3人點贊

若覺得本文不錯,就分享一下吧!

評論

相關文章推薦

正在加載中
意見反饋 幫助中心 APP下載
官方微信

舉報

0/150
提交
取消
lpl竞猜