標籤:簽名 res 列印 格式 nim src 添加 掃描 階段
1.AOP概念:Aspect Oriented Programming 面向切面編程 Aspect Oriented Programming(AOP)。AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。比如我們最常見的就是日誌記錄了,舉個例子,我們現在提供一個查詢學生資訊的服務,但是我們希望記錄有誰進行了這個查詢。 如果按照傳統的OOP的實現的話,那我們實現了一個查詢學生資訊的服務介面(StudentInfoService)和其實現 類 (StudentInfoServiceImpl.java)同時為了要進行記錄的話,那我們在實作類別(StudentInfoServiceImpl.java)中要添加其實現記錄的過程。這樣的話,假如我們要實現的服務有多個呢?那就要在每個實現的類都添加這些記錄過程。這樣做的話就會有點繁瑣,而且每個實作類別都與記錄服務日誌的行為緊耦合,違反了物件導向的規則【物件導向的設計準則:1、模組化 2、抽象 3、資訊隱藏 4、弱耦合 5、強內聚 6、可重用 】。那麼怎樣才能把記錄服務的行為與業務處理過程中分離出來呢?看起來好像就是查詢學生的服務自己在進行,但卻是背後日誌記錄對這些行為進行記錄,並且查詢學生的服務不知道存在這些記錄過程,這就是我們要討論AOP的目的所在。 AOP的編程,好像就是把我們在某個方面的功能提出來與一批對象進行隔離,這樣與一批對象之間降低了耦合性,可以就某個功能進行編程。 2.作用:本質上來說是一種簡化代碼的方式 繼承機制 封裝方法 動態代理 :BaseMath bean = (BaseMath) ioc.getBean("testmath"); ioc調用實作類別的ID去建立介面的對象,進而調用實作類別實現的方法 …… 3.情景舉例 ①數學計算機介面[MathCalculator] int add(int i,int j); int sub(int i,int j); int mul(int i, int j); int div(int i,int j); ②提供簡單實現[EasyImpl] ③在簡單實現的基礎上讓每一個計算方法都能夠列印日誌[LoginImpl] ④缺陷 [1]手動添加日誌繁瑣,重複 [2]統一修改不便 [3]對目標方法本來要實現的核心功能有幹擾,使程式碼很臃腫,不易於開發維護 ⑤使用動態代理實現 [1]建立一個類,讓這個類能夠提供一個目標對象的代理對象 [2]在代理對象中列印日誌 4.AOP術語![參見圖例和doc文檔] AOP概述 ●AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming,物件導向編程)的補充。 ●參見圖例和doc文檔解釋AOP的各個術語! ●Spring的AOP實現方法:①xml配置的方法 ②利用註解的方法 5.在Spring中使用AOP實現日誌功能 : 意思就是在每個函數在執行操作之前都要經過該方法,實現記錄執行資訊的功能
實現過程: ①Spring中可以使用註解或XML檔案配置的方式實現AOP。 ②匯入jar包 【導包】 com.springsource.net.sf.cglib -2.2.0.jar com.springsource.org.aopalliance-1.0.0 .jar com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar commons-logging-1.1.3. jar spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE. jar ③開啟基於註解的AOP功能 ,在此之前也要注意,需要在XML中插入能夠掃描包的代碼
<context:component-scan base-package="com.neuedu.aop.mathtest"/> <!-- 掃描包的代碼 --> <aop:aspectj-autoproxy/> <!-- 開啟註解aop功能 -->
④聲明一個切面類,並把這個切面類加入到IOC容器中
@Aspect//表示這是一個切面類 @Component//加入IOC容器 public class LogAspect {}
⑤在切面類中聲明通知方法 [1]前置通知:@Before [2]返回通知:@AfterReturning [3]異常通知:@AfterThrowing [4]後置通知:@After [5]環繞通知:@Around :環繞通知是前面四個通知的集合體!
@Aspect//表示這是一個切面類 @Component//將本類對象加入到IOC容器中! public class LogAspect { @Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") public void showBeginLog(){ System.out.println("AOP日誌開始"); } @After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") public void showReturnLog(){ System.out.println("AOP方法返回"); } @AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") public void showExceptionLog(){ System.out.println("AOP方法異常"); } @AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") public void showAfterLog(){ System.out.println("AOP方法結束"); } }
⑥被代理的對象也需要加入IOC容器
@Component//加入IOC容器 public class MathCalculatorImpl { public int add(int i,int j){ int result = i+j; return result; } public int sub(int i,int j){ int result = i-j; return result; } public int multi(int i,int j){ int result = i*j; return result; } public int divide(int i,int j){ int result = i/j; return result; } }
6.切入點運算式: 1.上述案例通過junit測試,會發現,我們調用目標類的四個方法只有add方法被加入了4個通知,如果想所有的方法都加上這些通知,可以 在切入點運算式處,將execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 換成: execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))這樣只要是有兩個參數,且 參數類型為int的方法在執行的時候都會執行其相應的通知方法! 2.①切入點運算式的文法格式
execution([許可權修飾符] [傳回值類型] [簡單類名/全類名] [方法名]([參數列表])) 1.任意參數,任意類型 2.任意傳回值 3.用@PointCut註解統一聲明,然後在其它通知中引用該統一聲明即可! 需要注意的是:許可權是不支援寫萬用字元的,當然你可以寫一個*表示所有許可權所有傳回值!
最詳細的切入點運算式: execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入點運算式: execution (* *.*(..)) 7.統一聲明切入點運算式
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))") public void myPointCut(){} //調用上面的函數,就可以代替重複的很多代碼@AfterReturning (value="myPointCut()", returning= "result")
8.通知方法的細節 ①在通知中擷取目標方法的方法名和參數列表 [1]在通知方法中聲明一個JoinPoint類型的形參 [2]調用JoinPoint對象的getSignature()方法擷取目標方法的簽名 [3]調用JoinPoint對象的getArgs()方法擷取目標方法的實際參數列表 ②在返回通知中擷取方法的傳回值 [1]在@AfterReturning註解中添加returning屬性
@AfterReturning (value="myPointCut()", returning= "result")
[2]在返回通知的通知方法中聲明一個形參,形參名和returning屬性的值一致
showReturnLog(JoinPoint joinPoint, Object result)
③在異常通知中擷取異常對象 [1]在@ AfterThrowing註解中添加throwing屬性
@AfterThrowing (value="myPointCut()",throwing= "throwable" )
[2]在異常通知的通知方法中聲明一個形參,形參名和throwing屬性值一致
showExceptinLog(JoinPoint joinPoint, Throwable throwable)
9.根據介面類型擷取target對象時,實際上真正放在IOC容器中的對象是代理對象,而並不是目標對象本身! 10.環繞通知:@Around 1.環繞通知需要在方法的參數中指定JoinPoint的子介面類型ProceedingJoinPoint為參數
@Around(value="pointCut()") public void around(ProceedingJoinPoint joinPoint){ }
2.環繞通知會將其他4個通知能乾的,自己都給幹了! 注意:@Around修飾的方法一定要將方法的傳回值返回!本身相當於代理!
@Around(value="pointCut()") public Object around(ProceedingJoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); List<Object> list = Arrays.asList(args); Object result = null; try { //目標方法之前要執行的操作 System.out.println("[環繞日誌]"+methodName+"開始了,參數為:"+list); //調用目標方法 result = joinPoint.proceed(args); //目標方法正常執行之後的操作 System.out.println("[環繞日誌]"+methodName+"返回了,傳回值為:"+result); } catch (Throwable e) { //目標方法拋出異常資訊之後的操作 System.out.println("[環繞日誌]"+methodName+"出異常了,異常對象為:"+e); throw new RuntimeException(e.getMessage()); }finally{ //方法最終結束時執行的操作! System.out.println("[環繞日誌]"+methodName+"結束了!"); } return result; }
11.切面的優先順序 對於同一個代理對象,可以同時有多個切面共同對它進行代理。 可以在切面類上通過@Order (value=50)註解來進行設定,值越小優先順序越高!
@Aspect @Component @Order(value=40) public class TxAspect { @Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))") public Object around(ProceedingJoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); List<Object> list = Arrays.asList(args); Object result = null; try { //目標方法之前要執行的操作 System.out.println("[交易記錄]"+methodName+"開始了,參數為:"+list); //調用目標方法 result = joinPoint.proceed(args); //目標方法正常執行之後的操作 System.out.println("[交易記錄]"+methodName+"返回了,傳回值為:"+result); } catch (Throwable e) { //目標方法拋出異常資訊之後的操作 System.out.println("[交易記錄]"+methodName+"出異常了,異常對象為:"+e); throw new RuntimeException(e.getMessage()); }finally{ //方法最終結束時執行的操作! System.out.println("[交易記錄]"+methodName+"結束了!"); } return result; } }
12.注意:上面的AOP都是通過註解實現的,AOP實際上也可以通過xml配置的方式實現!
<!-- 1.將需要載入到IOC容器中的bean配置好 --> <bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean> <bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean> <bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean> <!-- 2.配置AOP,需要匯入AOP名稱空間 --> <aop:config> <!-- 聲明切入點運算式 --> <aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/> <!-- 配置日誌切面類,引用前面的類 ,通過order屬性控制優先順序--> <aop:aspect ref="logAspect" order="25"> <!-- 通過method屬性指定切面類的切面方法,通過pointcut-ref指定切入點運算式 --> <aop:before method="showBeginLog" pointcut-ref="myPointCut"/> <aop:after method="showAfterLog" pointcut-ref="myPointCut"/> <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/> <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/> <aop:around method="around" pointcut-ref="myPointCut"/> </aop:aspect> <!-- 配置事務切面類,引用前面的類 --> <aop:aspect ref="txAspect" order="20"> <aop:around method="around" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config>
需要知道的是:事務的管理是和AOP是有很大關係的,即聲明式事務的底層是用事務實現的!
AOP--Aspect Oriented Programming