Spring系列.AOP使用

AOP簡介

利用面向對象的方法可以很好的組織代碼,也可以繼承的方式實現代碼重用。但是項目中總是會出現一些重複的代碼,並且不太方便使用繼承的方式把他們重用管理起來,比如說通用日誌打印,事務處理和安全檢查等。我們可以將這些代碼封裝起來,做成通用模塊,但是還是需要在代碼中每處需要的地方進行显示調用,使用起來不方便。這是時候就是利用AOP的時候。

AOP是一種編程範式,用來解決特定的問題,不能解決所有問題,可以看做是OOP的補充,常見的編程範式還有:

  • 面向過程編程;
  • 面向對象編程;
  • 面向函數編程(函數式編程);
  • 事件驅動編程(GUI開發中比較常見);
  • 面向切面編程

AOP的常見使用場景

  • 性能監控,在方法調用前後記錄調用時間,方法執行太長或超時報警;
  • 緩存代理,緩存某方法的返回值,下次執行該方法時,直接從緩存里獲取;
  • 軟件破解,使用AOP修改軟件的驗證類的判斷邏輯;
  • 記錄日誌,在方法執行前後記錄系統日誌;
  • 工作流系統,工作流系統需要將業務代碼和流程引擎代碼混合在一起執行,那麼我們可以使用AOP將其分離,並動態掛接業務;
  • 權限驗證,方法執行前驗證是否有權限執行當前方法,沒有則拋出沒有權限執行異常,由業務代碼捕捉;
  • 事務處理 。

Spring AOP相關概念

  • AOP:這種在運行時(或者編譯時或者加載時),動態地將某些公共代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程;
  • 切面(Aspect):A modularization of a concern that cuts across multiple classes。在Spring中切面就是一個標註@AspectJ的類,不要想得太複雜;
  • 連接點(Joinpoint):方法執行過程中的某個點,是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為;
  • 通知(advice):描述切面要完成什麼工作,以及在什麼時間點進行工作;
  • Pointcut:用來匹配一組連接點,並且pointcut會關聯advice,在pointcut匹配的連接點執行的時候,advice代碼會被執行;
  • Introduction
  • Target object:被織入切面的對象;
  • AOP proxy : 包裝了切面代碼和target代碼的對象,Spring中支持JDK動態代理和
    CGLIB,默認使用JDK動態代理,但是如果被代理的類沒有實現接口,或者用戶強制使用CGLIB,那麼Spring會使用CGLIB代理;
  • Weaving:將切面代碼添加到目標代碼的過程,織入的類型有編譯時織入,加載時織入和運行時織入(Spring是運行時織入)

SpringAOP可以應用5種類型的通知:

  • 前置通知(Before):在目標方法被調用之前調用通知功能。
  • 後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼。(不管執行是否成功都執行都執行)
  • 返回通知(After-returning):在目標方法成功執行之後調用通知。
  • 異常通知(After-throwing):在目標方法拋出異常后調用通知。
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。

Spring AOP相關

開啟Aop


//自動選擇合適的AOP代理
//傳統xml這樣配置:<aop:aspectj-autoproxy/>

//exposeProxy = true屬性設置成true,意思是將動態生成的代理類expose到AopContext的ThreadLocal線程
//可以通過AopContext.currentProxy();獲取到生成的動態代理類。

//proxyTargetClass屬性設置動態代理使用JDK動態代理還是使用CGlib代理,設置成true是使用CGlib代理,false的話是使用JDK動態代理

//注意:如果使用Spring Boot的話,下面的配置可以不需要。AopAutoConfiguration這個自動配置類中已經自動開啟了AOP
//默認使用CGLIB動態代理,Spring Boot配置的優先級高於下面的配置

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = false)
public class AopConfig {

}


如果使用傳統的配置方式的話,可按如下配置開啟AOP功能。

<aop:aspectj-autoproxy/>

定義一個Aspect

Aspects (classes annotated with @Aspect) can have methods and fields, the same as any other class. They can also contain pointcut, advice, and introduction (inter-type) declarations.

可以使用普通Bean的定義方式,或者加@Aspect註解的方式定義。一旦一個類被標註成切面類,它就不會成為其他切面的代理對象。

定義一個PointCut

切面表達式可以由指示器,通配符和運算符組成

  1. 指示器(Designators)
  • 匹配方法 execution() (重點掌握…)
  • 匹配註解 @target() @args() @within() @annotation()
  • 匹配包/類型 within()
  • 匹配對象 this() bean() target()
  • 匹配參數 args()
  1. Wildcards(通配符)
  • *匹配任意數量的字符
  • +匹配指定類及其子類
  • .. 一般用於匹配任意參數的子包或參數
  1. Operators(運算符)
  • && 與操作符
  • || 或操作符
  • ! 非操作符

下面給出一個定義PointCut的例子

package com.csx.demo.spring.boot.aspect;

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

@Component
@Aspect
public class MyAspect {

    //PointCut匹配的方法必須是Spring中bean的方法
    //Pointcut可以有下列方式來定義或者通過&& || 和!的方式進行組合.
    //下面定義的這些切入點就可以通過&& ||組合

    private static Logger logger = LoggerFactory.getLogger(MyAspect.class);

    //*:代表方法的返回值可以是任何類型
    //整個表達式匹配controller包下面任何的的echo方法,方法入參樂意是任意
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(..))")
    public void pointCut1(){}

    //代表echo方法必須有一個參數 參數的類型可以是任意類型
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(*))")
    public  void pointCut2(){}

    //代表echo方法必須有兩個參數,第一個類型任意,第二個類型必須是String
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(*,String))")
    public void pointCut3(){}

    //contrller包及其子包下面的任意類的任意方法
    //需要注意的是with和@with都是正對包級別的
    @Pointcut("within(com.csx.demo.spring.boot.controller..*)")
    public void pointCut4(){}

    //使用RestController這個註解標註任意類的任意方法
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void pointCut5(){}

    //用法和@Within類似
    @Pointcut("@target(org.springframework.web.bind.annotation.RestController)")
    public void pointCut10(){}

    //MyService這個接口實現類的任何方法
    //如果MyService是一個類的話,那匹配這個類內部的所有方法
    @Pointcut("this(com.csx.demo.spring.boot.service.MyService)")
    public void pointCut6(){}

    @Pointcut("this(com.csx.demo.spring.boot.service.MyServiceImpl)")
    public void pointCut7(){}

    //某個bean內部的所有方法
    @Pointcut("bean(myServiceImpl)")
    public void pointCut8(){}

    //@within和@target針對類的註解,@annotation是針對方法的註解
    //匹配任何標註GetMaping註解的方法
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void pointCut9(){}

    //匹配只有一個參數,參數類型是String的方法
    @Pointcut("args(String)")
    public void pointCut11(){}


    @Before("pointCut1()")
    public void befor(){
        logger.info("前置通知vvvv...");
        logger.info("我要做些事情...");
    }

    @After("pointCut1()")
    public void after(){
        logger.info("後置通知");
    }

    @AfterReturning("pointCut1()")
    public void afterReturn(){
       logger.info("後置返回");
    }

     //目標方法拋出相關異常后通知
    @AfterThrowing("pointCut1()")
    public void afterThrowing(){
        logger.info("後置異常");
    }

    @Around("pointCut1()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        logger.info("環繞通知...");
        logger.info("我要做些事情...");
        point.proceed();
        logger.info("結束環繞通知");
    }

}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?