切面:傳統的OOP構建的是對象之間的關係,是一種垂直的關係;假設,OOP程序是一個圓筒,那麼與業務或邏輯無關的東西,好比日誌,權限等html
AOP技術利用一種稱爲「橫切」的技術,解剖封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,這樣就能減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處都基本類似。好比權限認證、日誌、事務處理。java
幾個術語web
- 通知(Advice)
通知定義了切面是什麼以及什麼時候調用,什麼時候調用包含如下幾種Before 在方法被調用以前調用通知正則表達式
After 在方法完成以後調用通知,不管方法執行是否成功spring
After-returning 在方法成功執行以後調用通知express
After-throwing 在方法拋出異常後調用通知編程
Around 通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲緩存
- 切點(PointCut)
通知定義了切面的什麼和什麼時候,切點定義了何處,切點的定義會匹配通知所要織入的一個或多個鏈接點,咱們一般使用明確的類的方法名稱來指定這些切點,或是利用正則表達式定義匹配的類和方法名稱來指定這些切點。
切點的格式以下execution(* com.ganji.demo.service.user.UserService.GetDemoUser (..) )
- 鏈接點(JoinPoint)
鏈接點是在應用執行過程當中可以插入切面的一個點,這個點能夠是調用方法時,拋出異常時,甚至是修改一個字段時,切面代碼能夠利用這些鏈接點插入到應用的正常流程中,並添加新的行爲,如日誌、安全、事務、緩存等。
兩種方式:xml和註解(和其餘同樣,強烈推薦使用第二種,但第一種要了解透徹)安全
在web層,web-inf/dispatcher-servlet.xml中定義切面性能優化
<!--定義切面 指定攔截方法時 作什麼--> <bean id="xmlAopDemoUserLog" class="com.ganji.demo.service.aspect.XmlAopDemoUserLog"></bean> <aop:config> <aop:aspect ref="xmlAopDemoUserLog"> <!--指定切面--> <!--定義切點--> <aop:pointcut id="logpoint" expression="execution(* com.ganji.demo.service.user.UserService.GetDemoUser(..))"></aop:pointcut> <!--定義鏈接點--> <aop:before pointcut-ref="logpoint" method="beforeLog"></aop:before> <aop:after pointcut-ref="logpoint" method="afterLog"></aop:after> <aop:after-returning pointcut-ref="logpoint" method="afterReturningLog"></aop:after-returning> <aop:after-throwing pointcut-ref="logpoint" method="afterThrowingLog"></aop:after-throwing> </aop:aspect> </aop:config>
具體用法能夠參考
AOP配置元素 | 描述 ------------ | ------------- `<aop:advisor>` | 定義AOP通知器 `<aop:after>` | 定義AOP後置通知(無論該方法是否執行成功) `<aop:after-returning>` | 在方法成功執行後調用通知 `<aop:after-throwing>` | 在方法拋出異常後調用通知 `<aop:around>` | 定義AOP環繞通知 `<aop:aspect>` | 定義切面 `<aop:aspect-autoproxy>` | 定義`@AspectJ`註解驅動的切面 `<aop:before>` | 定義AOP前置通知 `<aop:config>` | 頂層的AOP配置元素,大多數的<aop:*>包含在<aop:config>元素內 `<aop:declare-parent>` | 爲被通知的對象引入額外的接口,並透明的實現 `<aop:pointcut>` | 定義切點
具體使用切面的類,定義以下
package com.ganji.demo.service.aspect; import org.aspectj.lang.ProceedingJoinPoint; /** * Created by admin on 2015/9/2. */ public class XmlAopDemoUserLog { // 方法執行前通知 public void beforeLog() { System.out.println("開始執行前置通知 日誌記錄"); } // 方法執行完後通知 public void afterLog() { System.out.println("開始執行後置通知 日誌記錄"); } // 執行成功後通知 public void afterReturningLog() { System.out.println("方法成功執行後通知 日誌記錄"); } // 拋出異常後通知 public void afterThrowingLog() { System.out.println("方法拋出異常後執行通知 日誌記錄"); } // 環繞通知 public Object aroundLog(ProceedingJoinPoint joinpoint) { Object result = null; try { System.out.println("環繞通知開始 日誌記錄"); long start = System.currentTimeMillis(); //有返回參數 則需返回值 result = joinpoint.proceed(); long end = System.currentTimeMillis(); System.out.println("總共執行時長" + (end - start) + " 毫秒"); System.out.println("環繞通知結束 日誌記錄"); } catch (Throwable t) { System.out.println("出現錯誤"); } return result; } }
spring-aop.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans>
其中的參數跟切點表達式相關,後面說
對於使用切面的類(Interceptor)標上@Aspect,具體到某個方法,標上相應的註解
package com.ganji.demo.service.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Service; /** * Created by admin on 2015/9/2. */ @Aspect @Service public class XmlAopDemoUserLog { // 配置切點 及要傳的參數 @Pointcut("execution(* com.ganji.demo.service.user.UserService.GetDemoUser(..)) && args(id)") public void pointCut(int id) { } // 配置鏈接點 方法開始執行時通知 @Before("pointCut(id)") public void beforeLog(int id) { System.out.println("開始執行前置通知 日誌記錄:"+id); } // 方法執行完後通知 @After("pointCut(id)") public void afterLog(int id) { System.out.println("開始執行後置通知 日誌記錄:"+id); } // 執行成功後通知 @AfterReturning("pointCut(id)") public void afterReturningLog(int id) { System.out.println("方法成功執行後通知 日誌記錄:"+id); } // 拋出異常後通知 @AfterThrowing("pointCut(id)") public void afterThrowingLog(int id) { System.out.println("方法拋出異常後執行通知 日誌記錄"+id); } // 環繞通知 @Around("pointCut(id)") public Object aroundLog(ProceedingJoinPoint joinpoint,int id) { Object result = null; try { System.out.println("環繞通知開始 日誌記錄"+id); long start = System.currentTimeMillis(); //有返回參數 則需返回值 result = joinpoint.proceed(); long end = System.currentTimeMillis(); System.out.println("總共執行時長" + (end - start) + " 毫秒"); System.out.println("環繞通知結束 日誌記錄"); } catch (Throwable t) { System.out.println("出現錯誤"); } return result; } }
關於切點表達式,就是出如今配置中的execution後面的表達式,指示符固然不止execution一個
有如下9種切點指示符:execution、within、this、target、args、@target、@args、@within、@annotation
具體以下:
- execution
execution是一種使用頻率比較高比較主要的一種切點指示符,用來匹配方法簽名,方法簽名使用全限定名,包括訪問修飾符(public/private/protected)、返回類型,包名、類名、方法名、參數,其中返回類型,包名,類名,方法,參數是必須的,以下面代碼片斷所示:
@Pointcut("execution(public String org.baeldung.dao.FooDao.findById(Long))")上面的代碼片斷裏的表達式精確地匹配到FooDao類裏的findById(Long)方法,可是這看起來不是很靈活。假設咱們要匹配FooDao類的全部方法,這些方法可能會有不一樣的方法名,不一樣的返回值,不一樣的參數列表,爲了達到這種效果,咱們可使用通配符。以下代碼片斷所示:
@Pointcut("execution(* org.baeldung.dao.FooDao.*(..))")第一個通配符匹配全部返回值類型,第二個匹配這個類裏的全部方法,()括號表示參數列表,括號裏的用兩個點號表示匹配任意個參數,包括0個
- within
使用within切點批示符能夠達到上面例子同樣的效果,within用來限定鏈接點屬於某個肯定類型的類。以下面代碼的效果與上面的例子是同樣的:
@Pointcut("within(org.baeldung.dao.FooDao)")咱們也可使用within指示符來匹配某個包下面全部類的方法(包括子包下面的全部類方法),以下代碼所示:
@Pointcut("within(org.baeldung..*)")
- this 和 target
this用來匹配的鏈接點所屬的對象引用是某個特定類型的實例,target用來匹配的鏈接點所屬目標對象必須是指定類型的實例;那麼這兩個有什麼區別呢?原來AspectJ在實現代理時有兩種方式:
一、若是當前對象引用的類型沒有實現自接口時,spring aop使用生成一個基於CGLIB的代理類實現切面編程
二、若是當前對象引用實現了某個接口時,Spring aop使用JDK的動態代理機制來實現切面編程
this指示符就是用來匹配基於CGLIB的代理類,通俗的來說就是,若是當前要代理的類對象沒有實現某個接口的話,則使用this;target指示符用於基於JDK動態代理的代理類,通俗的來說就是若是當前要代理的目標對象有實現了某個接口的話,則使用target.:
public class FooDao implements BarDao { ... }好比在上面這段代碼示例中,spring aop將使用jdk的動態代理來實現切面編程,在編寫匹配這類型的目標對象的鏈接點表達式時要使用target指示符, 以下所示:
@Pointcut("target(org.baeldung.dao.BarDao)")若是FooDao類沒有實現任何接口,或者在spring aop配置屬性:proxyTargetClass設爲true時,Spring Aop會使用基於CGLIB的動態字節碼技爲目標對象生成一個子類將爲代理類,這時應該使用this指示器:
@Pointcut("this(org.baeldung.dao.FooDao)")
- 參數
參數指示符是一對括號所括的內容,用來匹配指定方法參數:
@Pointcut("execution(* *..find*(Long))")這個切點匹配全部以find開頭的方法,而且只一個Long類的參數。若是咱們想要匹配一個有任意個參數,可是第一個參數必須是Long類的,咱們這可以使用下面這個切點表達式:
@Pointcut("execution(* *..find*(Long,..))")
- @Target
這個指示器匹配指定鏈接點,這個鏈接點所屬的目標對象的類有一個指定的註解:
@Pointcut("@target(org.springframework.stereotype.Repository)")
- @args
這個指示符是用來匹配鏈接點的參數的,@args指出鏈接點在運行時傳過來的參數的類必需要有指定的註解,假設咱們但願切入全部在運行時接受實@Entity註解的bean對象的方法:
@Pointcut("@args(org.baeldung.aop.annotations.Entity)") public void methodsAcceptingEntities() {}爲了在切面裏接收並使用這個被@Entity的對象,咱們須要提供一個參數給切面通知:JointPoint:
@Before("methodsAcceptingEntities()") public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) { logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]); }
- @within
這個指示器,指定匹配必須包括某個註解的的類裏的全部鏈接點:
@Pointcut("@within(org.springframework.stereotype.Repository)")上面的切點跟如下這個切點是等效的:
@Pointcut("within(@org.springframework.stereotype.Repository *)")
- @annotation
這個指示器匹配那些有指定註解的鏈接點,好比,咱們能夠新建一個這樣的註解@Loggable:
@Pointcut("@annotation(org.baeldung.aop.annotations.Loggable)") public void loggableMethods() {}咱們可使用@Loggable註解標記哪些方法執行須要輸出日誌:
@Before("loggableMethods()") public void logMethod(JoinPoint jp) { String methodName = jp.getSignature().getName(); logger.info("Executing method: " + methodName); }
通常格式是切點指示符+(參數表達式)
舉個栗子
* org.baeldung.dao.FooDao.*(..) 全部返回值 FooDao類下的全部方法的(全部入參) * *..find*(Long,..) 全部返回值 全部以find開頭的方法,且該方法第一個參數是Long類型
參數表達式根據不一樣指示符,指示的略有不一樣,==後續要再補充下==
一句話叫作動態代理,具體的話須要瞭解代理、靜態代理、動態代理,java提供的代理;
以不打擾縱向流程,而後加日誌爲目標/例子;以現實生活中的代理爲對照,理解代理的原理和機制吧;
代理的核心實現機制,就是實現同一個接口(委託方和代理方)
靜態代碼舉例:
public interface IHello { /** * 業務方法 * @param str */ void sayHello(String str); } public class Hello implements IHello{ @Override public void sayHello(String str) { System.out.println("hello "+str); } } public class ProxyHello implements IHello{ private IHello hello; public ProxyHello(IHello hello) { super(); this.hello = hello; } @Override public void sayHello(String str) { Logger.start();//添加特定的方法 hello.sayHello(str); Logger.end(); } } public class Logger { public static void start(){ System.out.println(new Date()+ " say hello start..."); } public static void end(){ System.out.println(new Date()+ " say hello end"); } } public class Test { public static void main(String[] args) { IHello hello = new ProxyHello(new Hello());//若是咱們須要日誌功能,則使用代理類 //IHello hello = new Hello();//若是咱們不須要日誌功能則使用目標類 hello.sayHello("明天"); } }
感受動態代理就是爲了避免這麼麻煩,用反射實現的;
AOP應用場景:
Authentication 權限
Caching 緩存
Context passing 內容傳遞
Error handling 錯誤處理
Lazy loading 懶加載
Debugging 調試
logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準
Performance optimization 性能優化
Persistence 持久化
Resource pooling 資源池
Synchronization 同步
Transactions 事務
@AfterReturning(value = "execution(* com.shit.Class.user(..)",returning="retVal") public RetClass onChange(JointPoint jp,Object retVal){ RetClass retClass = (RetClass)retVal; ... }
public class appClass extends ClassApp { @Resource private RemoteProxy remoteProxy; @Override public RobotAnswerResult responseMessage(UserRequest request) { remoteProxy.getResponse(this, request); ... } @Component public static class RemoteProxy { public void getResponse(ClassApp classApp , UserRequest request) { } } }