SSH框架之Spring進(jìn)階AOP面向切面編程(三)

第一節(jié):AOP簡(jiǎn)介

第二節(jié):AOP的作用

    對(duì)程序進(jìn)行增強(qiáng),不修改源碼的情況下AOP可以進(jìn)行權(quán)限校驗(yàn)、日志記錄、性能監(jiān)控、事務(wù)管理。

第三節(jié):Spring底層的AOP實(shí)現(xiàn)原理

動(dòng)態(tài)代理:

  • JDK動(dòng)態(tài)代理:只能對(duì)實(shí)現(xiàn)了接口的類產(chǎn)生代理。
  • Cglib動(dòng)態(tài)代理(類似于Javassist第三方代理技術(shù)):對(duì)沒(méi)有實(shí)現(xiàn)接口的類產(chǎn)生代理對(duì)象,生成子類對(duì)象。

第四節(jié):Spring中AOP的開(kāi)發(fā)(AspectJ的XML方式)

AOP思想最早是由AOP聯(lián)盟組織提出的,Spring是使用該思想最好的框架之一。
Spring的AOP有自己的實(shí)現(xiàn)方式(非常繁瑣)。AspectJ是一個(gè)AOP的框架。Spring引入AspectJ作為自身AOP的開(kāi)發(fā)。
Spring擁有兩套AOP開(kāi)發(fā)方式:

  • Spring傳統(tǒng)方式(棄用);
  • Spring基于AspectJ的AOP的開(kāi)發(fā);

4.1 AOP的7個(gè)專業(yè)術(shù)語(yǔ)

  • 4.1.1 增強(qiáng)(Advice)
    增強(qiáng)就很好理解了,AOP(切面編程)是用來(lái)給某一類特殊的連接點(diǎn),添加一些特殊的功能,那么我們添加的功能也就是增強(qiáng)啦~
    比如:添加日志、管理事務(wù)。
    不過(guò)增強(qiáng)不僅僅包含需要增加的功能代碼而已,它還包含了方位信息。
    那什么是方位信息呢?
    方位信息就是相對(duì)于方法的位置信息,如:方法前、方法后、方法環(huán)繞
    為什么要方位信息呢?切點(diǎn)不是確定了需要增強(qiáng)的位置了嗎?
    切點(diǎn)定位的是“在什么類的什么方法上”,也就是說(shuō),切點(diǎn)只是定位到了方法本身(也叫執(zhí)行點(diǎn),特殊的連接點(diǎn)),但是我們?cè)鰪?qiáng)的內(nèi)容是放在該方法的前面呢、后面呢?還是前后都要呢?這些切點(diǎn)卻沒(méi)有告訴我們,那么我們?cè)撊绾未_定具體位置呢?
    所以,我們才需要用到方位信息,進(jìn)一步的定位到具體的增強(qiáng)代碼放置的位置。
    咦?增強(qiáng)即包含了【功能】又包含了【方位】,那我是不是不用切點(diǎn)就可以匹配哪些方法,并添加功能了呢?
    恩,確實(shí)如此,因?yàn)橥ㄟ^(guò)方位信息,雖然只是簡(jiǎn)單的描述了【功能】需要放在方法前、后、還是前后都要等信息,但是我們還是可以通過(guò)方位定位到位置。只不過(guò),是匹配到所有類的所有方法!因?yàn)榉轿恢皇钦f(shuō)明在方法前還是方法后,并沒(méi)有要求是哪些類?哪些方法?
    — So,我們可以直接使用增強(qiáng)來(lái)生成一個(gè)切面,而不需要切點(diǎn),但這并不怎么推薦,因?yàn)樗瞧ヅ渌蟹椒ǖ摹K?,我們才需要用切點(diǎn)來(lái)進(jìn)一步確認(rèn)位置。
  • 4.4.2 切點(diǎn)(Pointcut)
    一個(gè)項(xiàng)目中有很多的類,一個(gè)類有很多個(gè)連接點(diǎn),當(dāng)我們需要在某個(gè)方法前插入一段增強(qiáng)(advice)代碼時(shí),我們就需要使用切點(diǎn)信息來(lái)確定,要在哪些連接點(diǎn)上添加增強(qiáng)。
    那么切點(diǎn)是什么?
    如果把連接點(diǎn)當(dāng)做數(shù)據(jù)庫(kù)中的記錄,那么切點(diǎn)就是查找該記錄的查詢條件。
    所以,一般我們要實(shí)現(xiàn)一個(gè)切點(diǎn)時(shí),那么我們需要判斷哪些連接點(diǎn)是符合我們的條件的,如:方法名是否匹配、類是否是某個(gè)類、以及子類等。
  • 4.4.3 連接點(diǎn)(Joinpoint)
    連接點(diǎn)就是程序執(zhí)行的某個(gè)特定的位置,如:類開(kāi)始初始化前、類初始化后、類的某個(gè)方法調(diào)用前、類的某個(gè)方法調(diào)用后、方法拋出異常后等。Spring 只支持類的方法前、后、拋出異常后的連接點(diǎn)。
  • 4.4.4 切面(Aspect)
    切面由切點(diǎn)和增強(qiáng)(或引介)組成,或者只由增強(qiáng)(或引介)實(shí)現(xiàn)。
  • 4.4.5 目標(biāo)對(duì)象(Target)
    目標(biāo)對(duì)象就是我們需要對(duì)它進(jìn)行增強(qiáng)的業(yè)務(wù)類~
    如果沒(méi)有AOP,那么該業(yè)務(wù)類就得自己實(shí)現(xiàn)需要的功能。
  • 4.4.6 AOP代理(AOP proxy)
    一個(gè)類被AOP織入后生成出了一個(gè)結(jié)果類,它是融合了原類和增強(qiáng)邏輯的代理類。
  • 4.4.7 織入(Weaving)
    織入就是將增強(qiáng)添加到目標(biāo)類具體連接點(diǎn)上的過(guò)程。
    編譯期織入,這要求使用特殊java編譯器
    類裝載期織入,這要求使用特殊的類裝載器
    動(dòng)態(tài)代理織入,在運(yùn)行期為目標(biāo)類添加增強(qiáng)生成子類的方式
    Spring采用的是動(dòng)態(tài)代理織入,而AspectJ采用編譯期織入和類裝載期織入。


第五節(jié):Spring的AOP開(kāi)發(fā)入門(mén)

5.1 Spring測(cè)試類的使用

測(cè)試類中整合Spring框架的注入功能, 需引入Jar包“spring-test-4.2.4.RELEASE.jar”從而減少測(cè)試類中對(duì)Spring工廠對(duì)象的創(chuàng)建

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

//結(jié)合Spring對(duì)測(cè)試類進(jìn)行屬性注入
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserDaoServiceTest {

    @Resource(name = "userDaoService")
    private UserDaoService userDaoService;


    @Test
    public void demo01(){
        userDaoService.save();
    }

}

5.2 AOP切面編程的準(zhǔn)備工作:

  • 引入jar包(使用AspectJ的需求前需引入所需的jar包)


  • 編寫(xiě)基礎(chǔ)測(cè)試類以接口實(shí)現(xiàn)
public class ProductImpl implements Product {
    @Override
    public void save() {
        System.out.println("保存商品");
    }

    @Override
    public void update() {
        System.out.println("更新商品");
    }

    @Override
    public void search() {
        System.out.println("查詢商品");
    }

    @Override
    public void del() {
        System.out.println("刪除商品");
    }
}
  • 通過(guò)配置文件,將該類交給Spring管理
<!--配置目標(biāo)對(duì)象:被增強(qiáng)的對(duì)象-->
    <bean id="product" class="com.seapp.spring02.ProductImpl"/>
  • 編寫(xiě)切面類,并建立增強(qiáng)方法
public class MyAspect {

    public void logInfo(){
        System.out.println("實(shí)現(xiàn)日志增強(qiáng)");
    }

}
  • 將切面類交給Spring管理,并對(duì)目標(biāo)對(duì)象配置增強(qiáng)
<!--將切面類交給Spring去管理-->
    <bean id="myAspect" class="com.seapp.spring02.MyAspect"/>

    <!--AOP配置-->
    <aop:config>
        <!--expression:是一個(gè)表達(dá)式,配置那些類的那些方法需要增強(qiáng)-->
        <aop:pointcut id="pointcut01" expression="execution(* com.seapp.spring02.ProductImpl.save(..))"/>

        <!--配置切面-->
        <aop:aspect ref="myAspect">

            <aop:before method="logInfo" pointcut-ref="pointcut01"/>

        </aop:aspect>

    </aop:config>

第六節(jié):Spring的通知類型

  • 前置通知:
    在目標(biāo)方法執(zhí)行之前進(jìn)行操作。
  • 后置通知:
    在目標(biāo)方法執(zhí)行之后進(jìn)行操作。
  • 環(huán)繞通知:
    在目標(biāo)方法執(zhí)行之前和之后進(jìn)行操作。
  • 異常拋出通知:
    在程序出現(xiàn)異常的時(shí)候進(jìn)行的操作。
  • 最終通知:
    程序執(zhí)行完成后進(jìn)行操作。無(wú)論有無(wú)異常都會(huì)執(zhí)行。
  • 引介通知:(不常用)
//6.1 切面類的定義以及增強(qiáng)
public class MyAspect {

    /**
     * 前置通知
     */
    public void logInfo(){
        System.out.println("實(shí)現(xiàn)日志增強(qiáng)");
    }

    /**
     * 后置通知
     * @param result
     */
    public void writeLog(Object result){
        System.out.println("log日志獲取" + result);
    }

    /**
     * 環(huán)繞通知
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("環(huán)繞前通知");
        Object proceed = joinPoint.proceed();
        System.out.println("環(huán)繞后通知");
        return proceed;
    }

    /**
     * 異常拋出通知
     */
    public void afterThrowing(Throwable ex){
        System.out.println("異常拋出信息" + ex );
    }

    /**
     * 最終通知
     */
    public void after(){
        System.out.println("最終通知信息");
    }

}

//接口實(shí)現(xiàn)類:
public class ProductImpl implements Product {
    @Override
    public void save() {
        System.out.println("保存商品");
    }

    @Override
    public String update() {
        System.out.println("更新商品");
        return "更新成功" ;
    }

    @Override
    public void search() {
        System.out.println("查詢商品");
    }

    @Override
    public void del() {
        System.out.println("刪除商品");
        int i = 10/0;
    }
}

//6.3 核心,在applicationContext.xml中關(guān)于切面的配置
    <!--配置目標(biāo)對(duì)象:被增強(qiáng)的對(duì)象-->
    <bean id="product" class="com.seapp.spring02.ProductImpl"/>

    <!--將切面類交給Spring去管理-->
    <bean id="myAspect" class="com.seapp.spring02.MyAspect"/>

    <!--AOP配置-->
    <aop:config>
        <!--expression:是一個(gè)表達(dá)式,配置那些類的那些方法需要增強(qiáng)-->
        <aop:pointcut id="pointcut01" expression="execution(* com.seapp.spring02.ProductImpl.save(..))"/>
        <aop:pointcut id="pointcut02" expression="execution(* com.seapp.spring02.ProductImpl.update(..))"/>
        <aop:pointcut id="pointcut03" expression="execution(* com.seapp.spring02.ProductImpl.search(..))"/>
        <aop:pointcut id="pointcut04" expression="execution(* com.seapp.spring02.ProductImpl.del(..))"/>

        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--前置通知配置-->
            <aop:before method="logInfo" pointcut-ref="pointcut01"/>
            <!--后置通知配置-->
            <aop:after-returning method="writeLog" pointcut-ref="pointcut02" returning="result"/>
            <!--環(huán)繞通知-->
            <aop:around method="around" pointcut-ref="pointcut03"/>
            <!--異常拋出信息-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut04" throwing="ex"/>
            <!--最終通知信息-->
            <aop:after method="after" pointcut-ref="pointcut04"/>
        </aop:aspect>

    </aop:config>

第七節(jié):Spring切入點(diǎn)表達(dá)式

  • 基于execution的函數(shù)完成的,語(yǔ)法定義如下:
[訪問(wèn)修飾符] 方法返回值 包名.類名.方法名(參數(shù))
public void com.seapp.product.save(..)
//其中除以為其余字段都可以使用*號(hào)代替
* com.seapp.product.save(..)
* *.*.product.*(..)
* *.*.*.*save(..)//以
...

第八節(jié):Spring使用AspectJ進(jìn)行AOP注解開(kāi)發(fā)方式

@Aspect//定義切面類的注解
/**通知類型:
*@Before    :前置通知
*@AfterReturing  :后置通知
*@Around :環(huán)繞通知
*@After:最終通知
*@AfterThrowing:異常拋出通知
**/
@poincut//定義切入點(diǎn)注解

切面類:

@Aspect
public class MyAspectAnno {

    /**
     * 前置通知
     */
    @Before("MyAspectAnno.pointcut1()")
    public void logInfo(){
        System.out.println("實(shí)現(xiàn)日志增強(qiáng)");
    }

    /**
     * 后置通知
     */
    @AfterReturning("MyAspectAnno.pointcut4()" )
    public void writeLog(){
        System.out.println("log日志獲取");
    }

    /**
     * 環(huán)繞通知
     */
    @Around("MyAspectAnno.pointcut2()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("環(huán)繞前通知");
        Object proceed = joinPoint.proceed();
        System.out.println("環(huán)繞后通知");
        return proceed;
    }

    /**
     * 異常拋出通知
     */
    @AfterThrowing("MyAspectAnno.pointcut3()")
    public void afterThrowing(){
        System.out.println("異常拋出信息");
    }

    /**
     * 最終通知
     */
    @After("MyAspectAnno.pointcut4()")
    public void after(){
        System.out.println("最終通知信息");
    }


    /**
     * 定義切入點(diǎn)
     */
    @Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.search(..))")
    private void pointcut1(){

    }

    @Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.save(..))")
    private void pointcut2(){

    }

    @Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.del(..))")
    private void pointcut3(){

    }

    @Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.update(..))")
    private void pointcut4(){

    }

}

Spring中applicationContext.xml中的配置項(xiàng):

<!--使用注解實(shí)現(xiàn)Spring的AspectJ  AOP開(kāi)發(fā)-->
    <!--配置目標(biāo)類-->
    <bean id="productDao" class="com.seapp.spring02.ProductDaoImpl"/>
    <!--配置切面類-->
    <bean id="MyAspectAnno" class="com.seapp.spring02.MyAspectAnno"/>
    <!--開(kāi)啟AOP注解的自動(dòng)代理-->
    <aop:aspectj-autoproxy/>
最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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