Spring AOP

Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are by definition not intercepted. For JDK proxies, only public interface method calls on the proxy can be intercepted. With CGLIB, public and protected method calls on the proxy will be intercepted, and even package-visible methods if necessary. However, common interactions through proxies should always be designed through public signatures.
Note that pointcut definitions are generally matched against any intercepted method. If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with potential non-public interactions through proxies, it needs to be defined accordingly.
If your interception needs include method calls or even constructors within the target class, consider the use of Spring-driven native AspectJ weavinginstead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.

Pointcut

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;
}

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);

    /**
     * Canonical instance of a ClassFilter that matches all classes.
     */
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * returns {@code false} or if the {@link #isRuntime()} method
     * returns {@code false}, no runtime check (i.e. no.
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made.
     * @param method the candidate method
     * @param targetClass the target class (may be {@code null}, in which case
     * the candidate class must be taken to be the method's declaring class)
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);

    /**
     * Is this MethodMatcher dynamic, that is, must a final call be made on the
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
     * runtime even if the 2-arg matches method returns {@code true}?
     * <p>Can be invoked when an AOP proxy is created, and need not be invoked
     * again before each method invocation,
     * @return whether or not a runtime match via the 3-arg
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method
     * is required if static matching passed
     */
    boolean isRuntime();

    /**
     * Check whether there a runtime (dynamic) match for this method,
     * which must have matched statically.
     * <p>This method is invoked only if the 2-arg matches method returns
     * {@code true} for the given method and target class, and if the
     * {@link #isRuntime()} method returns {@code true}. Invoked
     * immediately before potential running of the advice, after any
     * advice earlier in the advice chain has run.
     * @param method the candidate method
     * @param targetClass the target class (may be {@code null}, in which case
     * the candidate class must be taken to be the method's declaring class)
     * @param args arguments to the method
     * @return whether there's a runtime match
     * @see MethodMatcher#matches(Method, Class)
     */
    boolean matches(Method method, Class<?> targetClass, Object... args);

    /**
     * Canonical instance that matches all methods.
     */
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

ProxyFactory

  • targetObject
  • interface
  • advisor(advice+pointcut)
    基本與直接使用InvocationHandler和Proxy類似,不過是把InvocationHandler中的邏輯移到了advice,并且通過pointcut匹配需要添加邏輯的地方;
    如果沒有實現接口,那么就會使用cglib來生成子類,這里不一樣的地方在于上面一種代理對象是Proxy子類。

AopProxy -- AopProxyFactory -- AdvisedSupport 《Spring揭秘》p176

ProxyFactoryBean

Proxy的FactoryBean

AutoProxy

上面的ProxyFactory都是針對特定的對象,如果目標對象太多,則工作量很大。
通過BeanPostProcessor可以實現將滿足要求的bean代理后返回

AutoProxyCreator

  • BeanNameAutoProxyCreator
  • DefaultAdvisorAutoProxyCreator

BeanNameAutoProxyCreator需要聲明BeanName和InterceptorName,然后將Interceptor應用到bean上,這里的Interceptor可以是Advisor或者Advice(然后包裹成DefaultPointcutAdvisor)
DefaultAdvisorAutoProxyCreator掃描所有的Advisor(只有Advisor的bean),通過pointcut匹配后將advice織入bean(還是使用ProxyFactory實現)

@AspectJ Spring AOP

基于注解的Aspect或者aop命名空間
與之前的AOP的關鍵差別:

  • 使用POJO聲明Aspect和Advice,不需要實現特定的接口(Pointcut,Advice,Advisor)
  • AspectJ的Pointcut表述語言,而不是方法名或者正則
  • 本質沒有變,代理模式處理橫切邏輯
編程方式 AutoProxy XSD
SpringAOP1.0 ProxyFactory|ProxyFactoryBean DefaultAdvisorAutoProxyCreator|BeanNameAutoProxyCreator
SpringAOP2.0 AspectJProxyFactory AnnotationAwareAspectJAutoProxyCreator <aop:config>

AspectJProxyFactory 或 AnnotationAwareAspectjAutoProxyCreator 通過反射獲取了@Pointcut的定義后,會構造一個AspectJExpressionPointcut,而這個Pointcut在實現ClassFilter和MethodMatcher的邏輯的時候會委托AspectJ類庫完成。

  1. JoinPoint參數 (除了Around 和 Introduction)
  2. 除了execution之外,所有的標識符都可以綁定參數,然后傳入advice方法。
@Before
@Component(value = "huge")
public class TestImpl implements TestService {

    @Override
    @Transactional
    public void print(String word) {
        System.out.println(word);
    }

    @Override
    @Transactional
    public String getWord() {
        return "Hello world.";
    }
}

@Aspect
@Component
public class TestAspect {

    @Pointcut("execution(* TestService.*(..)) && args(word)")
    public void matchTestService(String word){}

    @Before(value = "matchTestService(word)")
    public void out(String word) {
        System.out.println(word);
    }

    @Before(value = "matchTestService(word) && this(obj) && target(obj2) && @within(info) && @target(info2) && @annotation(info3)")
    public void out2(String word, TestService obj, Object obj2, Component info, Component info2, Transactional info3) {
        System.out.println(obj);
        System.out.println(obj2);
        System.out.println(info.value());
        System.out.println(info2.value());
        System.out.println(info3);
    }
}
@AfterThrowing
    @AfterThrowing(value = "testServiceThrow()", throwing = "e")
    public void throwing(RuntimeException e){
        System.out.println(e.getMessage());
    }
@AfterReturning
    @AfterReturning(value = "testServiceReturnValue()", returning = "value")
    public void returning(String value) {
        System.out.println(value);
    }
@Around

第一個參數只能是ProceedingJoinPoint

@Around(value = "matchTestService(word)")
    public void hach(ProceedingJoinPoint proceedingJoinPoint, String word) {

        try {
            proceedingJoinPoint.proceed(new Object[]{"hahahah"});
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
執行順序

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).
When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.
When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

一個問題

如果一個被代理對象的方法A調用了自身的另一個方法B,那么如果B是被代理的方法,那么調用A間接調用B時不會觸發代理的
解決方法:AopContext.currentProxy()獲取代理對象,通過代理對象調用B會觸發代理

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]);
            }
            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                // The target does not implement the hashCode() method itself.
                return hashCode();
            }
            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;
            }

            // May be null. Get as late as possible to minimize the time we "own" the target,
            // in case it comes from a pool.
            target = targetSource.getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }

            // Get the interception chain for this method.
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            // Check whether we have any advice. If we don't, we can fallback on direct
            // reflective invocation of the target, and avoid creating a MethodInvocation.
            if (chain.isEmpty()) {
                // We can skip creating a MethodInvocation: just invoke the target directly
                // Note that the final invoker must be an InvokerInterceptor so we know it does
                // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
            }
            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);
            }
        }
    }

可以看到開始代理調用的時候,會把代理對象設置到AopContext中,這個是與線程綁定的,當完成了代理調用以后,會把之前的代理對象重新設置到AopContext中

應用場景

  • 異常處理
    指unchecked Exception,程序無法解決,只能人工干預,所以提供足夠的信息就可以了,各種類型的unchecked Exception 可以無差別對待。
  • 安全檢查
    Web應用常使用Filter實現安全檢查,通過AOP可以為任何類型的應用添加相應的安全支持。
  • 緩存
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • 基本知識 其實, 接觸了這么久的 AOP, 我感覺, AOP 給人難以理解的一個關鍵點是它的概念比較多, 而且坑爹...
    永順閱讀 8,307評論 5 114
  • 因為工作需求,自己去了解一下aop并做下的記錄,當然大部分都是參考他人博客以及官方文檔。 目錄 [關于 AOP](...
    forip閱讀 2,289評論 1 20
  • 以前寫的文章直接上源碼分析,這會讓不了解的人看著很累,得不到想要的效果。本篇文章則從背景-原理-使用-源碼的順序為...
    oneWeekOneTopic閱讀 15,351評論 2 25
  • **** AOP 面向切面編程 底層原理 代理!!! 今天AOP課程1、 Spring 傳統 AOP2、 Spri...
    luweicheng24閱讀 1,379評論 0 1
  • 1、AOP concepts(AOP術語) Aspect/Advisors(切面)一個關注點的模塊化,這個關注點可...
    codersm閱讀 1,515評論 0 5