Spring Boot Aop

spring-boot-aop

什麼是aop

面向切面的程序設計(Aspect-oriented programming,AOP,又譯做面向方面的程序設計、剖面導向程序設計)是計算機科學中的一種程序設計思想,旨在將橫切關注點與業務主體進行進一步分離,以提升程序代碼的模塊化程度。經過在現有代碼基礎上增長額外的通知(Advice)機制,可以對被聲明爲「切點(Pointcut)」的代碼塊進行統一管理與裝飾,如「對全部方法名以‘set*’開頭的方法添加後臺日誌」。該思想使得開發人員可以將與代碼核心業務邏輯關係不那麼密切的功能(如日誌功能)添加至程序中,同時又不下降業務代碼的可讀性。面向切面的程序設計思想也是面向切面軟件開發的基礎。java

面向切面的程序設計將代碼邏輯切分爲不一樣的模塊(即關注點(Concern),一段特定的邏輯功能)。幾乎全部的編程思想都涉及代碼功能的分類,將各個關注點封裝成獨立的抽象模塊(如函數、過程、模塊、類以及方法等),後者又可供進一步實現、封裝和重寫。部分關注點「橫切」程序代碼中的數個模塊,即在多個模塊中都有出現,它們即被稱做「橫切關注點(Cross-cutting concerns, Horizontal concerns)」。git

日誌功能便是橫切關注點的一個典型案例,由於日誌功能每每橫跨系統中的每一個業務模塊,即「橫切」全部有日誌需求的類及方法體。而對於一個信用卡應用程序來講,存款、取款、賬單管理是它的核心關注點,日誌和持久化將成爲橫切整個對象結構的橫切關注點。github

切面的概念源於對面向對象的程序設計的改進,但並不僅限於此,它還能夠用來改進傳統的函數。與切面相關的編程概念還包括元對象協議、主題(Subject)、混入(Mixin)和委託(Delegate)。web

AOP中的相關概念

看過了上面解釋,想必你們對aop已經有個大體的雛形了,可是又對上面提到的切面之類的術語有一些模糊的地方,接下來就來說解一下AOP中的相關概念,瞭解了AOP中的概念,才能真正的掌握AOP的精髓。正則表達式

  • Aspect(切面): Aspect 聲明相似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。spring

  • Joint point(鏈接點):表示在程序中明肯定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還能夠嵌套其它 joint point。編程

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

  • Advice(加強):Advice 定義了在 Pointcut 裏面定義的程序點具體要作的操做,它經過 before、after 和 around 來區別是在每一個 joint point 以前、以後仍是代替執行的代碼。bash

  • Target(目標對象):織入 Advice 的目標對象.。 Weaving(織入):將 Aspect 和其餘對象鏈接起來, 並建立 Adviced object 的過程app

spring aop

Spring AOP使用純Java實現,它不須要專門的編譯過程,也不須要特殊的類裝載器,它在運行期經過代理方式向目標類織入加強代碼。在Spring中能夠無縫地將Spring AOP、IoC和AspectJ整合在一塊兒。Spring AOP構建在動態代理基礎之上,所以,Spring對AOP的支持侷限於方法攔截。在Java中動態代理有兩種方式:JDK動態代理和CGLib動態代理

  • jdk proxy

java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用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) {
            // 業務
            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("------方法調用前---------");
            //執行相應的目標方法
            Object result = method.invoke(target, args);
            System.out.println("------方法調用後---------");
            return result;
        }
    }

    public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        // 建立調用處理類
        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動態代理是利用asm開源包,對代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理。

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;

        /**
         * 業務方法
         *
         * @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;
            // 建立增強器,用來建立動態代理類
            Enhancer enhancer = new Enhancer();
            // 爲增強器指定要代理的業務類(即:爲下面生成的代理類指定父類)
            enhancer.setSuperclass(target.getClass());
            // 設置回調:對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實現intercept()方法進行攔
            enhancer.setCallback(this);
            // 建立動態代理類對象並返回
            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);
    }

}

複製代碼

一、若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP,能夠強制使用CGLIB實現AOP 二、若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

spirng boot aop

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
複製代碼

Advice的主要類型

  • @Before:該註解標註的方法在業務模塊代碼執行以前執行,其不能阻止業務模塊的執行,除非拋出異常;

  • @AfterReturning:該註解標註的方法在業務模塊代碼執行以後執行;

  • @AfterThrowing:該註解標註的方法在業務模塊拋出指定異常後執行;

  • @After:該註解標註的方法在全部的Advice執行完成後執行,不管業務模塊是否拋出異常,相似於finally的做用;

  • @Around:該註解功能最爲強大,其所標註的方法用於編寫包裹業務模塊執行的代碼,其能夠傳入一個ProceedingJoinPoint用於調用業務模塊的代碼,不管是調用前邏輯仍是調用後邏輯,均可以在該方法中編寫,甚至其能夠根據必定的條件而阻斷業務模塊的調用;

  • @DeclareParents:其是一種Introduction類型的模型,在屬性聲明上使用,主要用於爲指定的業務模塊添加新的接口和相應的實現。

切點表達式

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的業務場景也只須要達到方法級別便可,於是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 實戰

  • 配置切面類,實現代理

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() {
    }

    /**
     * 環繞通知在 target 開始和結束執行
     *
     * @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 前執行
     *
     * @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後執行
     *
     * @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返回後執行
     *
     * @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異常後執行
     *
     * @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

相關文章
相關標籤/搜索