Spring Boot Aop

spring-boot-aop

什么是aop

面向切面的程序設計(Aspect-oriented programming,AOP滑负,又譯作面向方面的程序設計、剖面導向程序設計)是計算機科學中的一種程序設計思想用含,旨在將橫切關注點與業(yè)務主體進行進一步分離矮慕,以提高程序代碼的模塊化程度。通過在現有代碼基礎上增加額外的通知(Advice)機制啄骇,能夠對被聲明為“切點(Pointcut)”的代碼塊進行統(tǒng)一管理與裝飾痴鳄,如“對所有方法名以‘set*’開頭的方法添加后臺日志”。該思想使得開發(fā)人員能夠將與代碼核心業(yè)務邏輯關系不那么密切的功能(如日志功能)添加至程序中缸夹,同時又不降低業(yè)務代碼的可讀性痪寻。面向切面的程序設計思想也是面向切面軟件開發(fā)的基礎螺句。

面向切面的程序設計將代碼邏輯切分為不同的模塊(即關注點(Concern),一段特定的邏輯功能)橡类。幾乎所有的編程思想都涉及代碼功能的分類蛇尚,將各個關注點封裝成獨立的抽象模塊(如函數、過程顾画、模塊取劫、類以及方法等),后者又可供進一步實現研侣、封裝和重寫谱邪。部分關注點“橫切”程序代碼中的數個模塊,即在多個模塊中都有出現庶诡,它們即被稱作“橫切關注點(Cross-cutting concerns, Horizontal concerns)”惦银。

日志功能即是橫切關注點的一個典型案例,因為日志功能往往橫跨系統(tǒng)中的每個業(yè)務模塊灌砖,即“橫切”所有有日志需求的類及方法體璧函。而對于一個信用卡應用程序來說,存款基显、取款蘸吓、帳單管理是它的核心關注點,日志和持久化將成為橫切整個對象結構的橫切關注點撩幽。

切面的概念源于對面向對象的程序設計的改進库继,但并不只限于此,它還可以用來改進傳統(tǒng)的函數窜醉。與切面相關的編程概念還包括元對象協議宪萄、主題(Subject)、混入(Mixin)和委托(Delegate)榨惰。

AOP中的相關概念

看過了上面解釋拜英,想必大家對aop已經有個大致的雛形了,但是又對上面提到的切面之類的術語有一些模糊的地方,接下來就來講解一下AOP中的相關概念琅催,了解了AOP中的概念居凶,才能真正的掌握AOP的精髓。

  • Aspect(切面): Aspect 聲明類似于 Java 中的類聲明藤抡,在 Aspect 中會包含著一些 Pointcut 以及相應的 Advice侠碧。

  • Joint point(連接點):表示在程序中明確定義的點,典型的包括方法調用缠黍,對類成員的訪問以及異常處理程序塊的執(zhí)行等等弄兜,它自身還可以嵌套其它 joint point。

  • Pointcut(切點):表示一組 joint point,這些 joint point 或是通過邏輯關系組合起來替饿,或是通過通配贬堵、正則表達式等方式集中起來晒哄,它定義了相應的 Advice 將要發(fā)生的地方。

  • Advice(增強):Advice 定義了在 Pointcut 里面定義的程序點具體要做的操作,它通過 before疏旨、after 和 around 來區(qū)別是在每個 joint point 之前侈沪、之后還是代替執(zhí)行的代碼堤撵。

  • Target(目標對象):織入 Advice 的目標對象.价脾。
    Weaving(織入):將 Aspect 和其他對象連接起來, 并創(chuàng)建 Adviced object 的過程

spring aop

Spring AOP使用純Java實現,它不需要專門的編譯過程蝶俱,也不需要特殊的類裝載器班利,它在運行期通過代理方式向目標類織入增強代碼。在Spring中可以無縫地將Spring AOP榨呆、IoC和AspectJ整合在一起罗标。Spring AOP構建在動態(tài)代理基礎之上,因此积蜻,Spring對AOP的支持局限于方法攔截闯割。在Java中動態(tài)代理有兩種方式:JDK動態(tài)代理和CGLib動態(tài)代理

  • jdk proxy

java動態(tài)代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理竿拆。


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class JdkProxy {

    interface IUserService {
        Integer delete(Integer userId);
    }

    static class UserServiceImpl implements IUserService {
        @Override
        public Integer delete(Integer userId) {
            // 業(yè)務
            System.out.println("delete user");
            return userId;
        }
    }

    // 自定義InvocationHandler
    static class UserServiceProxy implements InvocationHandler {
        // 目標對象
        private Object target;

        public UserServiceProxy(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------方法調用前---------");
            //執(zhí)行相應的目標方法
            Object result = method.invoke(target, args);
            System.out.println("------方法調用后---------");
            return result;
        }
    }

    public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        // 創(chuàng)建調用處理類
        UserServiceProxy handler = new UserServiceProxy(userService);
        // 得到代理類實例
        IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),
                                 new Class[]{IUserService.class}, handler);
        // 調用代理類的方法
        Integer userId = proxy.delete(3);
        System.out.println(userId);
    }

}

  • cglib proxy

而cglib動態(tài)代理是利用asm開源包宙拉,對代理對象類的class文件加載進來,通過修改其字節(jié)碼生成子類來處理丙笋。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class CglibProxy {

    static class UserService implements MethodInterceptor {

        private Object target;

        /**
         * 業(yè)務方法
         *
         * @param userId
         * @return
         */
        public Integer delete(Integer userId) {
            System.out.println("delete user");
            return userId;
        }

        /**
         * 利用Enhancer類生成代理類
         *
         * @param target
         * @return
         */
        public Object getInstance(Object target) {
            this.target = target;
            // 創(chuàng)建加強器谢澈,用來創(chuàng)建動態(tài)代理類
            Enhancer enhancer = new Enhancer();
            // 為加強器指定要代理的業(yè)務類(即:為下面生成的代理類指定父類)
            enhancer.setSuperclass(target.getClass());
            // 設置回調:對于代理類上所有方法的調用,都會調用CallBack御板,而Callback則需要實現intercept()方法進行攔
            enhancer.setCallback(this);
            // 創(chuàng)建動態(tài)代理類對象并返回
            return enhancer.create();
        }


        /**
         * @param o
         * @param method
         * @param objects
         * @param methodProxy
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
                                throws Throwable {
            System.out.println("------方法調用前---------");
            Object object = methodProxy.invokeSuper(o, objects);
            System.out.println("------方法調用后---------");
            return object;
        }

    }

    public static void main(String[] args) {
        UserService userService = new UserService();
        UserService proxy = (UserService) userService.getInstance(userService);
        Integer userId = proxy.delete(2);
        System.out.println(userId);
    }

}

1锥忿、如果目標對象實現了接口,默認情況下會采用JDK的動態(tài)代理實現AOP怠肋,可以強制使用CGLIB實現AOP
2敬鬓、如果目標對象沒有實現了接口,必須采用CGLIB庫笙各,spring會自動在JDK動態(tài)代理和CGLIB之間轉換

spirng boot aop

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Advice的主要類型

  • @Before:該注解標注的方法在業(yè)務模塊代碼執(zhí)行之前執(zhí)行钉答,其不能阻止業(yè)務模塊的執(zhí)行,除非拋出異常酪惭;

  • @AfterReturning:該注解標注的方法在業(yè)務模塊代碼執(zhí)行之后執(zhí)行希痴;

  • @AfterThrowing:該注解標注的方法在業(yè)務模塊拋出指定異常后執(zhí)行者甲;

  • @After:該注解標注的方法在所有的Advice執(zhí)行完成后執(zhí)行春感,無論業(yè)務模塊是否拋出異常,類似于finally的作用;

  • @Around:該注解功能最為強大鲫懒,其所標注的方法用于編寫包裹業(yè)務模塊執(zhí)行的代碼嫩实,其可以傳入一個ProceedingJoinPoint用于調用業(yè)務模塊的代碼,無論是調用前邏輯還是調用后邏輯窥岩,都可以在該方法中編寫甲献,甚至其可以根據一定的條件而阻斷業(yè)務模塊的調用;

  • @DeclareParents:其是一種Introduction類型的模型颂翼,在屬性聲明上使用晃洒,主要用于為指定的業(yè)務模塊添加新的接口和相應的實現。

切點表達式

1.通配符

  • [*] 匹配任意字符朦乏,但只能匹配一個元素

  • [..] 匹配任意字符球及,可以匹配任意多個元素,表示類時呻疹,必須和*聯合使用

  • [+] 必須跟在類名后面吃引,如Horseman+,表示類本身和繼承或擴展指定類的所有類

2.邏輯運算符

表達式可由多個切點函數通過邏輯運算組成

  • && 與操作刽锤,求交集镊尺,也可以寫成and

例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子類的chop方法

  • || 或操作,任一表達式成立即為true并思,也可以寫成 or

例如 execution(* chop(..)) || args(String) 表示名稱為chop的方法或者有一個String型參數的方法

  • ! 非操作庐氮,表達式為false則結果為true,也可以寫成 not

例如 execution(* chop(..)) and !args(String) 表示名稱為chop的方法但是不能是只有一個String型參數的方法

  • execution() 方法匹配模式串

表示滿足某一匹配模式的所有目標類方法連接點纺荧。如execution(* save(..))表示所有目標類中的 save()方法旭愧。

由于Spring切面粒度最小是達到方法級別,而execution表達式可以用于明確指定方法返回類型宙暇,類名输枯,方法名和參數名等與方法相關的部件,并且在Spring中占贫,大部分需要使用AOP的業(yè)務場景也只需要達到方法級別即可桃熄,因而execution表達式的使用是最為廣泛的。如下是execution表達式的語法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

execution(<修飾符> <返回類型> <類路徑> <方法名>(<參數列表>) <異常模式> )

  • modifiers-pattern:方法的可見性型奥,如public瞳收,protected;

  • ret-type-pattern:方法的返回值類型厢汹,如int螟深,void等;

  • declaring-type-pattern:方法所在類的全路徑名烫葬,如com.spring.Aspect界弧;

  • name-pattern:方法名類型凡蜻,如buisinessService();

  • param-pattern:方法的參數類型垢箕,如java.lang.String划栓;

  • throws-pattern:方法拋出的異常類型,如java.lang.Exception条获;

切點函數

  • @annotation(annotation-type) 方法注解類名

    如下示例表示匹配使用com.leone.aop.AopTest注解標注的方法:

    @annotation(com.leone.aop.AopTest)

  • args(param-pattern) 方法入參切點函數

    如下示例表示匹配所有只有一個參數忠荞,并且參數類型是java.lang.String類型的方法:

    args(java.lang.String)

  • @args(annotation-type) 方法入參類注解切點函數

    如下示例表示匹配使用了com.leone.aop.AopTest注解標注的類作為參數的方法:

    @args(com.leone.aop.AopTest)

  • within(declaring-type-pattern) 類名匹配切點函數

    within表達式只能指定到類級別,如下示例表示匹配com.leone.aop.UserService中的所有方法:

    within(com.leone.aop.UserService)

  • @within(annotation-type) 類注解匹配切點函數

    如下示例表示匹配使用org.springframework.web.bind.annotation.RestController注解標注的類:

    @within(org.springframework.web.bind.annotation.RestController)

  • target(declaring-type-pattern) 類名切點函數

    如下示例表示匹配com.leone.aop.UserService中的所有方法:

    target(com.leone.aop.UserService)

  • this

spring-boot-aop 實戰(zhàn)

  • 配置切面類帅掘,實現代理

1.在類上使用 @Component 注解把切面類加入到IOC容器中
2.在類上使用 @Aspect 注解使之成為切面類

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 描述一個切面類
 *
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@Aspect
@Component
public class AopConfig {

    /**
     * 1.通配符
     * [*]  匹配任意字符委煤,但只能匹配一個元素
     * <p>
     * [..] 匹配任意字符,可以匹配任意多個元素修档,表示類時素标,必須和*聯合使用
     * <p>
     * [+]  必須跟在類名后面,如Horseman+萍悴,表示類本身和繼承或擴展指定類的所有類
     * <p>
     * 切點表達式分為 修飾符  返回類型  包路徑  方法名  參數
     * <p>
     * 2.切點表達式
     * <p>
     * 3.邏輯運算符
     * 表達式可由多個切點函數通過邏輯運算組成
     * ** && 與操作头遭,求交集,也可以寫成and
     * <p>
     * 例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子類的chop方法
     * <p>
     * ** || 或操作癣诱,任一表達式成立即為true计维,也可以寫成 or
     * <p>
     * 例如 execution(* chop(..)) || args(String)  表示名稱為chop的方法或者有一個String型參數的方法
     * <p>
     * ** ! 非操作,表達式為false則結果為true撕予,也可以寫成 not
     * <p>
     * 例如 execution(* chop(..)) and !args(String)  表示名稱為chop的方法但是不能是只有一個String型參數的方法
     */
    @Pointcut("execution(* com.leone.boot.aop.service.*.*(..))")
    public void pointCut() {
    }

    /**
     * 環(huán)繞通知在 target 開始和結束執(zhí)行
     *
     * @param point
     * @return
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint point) {
        long start = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        log.info("around method name: {} params: {}", methodName, Arrays.asList(point.getArgs()));
        try {
            log.info("around end time: {}", (System.currentTimeMillis() - start) + " ms!");
            return point.proceed();
        } catch (Throwable e) {
            log.error("message: {}", e.getMessage());
        }
        return null;
    }

    /**
     * 前置通知在 target 前執(zhí)行
     *
     * @param joinPoint
     */
    // @Before("@annotation(com.leone.boot.aop.anno.AopBefore)")
    // @Before("within(com.leone.boot.aop.controller.*)")
    // @Before("@within(org.springframework.web.bind.annotation.RestController)")
    // @Before("target(com.leone.boot.aop.controller.UserController)")
    @Before("@target(com.leone.boot.aop.anno.ClassAop) && @annotation(com.leone.boot.aop.anno.AopBefore)")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("before inform method name: {} param: {}", methodName, args);
    }

    /**
     * 后置通知在target后執(zhí)行
     *
     * @param joinPoint
     */
    @After("@args(org.springframework.stereotype.Component) && 
            execution(* com.leone.boot.aop.controller.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("after inform method name : {} param: {}", methodName, args);
    }

    /**
     * 后置返回在target返回后執(zhí)行
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "within(com.leone.boot.aop.controller.*)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterReturning inform method name: {} return value: {}", methodName, result);
    }

    /**
     * 后置異常通知在target異常后執(zhí)行
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "args(com.leone.boot.common.entity.User) && 
            execution(* com.leone.boot.aop.controller.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterThrowing inform method name: {} exceptions: {}" + methodName, ex);
    }
}

  • 測試類
import com.leone.boot.aop.anno.AopBefore;
import com.leone.boot.aop.anno.ClassAop;
import com.leone.boot.aop.interf.UserService;
import com.leone.boot.common.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@ClassAop
@RestController
@RequestMapping("/api")
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


    @AopBefore
    @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
    public User findOne(@PathVariable Long userId) {
        return userService.findOne(userId);
    }

    @AopBefore
    @RequestMapping("/user")
    public User save(User user) {
        return user;
    }
}

github

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末鲫惶,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子实抡,更是在濱河造成了極大的恐慌欠母,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吆寨,死亡現場離奇詭異赏淌,居然都是意外死亡,警方通過查閱死者的電腦和手機啄清,發(fā)現死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門六水,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辣卒,你說我怎么就攤上這事掷贾。” “怎么了荣茫?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵想帅,是天一觀的道長。 經常有香客問我啡莉,道長港准,這世上最難降的妖魔是什么憎乙? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮叉趣,結果婚禮上,老公的妹妹穿的比我還像新娘该押。我一直安慰自己疗杉,他們只是感情好,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布蚕礼。 她就那樣靜靜地躺著烟具,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奠蹬。 梳的紋絲不亂的頭發(fā)上朝聋,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天,我揣著相機與錄音囤躁,去河邊找鬼冀痕。 笑死,一個胖子當著我的面吹牛狸演,可吹牛的內容都是我干的言蛇。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼宵距,長吁一口氣:“原來是場噩夢啊……” “哼腊尚!你這毒婦竟也來了?” 一聲冷哼從身側響起满哪,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤婿斥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哨鸭,有當地人在樹林里發(fā)現了一具尸體民宿,經...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年像鸡,在試婚紗的時候發(fā)現自己被綠了勘高。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡坟桅,死狀恐怖华望,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情仅乓,我是刑警寧澤赖舟,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站夸楣,受9級特大地震影響宾抓,放射性物質發(fā)生泄漏子漩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一石洗、第九天 我趴在偏房一處隱蔽的房頂上張望幢泼。 院中可真熱鬧,春花似錦讲衫、人聲如沸缕棵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽招驴。三九已至,卻和暖如春枷畏,著一層夾襖步出監(jiān)牢的瞬間别厘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工拥诡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留触趴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓渴肉,卻偏偏與公主長得像雕蔽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宾娜,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內容