Spring(三)--AOP【面向切面編程】、通知類型及使用、切入點表達式
繼承機制
封裝方法
動態代理
……
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); 因為後面的通知方法需要返回值,所以在這裏類型聲明為 int 類型
public interface MathCaculator { public int add(int i,int j); public int sub(int i,int j); public int mul(int i,int j); public int div(int i,int j); }
@Component public class CacultorEasyImpl implements MathCaculator{ @Override public void add(int i, int j) { System.out.println("[日誌],【參數:】"+i+","+j); int result = i + j; System.out.println("[日誌],【參數:】"+i+","+j+"--"+result); } @Override public void sub(int i, int j) { System.out.println("[日誌],【參數:】"+i+","+j); int result = i - j; System.out.println("[日誌],【參數:】"+i+","+j+"--"+result); } @Override public void mul(int i, int j) { System.out.println("[日誌],【參數:】"+i+","+j); int result = i * j; System.out.println("[日誌],【參數:】"+i+","+j+"--"+result); } @Override public void div(int i, int j) { System.out.println("[日誌],【參數:】"+i+","+j); int result = i / j; System.out.println("[日誌],【參數:】"+i+","+j+"--"+result); } }
<context:component-scan base-package="com.neuedu.aop"></context:component-scan>
③在簡單實現的基礎上讓每一個計算方法都能夠打印日誌[LoginImpl]
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() { CacultorEasyImpl bean = ioc.getBean(CacultorEasyImpl.class); bean.add(10, 2); bean.sub(10, 2); bean.mul(10, 2); bean.div(10, 2); }
④缺陷
[1]手動添加日誌繁瑣,重復
[2]統一修改不便
[3]對目標方法本來要實現的核心功能有幹擾,使程序代碼很臃腫,不易於開發維護
⑤使用動態代理實現
[1]創建一個類,讓這個類能夠提供一個目標對象的代理對象
[2]在代理對象中打印日誌
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功能 < aop:aspectj-autoproxy />
<context:component-scan base-package="com.neuedu.aop"></context:component-scan> <aop:aspectj-autoproxy/>
④聲明一個切面類,並把這個切面類加入到 IOC容器中
在類上加以下兩個註解
@Aspect :表示這是一個切面類
@Component :加入IOC容器
⑤在切面類中聲明通知方法
[1] 前置通知:@Before
[2] 返回通知:@AfterReturning
[3] 異常通知:@AfterThrowing
[4] 後置通知:@After
[5] 環繞通知:@Around :環繞通知是前面四個通知的集合體!
@Component @Aspect public class CaculatorAspect { @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showBeginLog(){ System.out.println("日誌開始"); } @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showReturnLog(){ System.out.println("日誌正常返回"); } @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showExceptionLog(){ System.out.println("日誌有錯"); } @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showAfterLog(){ System.out.println("日誌最終結束"); } }
⑥被代理的對象也需要加入IOC容器
@Component public class RawCaculatorImpl implements MathCaculator{ @Override public void add(int i, int j) { int result = i + j; System.out.println(i+"+"+j+"="+result); } @Override public void sub(int i, int j) { int result = i - j; System.out.println(i+"-"+j+"="+result); } @Override public void mul(int i, int j) { int result = i * j; System.out.println(i+"*"+j+"="+result); } @Override public void div(int i, int j) { int result = i / j; System.out.println(i+"/"+j+"="+result); } }
Test 中 用 id 查找,通過強轉,調用加減乘除四個方法
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() { MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculatorImpl"); bean.add(10, 2); bean.sub(10, 2); bean.mul(10, 2); bean.div(10, 2); }
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的方法在執行的時候都會執行其相應的通知方法!
@Component @Aspect public class CaculatorAspect { @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showBeginLog(){ System.out.println("日誌開始"); } @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showReturnLog(){ System.out.println("日誌正常返回"); } @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showExceptionLog(){ System.out.println("日誌有錯"); } @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showAfterLog(){ System.out.println("日誌最終結束"); } }
如果方法中的參數類型不一致,可以用 (..) 代替 (int,int)
@Component @Aspect public class CaculatorAspect { @Pointcut(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))") public void showLog(){} @Before(value="showLog()") public void showBeginLog(JoinPoint point){ Object[] args = point.getArgs();//獲取參數 List<Object> asList = Arrays.asList(args);//轉為list類型 Signature signature = point.getSignature();//獲取簽名 String name = signature.getName();//獲取方法名字 System.out.println("【前置通知】目標方法名:"+name+",參數為:"+asList); } @After(value="showLog()") public void showReturnLog(){ System.out.println("【後置通知】日誌最終返回"); } @AfterThrowing(value="showLog()",throwing="ex") public void showExceptionLog(JoinPoint point,Exception ex){ System.out.println("【異常通知】異常信息為:"+ex.getMessage()); } @AfterReturning(value="showLog()",returning="result") public void showAfterLog(JoinPoint point,Object result){ System.out.println("【返回通知】方法的返回值:"+result); System.out.println(); } }
(2)①切入點表達式的語法格式[參見第5章AOP細節] execution([權限修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]))
參見第5章AOP細節:演示驗證
1.任意參數,任意類型
2.任意返回值
3.用@PointCut註解統一聲明,然後在其它通知中引用該統一聲明即可!
需要註意的是:權限是不支持寫通配符的,當然你可以寫一個*表示所有權限所有返回值!
最詳細的切入點表達式:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入點表達式: execution (* *.*(..)) 第一個 * 代表 權限和返回類型 但是不可以 無權限卻有返回類型 例如: execution(* int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 7.統一聲明切入點表達式
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
public void myPointCut(){}
8.通知方法的細節
①在通知中獲取目標方法的方法名和參數列表 [1]在通知方法中聲明一個JoinPoint類型的形參 JointPoint 是切入點, [2]調用JoinPoint對象的getSignature()方法獲取目標方法的簽名
[3]調用JoinPoint對象的getArgs()方法獲取目標方法的實際參數列表
@Before(value="showLog()") public void showBeginLog(JoinPoint point){ Object[] args = point.getArgs();//獲取參數 List<Object> asList = Arrays.asList(args);//轉為list類型 Signature signature = point.getSignature();//獲取簽名 String name = signature.getName();//獲取方法名字 System.out.println("目標方法名:"+name+",參數為:"+asList); System.out.println("日誌開始"); }②在返回通知中獲取方法的返回值 [1]在 @AfterReturning 註解中添加returning屬性 [2]在返回通知的通知方法中聲明一個形參,形參名和returning屬性的值一致 不知道返回什麽類型,所以用 Object 類型
@AfterReturning(value="showLog()",returning="result") public void showAfterLog(JoinPoint point,Object result){ System.out.println("方法的返回值:"+result); System.out.println("日誌正常結束"); System.out.println(); }③在異常通知中獲取異常對象 [1]在 @AfterThrowing 註解中添加 throwing 屬性 [2]在異常通知的通知方法中聲明一個形參,形參名和throwing屬性值一致
@AfterThrowing(value="showLog()",throwing="ex") public void showExceptionLog(JoinPoint point,Exception ex){ System.out.println("異常信息為:"+ex.getMessage()); System.out.println("日誌有錯"); }
9.根據接口類型獲取target對象時,實際上真正放在IOC容器中的對象是代理對象,而並不是目標對象本身!
10.環繞通知:@Around
1.環繞通知需要在方法的參數中指定JoinPoint的子接口類型ProceedingJoinPoint為參數
@Around(value="pointCut()")
public void around(ProceedingJoinPoint joinPoint){
}
2.環繞通知會將其他4個通知能幹的,自己都給幹了! 註意:@Around修飾的方法一定要將方法的返回值返回!本身相當於代理! 但是不知道返回值的類型 ,所以用 Object
@Around(value="execution(public * com.neuedu.aop.RawCaculatorImpl.*(..))") public Object showLog(ProceedingJoinPoint point){ Object[] args = point.getArgs(); List<Object> asList = Arrays.asList(args); Signature signature = point.getSignature();//獲取簽名 String name = signature.getName(); Object result=null; try { try{ System.out.println("【前置通知】目標方法名:"+name+",參數為:"+asList); result = point.proceed(args); }finally{ System.out.println("【後置通知】日誌最終返回"); } System.out.println("【返回通知】方法的返回值:"+result); } catch (Throwable e) { System.out.println("【異常通知】異常信息為:"+e.getMessage()); } System.out.println(); return result; }11.切面的優先級
對於同一個代理對象,可以同時有多個切面共同對它進行代理。 可以在切面類上通過@Order (value=50)註解來進行設置,值越小優先級越高! 沒有 @Order 註解默認最大
@Component @Aspect @Order(value=20) public class BAspect { @Around(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))") public Object showLog(ProceedingJoinPoint point){ Object[] args = point.getArgs(); List<Object> asList = Arrays.asList(args); Signature signature = point.getSignature();//獲取簽名 String name = signature.getName(); Object result=null; try { try{ System.out.println("【前置】目標方法名:"+name+",參數為:"+asList); result = point.proceed(args); }finally{ System.out.println("【後置】日誌最終返回"); } System.out.println("【返回】方法的返回值:"+result); } catch (Throwable e) { System.out.println("【異常】異常信息為:"+e.getMessage()); } System.out.println(); return result; } }
12.註意:上面的AOP都是通過註解實現的,AOP實際上也可以通過xml配置的方式實現! 將註解全刪掉
<!-- 將需要加載到IOC容器中的bean配置好 --> <bean id="caculatorAspect" class="com.neuedu.aop.CaculatorAspect"></bean> <bean id="bAspect" class="com.neuedu.aop.BAspect"></bean> <bean id="rawCaculatorImpl" class="com.neuedu.aop.RawCaculatorImpl"></bean> <!-- 配置AOP,需要導入AOP名稱空間 --> <aop:config> <!-- 聲明切入點表達式 --> <aop:pointcut expression="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))" id="myPointCut"/> <!-- 配置日誌切面類,引用前面的類 ,通過order屬性控制優先級--> <aop:aspect ref="caculatorAspect" order="20"> <!-- 通過method屬性指定切面類的切面方法,通過pointcut-ref指定切入點表達式 --> <aop:before method="showBeginLog" pointcut-ref="myPointCut"/> <aop:after method="showAfterLog" pointcut-ref="myPointCut"/> <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/> <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/> </aop:aspect> <!-- 配置事務切面類,引用前面的類 --> <aop:aspect ref="bAspect" order="25"> <aop:around method="showLog" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config>
需要知道的是:事務的管理是和AOP是有很大關系的,即聲明式事務的底層是用AOP實現的!
Spring(三)--AOP【面向切面編程】、通知類型及使用、切入點表達式