Spring AOP 代碼示例


AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等。其實說起aop大家都對其有一定的概念。今天主要是總結一下他代碼中的實現方式,以方便后邊查看使用。

說起AOP,首先我們需要先了解其中的幾個概念:

1.通知(Advice)

   AOP在特定的切入點上執行的增強處理,就是你想要的功能,也就是上面說的日志、事務、權限等。有before(前置),after(后置),afterReturning(最終),afterThrowing(異常),around(環繞),就是在上面這幾中類型所實現的功能。比如在使用redis操作前打印日志。

2.連接點(JoinPoint)

  就是spring允許你使用通知的地方,例如每個方法的前,后(兩者都有也行),或拋出異常時,也就是說和方法有關的前后(拋出異常),都是連接點。Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器,如aspectJ。

3.切入點(Pointcut)

  就是帶有通知的連接點,在連接點的基礎上來定義切入點,假如一個類里面有5個方法,那就有5個連接點,但是並不想在所有方法上都實現切面的功能,只是想讓其中的幾個方法在調用之前,之后或者拋出異常時干點什么,那么就用切點來定義這幾個方法,讓切點來篩選連接點,選中那幾個你想要的方法。 在程序中主要體現為書寫切入點表達式。

4.切面(Aspect)

  通常是一個類,里面可以定義切入點和通知。通知說明了干什么和什么時候干(之前,之后,還是異常),而切入點說明了在哪干(指定到底是哪個方法),這就是一個完整的切面定義。

5.引入(introduction)

  在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段。就是把我們定義的功能用到目標類中。

6.目標(target)

  包含連接點的對象。也被稱作被通知或被代理對象。就是上面引入中所提到的目標類。

7.代理(proxy)

   AOP框架創建的對象,代理就是目標對象的加強版本。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於接口,后者基於子類。

8.織入(weaving)

   把切面應用到目標對象來創建新的代理對象的過程。有3種方式,spring采用的是運行時。

下面我們用注解的方式實現下AOP的功能。
增加AOP所需要的依賴包
        <!--AOP依賴包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

定義一個切面類。

package com.example.demo.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component //讓spring能夠掃描到
@Aspect //定義這是一個切面類
public class LogAspect {

    /**
     * modifier-pattern:用於匹配public、private等訪問修飾符
     * ret-type-pattern:用於匹配返回值類型,不可省略
     * declaring-type-pattern:用於匹配包類型
     * modifier-pattern(param-pattern):用於匹配類中的方法,不可省略
     * throws-pattern:用於匹配拋出異常的方法
     *
     *
     * 多個表達式之間使用連接符匹配多個條件, 如使用||表示“或”,使用 &&表示“且”
     */
    @Pointcut("@annotation(com.example.demo.annotation.LogAop) &&" +
            "execution(public * com.example.demo.service.impl.AopTestServiceImpl.get*(..))")
    public void log(){

    }

    /**
     * 匹配com.example.demo.service.impl包下所有類下的方法名以update結尾、參數類型不限的public方法
     */
    @Pointcut("execution(public * com.example.demo.service.impl.*.*update(..))")
    public void say(){

    }

    @Order(2)
    @Before("log()")
    public void beforeLog2(){
        System.out.println("后執行,增加log()方法...");
    }

    @Order(1) // Order 代表優先級,數字越小優先級越高
    @Before("log()||say()") //多個的話用 @Before("log()||say()")
    public void beforeLog1(){
        System.out.println("先執行,增加log()方法...");
    }

    /**
     * Advice注解一共有五種,分別是:
     * 1.@Before前置通知
     * 前置通知在切入點運行前執行,不會影響切入點的邏輯
     * 2.@After后置通知
     * 后置通知在切入點正常運行結束后執行,如果切入點拋出異常,則在拋出異常前執行
     * 3.@AfterThrowing異常通知
     * 異常通知在切入點拋出異常前執行,如果切入點正常運行(未拋出異常),則不執行
     * 4.@AfterReturning返回通知
     * 返回通知在切入點正常運行結束后執行,如果切入點拋出異常,則不執行
     * 5.@Around環繞通知
     * 環繞通知是功能最強大的通知,可以在切入點執行前后自定義一些操作。環繞通知需要負責決定是繼續處理join point(調用ProceedingJoinPoint的proceed方法)還是中斷執行
     */
    @Before("say()")
    public void beforeSay(){
        System.out.println("增加say()方法...");
    }
}

此類就是一個切面,其中一共定義了兩個切入點,一個是log(),一個是say()。在滿足切入點要求的方法前會做三中類型的通知,也就是beforeLog21(),beforeLog2(),beforeSay()。為什么是方法前呢,因為用的注解是@Before。其他具體的信息,可以看代碼里面的注釋。里面用到了一個我們自定義的注解,其代碼如下:

package com.example.demo.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
public @interface LogAop {

    /**
     * 使用@annotation、@within、@target、@args匹配注解
     * //匹配標注有LogAop注解的方法
     * @Pointcut("@annotation(com.example.demo.annotation.LogAop)")
     * public void matchAnno() {}
     *
     * //匹配標注有LogAop的類底下的方法,要求annotation的Retention級別為CLASS
     * @Pointcut("@within(com.example.demo.annotation.LogAop)")
     * public void matchWithin() {}
     *
     * //匹配標注有LogAop的類底下的方法,要求annotation的Retention級別為RUNTIME
     * @Pointcut("@target(com.example.demo.annotation.LogAop)")
     * public void matchTarget() {}
     *
     * //匹配傳入的參數類標注有LogAop注解的方法
     * @Pointcut("@args(com.example.demo.annotation.LogAop)")
     * public void matchArgs() {}
     */
}

然后就是我們的業務處理代碼,也就是目標對象。

package com.example.demo.service.impl;

import com.example.demo.annotation.LogAop;
import com.example.demo.model.Student;
import com.example.demo.service.GetStudentService;
import org.springframework.stereotype.Service;

/**
* @Description: AOP測試
* @Author:      haoqiangwang3
* @CreateDate:  2020/1/13
*/
@Service
public class AopTestServiceImpl implements GetStudentService {

    @LogAop
    @Override
    public Student getStudentInfo() {
        System.out.println("調用業務處理中的get()方法...");
        return null;
    }

    @Override
    public int update() {
        System.out.println("調用業務處理中的update()方法...");
        return 0;
    }
}

再就是程序的入口,controlle方法

package com.example.demo.controller;

import com.example.demo.service.impl.AopTestServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Description: AOP測試controller
* @Author:      haoqiangwang3
* @CreateDate:  2020/1/13
*/
@RestController
public class AopController {
    @Autowired
    private AopTestServiceImpl aopTestServiceImpl;

    @RequestMapping("/boot/getAop")
    public String aopGet(){
        aopTestServiceImpl.getStudentInfo();
        return "success";
    }

    @RequestMapping("/boot/updateAop")
    public String aopUpdate(){
        aopTestServiceImpl.update();
        return "success";
    }
}

上面的代碼是在我之前學習spring boot程序的基礎上增加的。以上完成后,就可以測試我們的aop功能了,測試結果就不粘貼了,親測是可以生效的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM