一起來學(xué)習(xí)Spring:Spring入門與AOP篇

前言:
前面我們簡單介紹了Spring的IOC容器,其實(shí),本篇要講述的是Spring中另外一個(gè)常用的模塊AOP。在看本篇之前,要確保已經(jīng)有代理模式的基本知識(shí),這樣看起來才不會(huì)費(fèi)勁!

1.AOP簡介

AOP(面向切面編程)是對(duì)OOP中遇到的一些問題的補(bǔ)充,是OOP的擴(kuò)展,在不改變原有代碼的情況下增加新的功能,比如我們常見的日志功能,權(quán)限,異常處理,緩存等公共業(yè)務(wù)(即與軟件的開發(fā)業(yè)務(wù)沒有關(guān)聯(lián))。
舉一個(gè)簡單的例子:

public class UserService{
    //添加用戶的方法
      public void add() {
       //加一個(gè)輸出日志
        log();
          //代碼邏輯
      }
       public void delete() {
       //加一個(gè)輸出日志
        log();
   
          //代碼邏輯
      }

}

我們可以看到如果按照我們以前的編寫代碼的方式的話,每個(gè)方法都有寫重復(fù)的代碼,而且log()這個(gè)方法還不是我們主要的業(yè)務(wù)邏輯,這樣編碼的話充滿耦合性,這時(shí)候我們就要利用Spring的AOP技術(shù)將log()這一塊抽取出來
Spring的AOP底層實(shí)現(xiàn)
Spring的AOP底層用到了我們兩種動(dòng)態(tài)代理模式JDK的動(dòng)態(tài)代理(針對(duì)實(shí)現(xiàn)了接口的類產(chǎn)生代理),Cglib的動(dòng)態(tài)代理(針對(duì)沒有實(shí)現(xiàn)接口的類產(chǎn)生代理,應(yīng)用的是底層的字節(jié)碼增強(qiáng)的技術(shù),生成當(dāng)前的子類對(duì)象)

2.AOP開發(fā)中的相關(guān)術(shù)語

  • 關(guān)注點(diǎn):重復(fù)代碼就叫做關(guān)注點(diǎn)即公共業(yè)務(wù)。

  • Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些被攔截到的點(diǎn)。在Spring中,這些點(diǎn)指的是方法,因?yàn)镾pring只支持方法類型的連接點(diǎn)。

  • Pointcut(切入點(diǎn)):所謂切入點(diǎn)是指我們要對(duì)哪些Joinpoint(方法)進(jìn)行攔截的定義。

  • Advice(通知/增強(qiáng)):所謂通知是指攔截到Joinpoint(方法)之后所要做的事情就是通知。通知分為前置通知、后置通知、異常通知、最終通知和環(huán)繞通知(切面要完成的功能)。

  • Aspect(切面):是切入點(diǎn)和通知的結(jié)合。

  • Target(目標(biāo)對(duì)象):代理的目標(biāo)對(duì)象(要增強(qiáng)的類)

  • Weaving(織入):是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建新的代理對(duì)象的過程。Spring采用動(dòng)態(tài)代理織入,而AspectJ采用編譯期織入和類裝載期織入。

  • Proxy(代理):一個(gè)類被AOP織入增強(qiáng)后,就產(chǎn)生一個(gè)結(jié)果代理類。
    Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下,Introduction可以在運(yùn)行期為類動(dòng)態(tài)地添加一些方法或Field。
    我們還是用上面的例子解釋一下這些術(shù)語:

  • 連接點(diǎn):就是里面的三個(gè)方法

  • 切入點(diǎn):假設(shè)里面的add()方法加入了日志的功能,這個(gè)就add()方法就是切入點(diǎn)

  • 通知/增強(qiáng):比如增強(qiáng)UserService類里面的add方法,在add方法中添加了日志功能,這個(gè)日志功能就稱為增強(qiáng)。
    通知類型:

  • 前置通知:在增強(qiáng)的方法執(zhí)行之前進(jìn)行操作。

  • 后置通知:在增強(qiáng)的方法執(zhí)行之后進(jìn)行操作。

  • 環(huán)繞通知:在增強(qiáng)的方法執(zhí)行之前和執(zhí)行之后進(jìn)行操作。

  • 最終通知:增強(qiáng)了兩個(gè)方法,執(zhí)行第一個(gè)方法,執(zhí)行第二個(gè)方法,在第二個(gè)方法執(zhí)行之后進(jìn)行操作。
    也可理解為后置通知后面執(zhí)行的通知或者無論目標(biāo)方法是否出現(xiàn)異常,最終通知都會(huì)執(zhí)行。
    異常通知:程序出現(xiàn)異常之后執(zhí)行的通知。

  • 切面:假設(shè)有個(gè)日志功能,把日志功能加到add()方法上面的過程

  • 目標(biāo)對(duì)象:UserService

  • 織入:把日志功能增加到目標(biāo)對(duì)象(UserService)的過程

3.使用SpringAOP開發(fā)步驟(基于AspectJXML方式)

注意對(duì)切面的配置有兩種方式:一種是傳統(tǒng)的aop聯(lián)盟制定的aop通知的配置,一種是spring支持的AspectJ框架的切面


image.png

1.引入相關(guān)的jar文件

spring-aop-3.2.5.RELEASE.jar 【spring3.2源碼】
aopalliance.jar 【spring2.5源碼/lib/aopalliance】
aspectjweaver.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】

2.bean.xml中引入aop名稱空間

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

3.創(chuàng)建業(yè)務(wù)功能代碼和公共代碼

public class TestAop {
    public void sayHello(String name) {
        System.out.println("sayHello方法被執(zhí)行");
    }
}

public class Log {
    //方法執(zhí)行前
    public void  before() {
        System.out.println("方法執(zhí)行前" );
    }
    //方法執(zhí)行后
    public void  after() {
        System.out.println("方法執(zhí)行后" );
    }
  
}

4.配置bean.xml

    <!--配置業(yè)務(wù)Bean-->
        <bean id="testAop" class="test.TestAop"></bean>
        <!--配置切面Bean-->
        <bean id="log" class="test.Log"></bean>
        <!--配置切面-->
        <aop:config>
            <!--定義切入表達(dá)式,要攔截什么方法-->
            <aop:pointcut id="pointCut" expression="execution(* test.TestAop.*(..))"></aop:pointcut>
            <!--指定切面是那個(gè)類-->
            <aop:aspect ref="log">
                <!--指定來攔截的時(shí)候執(zhí)行切面類的哪些方法-->
                <!-- 配置前置通知 -->
                <aop:before method="before" pointcut-ref="pointCut" ></aop:before>
                <!-- 配置后通知 -->
                <aop:after method="after" pointcut-ref="pointCut" />

            </aop:aspect>
        </aop:config>

結(jié)果


image.png

切入點(diǎn)表達(dá)式

通過execution函數(shù),可以定義切點(diǎn)的方法切入。
語法為:execution(<訪問修飾符>?<返回類型><方法名>(<參數(shù)>)<異常>)。
例如:

  • 匹配所有類的public方法:execution(public .(..))
  • 匹配指定包下所有類的方法:execution(* cn.itcast.dao.*(..)),但不包含子包
  • execution(* cn.itcast.dao..(..)),..表示包、子孫包下所有類。
  • 匹配指定類所有方法:execution(* cn.itcast.service.UserService.*(..))
  • 匹配實(shí)現(xiàn)特定接口的所有類的方法:execution(* cn.itcast.dao.GenericDAO+.*(..))
  • 匹配所有save開頭的方法:execution(* save*(..))
  • 匹配所有類里面的所有的方法:execution(* .(..))
    表達(dá)式有如下形式:
        <!-- 【攔截所有public方法】 -->
        <!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->

        <!-- 【攔截所有save開頭的方法 】 -->
        <!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->

        <!-- 【攔截指定類的指定方法, 攔截時(shí)候一定要定位到方法】 -->
        <!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->

        <!-- 【攔截指定類的所有方法】 -->
        <!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->

        <!-- 【攔截指定包,以及其自包下所有類的所有方法】 -->
        <!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->

        <!-- 【多個(gè)表達(dá)式】 -->
        <!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
        <!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
        <!-- 下面2個(gè)且關(guān)系的,沒有意義 -->
        <!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) &amp;&amp; execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
        <!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->

        <!-- 【取非值】 -->
        <!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->

4.使用注解的形式

在實(shí)際開發(fā)中,如果我們可以用注解形式的話,還是推薦使用注解的形式

1.在bean.xml中開啟AOP注解方式

 <!-- 開啟aop注解方式 -->
 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

切面類

@Aspect//指定為切面類
public class Log {
    //方法執(zhí)行前
    @Before("execution(* test.TestAop.*(..))")
    public void  before() {
        System.out.println("方法執(zhí)行前");
    }
    //方法執(zhí)行后
    @After("execution(* test.TestAop.*(..))")
    public void  after() {
        System.out.println("方法執(zhí)行后" );
    }

}

image.png

優(yōu)化:

@Aspect//指定為切面類
public class Log {
    // 指定切入點(diǎn)表達(dá)式,攔截哪個(gè)類的哪些方法
    // 指定切入點(diǎn)表達(dá)式,攔截哪個(gè)類的哪些方法
    @Pointcut("execution(* test.TestAop.*(..))")
    public void pt() {

    }
    //方法執(zhí)行前
    @Before ("pt()")
    public void  before() {
        System.out.println("方法執(zhí)行前");
    }
    //方法執(zhí)行后
    @After( "pt()")
    public void  after() {
        System.out.println("方法執(zhí)行后" );
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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