Spring Aop:一、四種advice

本文參考實驗樓教程:https://www.shiyanlou.com/courses/578/learning/?id=1940

??Spring Aop即 Aspect-Oriented Programming,面向切面編程。是用于處理系統(tǒng)中各個模塊(不同方法)之間的交叉關(guān)注的問題。
??簡單地說,就是一個攔截器,攔截一些處理過程。
??例如:當一個method被執(zhí)行,Spring AOP能夠劫持正在運行的method,在method執(zhí)行前后加入一些額外的功能。(日志記錄、性能統(tǒng)計、權(quán)限控制等)

實驗環(huán)境:
??JDK1.8
??idea 開發(fā)環(huán)境

一、Spring AOP 支持4種類型的通知(advice)

  • Before advice --- method 執(zhí)行前通知
  • After returning advice --- method 執(zhí)行完畢或者返回一個結(jié)果后通知
  • After throwing advice --- method 拋出異常后通知
  • Around advice --- 環(huán)繞通知,結(jié)合了以上三種

下面來學習如何使用這四種通知。
以下是整個實驗項目結(jié)構(gòu):

project.png

1、準備環(huán)境,創(chuàng)建一個maven工程SpringAop

?1)、添加maven依賴,對應(yīng)的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.shiyanlou.spring</groupId>
  <artifactId>SpringAopTest</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>SpringAopTest</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <spring.version>5.1.1.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.2</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.9.2</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.9.2</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.9</version>
    </dependency>
  </dependencies>

</project>

?2)、創(chuàng)建類 CustomerService.java 如下:

package com.shiyanlou.spring.aop.advice;

import org.springframework.stereotype.Service;

/**
 * Created by Administrator on 2019/10/30.
 */

public class CustomerService {
    private String name;
    private String url;

    public CustomerService() {
    }

    public CustomerService(String name, String url) {
        this.name = name;
        this.url = url;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void printName(){
        System.out.println("Customer name: " + name);
    }

    public void printURL(){
        System.out.println("Customer URL: " + url);
    }

    public void printThrowException(){
        throw new IllegalArgumentException();
    }
}

?3)、在src/main/resources/下新建 Xml 配置文件 SpringAopAdvice.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"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

</beans>

?4)、在com.shiyanlou.spring包下的App.java中編寫如下:

package com.shiyanlou.spring;

import com.shiyanlou.spring.aop.advice.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App
{
    private static ApplicationContext context;

    public static void main( String[] args ) {

        context = new ClassPathXmlApplicationContext("SpringAopAdvice.xml");

        CustomerService cust = (CustomerService) context.getBean("customerService");

        System.out.println("*************************");
        cust.printName();
        System.out.println("*************************");
        cust.printURL();
        System.out.println("*************************");
        try {
            cust.printThrowException();
        } catch (IllegalArgumentException e){
        }

    }
}

?5)、運行App.java文件,結(jié)果如下:


*************************
Customer name: weihouye
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************

2、Before advice

?1)、定義org.springframework.aop.MethodBeforeAdvice的實現(xiàn)類WeiBeforeMethod,重寫before方法,該方法將在被代理的類的方法之前運行。代碼如下:

package com.shiyanlou.spring.aop.advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * Created by Administrator on 2019/10/30.
 */
public class WeiBeforeMethod implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("WeiBeforeMethod: Before method hi wei!!!");
    }
}

?2)、在SpringAopAdvice.xml配置文件中加入新的bean配置weiBeforeMethod,然后創(chuàng)建一個代理bean(Proxy),命名為customerServiceProxy。
??代理bean中target定義了被代理(被劫持)的類,攔截器interceptorNames則定義了想要用哪個類(advice)劫持target,攔截器可以有多個,所以寫在<list>中。配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

    <bean id="weiBeforeMethod" class="com.shiyanlou.spring.aop.advice.WeiBeforeMethod"/>

    <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerService"/>
        <property name="interceptorNames">
            <list>
                <value>weiBeforeMethod</value>
            </list>
        </property>
    </bean>
</beans>

用 Spring proxy 之前,必須添加 CGLIB 類庫,在之前的 pom.xml 文件中,已經(jīng)添加到了其中,以下是 pom.xml 依賴:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.9</version>
        </dependency>

?3)、App.java如下

package com.shiyanlou.spring;

import com.shiyanlou.spring.aop.advice.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Hello world!
 *
 */
public class App
{
    private static ApplicationContext context;

    public static void main( String[] args ) {

        context = new ClassPathXmlApplicationContext("SpringAopAdvice.xml");

        CustomerService cust = (CustomerService) context.getBean("customerServiceProxy");

        System.out.println("*************************");
        cust.printName();
        System.out.println("*************************");
        cust.printURL();
        System.out.println("*************************");
        try {
            cust.printThrowException();
        } catch (IllegalArgumentException e){
        }
    }
}

ps:注意 App.java中獲取的是代理bean,即customerServiceProxy,由代理bean去執(zhí)行原來的功能。

?4)、運行結(jié)果如下:


*************************
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
*************************
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
*************************
WeiBeforeMethod: Before method hi wei!!!

??可以看到,被代理類CustomerServiceWeiBeforeMethod劫持,被代理類中的方法被執(zhí)行前,均先執(zhí)行WeiBeforeMethod中的before方法。

3、After Returning Advice

?1)、創(chuàng)建一個org.springframework.aop.AfterReturningAdvice接口的實現(xiàn)類WeiAfterMethod.java,重寫afterReturning方法,用于劫持被代理類;被代理類中的方法執(zhí)行完畢或者返回結(jié)果后,將執(zhí)行WeiAfterMethod中的afterReturning方法,若中途異常退出,則不會執(zhí)行該方法。
?WeiAfterMethod.java代碼如下:

package com.shiyanlou.spring.aop.advice;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

/**
 * Created by Administrator on 2019/10/30.
 */
public class WeiAfterMethod implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("WeiAfterMethod: After Method hi wei!!");
    }
}

?2)、修改bean配置xml文件 SpringAopAdvice.xml,加入weiAfterMethod配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

    <bean id="weiBeforeMethod" class="com.shiyanlou.spring.aop.advice.WeiBeforeMethod"/>
    <bean id="weiAfterMethod" class="com.shiyanlou.spring.aop.advice.WeiAfterMethod"/>

    <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerService"/>
        <property name="interceptorNames">
            <list>
                <!--<value>weiBeforeMethod</value>-->
                <value>weiAfterMethod</value>
            </list>
        </property>
    </bean>
</beans>

?3)、同樣在App.java中獲取并運行代理bean,結(jié)果如下:


*************************
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************

&emps;可以看到,在CustomerService.java中的每個方法執(zhí)行完畢后,均會執(zhí)行WeiAfterMethod.javaafterReturning方法,拋出異常除外。

4、Afetr Throwing Advice

?1)、創(chuàng)建一個實現(xiàn)org.springframework.aop.ThrowsAdvice接口的實現(xiàn)類WeiThrowExceptionMethod.java,劫持IllegalArgumentException異常,當被劫持的類拋出該異常時,將會執(zhí)行afterThrowing方法;代碼如下:

package com.shiyanlou.spring.aop.advice;

import org.springframework.aop.ThrowsAdvice;

/**
 * Created by Administrator on 2019/10/30.
 */
public class WeiThrowExceptionMethod implements ThrowsAdvice {

    public void afterThrowing(Exception ex){
        System.out.println("WeiThrowException : Throw exception hi wei!!");
    }
}

?2)、修改bean配置xml文件 SpringAopAdvice.xml,加入weiThrowExceptionMethod配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

    <bean id="weiBeforeMethod" class="com.shiyanlou.spring.aop.advice.WeiBeforeMethod"/>
    <bean id="weiAfterMethod" class="com.shiyanlou.spring.aop.advice.WeiAfterMethod"/>
    <bean id="weiThrowExceptionMethod" class="com.shiyanlou.spring.aop.advice.WeiThrowExceptionMethod"/>
    <bean id="weiAroundMethod" class="com.shiyanlou.spring.aop.advice.WeiAroundMethod"/>

    <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerService"/>
        <property name="interceptorNames">
            <list>
                <!--<value>weiBeforeMethod</value>-->
                <!--<value>weiAfterMethod</value>-->
                <value>weiThrowExceptionMethod</value>
            </list>
        </property>
    </bean>
</beans>

?3)、同樣在App.java中獲取并運行代理bean,結(jié)果如下:


*************************
Customer name: weihouye
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************
WeiThrowException : Throw exception hi wei!!

?可以看到當執(zhí)行到CustomerService.javaprintThrowException方法時,我們拋出了IllegalArgumentException異常,異常拋出后執(zhí)行了攔截器WeiThrowExceptionMethod.javaafterThrowing方法。


5、Around advice

?1)、結(jié)合上面3種形式的advice,創(chuàng)建一個org.aopalliance.intercept.MethodInterceptor接口的實現(xiàn)類WeiAroundMethod.java,重寫public Object invoke(MethodInvocation invocation) throws Throwable方法;
?必須通過方法調(diào)用對象invocation.proceed()來調(diào)用原來的被代理的方法,當然也可以不調(diào)用;代碼如下,注意注釋中包含十分重要的信息。

package com.shiyanlou.spring.aop.advice;


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.util.Arrays;

/**
 * Created by Administrator on 2019/10/30.
 */
public class WeiAroundMethod implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        //Method調(diào)用的處理
        System.out.println("Method Name: " + invocation.getMethod().getName());
        System.out.println("Method Arguments: " + Arrays.toString(invocation.getArguments()));
        //相當于MethodBeforeAdvice
        System.out.println("WeiBeforeMethod: Before method hi wei!!!");

        try {
            //調(diào)用原方法,即調(diào)用CustomerService中的方法
            Object result = invocation.proceed();

            //相當于AfterReturningAdvice
            System.out.println("WeiAfterMethod: After Method hi wei!!");

            return result;
        } catch (IllegalArgumentException e) {
            //相當于ThrowsAdvice
            System.out.println("WeiThrowException : Throw exception hi wei!!");

            throw e; //將異常拋出
        }
    }
}

?2)、修改bean配置xml文件 SpringAopAdvice.xml,加入weiAroundMethod配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

    <bean id="weiBeforeMethod" class="com.shiyanlou.spring.aop.advice.WeiBeforeMethod"/>
    <bean id="weiAfterMethod" class="com.shiyanlou.spring.aop.advice.WeiAfterMethod"/>
    <bean id="weiThrowExceptionMethod" class="com.shiyanlou.spring.aop.advice.WeiThrowExceptionMethod"/>
    <bean id="weiAroundMethod" class="com.shiyanlou.spring.aop.advice.WeiAroundMethod"/>

    <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerService"/>
        <property name="interceptorNames">
            <list>
                <!--<value>weiBeforeMethod</value>-->
                <!--<value>weiAfterMethod</value>-->
                <!--<value>weiThrowExceptionMethod</value>-->
                <value>weiAroundMethod</value>
            </list>
        </property>
    </bean>
</beans>

?3)、運行App.java結(jié)果如下:


*************************
Method Name: printName
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Method Name: printURL
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************
Method Name: printThrowException
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
WeiThrowException : Throw exception hi wei!!

?可以看出Around advice 結(jié)合了前面三種advice。


6、改進

?在上邊的結(jié)果中,CustomerService.java的全部方法均被攔截,下面展示如何使用Pointcuts只攔截printName()。
?在Spring AOP中,有3個常用的名詞,Advices、Pointcuts、Advisor,解釋如下:

  • Advices:表示一個方法執(zhí)行前或者執(zhí)行后的動作;
  • Pointcuts:表示根據(jù)一個方法的名字或者正則表達式去攔截一個方法;
  • Advisor:Advices和Pointcuts組成的獨立的單元,且可以傳給proxy factory對象。

?我們可以用名字匹配法或者正則表達式去匹配要攔截的方法。



?1)、Pointcuts ——Name match example (根據(jù)方法名匹配)

?SpringAopAdvice.xml中,創(chuàng)建一個NameMatchMethodPointcut的 Bean,并將想要攔截的方法名printName注入到屬性mappedName中。配置如下:

    <bean id="customerPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedName" value="printName"/>
    </bean>



?創(chuàng)建一個DefaultPointcutAdvisor的 Advisor bean,將 Pointcut 和 Advice 注入到 Advisor中。如下:

    <bean id="customerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut" ref="customerPointcut"/>
        <property name="advice" ref="weiAroundMethod"/>
    </bean>



?更改代理customerServiceProxyinterceptorNames中的值,將上面的Advisor代替原來的weiAroundMethod,所有的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                            http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="customerService" class="com.shiyanlou.spring.aop.advice.CustomerService">
        <property name="name" value="weihouye"/>
        <property name="url" value="www.weihouyeaiyangzhan.com"/>
    </bean>

    <!--<bean id="weiBeforeMethod" class="com.shiyanlou.spring.aop.advice.WeiBeforeMethod"/>-->
    <!--<bean id="weiAfterMethod" class="com.shiyanlou.spring.aop.advice.WeiAfterMethod"/>-->
    <!--<bean id="weiThrowExceptionMethod" class="com.shiyanlou.spring.aop.advice.WeiThrowExceptionMethod"/>-->
    <bean id="weiAroundMethod" class="com.shiyanlou.spring.aop.advice.WeiAroundMethod"/>

    <bean id="customerPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedName" value="printName"/>
    </bean>

    <bean id="customerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut" ref="customerPointcut"/>
        <property name="advice" ref="weiAroundMethod"/>
    </bean>

    <bean id="customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerService"/>
        <property name="interceptorNames">
            <list>
                <!--<value>weiBeforeMethod</value>-->
                <!--<value>weiAfterMethod</value>-->
                <!--<value>weiThrowExceptionMethod</value>-->
                <value>customerAdvisor</value>
            </list>
        </property>
    </bean>
</beans>



?運行一下App.java,發(fā)現(xiàn)只有個printName方法被攔截了。結(jié)果輸出如下:

*************************
Method Name: printName
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************



?以上 Poincut 和 Advisor可以一起配置,即不用單獨配置 customerPointcut 和 customerAdvisor,只要在配置 customerAdvisor 時選擇 NameMatchMethodPointcutAdvisor,如下:

    <!--pointcut和advisor可以一起配置,只要在配置customerAdvisor時,選擇NameMatchMethodPointcutAdvisor-->
    <bean id="customerAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="mappedName" value="printName"/>
        <property name="advice" ref="weiAroundMethod"/>
    </bean>



?2)、Pointcuts ——Regular expression match example (根據(jù)正則表達式匹配)

?可以使用正則表達式匹配攔截方法,Advisor 配置如下:

    <bean id="customerAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="patterns">
            <list>
                <value>.*URL.*</value>
            </list>
        </property>
        <property name="advice" ref="weiAroundMethod"/>
    </bean>

?運行結(jié)果:只有個printURL方法被攔截

*************************
Customer name: weihouye
*************************
Method Name: printURL
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容