Spring AOP 創建增強類


    AOP聯盟為增強定義了org.aopalliance.aop.Advice接口,Spring支持5種類型的增強:

     1)前置增強:org.springframework.aop.BeforeAdvice 代表前置增強,因為Spring 只支持方法級的增強,所有MethodBeforeAdvice是目前可用的前置增強,表示在目標方法執行前實施增強,而BeforeAdvice是為了將來版本擴展需要而定義的;
     2)后置增強:org.springframework.aop.AfterReturningAdvice 代表后增強,表示在目標方法執行后實施增強;
     3)環繞增強:org.aopalliance.intercept.MethodInterceptor 代表環繞增強,表示在目標方法執行前后實施增強;
     4)異常拋出增強:org.springframework.aop.ThrowsAdvice 代表拋出異常增強,表示在目標方法拋出異常后實施增強;
     5)引介增強:org.springframework.aop.IntroductionInterceptor 代表引介增強,表示在目標類中添加一些新的方法和屬性。
 
1、前置增強
    模擬服務員向顧客表示歡迎和對顧客提供服務。
Waiter接口:
package com.yyq.advice;
public interface Waiter {
    void greetTo(String name);
    void serveTo(String name);
}

NaiveWaiter服務類:

package com.yyq.advice;
public class NaiveWaiter implements Waiter {
    @Override
    public void greetTo(String name) {
        System.out.println("greet to " + name + "...");
    }
    @Override
    public void serveTo(String name) {
        System.out.println("serving to " + name + "...");
    }
}

前置增強實現類:

package com.yyq.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        String clientName = (String) objects[0];
        System.out.println("How are you ! Mr." + clientName + ".");
    }
}

測試方法:

@Test
    public void testBeforeAdvice(){
        Waiter target = new NaiveWaiter();
        BeforeAdvice advice = new GreetingBeforeAdvice();
        ProxyFactory pf = new ProxyFactory(); //Spring提供的代理工廠
        pf.setTarget(target);    //設置代理目標
        pf.addAdvice(advice);
        Waiter proxy = (Waiter)pf.getProxy();    //生成代理實例
        proxy.greetTo("John");
        proxy.serveTo("Tom");
    }
輸出結果:
How are you ! Mr.John.
greet to John...
How are you ! Mr.Tom.
serving to Tom...
 
在Spring中配置:beans.xml
<bean id="greetingAdvice" class="com.yyq.advice.GreetingBeforeAdvice"/>
<bean id="target" class="com.yyq.advice.NaiveWaiter"/>

<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
     p:proxyInterfaces="com.yyq.advice.Waiter"
     p:interceptorNames="greetingAdvice"
     p:target-ref="target"/>

  ProxyFactoryBean 是FactoryBean接口的實現類。

    · target:代理的目標對象;
    · proxyInterfaces:代理所實現的接口,可以是多個接口。該屬性還有一個別名屬性interfaces;
    · interceptorNames:需要織入目標對象的Bean列表,采用Bean的名稱指定。這些Bean必須是實現了org.aopalliance.intercept.Method 或 org.springframework.aop.Advisor的Bean,配置中的順序對應調用的順序;
    · singleton:返回的代理是否是單實例,默認為單實例;
    · optimize:當設置為true時,強制使用CGLib代理。對於singleton的代理,我們推薦使用CGLib,對於其他作用域類型的代理,最好使用JDK代理。原因是CGLib創建代理時速度慢,而創建出的代理對象運行效率較高,而使用JDK代理的表現正好相反;
    · proxyTargetClass:是否對類進行代理(而不是對接口進行代理),設置為true時,使用CGLib代理。
測試方法:
@Test
    public void testBeforeAdvice2(){
        String configPath = "com\\yyq\\advice\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter1");
        waiter.greetTo("Joe");
    }
輸出結果:
How are you ! Mr.Joe.
greet to Joe...
 
2、后置增強
    后置增強在目標類方法調用后執行。模擬服務員在每次服務后使用禮貌用語。
GreetingAfterAdvice后置增強實現類:
package com.yyq.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class GreetingAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o2) throws Throwable {
        System.out.println("Please enjoy yourself.");
    }
}

 在beans.xml文件添加后置增強:

<bean id="greetingBefore" class="com.yyq.advice.GreetingBeforeAdvice"/>
<bean id="greetingAfter" class="com.yyq.advice.GreetingAfterAdvice"/>
<bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean"
     p:proxyInterfaces="com.yyq.advice.Waiter"
     p:interceptorNames="greetingBefore,greetingAfter"
     p:target-ref="target"/>

測試方法:

 @Test
    public void testBeforeAndAfterAdvice(){
        String configPath = "com\\yyq\\advice\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter2");
        waiter.greetTo("Joe");
    }
結果輸出:
How are you ! Mr.Joe.
greet to Joe...
Please enjoy yourself.
 
3、環繞增強
    環繞增強允許在目標類方法調用前后織入橫切邏輯,它綜合實現了前置、后置增強兩者的功能。
GreetingInterceptor環繞增強實現類:
package com.yyq.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class GreetingInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object[] args = methodInvocation.getArguments();
        String clientName = (String) args[0];
        System.out.println("Hi,Mr " + clientName + ".");
        Object obj = methodInvocation.proceed();
        System.out.println("Please enjoy yourself~");
        return obj;
    }
}

   Spring 直接使用AOP聯盟所定義的MethodInterceptor作為環繞增強的接口。該接口擁有唯一的接口方法 Object invoke(MethodInvocation invocation), MethodInvocation不但封裝目標方法及其入參數組,還封裝了目標方法所在的實例對象,通過MethodInvocation的getArguments()可以獲取目標方法的入參數組,通過proceed()反射調用目標實例相應的方法。

在beans.xml文件添加環繞增強:
 <bean id="greetingAround" class="com.yyq.advice.GreetingInterceptor"/>
    <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.yyq.advice.Waiter"
          p:interceptorNames="greetingAround"
          p:target-ref="target"/>

測試方法:

  @Test
    public void testAroundAdvice(){
        String configPath = "com\\yyq\\advice\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter3");
        waiter.greetTo("Joe");
    }
結果輸出:
Hi,Mr Joe.
greet to Joe...
Please enjoy yourself~
 
4、異常拋出增強
    異常拋出增強最適合的應用場景是事務管理,當參與事務的某個Dao發送異常時,事務管理器就必須回滾事務。
Forum業務類:
package com.yyq.advice;
public class Forum {
    private int forumId;
    public int getForumId() {
        return forumId;
    }
    public void setForumId(int forumId) {
        this.forumId = forumId;
    }
}

 ForumService業務類:

package com.yyq.advice;
import java.sql.SQLException;
public class ForumService {
    public void removeForum(int forumId){
        System.out.println("removeForum....");
        throw new RuntimeException("運行異常");
    }
    public void updateForum(Forum forum)throws Exception{
        System.out.println("updateForum");
        throw new SQLException("數據更新操作異常。");
    }
}

TransactionManager異常拋出增強實現類:

package com.yyq.advice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class TransactionManager implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
        System.out.println("----------------");
        System.out.println("method:" + method.getName());
        System.out.println("拋出異常:" + ex.getMessage());
        System.out.println("成功回滾事務。");
    }
}
    ThrowAdvice異常拋出增強接口沒有定義任何方法,它是一個標識接口,在運行期Spring使用反射的機制自行判斷,我們采用以下簽名形式定義異常拋出的增強方法:void afterThrowing(Mehod method, Object[] args, Object target, Throwable);方法名必須為afterThrowing,方法入參規定,前三個參數是可選的,要么三個入參提供,要么不提供,最后一個入參是Throwable或者子類。
在beans.xml文件添加異常拋出增強:
<bean id="transactionManager" class="com.yyq.advice.TransactionManager"/>
    <bean id="forumServiceTarget" class="com.yyq.advice.ForumService"/>
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:interceptorNames="transactionManager"
          p:target-ref="forumServiceTarget"
          p:proxyTargetClass="true"/>

 測試方法:

@Test
    public void testThrowsAdvice(){
        String configPath = "com\\yyq\\advice\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        ForumService fs = (ForumService)ctx.getBean("forumService");
        try{
            fs.removeForum(10);
        } catch (Exception e) {}
        try{
            fs.updateForum(new Forum());
        } catch (Exception e) {}
    }
結果輸出:
----------------
method:removeForum
拋出異常:運行異常
成功回滾事務。
updateForum
----------------
method:updateForum
拋出異常:數據更新操作異常。
成功回滾事務。
 
5、引介增強
        引介增強為目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別的。通過引介增強,我們可以為目標類添加一個接口的實現,即原來目標類未實現某個接口,通過引介增強可以為目標類創建實現某個接口的代理。Spring定義了引介增強接口IntroductionInterceptor,該接口沒有定義任何的方法,Spring為該接口提供了DelegatingIntroductionInterceptor實現類。
Monitorable:用於標識目標類是否支持性能監視的接口
package com.yyq.advice;
public interface Monitorable {
    void setMonitorActive(boolean active);
}

 ControllablePerformanceMonitor 為引介增強實現類:

package com.yyq.advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable {
    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
    @Override
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);
    }
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object obj = null;
        if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
            PerformanceMonitor.begin(mi.getClass().getName() + "." + mi.getMethod().getName());
            obj = super.invoke(mi);
            PerformanceMonitor.end();
        } else {
            obj = super.invoke(mi);
        }
        return obj;
    }
}

PerformanceMonitor監視類:

package com.yyq.advice;

public class PerformanceMonitor {
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }
    public static void end(){
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }
}

MethodPerformance記錄性能信息:

public class MethodPerformance {
    private long begin;
    private long end;
    private String serviceMethod;
    public MethodPerformance(String serviceMethod){
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }
    public void printPerformance(){
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod + "花費" + elapse + "毫秒。");
    }
}

在beans.xml文件添加引介增強:

<bean id="pmonitor" class="com.yyq.advice.ControllablePerformanceMonitor"/>
<bean id="forumServiceImplTarget" class="com.yyq.advice.ForumServiceImpl"/>
<bean id="forumService2" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:interfaces="com.yyq.advice.Monitorable"
          p:interceptorNames="pmonitor"
          p:target-ref="forumServiceImplTarget"
          p:proxyTargetClass="true"/>

 

測試方法:
@Test
    public void testIntroduce(){
        String configPath = "com\\yyq\\advice\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        ForumServiceImpl forumServiceImpl = (ForumServiceImpl)ctx.getBean("forumService2");
        forumServiceImpl.removeForum(23);
        forumServiceImpl.removeTopic(1023);
        Monitorable monitorable = (Monitorable)forumServiceImpl;
        monitorable.setMonitorActive(true);
        forumServiceImpl.removeForum(22);
        forumServiceImpl.removeTopic(1023);
    }
結果輸出:
模擬刪除Forum記錄:23
模擬刪除Topic記錄:1023
begin monitor...
模擬刪除Forum記錄:22
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeForum花費40毫秒。
begin monitor...
模擬刪除Topic記錄:1023
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeTopic花費21毫秒。
 
 


免責聲明!

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



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