1. 程式人生 > >Spring(三)--AOP【面向切面編程】、通知類型及使用、切入點表達式

Spring(三)--AOP【面向切面編程】、通知類型及使用、切入點表達式

1.3 一聲 重復 信息 術語 臃腫 lib pac fin

1.概念:Aspect Oriented Programming 面向切面編程 在方法的前後添加方法 2.作用:本質上來說是一種簡化代碼的方式
繼承機制
封裝方法
動態代理
……

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);
}
   ②提供簡單實現[EasyImpl]    一定要在類上寫註解,否則在 IOC容器中找不到
@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]在代理對象中打印日誌

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功能 < 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【面向切面編程】、通知類型及使用、切入點表達式