基於XML和註解方式的springAOP配置(基礎知識+簡單實例)

基礎知識部分


一、概念

官方解釋
        面向切面編程(也叫面向方面編程):Aspect Oriented Programming(AOP),通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

我的理解
舉個例子:
生產環境中,我們需要對一些方法(幾乎所有方法)的調用都要做一些日誌統計,即在方法調用前打印日誌“XXXX方法開始運行”,方法調用結束後打印“xxx方法運行結束”,假設需要對所有的方法都執行這樣的操作,傳統的方法是將打日誌的這個操作封裝成一個util類,裏面分別寫上方法調用前後打印日誌的對應方法。這樣能夠滿足要求,但人不可避免的是,我們仍然需要在業務demo中嵌入Util類的代碼,這些代碼是與業務無關的,我們需要將這些與業務無關的代碼抽出來封裝成一個切面(一個類),當對這個切面有使用需求時,只需在Spring的配置文件中進行配置即可,而無需在業務代碼中嵌入與業務無關的代碼。(這是aop的一個應用場景)
        
主要的意圖是
將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼。

二、AOP基本術語

1、通知、增強(Advice)

通知就是你想要的功能,在特定的連接點,AOP框架執行的動作。
Spring切面可以應用5種類型的通知:
前置通知(Before):在目標方法被調用之前調用通知功能;
後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning):在目標方法成功執行之後調用通知;
異常通知(After-throwing):在目標方法拋出異常後調用通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行爲。

2、連接點(JoinPoint)

程序執行過程中明確的點,如方法調用前,方法調用後,方法返回,方法拋出異常等。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些點中的特定點就稱爲“連接點”。Spring僅支持方法的連接點。允許使用通知的地方,都算連接點。

3、切點(PointCut)

相當於特定的連接點(JoinPoint),AOP通過“切點”定位特定的連接點。連接點相當於數據庫中的記錄,而切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點可以匹配多個連接點。
上面說的連接點的基礎上,來定義切入點,你的一個類裏,有15個方法,那就有幾十個連接點了對把,但是你並不想在所有方法附近都使用通知(使用叫織入,下面說),你只想讓其中的幾個,在調用這幾個方法之前,之後或者拋出異常時乾點什麼,那麼就用切點來定義這幾個方法,讓切點來篩選連接點,選中那幾個你想要的方法。

4、切面(Aspect)

通知和切入點共同組成了切面。說到這裏,其實發現連接點的概念就是給你理解切點用的。通知說明了幹什麼和什麼時候幹,而切點說明了在哪兒幹,兩個加起來,何時、何處、做什麼。
在spring裏可以利用xml或者@Aspect註解定義切面
從下面的xml定義就可以看到切面是咋回事了,包含了pointcut和adivce(before)。

<aop:aspect id="aspectDemo" ref="aspectBean">
  <aop:pointcut id="myPointcut" expression="execution(* package1.Foo.handle*(..)"/>
  <aop:before pointcut-ref="myPointcut" method="doLog" />
</aop:aspect>

5、引入(Introduction)

引入是指給一個現有類添加方法或字段屬性,引入還可以在不改變現有類代碼的情況下,讓現有的Java類實現新的接口(以及一個對應的實現)。相對於Advice可以動態改變程序的功能或流程來說,引介(Introduction)則用來改變一個類的靜態結構。
Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現 IsModified接口,來簡化緩存。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的接口
(引入允許我們向業務類中業務方法添加新的方法和屬性【spring提供了一個方法注入的功能】)

6、目標(Target)

引入中所提到的目標類,也就是要被通知的對象,也就是真正的業務邏輯,他可以在毫不知情的情況下,被咱們織入切面。而自己專注於業務本身的邏輯。

7、代理(Proxy)

應用通知的對象,詳細內容參見設計模式裏面的代理模式
(換句話:就是代理做了切面的業務,也做了真正的業務。)
AOP代理(AOP Proxy): AOP框架創建的對象,包含通知。 在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
Spring缺省使用J2SE 動態代理(dynamic proxies)來作爲AOP的代理。這樣任何接口都可以被代理。
Spring也支持使用CGLIB代理. 對於需要代理類而不是代理接口的時候CGLIB代理是很有必要的。 如果一個業務對象並沒有實現一個接口,默認就會使用CGLIB。 作爲面向接口編程的最佳實踐,業務對象通常都會實現一個或多個接口。

8、織入(Weaving)

把切面應用到目標對象來創建新的代理對象的過程,織入一般發生在如下幾個時機:

編譯時:當一個類文件被編譯時進行織入,這需要特殊的編譯器纔可以做的到,例如AspectJ的織入編譯器
類加載時:使用特殊的ClassLoader在目標類被加載到程序之前增強類的字節代碼
運行時:切面在運行的某個時刻被織入,SpringAOP就是以這種方式織入切面的,原理應該是使用了JDK的動態代理技術

織入是將通知添加對目標類具體連接點上的過程。AOP像一臺織布機,將目標類、通知或引介通過AOP這臺織布機天衣無縫地編織到一起。
把切面應用到目標對象來創建新的代理對象的過程,Spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。

基本術語參考博客:https://www.jianshu.com/p/22a29acddae9


簡單實例部分

兩種方式:
1、XML方式配置springAOP:工作中不常用,因爲配置過程過於繁瑣,後被註解方式取代,學習xml配置有助於理解AOP。
2、註解方式:如上。

一、XML方式配置AOP

1、搭建maven工程(不贅述)。

2、導依賴包

<!-- 切面編程依賴包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.2.5</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- Spring依賴 -->
        <!-- 1.Spring核心依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

3、切面類

package com.aop.config;
import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAop {
    public void doBefore(){
        System.out.println("before。。。");
    }
    public void doAfter(){
        System.out.println("after。。。");
    }
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("環繞通知開始");
        Object proceed = joinPoint.proceed();
        System.out.println("環繞通知結束");
    }
}

4、業務代碼類

public class UserDao {
    public void saySomthing(){
        System.out.println("this is DAO module");
    }
}

5、配置文件AopBeans.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <!--業務類bean-->
    <bean id="userDaoAop" class="com.aop.dao.UserDao"></bean>
    <!--切面bean-->
    <bean id="myPoint" class="com.aop.config.XmlAop"></bean>
    <!--配置aop-->
    <aop:config>
        <!--定義切點-->
        <!--execution(* com.aop.dao.UserDao.*(..))  表示:任意返回類型 com.aop.dao.UserDao下=任意方法任意參數-->
        <aop:pointcut id="pointcut1" expression="execution(* com.aop.dao.UserDao.saySomthing(..))"/>
        <!--   切面   -->
        <aop:aspect ref="myPoint">
            <!-- doBefore   doAfter   doAround 分別對應 com.aop.config.XmlAop中的 doBefore()doAfter()doAround()      -->
            <aop:before method="doBefore" pointcut-ref="pointcut1"></aop:before>
            <aop:after method="doAfter" pointcut-ref="pointcut1"></aop:after>
            <aop:around method="doAround" pointcut-ref="pointcut1"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

6、測試類UserController.java

public class UserController {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("AopBeans.xml");
        UserDao userDao = (UserDao)applicationContext.getBean("userDao");
        userDao.saySomthing();
    }
}

運行結果:

before。。。
環繞通知開始
this is DAO module
環繞通知結束
after。。。

二、註解方式配置AOP

只有上面第3步和配置文件不一樣:

1、切面類

package com.aop.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Description:
 * @Author: renxin.tang
 * @Date: 2019-08-19 16:40
 */
//聲明這是一個切面bean
@Aspect
//聲明這是一個組件
@Component
public class AnnotationAop {
    //配置切入點,該方法無方法體,主要爲方便同類中其他方法使用此處配置的切入點
    @Pointcut("execution(* com.aop.dao.UserDao.*(..))")
    public void aspect(){	}
    /*
     * 配置前置通知,使用在方法aspect()上註冊的切入點
     * 同時接受JoinPoint切入點對象,可以沒有該參數
     */
    @Before("aspect()")
    public void doBefore() {
        System.out.println("annotation  before。。。");
    }
    //配置後置通知,使用在方法aspect()上註冊的切入點
    @After("aspect()")
    public void doAfter() {
        System.out.println("annotation  after。。。");
    }
    //配置環繞通知,使用在方法aspect()上註冊的切入點
    @Around("aspect()")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("annotation  環繞通知開始");
        Object proceed = joinPoint.proceed();
        System.out.println("annotation  環繞通知結束");
    }
}

2、配置文件AnnocationAopBeans.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- 激活組件掃描功能,在包com.aop及其子包下面自動掃描通過註解配置的組件 -->
    <context:component-scan base-package="com.aop"/>
    <!-- 激活自動代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <!-- 用戶服務對象 -->
    <bean id="userDao" class="com.aop.dao.UserDao"></bean>
</beans>

3、測試類


package com.aop.controller;

import com.aop.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Description:
 * @Author: renxin.tang
 * @Date: 2019-08-19 08:54
 */
public class AnnocationAopTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("AnnocationAopBeans.xml");
        UserDao userDao = (UserDao)applicationContext.getBean("userDao");
        userDao.saySomthing();
    }
}

運行結果:

annotation  環繞通知開始
annotation  before。。。
this is DAO module
annotation  環繞通知結束
annotation  after。。。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章