1. 程式人生 > >Spring增強型別介紹

Spring增強型別介紹

1 增強型別

Spring中提供的增強類包含兩類1.Spring定義的擴充套件增強 2.aoppalliance定義的擴充套件增強

其中Spring提供的擴充套件增強主要針對於方法即方法級增強,aoppalliace即提供了方法級的增強(MethodInterceptor)也提供引介增強(IntroductionInterceptor),引介增強可以給類新增屬性和行為所以引介增強屬於類級別


1.1 前置增強

顧名思義就是在目標方法執行前織入增強。BeforeAdvice表示前置增強。屬於Spring提供的增強所以屬於方法級增強。MethodBeforeAdvice為目前可用前置增強。

1.2 後置增強

顧名思義就是在目標方法執行後織入增強。AfterReturningAdvice表示後置增強。屬於Spring提供的增強所以屬於方法級增強。

1.3 環繞贈強

MethodInterceptor是前置增強和後置增強的綜合,在目標方法執行前後都織入增強。可以用該增強替換前置增強和後置增強

1.4 異常丟擲增強

ThrowsAdvice表示在目標方法丟擲異常時實施增強

1.5 引介增強

IntroductionInterceptor在目標類中新增屬性和行為

2 ProxyFactory簡介

通過編碼的方式實現增強時需要使用ProxyFactoryProxyFactory底層採用JDK或者CGLib動態代理將對應的增強織入到目標方法或者類中。

AopProxy提供了兩個final的實現類

 1.Cglib2AopProxy:針對類的代理

 2.JdkDynamicAopProxy:針對介面的代理

如果ProxyFactory#setInterfaces(Class[])指定了需要代理的介面,那麼ProxyFactory使用JdkDynamicAopProxy,否則使用Cglib2AopProxy.可以通過ProxyFactory#setOptimize(true)讓ProxyFactory啟動優化代理方式,這樣使用介面的代理也可以使用Cglib2AopProxy。通過ProxyFactory#addAdvice(Advice)或者ProxyFactory#addAdvice(int index,Advice)向目標物件中新增增強,其中index為增強的順序,順序的序列號從0開始。按照新增順序執行或者

index指定的順序執行。index值越小對應的增強優先執行

3 實現前置增強

具體的思路如下:

1.定義介面

2.目標類實現介面

3.實現MethodBeforeAdvice介面,重寫before方法.此處可以通過過濾的方式實現只對特定類的特定方法織入增強

4.建立ProxyFactory

5.設定ProxyFactory的目標物件,為ProxyFactory新增增強,通過ProxyFactory獲得代理例項

6.採用代理物件呼叫介面方法

public interface Dog {
    public void eat();
}
public class SiberianHusky implements Dog {

    @Override
    public void eat() {
        System.out.println("哈士奇 eat");
    }
}
public class SiberianHuskyAdvice implements MethodBeforeAdvice {
    /**
     *
     * @param method 目標物件方法
     * @param args 目標物件方法引數
     * @param target 目標物件
     * @throws Throwable
     */
    //此處可以通過過濾的方式實現只對特定類的特定方法織入增強,當增強方法發生異常時,將阻止目標方法的執行
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + " " + method.getName() + "  之前搖尾巴");
    }
}
    @Test
    public void testEatBeforeAdvice() {
        ProxyFactory proxyFactory = new ProxyFactory();

        //建立目標物件
        Dog target = new SiberianHusky();

        //建立增強物件
        Advice advice = new SiberianHuskyAdvice();

        //設定目標物件
        proxyFactory.setTarget(target);

        //設定增強
        proxyFactory.addAdvice(advice);

        //獲得目標代理物件
        Dog proxy = (Dog) proxyFactory.getProxy();

        //方法呼叫
        proxy.eat();
    }

輸出

com.before.advice.SiberianHusky eat  之前搖尾巴
哈士奇 eat

以上是採用編碼的方式進行測試,下面是採用配置的方式進行測試

    <!--定義目標物件-->
    <bean id="target" class="com.before.advice.SiberianHusky"></bean>
    <!--定義增強物件-->
    <bean id="beforeAdvice" class="com.before.advice.SiberianHuskyAdvice"></bean>
    <!--配置ProxyFactory-->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要織入目標物件的增強,此處採用的是BeanName,多個增強使用逗號分割,增強執行的順序和新增的順序一致-->
        <property name="interceptorNames" value="beforeAdvice"></property>
        <!--代理需要實現的介面,多個介面採用逗號分割-->
        <property name="proxyInterfaces" value="com.before.advice.Dog"></property>
        <!--代理的目標物件-->
        <property name="target" ref="target"></property>
        <!--optimize屬性為true時強制使用CGLib,單例時推薦使用CGLib,
        其他情況建議採用JDK動態代理,CGLib建立代理時速度比較慢
        但是建立的代理物件執行效率高,JDK剛好相反.該配置為可選配置-->
        <!--<property name="optimize" value="true"></property>-->
        <!--proxyTargetClass表示是否對類代理而不是介面,當設定為true時使用CGLib,該配置為可選配置-->
        <!--<property name="proxyTargetClass" [value | ref]=""></property>-->
    </bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();

4 實現後置增強

實現後置增強的思路和實現前置增強的思路基本一致。唯一的不同是需要實現AfterReturningAdvice介面並重寫afterReturning(....)

public class SiberianHuskyEatAfterAdvice implements AfterReturningAdvice {
    /**
     *
     * @param returnObj 目標方法返回值
     * @param method 目標物件方法
     * @param args 目標物件方法引數
     * @param target 目標物件
     * @throws Throwable
     */
    //此處可以通過過濾的方式實現只對特定類的特定方法織入增強,
    //增強方法丟擲異常,如果異常為目標方法宣告的異常則將異常歸併的目標方法中
    //否則Spring將異常轉化為執行時異常丟擲
   @Override
    public void afterReturning(Object returnObj, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + " " + method.getName() + "  之後汪汪汪");
    }
}

採用code呼叫

Advice afterAdvice = new SiberianHuskyEatAfterAdvice();
proxyFactory.addAdvice(afterAdvice);

採用配置以及呼叫該增強

<bean id="afterAdvice" class="com.after.advice.SiberianHuskyEatAfterAdvice"></bean>
    <!--配置ProxyFactory-->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要織入目標物件的增強,此處採用的是BeanName,多個增強使用逗號分割,增強執行的順序和新增的順序一致-->
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterAdvice</value>
            </list>
        </property>
        <!--代理需要實現的介面,多個介面採用逗號分割-->
        <property name="proxyInterfaces" value="com.before.advice.Dog"></property>
        <!--代理的目標物件-->
        <property name="target" ref="target"></property>
        <!--optimize屬性為true時強制使用CGLib,單例時推薦使用CGLib,
        其他情況建議採用JDK動態代理,CGLib建立代理時速度比較慢
        但是建立的代理物件執行效率高,JDK剛好相反.該配置為可選配置-->
        <!--<property name="optimize" value="true"></property>-->
        <!--proxyTargetClass表示是否對類代理而不是介面,當設定為true時使用CGLib,該配置為可選配置-->
        <!--<property name="proxyTargetClass" [value | ref]=""></property>-->
    </bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();

5 實現環繞增強

和實現前置增強的方式思路一致。唯一不同的是需要實現MethodInterceptor介面,重寫invoke(MethodInvocation invocation)方法。MethodInvocation封裝了目標方法的入參可以通過getArguments()獲得目標方法的入引數組。通過procced()方法反射呼叫目標物件的方法。

public class SiberianHuskyARoundAdvice implements MethodInterceptor {
    //同樣可以通過過濾來篩選類和方法
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //獲得目標方法引數列表
        methodInvocation.getArguments();
        System.out.println(methodInvocation.getMethod().getClass().getName() + " " + methodInvocation.getMethod().getName()+ " 之前搖尾巴");
        //呼叫目標方法
        methodInvocation.proceed();
        System.out.println(methodInvocation.getMethod().getClass().getName() + " " + methodInvocation.getMethod().getName()+ " 之後汪汪汪");
        //返回目標方法返回值
        return methodInvocation.getMethod().getReturnType();
    }

採用code呼叫

Advice roundAdvice = new SiberianHuskyARoundAdvice();
proxyFactory.addAdvice(roundAdvice);
採用配置以及呼叫該增強
<bean id="roundAdvice" class="com.around.advice.SiberianHuskyARoundAdvice"></bean>
    <!--配置ProxyFactory-->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--需要織入目標物件的增強,此處採用的是BeanName,多個增強使用逗號分割,增強執行的順序和新增的順序一致-->
        <property name="interceptorNames">
            <list>
                <!--<value>beforeAdvice</value>
                <value>afterAdvice</value>-->
                <value>roundAdvice</value>
            </list>
        </property>
        <!--代理需要實現的介面,多個介面採用逗號分割-->
        <property name="proxyInterfaces" value="com.before.advice.Dog"></property>
        <!--代理的目標物件-->
        <property name="target" ref="target"></property>
        <!--optimize屬性為true時強制使用CGLib,單例時推薦使用CGLib,
        其他情況建議採用JDK動態代理,CGLib建立代理時速度比較慢
        但是建立的代理物件執行效率高,JDK剛好相反.該配置為可選配置-->
        <!--<property name="optimize" value="true"></property>-->
        <!--proxyTargetClass表示是否對類代理而不是介面,當設定為true時使用CGLib,該配置為可選配置-->
        <!--<property name="proxyTargetClass" [value | ref]=""></property>-->
    </bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();

6 異常丟擲增強

實現該增強的具體思路如下

1.通過實現ThrowsAdvice介面定義增強

2.實現具體的邏輯,在業務需要的情況下丟擲異常

3.建立ProxyFactory

4.設定ProxyFactory的目標物件,為ProxyFactory新增增強,此時是為類實現增強所以需要使用CGLib生成代理類因此需要呼叫setProxyTargetClass(true)。通過ProxyFactory獲得代理例項

5.採用代理物件呼叫介面方法

實現ThrowsAdvice介面,該介面屬於標籤介面,該介面中不含有任何的介面方法。需要自定義如下的方法

public void afterThrowing([Method method,Object[] args, Object target,] Throwable exp) {}

該方法必須符合如下要求

1.前三個引數為可選引數,這三個引數要麼同時提供,要麼同時不提供。

2.方法名必須是afterThrowing

3.最後一個引數必須是Throwable或者其子類

從類的繼承樹上來看,兩個類的距離越近,兩個類就越相似。因此在目標方法丟擲對應異常時優先選擇和丟擲異常相似度高的afterThrowing方法.

public class Errors {

    public void throwIndexError() throws ArrayIndexOutOfBoundsException {
        throw new ArrayIndexOutOfBoundsException("陣列越界異常");
    }
}
public class ErrorAdvice implements ThrowsAdvice {

    public void afterThrowing(Method method,Object[] args, Object target, NullPointerException exp) {
        System.out.println(method.getName() + " " + exp.getMessage());
    }

    public void afterThrowing(Throwable exp) {
        System.out.println(exp.getMessage());
    }

    public void afterThrowing(ArrayIndexOutOfBoundsException exp) {
        System.out.println(exp.getMessage());
    }
}

採用code呼叫

ProxyFactory proxyFactory = new ProxyFactory();
//建立目標物件
Errors target = new Errors();

//建立增強物件
Advice errorAdvice = new ErrorAdvice();
//設定目標物件
proxyFactory.setTarget(target);
//設定增強
proxyFactory.addAdvice(errorAdvice);
proxyFactory.setProxyTargetClass(true);
//獲得目標代理物件
Errors proxy = (Errors) proxyFactory.getProxy();
//方法呼叫
try {
    proxy.throwIndexError();
} catch (Exception e) {}

採用配置以及呼叫該增強

配置1

    <!--增強類-->
    <bean id="errorAdvice" class="com.throwed.advice.ErrorAdvice"></bean>
    <!--具體業務邏輯類-->
    <bean id="errorTarget" class="com.throwed.advice.Errors"></bean>
    <!--ProxyFactoey-->
    <bean id="errorProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interceptorNames" value="errorAdvice"></property>
        <property name="targetName" value="errorTarget"></property>
        <property name="proxyTargetClass" value="true"></property>
    </bean>

配置2

    <!--增強類-->
    <bean id="errorAdvice" class="com.throwed.advice.ErrorAdvice"></bean>
    <!--具體業務邏輯類-->
    <bean id="errorTarget" class="com.throwed.advice.Errors"></bean>
    <!--ProxyFactoey-->
    <bean id="errorProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
    p:proxyTargetClass="true"
    p:target-ref="errorTarget"
    p:interceptorNames="errorAdvice"/>

呼叫

Errors error = (Errors) beanFactory.getBean("errorProxy");
error.throwNullPointError();

7 實現引介增強

該種增強不是在方法周圍織入增強,而是為目標類新增新的屬性或者行為。所以該種增強屬於類級別。因此通過該種增強可以為目標類新增介面實現併為目標類建立實現介面的代理。引介增強的介面為IntroductionIntercepter,屬於標籤介面。該介面中沒有定義任何的方法。可以通過擴充套件DelegatingIntroductionIntercepter實現自己的增強。

實現引介增強的具體思路如下

1.定義介面,該介面將用來給目標類新增屬性或行為

2.編寫引介增強類,擴充套件DelegatingIntroductionIntercepter並實現介面在。重寫DelegatingIntroductionIntercepter#invoke(...)方法以及實現介面方法

3.建立ProxyFactory

4.設定ProxyFactory的目標物件,為ProxyFactory新增增強,通過ProxyFactory獲得代理例項

5.採用代理物件呼叫目標物件和介面方法

public interface Monitorable {
    public void setActive(boolean isActive);
}
//目標類
public class UserDao {
    public void insert(){
        System.out.println("插入資料");
    }
}
public class DaoIntroductionAdvice extends DelegatingIntroductionInterceptor implements Monitorable {

    private ThreadLocal<Boolean> isActive = new ThreadLocal<Boolean>();

    @Override
    public void setActive(boolean isActive) {
        this.isActive.set(isActive);
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object returnObj = null;
        if(this.isActive.get() != null && this.isActive.get()) {
            System.out.println("輸出呼叫方法:" + mi.getMethod().getName());
            returnObj = super.invoke(mi);//呼叫目標方法
        } else {
            returnObj = super.invoke(mi);
        }
        return returnObj;
    }
}
        ProxyFactory proxyFactory = new ProxyFactory();

        //建立目標物件
        UserDao target = new UserDao();

        //建立增強物件
        Advice advice = new DaoIntroductionAdvice();

        //設定目標物件
        proxyFactory.setTarget(target);

        //設定增強
        proxyFactory.addAdvice(advice);
        proxyFactory.setProxyTargetClass(true);

        //獲得目標代理物件
        UserDao proxy = (UserDao) proxyFactory.getProxy();
        ((Monitorable) proxy).setActive(true);

xml配置

    <!--目標物件-->
    <bean id="userDaoTarget" class="com.introduction.advice.UserDao"></bean>
    <!--引介增強實現類-->
    <bean id="introductionAdvice" class="com.introduction.advice.DaoIntroductionAdvice"></bean>
    <!--ProxyFactory-->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--採用生成子類方式實施增強,底層採用的CGLib,所以此處設定為ture-->
        <property name="proxyTargetClass" value="true"></property>
        <property name="targetName" value="userDaoTarget"></property>
        <!--配置引介增強-->
        <property name="interceptorNames" value="introductionAdvice"></property>
        <!--實現的介面-->
        <property name="interfaces" value="com.introduction.advice.Monitorable"></property>
    </bean>
        UserDao dao = (UserDao) beanFactory.getBean("userDaoProxy");
        ((Monitorable) dao).setActive(true);
        dao.insert();