咱們有一個Pay(接口) 而後兩個實現類DollarPay和RmbPay,都須要重寫pay()方法, 這時咱們須要對pay方法進行性能監控,日誌的添加等等怎麼作?java
對每一個字符方法均作日誌代碼的編寫處理,以下面方式spring
缺點: 代碼重複太多, 添加的日誌代碼耦合度過高(若是須要更改日誌記錄代碼功能需求,類中方法須要所有改動,工程量浩大)express
裝飾器模式:動態地給一個對象添加一些額外的職責。編程
代理模式:以上剛講過。因而得出如下結構:安全
仔細考慮事後發現雖然對原有內部代碼沒有進行改動,對於每一個類作日誌處理,並引用目標類,可是若是待添加日誌的業務類的數量不少,此時手動爲每一個業務類實現一個裝飾器或建立對應的代理類,同時代碼的耦合度也加大,需求一旦改變,改動的工程量也是可想而知的。ide
有沒有更好的解決方案,只要寫一次代碼,對想要添加日誌記錄的地方可以實現代碼的複用,達到鬆耦合的同時,又可以完美完成功能?oop
答案是確定的,存在這樣的技術,aop已經對其提供了完美的實現!性能
若有疑問,可加入羣:10803-55292,輸入暗號13,便可有大佬幫助代理
Aspect Oriented Programing 面向切面編程,相比較 oop 面向對象編程來講,Aop關注的再也不是程序代碼中某個類,某些方法,而aop考慮的更多的是一種面到面的切入,即層與層之間的一種切入,因此稱之爲切面。聯想你們吃的漢堡(中間夾肉)。那麼aop是怎麼作到攔截整個面的功能呢?考慮前面學到的servlet filter /* 的配置 ,實際上也是aop 的實現。日誌
AOP主要應用於日誌記錄,性能統計,安全控制,事務處理等方面,實現公共功能性的重複使用。
1. 下降模塊與模塊之間的耦合度,提升業務代碼的聚合度。(高內聚低耦合)
2. 提升了代碼的複用性。
3. 提升系統的擴展性。(高版本兼容低版本)
4. 能夠在不影響原有的功能基礎上添加新的功能
動態代理(JDK + CGLIB)
被攔截到的每一個點,spring中指被攔截到的每個方法,spring aop一個鏈接點即表明一個方法的執行。
對鏈接點進行攔截的定義(匹配規則定義 規定攔截哪些方法,對哪些方法進行處理),spring 有專門的表達式語言定義。
攔截到每個鏈接點即(每個方法)後所要作的操做
切入點與通知的結合,決定了切面的定義,切入點定義了要攔截哪些類的哪些方法,通知則定義了攔截過方法後要作什麼,切面則是橫切關注點的抽象,與類類似,類是對物體特徵的抽象,切面則是橫切關注點抽象。
被代理的目標對象
將切面應用到目標對象並生成代理對象的這個過程即爲織入
在不修改原有應用程序代碼的狀況下,在程序運行期爲類動態添加方法或者字段的過程稱爲引入
<!--Spring AOP--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
添加命名空間
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
/** * 切面 * 切入點和通知的抽象 (與面向對象中的 類 類似) * 定義 切入點和通知 (切入點定義了要攔截哪些類的哪些方法,通知則定義了攔截過方法後要作什麼) */ @Component // 將對象交給IOC容器去實例化 @Aspect // 聲明當前類是一個切面 public class LogCut { /** * 切入點: * 匹配規則。規定什麼方法被攔截、須要處理什麼方法 * 定義切入點 * @Pointcut("匹配規則") * * Aop 切入點表達式簡介 * 1. 執行任意公共方法: * execution(public *(..)) * 2. 執行任意的set方法 * execution(* set*(..)) * 3. 執行com.xxxx.service包下任意類的任意方法 * execution(* com.xxxx.service.*.*(..)) * 4. 執行com.xxxx.service 包 以及子包下任意類的任意方法 * execution(* com.xxxx.service..*.*(..)) * * 注:表達式中的第一個* 表明的是方法的修飾範圍 * 可選值:private、protected、public (* 表示全部範圍) */ @Pointcut("execution (* com.xxxx.service..*.*(..) )") public void cut(){} /** * 聲明前置通知 並將通知應用到定義的切入點上 * 目標類方法執行前 執行該通知 * */ @Before(value = "cut()") public void before() { System.out.println("前置通知....."); } /** * 聲明返回通知 並將通知應用到定義的切入點上 * 目標類方法(無異常)執行後 執行該通知 * */ @AfterReturning(value = "cut()") public void afterReturn() { System.out.println("返回通知....."); } /** * 聲明最終通知 並將通知應用到定義的切入點上 * 目標類方法(無異常或有異常)執行後 執行該通知 * */ @After(value = "cut()") public void after() { System.out.println("最終通知....."); } /** * 聲明異常通知 並將通知應用到定義的切入點上 * 目標類方法出現異常時 執行該通知 */ @AfterThrowing(value="cut()",throwing = "e") public void afterThrow(Exception e) { System.out.println("異常通知....." + " 異常緣由:" + e.getCause()); } /** * 聲明環繞通知 並將通知應用到切入點上 * 方法執行先後 經過環繞通知定義相應處理 * 須要經過顯式調用對應的方法,不然沒法訪問指定方法 (pjp.proceed();) * @param pjp * @return */ @Around(value = "cut()") public Object around(ProceedingJoinPoint pjp) { System.out.println("前置通知..."); Object object = null; try { object = pjp.proceed(); System.out.println(pjp.getTarget() + "======" + pjp.getSignature()); // System.out.println("返回通知..."); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("異常通知..."); } System.out.println("最終通知..."); return object; } }
<!--配置AOP代理--> <aop:aspectj-autoproxy/>
** * 切面 * 切入點和通知的抽象 (與面向對象中的 類 類似) * 定義 切入點和通知 (切入點定義了要攔截哪些類的哪些方法,通知則定義了攔截過方法後要作什麼) */ @Component // 將對象交給IOC容器去實例化 public class LogCut02 { public void cut(){} /** * 聲明前置通知 並將通知應用到定義的切入點上 * 目標類方法執行前 執行該通知 * */ public void before() { System.out.println("前置通知....."); } /** * 聲明返回通知 並將通知應用到定義的切入點上 * 目標類方法(無異常)執行後 執行該通知 * */ public void afterReturn() { System.out.println("返回通知....."); } /** * 聲明最終通知 並將通知應用到定義的切入點上 * 目標類方法(無異常或有異常)執行後 執行該通知 * */ public void after() { System.out.println("最終通知....."); } /** * 聲明異常通知 並將通知應用到定義的切入點上 * 目標類方法出現異常時 執行該通知 */ public void afterThrow(Exception e) { System.out.println("異常通知....." + " 異常緣由:" + e.getCause()); } /** * 聲明環繞通知 並將通知應用到切入點上 * 方法執行先後 經過環繞通知定義相應處理 * 須要經過顯式調用對應的方法,不然沒法訪問指定方法 (pjp.proceed();) * @param pjp * @return */ public Object around(ProceedingJoinPoint pjp) { System.out.println("前置通知..."); Object object = null; try { object = pjp.proceed(); System.out.println(pjp.getTarget() + "======" + pjp.getSignature()); // System.out.println("返回通知..."); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("異常通知..."); } System.out.println("最終通知..."); return object; } }
<!--aop相關配置--> <aop:config> <!--aop切面--> <aop:aspect ref="logCut02"> <!-- 定義aop 切入點 --> <aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/> <!-- 配置前置通知 指定前置通知方法名 並引用切入點定義 --> <aop:before method="before" pointcut-ref="cut"/> <!-- 配置返回通知 指定返回通知方法名 並引用切入點定義 --> <aop:after-returning method="afterReturn" pointcut-ref="cut"/> <!-- 配置異常通知 指定異常通知方法名 並引用切入點定義 --> <aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/> <!-- 配置最終通知 指定最終通知方法名 並引用切入點定義 --> <aop:after method="after" pointcut-ref="cut"/> <!-- 配置環繞通知 指定環繞通知方法名 並引用切入點定義 --> <aop:around method="around" pointcut-ref="cut"/> </aop:aspect> </aop:config>
靜態代理:手動爲目標對象製做代理對象,即在程序編譯階段完成代理對象的建立
動態代理:在程序運行期動態建立目標對象對應代理對象。
jdk動態代理:被代理目標對象必須實現某一或某一組接口 實現方式 經過回調建立代理對象。
cglib 動態代理:被代理目標對象能夠沒必要實現接口,繼承的方式實現。
動態代理相比較靜態代理,提升開發效率,能夠批量化建立代理,提升代碼複用率。