Spring學習4-面向切面(AOP)之Spring接口方式


一、初識AOP

   關於AOP的學習可以參看幫助文檔:spring-3.2.0.M2\docs\reference\html目錄下index.html的相關章節   
   1、AOP:Aspect-Oriented Programming。AOP是OOP的補充,是GOF的延續。說到AOP,我們就不得不來提一下軟件的縱向和橫向問題。從縱向結構來看 就是我們軟件系統的各個模塊,它主要負責處理我們的核心業務(例如商品訂購、購物車查看);而從橫向結構來看,我們幾乎每個系統又包含一些公共模塊(例如 權限、日志模塊等)。這些公共模塊分布於我們各個核心業務之中(例如訂購和查看商品明細的過程都需要檢查用戶權限、記錄系統日志等)。這樣一來不僅在開發 過程中要處處關注公共模塊的處理而且開發后維護起來也是十分麻煩。而有了AOP之后將應用程序中的商業邏輯同對其提供支持的通用服務進行分離,使得開發人 員可以更多的關注核心業務開發。 Spring學習4-面向切面(AOP)之Spring接口方式
  2、AOP術語
 

    切面(aspect):用來切插業務方法的類。
  連接點(joinpoint):是切面類和業務類的連接點,其實就是封裝了業務方法的一些基本屬性,作為通知的參數來解析。
  通知(advice):在切面類中,聲明對業務方法做額外處理的方法。
  切入點(pointcut):業務類中指定的方法,作為切面切入的點。其實就是指定某個方法作為切面切的地方。
  目標對象(target object):被代理對象。
  AOP代理(aop proxy):代理對象。
  通知:
  前置通知(before advice):在切入點之前執行。
  后置通知(after returning advice):在切入點執行完成后,執行通知。
  環繞通知(around advice):包圍切入點,調用方法前后完成自定義行為。
  異常通知(after throwing advice):在切入點拋出異常后,執行通知。
   AOP是基於代理模式,了解了jdk動態代理和cglib的用法,對我們學習大有裨益。

Spring學習4-面向切面(AOP)之Spring接口方式

Spring學習4-面向切面(AOP)之Spring接口方式

Spring學習4-面向切面(AOP)之Spring接口方式

Spring學習4-面向切面(AOP)之Spring接口方式

Spring學習4-面向切面(AOP)之Spring接口方式

二、Spring AOP環境
  要在項目中使用Spring AOP 則需要在項目中導入除了spring jar包之外,還有aspectjrt.jar,aspectjweaver.jar,aopalliance.jar ,spring-aop-3.2.0.M2.jar和cglib.jar 。
    好了,前提工作准備完成,Spring 提供了很多的實現AOP的方式:Spring 接口方式,schema配置方式和注解等,好了廢話不多說了,開始spring aop學習之旅,這篇先以Spring接口的方式學起!

三、Spring接口方式實現AOP步驟
 
   利用Spring AOP接口實現AOP,主要是為了指定自定義通知來供spring AOP機制識別。主要接口:前置通知 MethodBeforeAdvice ,后置通知:AfterReturningAdvice,環繞通知:MethodInterceptor,異常通知:ThrowsAdvice 。見例子代碼:
 
步驟一、業務接口的編寫
 // 代理類接口,也是業務類接口<br>
 // 利用接口的方式,spring aop 將默認通過jdk 動態代理來實現代理類<br>
 // 不利用接口,則spring aop 將通過cglib 來實現代理類

public interface IBaseBusiness {

   // 用作代理的切入點方法
      public String delete(String obj)

 
    // 這方法不被切面切
     public String add(String obj);
  
    // 這方法切不切呢?可以設置
    public String modify(String obj);
}

步驟二、業務類:

 //業務類,也是目標對象

public class BaseBusiness implements IBaseBusiness {
    //切入點
      public String delete(String obj) {
        System.out.println("==========調用切入點:" + obj + "說:你敢刪除我!===========\n");
        return obj + ":瞄~";
    }

    public String add(String obj) {
        System.out.println("================這個方法不能被切。。。============== \n");
        return obj + ":瞄~ 嘿嘿!";
    }

    public String modify(String obj) {
        System.out.println("=================這個也設置加入切吧====================\n");
        return obj + ":瞄改瞄啊!";
    }

}

步驟三、通知類:
    1、前置通知:

public class BaseBeforeAdvice implements MethodBeforeAdvice {

     // method : 切入的方法 <br>
     //args :切入方法的參數 <br>
     // target :目標對象
   
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("===========進入beforeAdvice()============ \n");

        System.out.print("准備在" + target + "對象上用");
        System.out.print(method + "方法進行對 '");
        System.out.print(args[0] + "'進行刪除!\n\n");

        System.out.println("要進入切入點方法了 \n");
    }

}

 2、后置通知:

public class BaseAfterReturnAdvice implements AfterReturningAdvice {
     //returnValue :切入點執行完方法的返回值,但不能修改 <br>
     // method :切入點方法 <br>
    // args :切入點方法的參數數組 <br>
     // target :目標對象
    
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("==========進入afterReturning()=========== \n");
        System.out.println("切入點方法執行完了 \n");

        System.out.print(args[0] + "在");
        System.out.print(target + "對象上被");
        System.out.print(method + "方法刪除了");
        System.out.print("只留下:" + returnValue + "\n\n");
    }

}

  3、環繞通知:

public class BaseAroundAdvice implements MethodInterceptor {

     // invocation :連接點
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("===========進入around環繞方法!=========== \n");

        // 調用目標方法之前執行的動作
        System.out.println("調用方法之前: 執行!\n");

        // 調用方法的參數
        Object[] args = invocation.getArguments();
        // 調用的方法
        Method method = invocation.getMethod();
        // 獲取目標對象
        Object target = invocation.getThis();
        // 執行完方法的返回值:調用proceed()方法,就會觸發切入點方法執行
        Object returnValue = invocation.proceed();

        System.out.println("===========結束進入around環繞方法!=========== \n");
      
        System.out.println("輸出:" + args[0] + ";" + method + ";" + target + ";" + returnValue + "\n");

        System.out.println("調用方法結束:之后執行!\n");

        return returnValue;
    }

}

 4、異常通知:


// 異常通知,接口沒有包含任何方法。通知方法自定義

public class BaseAfterThrowsAdvice implements ThrowsAdvice {
     // 通知方法,需要按照這種格式書寫
     // @param method
     //           可選:切入的方法
     //@param args
     //           可選:切入的方法的參數
    // @param target
     //          可選:目標對象
     // @param throwable
     //  必填 : 異常子類,出現這個異常類的子類,則會進入這個通知。
   
    public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
        System.out.println("刪除出錯啦");
    }

}

步驟四、定義指定切點:

 // 定義一個切點,指定對應方法匹配。來供切面來針對方法進行處理<br>
 // 繼承NameMatchMethodPointcut類,來用方法名匹配

public class Pointcut extends NameMatchMethodPointcut {

    private static final long serialVersionUID = 3990456017285944475L;

    @SuppressWarnings("rawtypes")
    @Override
    public boolean matches(Method method, Class targetClass) {
        // 設置單個方法匹配
        this.setMappedName("delete");
        // 設置多個方法匹配
        String[] methods = { "delete", "modify" };
      
        //也可以用“ * ” 來做匹配符號
        // this.setMappedName("get*");
      
        this.setMappedNames(methods);

        return super.matches(method, targetClass);
    }

}

步驟五、配置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:p="http://www.springframework.org/schema/p"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="   
          http://www.springframework.org/schema/beans   
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           <!-- 基於注解導入的 -->        
          http://www.springframework.org/schema/context   
          http://www.springframework.org/schema/context/spring-context-3.0.xsd
        <!-- 基於aop導入的 -->
         http://www.springframework.org/schema/aop   
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
    default->

    <!-- ==============================利用spring自己的aop配置================================ -->
    <!-- 聲明一個業務類 -->
    <bean id="baseBusiness" class="aop.base.BaseBusiness" />
  
    <!-- 聲明通知類 -->
    <bean id="baseBefore" class="aop.base.advice.BaseBeforeAdvice" />
    <bean id="baseAfterReturn" class="aop.base.advice.BaseAfterReturnAdvice" />
    <bean id="baseAfterThrows" class="aop.base.advice.BaseAfterThrowsAdvice" />
    <bean id="baseAround" class="aop.base.advice.BaseAroundAdvice" />

    <!-- 指定切點匹配類 -->
    <bean id="pointcut" class="aop.base.pointcut.Pointcut" />

    <!-- 包裝通知,指定切點 -->
    <bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <ref bean="pointcut" />
        </property>
        <property name="advice">
            <ref bean="baseBefore" />
        </property>
    </bean>

    <!-- 使用ProxyFactoryBean 產生代理對象 -->
    <bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理對象所實現的接口 ,如果有接口可以這樣設置 -->
        <property name="proxyInterfaces">
            <value>aop.base.IBaseBusiness</value>
        </property>

        <!-- 設置目標對象 -->
        <property name="target">
            <ref local="baseBusiness" />
        </property>
        <!-- 代理對象所使用的攔截器 -->
        <property name="interceptorNames">
            <list>
                <value>matchBeforeAdvisor</value>
                <value>baseAfterReturn</value>
                <value>baseAround</value>
            </list>
        </property>
    </bean>
</beans>

步驟六、測試類:

public class Debug {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/schema_aop.xml");
        AspectBusiness business = (AspectBusiness) context.getBean("aspectBusiness");
        business.delete("貓");
    }

}

g、測試結果:運行下測試類,清晰明了。由於結果呈現太長就不貼了。
  具體的代碼實現可以從代碼注釋中很容易理解 接口方式的實現。結果也可想而知,前置方法會在切入點方法之前執行,后置會在切入點方法執行之后執行,環繞則會在切入點方法執行前執行同事方法結束也會執行對應的部分。主要是調用proceed()方法來執行切入點方法。來作為環繞通知前后方法的分水嶺。然后在實現的過程中,有幾點卻是可以細揣摩一下的。
  可以看出在xml 配置 businessProxy這個bean的時候,ProxyFactoryBean類中指定了,proxyInterfaces參數。這里我把他配置了 IBaseBusiness接口。因為在項目開發過程中,往往業務類都會有對應的接口,以方便利用IOC解耦。但Spring AOP卻也能支持沒有接口的代理。這就是為什么需要導入cglib.jar的包了。看過spring的源碼,知道在目標切入對象如果有實現接口,spring會默認使用jdk動態代理來實現代理類。如果沒有接口,則會通過cglib來實現代理類。
  這個業務類現在有 前置通知,后置通知,環繞三個通知同時作用,可能以及更多的通知進行作用。那么這些通知的執行順序是怎么樣的?就這個例子而言,同時實現了三個通知。在例 子xml中,則顯示執行before通知,然后執行around的前處理,執行切點方法,再執行return處理。最后執行around的后處理。經過測 試,知道spring 處理順序是按照xml配置順序依次處理通知,以隊列的方式存放前通知,以壓棧的方式存放后通知。所以是前通知依次執行,后通知到切入點執行完之后,從棧里 在后進先出的形式把后通知執行。
  在實現過程中發現通知執行對應目標對象的整個類中的方法,如何精確到某個方法,則需要定義一個切點匹配的方式:spring提供了方法名匹配或正則方式來匹配。然后通過DefaultPointcutAdvisor來包裝通知,指定切點。

 
 利用方式一的配置起來,可見代碼還是非常的厚重的,定義一個切面就要定義一個切面類,然而切面類中,就一個通知方法,着實沒有必要。所以Spring提 供了,依賴aspectj的schema配置和基於aspectj 注解方式。這兩種方式非常簡介方便使用,也是項目中普遍的使用方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM