探秘Spring AOP

編程范式概覽

  • 面向過程編程
  • 面向?qū)ο缶幊?/li>
  • 函數(shù)式編程
  • 事件驅(qū)動編程
  • 面向切面編程

AOP是什么

  • 是一種編程范式旱幼,不是編程語言
  • 解決特定問題突委,不能解決所有問題
  • 是OOP的補充匀油,不是替代

AOP的初衷

  • DRY:Don't Repeat Yourself 解決代碼重復性問題
  • SoC:Separation of Concerns 解決關(guān)注點問題
    • 水平分離:展示層 --> 服務層 --> 持久層
    • 垂直分離:模塊劃分(訂單、庫存等)
    • 切面分離:分離功能性需求與非功能性需求(把非功能性需求從功能性需求中分離出來桥滨,從而實現(xiàn)DRY)

使用AOP的好處

  • 集中處理某一關(guān)注點/橫切邏輯
  • 可以很方便地添加/刪除關(guān)注點
  • 侵入性少齐媒,增強代碼可讀性及可維護性

AOP應用場景

  • 權(quán)限控制
  • 緩存控制
  • 事務控制
  • 審計日志
  • 性能監(jiān)控
  • 分布式追蹤
  • 異常處理

支持AOP的編程語言

  • Java
  • .Net
  • C/C++
  • Ruby
  • Python
  • PHP

注解:

  • @Aspect - 申明成切面類
  • @Pointcut("@annotation(AdminOnly)") - 申明表達式,攔截有AdminOnly的注解邀杏;
  • @Before("adminOnly()") - 在adminOnly()方法執(zhí)行之前双妨,執(zhí)行該注解方法刁品。

Spring AOP使用方式

  • XML配置
  • 注解方式

主要注解

  • AspectJ 注解
    • @Aspect:用來標注說明這個Java類是一個切面配置類
    • @Pointcut:描述在哪些類的哪些方法進行植入你的代碼
    • Advice:你想要在這些方法的執(zhí)行什么時機進行植入

Pointcut expression:切面表達式

  • designators:execution() 、 ...
  • wildcards:* / .. / +
  • operators:&& / || / !

Wildcards(通配符)

  • * :匹配任意數(shù)量的字符
  • + :匹配指定類及其子類
  • .. :一般用于匹配任意數(shù)的子包或參數(shù)

Operators(運算符)

  • && :與操作符
  • || :或操作符
  • ! :非操作符

designators

  • 匹配方法:execution()
  • 匹配注解:
    • @target()
    • @args()
    • @within()
    • @annotation()
  • 匹配包/類型
    • within()
  • 匹配對象
    • this()
    • bean()
    • target()
  • 匹配參數(shù)
    • args()

匹配包/類型

// 匹配ProductService類里頭的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}

// 匹配com.imooc包及子包下所有類的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}

匹配對象

// 匹配AOP對象的目標對象為指定類型的方法,即DemoDao的aop代理對象的方法
@Pointcut("this(com.imooc.DemoDao)")
public void thisDemo(){}

// 匹配實現(xiàn)IDao接口的目標對象(而不是aop代理后的對象)的方法兜挨,這里即DemoDao的方法
@Pointcut("target(com.imooc.IDao)")
public void targetDemo(){}

// 匹配所有以Service結(jié)尾的bean里頭的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}

參數(shù)匹配

// 匹配任何以find開頭而且只有一個Long參數(shù)的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}

// 匹配任何只有一個Long參數(shù)的方法
@Pointcut("args(Long)")
public void argsDemo2(){}

// 匹配任何以find開頭的而且第一個參數(shù)為Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}

// 匹配第一個參數(shù)為Long型的方法
@Pointcut("args(Long,..)")
public void argsDemo4(){}

匹配注解

// 匹配方法標注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.demo.security.AdminOnly)")
public void annoDemo(){}

// 匹配標注有Beta的類底下的方法拌汇,要求的annotation的RetentionPolicy級別為CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo(){}

// 匹配標注有Repository的類底下的方法噪舀,要求的annotation的RetentionPolicy級別為RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo(){}

// 匹配傳入的參數(shù)類標注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo(){}

execution()表達式

格式

execution(
    modifier-pattern?   - 修飾符
    ret-type-pattern    - 返回值
    declaring-type-pattern? - 描述包名
    name-pattern(param-pattern) - 描述方法名(方法參數(shù))
    throws-pattern? - 匹配方法拋出的異常 
)

標注?表示可省略的与倡!

5種Advice注解

1、@Before息拜,前置通知
2净响、@After(finally),后置通知狈茉,方法執(zhí)行完之后
3掸掸、@AfterReturning,返回通知堤撵,成功執(zhí)行之后
4实昨、@AfterThrowing,異常通知丈挟,拋出異常之后
5志电、@Around挑辆,環(huán)繞通知

Advice中的參數(shù)及結(jié)果綁定

// 攔截獲取方法的參數(shù)進行校驗
@Before(value="annoTargetVsWithinDemo() && within(com.imooc..*) && args(userId)")
public void beforeWithArgs(JoinPoint joinPoint, Long userId){
    System.out.println("before, args:"+userId) ;
}

// 打印方法的返回值
@AfterReturning(value="annoTargetVsWithinDemo() && within(com.imooc..*)",returning="returnValue")
public void getReulst(Obgect returnValue){
    if(returnValue != null){
        System.out.println("after return, result:"+returnValue) ;
    }
}

原理概述:織入的時機

1、編譯期(AspectJ)
2洒嗤、類加載時(AspectJ 5+)
3魁亦、運行時(Spring AOP)

原理概述:運行時織入

  • 運行時織入是怎么實現(xiàn)的洁奈? 代理對象
  • 從靜態(tài)代理到動態(tài)代理
    • 靜態(tài)代理的缺點:每當代理的方法越多時,重復的邏輯也越多终吼。
    • 動態(tài)代理的兩類實現(xiàn):基于接口代理與基于繼承代理。
    • 兩大實現(xiàn)的代表:JDK代理與Cglib代理商佛。
  • 基于接口代理與基于繼承代理
  • JDK實現(xiàn)要點

    • 類:java.lang.reflect.Proxy
    • 接口:InvocationHandler
    • 只能基于接口進行動態(tài)代理
  • JDK代理源碼解析

    • Proxy.newProxyInstance:生成代理類的實現(xiàn)類
    • getProxyClass0:生成指定的代理類良姆、ProxyClassFactory、ProxyGenerator
    • newInstance

Cglib實現(xiàn)

static class DemoCglibInterceptor implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
        System.out.println("before...");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("after...");
        return result ;
    }
}

public static void main(String[] args){
    Subject subject = (Subject)getProxy(RealSubject.class, new DemoCglibInterceptor());
    subject.request();
}

JDK與Cglib代理對比

  • JDK只能針對有接口的類的接口方法進行動態(tài)代理
  • Cglib基于繼承來實現(xiàn)代理税课,無法對static韩玩、final類進行代理
  • Cglib基于繼承來實現(xiàn)代理,無法對private合愈、static方法進行代理

Spring中AOP代理由Spring的IOC容器負責生成击狮、管理彪蓬,其依賴關(guān)系也由IOC容器負責管理。因此储狭,AOP代理可以直接使用容器中的其它bean實例作為目標捣郊,這種關(guān)系可由IOC容器的依賴注入提供呛牲。

DefaultAopProxyFactory

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable{

    @Override
    public AopProxy creatAopProxy(AdvisedSupport config) throws AopConfigException{
        if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)){
            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.")
            }
            if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }else{
            return new JdkDynamicAopProxy(config);
        }
    }
}

Spring創(chuàng)建代理的規(guī)則為:
1娘扩、如果目標對象實現(xiàn)了接口,則默認才用JDK動態(tài)代理涮阔。
2灰殴、如果目標對象沒有實現(xiàn)接口,則才用CGLIB動態(tài)代理伟阔。
3掰伸、如果目標對象實現(xiàn)了接口狮鸭,并指定為CGLIB代理,則使用CGLIB動態(tài)代理多搀。

強制使用Cglib代理

@SpringBootApplication
// 強制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopDemoApplication{
    public static void main(String[] args){
        SpringApplication.run(AopDemoApplication.class, args);
    }
}

安全校驗@PreAuthorize

  • MethodSecurityInterceptor
  • PreInvocationAuthorizationAdviceVoter
  • ExpressionBasedPreInvocationAdvice

緩存@Cacheable

  • AnnotationCacheAspect
  • CacheInterceptor
  • CacheAspectSupport
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市梳猪,隨后出現(xiàn)的幾起案子春弥,更是在濱河造成了極大的恐慌,老刑警劉巖扫责,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逃呼,死亡現(xiàn)場離奇詭異鳖孤,居然都是意外死亡,警方通過查閱死者的電腦和手機抡笼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門苏揣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人推姻,你說我怎么就攤上這事平匈。” “怎么了藏古?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵增炭,是天一觀的道長。 經(jīng)常有香客問我隙姿,道長,這世上最難降的妖魔是什么厂捞? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任输玷,我火速辦了婚禮,結(jié)果婚禮上蔫敲,老公的妹妹穿的比我還像新娘饲嗽。我一直安慰自己炭玫,他們只是感情好奈嘿,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吞加,像睡著了一般裙犹。 火紅的嫁衣襯著肌膚如雪尽狠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天叶圃,我揣著相機與錄音袄膏,去河邊找鬼。 笑死掺冠,一個胖子當著我的面吹牛沉馆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播德崭,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斥黑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了眉厨?” 一聲冷哼從身側(cè)響起锌奴,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎憾股,沒想到半個月后鹿蜀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡服球,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年茴恰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斩熊。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡琐簇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出座享,到底是詐尸還是另有隱情婉商,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布渣叛,位于F島的核電站丈秩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏淳衙。R本人自食惡果不足惜蘑秽,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箫攀。 院中可真熱鬧肠牲,春花似錦、人聲如沸靴跛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梢睛。三九已至肥印,卻和暖如春识椰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背深碱。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工腹鹉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敷硅。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓功咒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绞蹦。 傳聞我的和親對象是個殘疾皇子航瞭,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 只有光頭才能變強 上一篇已經(jīng)講解了Spring IOC知識點一網(wǎng)打盡!坦辟,這篇主要是講解Spring的AOP模...
    Java3y閱讀 6,884評論 8 181
  • 1. 簡介 面向?qū)ο缶幊炭睿卜Q為OOP(即Object Oriented Programming)最大的優(yōu)點在于能...
    charming_coder閱讀 2,185評論 1 7
  • Spring AOP解決問題 相比較于面向過程程序設計(POP),面向?qū)ο蟪绦蛟O計(OOP)更加注重封裝與隔離锉走,以...
    桴海閱讀 193評論 0 0
  • IoC 容器 Bean 的作用域 自定義作用域?qū)崿F(xiàn) org.springframework.beans.facto...
    Hsinwong閱讀 2,473評論 0 7
  • 今日意圖:愉快順利的旅程滨彻,放松臣服的一天 今天一天又是特別忙碌的一天,早上起床送兩個孩子去上學挪蹭,然后就開始吃亭饵,單位...
    心晴椰子閱讀 55評論 0 1