Spring AOP中的前置通知和後置通知詳解

阿木俠發表於2017-05-19

不同版本的spring對AOP的支援有所不同,spring2.0之前,它主要針對不同型別的攔截器使用XML配置檔案通過代理來實現。而spring2.0之後,它可以使用JDK5的註解來完成AOP的實現,只是幾個簡單標籤就可以完成,使得開發更加簡單,便捷。所以推薦使用後一種方法。但是很多舊的專案中使用了前一種實現方法,所以我們也應該對第一種方法有所掌握。


首先通過程式碼介紹spring2.0之前如何實現前後置通知

定義一個User介面:

package org.spring.advice;
 
publicinterface User {
   publicvoid normalUser();
   publicvoid vip();
   publicvoid vvip();
  
}


寫一個類去實現這個介面:

package org.spring.advice;
 
publicclass UserImpl implements User{
 
   publicvoid normalUser() {
      System.out.println("普通使用者訪問資源");
   }
 
   publicvoid vip() {
      System.out.println("VIP使用者訪問資源");
   }
 
   publicvoid vvip() {
      System.out.println("超級VIP使用者訪問資源");
   }
 
}


這裡模擬網站的三種資源針對不同的使用者,不同角色使用者在訪問之前就要進行驗證,這個驗證用前置通知模擬,使用者訪問後反饋給後臺提示資訊,這個訪問後提示用後置通知模擬

下面實現前置通知類,執行業務方法之前所要進行的操作都可以放在前置方法中進行處理

package org.spring.advice;
 
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
publicclass AdviceBeforeOfUser implements MethodBeforeAdvice{
 
   publicvoid before(Method method, Object[] obj1, Object obj2)throws Throwable {
      System.out.println("-----------------------");
      System.out.println("驗證使用者是否有訪問此資源的許可權...");
   }
}


前置通知類實現MethodBeforeAdvice介面,覆蓋其before()方法。

接著實現後置通知類,業務方法執行完之後想要返回什麼資訊,如日誌等,可以放在後置方法中

package org.spring.advice;
 
import java.lang.reflect.Method;
 
import org.springframework.aop.AfterReturningAdvice;
publicclass AdviceAfterOfUser implements AfterReturningAdvice{
 
   publicvoid afterReturning(Object arg0, Method arg1, Object[] arg2,
         Object arg3) throws Throwable {
      System.out.println("通知資訊:該使用者成功訪問");
   }
}
 


接下來,最重要的就是配置檔案,所有的連線操作都通過配置檔案來完成。

<?xmlversion="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"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  
   <!-- 註冊前置通知類 -->
   <beanid="beforeAdvice"class="org.spring.advice.AdviceBeforeOfUser"/>
   <!-- 註冊後置通知類 -->
   <beanid="afterAdvice"class="org.spring.advice.AdviceAfterOfUser"/>
   <beanid="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">
      <propertyname="proxyInterfaces"value="org.spring.advice.User"/>
      <propertyname="target"ref="userImpl"></property>
      <propertyname="interceptorNames">
         <list>
            <value>beforeAdvice</value>
            <value>afterAdvice</value>
         </list>
      </property>
   </bean>
   <beanid="userImpl"class="org.spring.advice.UserImpl"></bean>
</beans>
 


解釋一下下面這段配置檔案中的資訊:

<bean id="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">

      <propertyname="proxyInterfaces"value="org.spring.advice.User"/>

      <propertyname="target"ref="userImpl"></property>

      <propertyname="interceptorNames">

         <list>

            <value>beforeAdvice</value>

            <value>afterAdvice</value>

         </list>

      </property>

   </bean>


這是一個代理Bean,這個代理Bean指定了應用的介面(<propertyname="proxyInterfaces" value="org.spring.advice.User" />)

指明瞭操作的目標物件(<propertyname="target" ref="userImpl"></property>)

宣告瞭攔截器名稱(<propertyname="interceptorNames">)

interceptorNames這個攔截器實現了對這個方法的攔截攔截後就可以實現在這個方法前後加自定義的切面模組功能。攔截器在AOP框架中起著核心功能


最後編寫測試類:

package org.spring.advice;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
publicclass Test {
   publicstatic void main(String[] args) {
      ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      User user = (User)ac.getBean("proxy");
      user.normalUser();
      user.vip();
      user.vvip();
   }
}
 


執行結果:

我們可以看到,已經成功實現了,使用spring的AOP代理,我們不需要為業務類每個都去寫驗證方法,而是統一實現,如果不想要後置通知或不想要前置通知,直接在配置檔案中去掉value值即可,不需要更改程式碼,非常方便。


而spring2.0之後,提供Annotation設定AOP的通知,更加簡化了AOP的實現,還是用之前的例子,來對比看看spring2.0之後AOP的通知有多簡便。

 

介面和實現類不變,前置通知類的實現:

package org.spring.advice2;
 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
publicclass AdviceBeforeOfUser{
   @Before("execution(* org.spring.advice2.UserImpl.*(..))")
   publicvoid before(){
      System.out.println("-----------------------");
      System.out.println("驗證使用者是否有訪問此資源的許可權...");
   }
}



後置通知類的實現:
 

package org.spring.advice2;
 
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
 
@Aspect
publicclass AdviceAfterOfUser{
   @AfterReturning("execution(* org.spring.advice2.UserImpl.*(..))")
   publicvoid afterReturning(){
      System.out.println("通知資訊:該使用者成功訪問");
   }
}


@Aspect標籤宣告該類是一個Aspect(切面)類

@Before,@AfterReturning分別宣告該方法是一個前置通知和後置通知

"execution(* org.spring.advice2.UserImpl.*(..))" 這是一個正規表示式,這裡表明對UserImpl類中的所有方法進行前置後置通知,也可以具體到某個方法進行通知,修改該表示式即可。

這裡需要注意:在匹配正規表示式時,“*”和後面的介面之間要有一個空格,否則會報錯:Pointcut is notwell-formed


配置檔案:

<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"
   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/aop   
    http://www.springframework.org/schema/aop/spring-aop.xsd">
   
   <aop:aspectj-autoproxy/>
   <!-- 註冊前置通知類 -->
   <beanid="beforeAdvice"class="org.spring.advice2.AdviceBeforeOfUser"/>
   <!-- 註冊後置通知類 -->
   <beanid="afterAdvice"class="org.spring.advice2.AdviceAfterOfUser"/>
  
   <beanid="userImpl"class="org.spring.advice2.UserImpl"></bean>
</beans>

我們可以看到,配置檔案大大簡化了,僅僅加入一句<aop:aspectj-autoproxy/>   表示自動進行代理,spring替我們管理一切事務。 

測試方法基本不變,只是沒人寫代理類,直接獲取介面實現類的id即可,

User user = (User)ac.getBean("userImpl");


執行結果不變,很快捷的就實現了前置和後置通知。


注意:這裡配置檔案頭和前面有所區別,要引進

xmlns:aop=http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop    

http://www.springframework.org/schema/aop/spring-aop.xsd"

 

前置通知,後置通知的實現是spring封裝了Java的代理類,那麼他的原理是什麼呢?我們可以拋開spring,用Java來模擬他的實現流程。

首先,介面和實現類依然不變,然後自己定義一個代理類

package org.spring.proxyaop;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
publicclass UserProxy {
   //指定要被代理的類
   private Usertarget;
 
   public UserProxy(User ui) {
      this.target = ui;
   }
   //返回UserImpl值型別的方法
   public User getUserProxy(){
      User proxy = null;
      //指定負責載入的類載入器
      ClassLoader loader = target.getClass().getClassLoader();
     
      //載入要代理介面中的所有方法
      Class[] interfaces = new Class[]{User.class};
     
      InvocationHandler ih = new InvocationHandler() {
        
         /*
           proxy---代表了正在返回的代理物件
            method----代表正在執行的方法
           obj-----代表方法執行時傳入的引數
          */
         public Object invoke(Object proxy, Method method, Object[] obj)
                throws Throwable {
 
            System.out.println("-----------------------");
            System.out.println("驗證使用者是否有訪問此資源的許可權...");
            Object result = method.invoke(target, null);
            System.out.println("通知資訊:該使用者成功訪問");
            /*
             * method.invoke(obj,args)
             *obj代表了被代理的類
             *args代表了為代理類中的方法傳入引數,我們這裡的方法沒有用到引數,所以傳入null
             * */
            returnresult;
         }
      };
     
      //Proxy 這是Java提供的代理類
      proxy = (User) Proxy.newProxyInstance(loader, interfaces, ih);
      return proxy;
   }
}

這裡有詳細註解,不進行解釋。最基本的就是重寫了invoke()方法。

最後寫測試方法

package org.spring.proxyaop;
 
publicclass Test {
   publicstatic void main(String[] args) {
      User user = new UserImpl();
      User proxy = new UserProxy(user).getUserProxy();
      proxy.normalUser();
      proxy.vip();
      proxy.vvip();
   }
}

執行結果依然不變,spring無論對其怎麼簡化,核心的東西是不會變的。

 

相關文章