1. 程式人生 > >spring-AOP之通知和顧問

spring-AOP之通知和顧問

多個 targe ges 配置 context color ive 後置 功能

通知和顧問都是切面的實現形式,其中通知可以完成對目標對象方法簡單的織入功能。

而顧問包裝了通知,可以讓我們對通知實現更加精細化的管理,讓我們可以指定具體的切入點。

通知分為前置通知,環繞通知及後置通知。

前置通知:在目標方法執行之前執行,不改變方法的執行流程及執行結果,前置通知的實現類要實現“MethodBeforeAdvice”這個接口。

環繞通知:也叫方法攔截器,可以改變方法的執行流程及執行結果,環繞通知的實現類要實現“MethodInterceptor”這個接口。

後置通知:在目標方法執行之後執行,不改變方法的執行流程及執行結果,後置通知的實現類要實現“AfterReturningAdvice”這個接口。

為了說明以上三者的區別,我們還是用實驗來說明,這次的實例是實現一個計算器,有加法和除法。

首先是計算器的接口。

public interface ICalculatorService {
    
    int add(int a,int b);
    
    int division(int a ,int b); 

}

實現類:

public class CalculatorServiceImpl implements ICalculatorService {

    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int division(int a, int b) {
        return a/b;
    }

}

前置通知的實現類:

public class TestMethodBeforeAdive implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行前置通知--->"+"正在執行的方法名為"+method.getName());
    }

}

環繞通知的實現類:

public class TestMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        
        System.out.println("執行環繞通知--->"+"正在執行的方法名為"+invocation.getMethod().getName());
        
        Object[] arguments = invocation.getArguments();
        int a = (int)arguments[0];
        int b = (int)arguments[1];
        if(b == 0){
            System.err.println("除數不能為0");
            return -1;
        }
        if(a == 0){
            return 0;
        }
        
        
        return invocation.proceed();
    }

}

後置通知的實現類:

public class TesAfterRunningAdive implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        
        System.out.println("執行後置通知--->"+"正在執行的方法名為"+method.getName());
        System.err.println("執行結果為:"+returnValue.toString());
    }

}

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" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:task="http://www.springframework.org/schema/task" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
                    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
        
    <!-- 目標對象 -->            
    <bean id="calculatorServiceTarget" class="com.opensource.service.impl.CalculatorServiceImpl"/>
    <!-- 通知 -->
    <bean id="methodBeforeAdive" class="com.opensource.service.impl.TestMethodBeforeAdive"/>
    <bean id="afterRunningAdive" class="com.opensource.service.impl.TesAfterRunningAdive"/>
    <bean id="methodInterceptor" class="com.opensource.service.impl.TestMethodInterceptor"/>
    
    <!-- 代理對象 -->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="calculatorServiceTarget"/>
        <property name="interfaces" value="com.opensource.service.ICalculatorService"/>
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdive</value>
                <value>afterRunningAdive</value>
                <value>methodInterceptor</value>
            </list>
        </property>
    </bean>   
</beans>

測試類:

public class MyTest {
    public static void main(String[] args) {
        
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-bean.xml");
        ICalculatorService bean = (ICalculatorService)ac.getBean("proxyFactoryBean");
        int division = bean.division(10, 0);
        System.out.println("兩數相除商為:"+division);
        //bean.add(0, 2);   
    }


}

實驗結果:

技術分享

使用通知這種切面對目標對象的方法進行織入的缺點是是顯而易見的,因為他會對目標對象中的所有方法進行織入。如上例中,我們定義的環繞通知這個切面只是用來對目標對象中的"division"這一方法進行織入而對“add”方法,不加織入。但使用通知進行織入的話,會把目標對象中所有的方法都進行了織入。也就是說目標對象中所有的方法都成為了切入點。要實現對通知更加精細化的管理,就要引入顧問,可以讓我們有選擇性的對目標對象的方法進行織入。

如上例中,我們希望環繞通知只對目標對象的“division”方法進行織入,那麽使用顧問就可以這麽做(這裏我們使用NameMatchMethodPointcutAdvisor這種顧問):

修改配置文件:

<?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" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:task="http://www.springframework.org/schema/task" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
                    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
        
    <!-- 目標對象 -->            
    <bean id="comparatorServiceTarget" class="com.opensource.service.impl.ComparatorServiceImpl"/>
    <bean id="calculatorServiceTarget" class="com.opensource.service.impl.CalculatorServiceImpl"/>
    <!-- 通知 -->
    <bean id="methodBeforeAdive" class="com.opensource.service.impl.TestMethodBeforeAdive"/>
    <bean id="afterRunningAdive" class="com.opensource.service.impl.TesAfterRunningAdive"/>
    <bean id="methodInterceptor" class="com.opensource.service.impl.TestMethodInterceptor"/>
    <!-- 顧問 -->
    <bean id="advisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="methodInterceptor"/>
        <property name="mappedNames" value="division"/>
    </bean>
    <!-- 代理對象 -->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="calculatorServiceTarget"/>
        <property name="interfaces" value="com.opensource.service.ICalculatorService"/>
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdive</value>
                <value>afterRunningAdive</value>
                <value>advisor</value>
            </list>
        </property>
    </bean>
</beans>

測試類:

public class MyTest {
    public static void main(String[] args) {
        
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-bean.xml");
        ICalculatorService bean = (ICalculatorService)ac.getBean("proxyFactoryBean");
        /*int division = bean.division(10, 0);
        System.out.println("兩數相除商為:"+division);*/
        int add = bean.add(0, 2);
        System.out.println("兩數想加和為:"+add);
    }


}

實驗結果:

技術分享

另外在顧問中對多個切入點進行指定的時候可以使用逗號隔開,還有可以使用模糊匹配的方式例如“query*”這種方式。

分析以上的使用方式我們會發現兩個缺點

1):由於我們的代理對象是由ProxyFactoryBean工具類生成的,這就決定了一個代理對象只能代理一個目標對象,當有多個目標對象時,就需要有多個代理對象,這樣就很麻煩。

2):我們在測試類中獲取bean時,用的是代理對象的id獲取的,不是通過我們定義的目標對象的id來獲取的,我們真正想要的是目標對象,而不是代理對象。

spring-AOP之通知和顧問