SpringBoot 通過自定義注解實現AOP切面編程實例


一直心心念的想寫一篇關於AOP切面實例的博文,拖更了許久之后,今天終於着手下筆將其完成。

基礎概念

1、切面(Aspect)

首先要理解‘切’字,需要把對象想象成一個立方體,傳統的面向對象變成思維,類定義完成之后(封裝)。每次實例化一個對象,對類定義中的成員變量賦值,就相當於對這個立方體進行了一個定義,定義完成之后,那個對象就在那里,不卑不亢,不悲不喜,等着被使用,等着被回收。

面向切面編程則是指,對於一個我們已經封裝好的類,我們可以在編譯期間或在運行期間,對其進行切割,把立方體切開,在原有的方法里面添加(織入)一些新的代碼,對原有的方法代碼進行一次增強處理。而那些增強部分的代碼,就被稱之為切面,如下面代碼實例中的通用日志處理代碼,常見的還有事務處理、權限認證等等。

2、切入點(PointCut)

要對哪些類中的哪些方法進行增強,進行切割,指的是被增強的方法。即要切哪些東西。

3、連接點(JoinPoint)

我們知道了要切哪些方法后,剩下的就是什么時候切,在原方法的哪一個執行階段加入增加代碼,這個就是連接點。如方法調用前,方法調用后,發生異常時等等。

4、通知(Advice)

通知被織入方法,改如何被增強。定義切面的具體實現。那么這里面就涉及到一個問題,空間(切哪里)和時間(什么時候切,在何時加入增加代碼),空間我們已經知道了就是切入點中定義的方法,而什么時候切,則是連接點的概念,如下面實例中,通用日志處理(切面),@Pointcut規則中指明的方法即為切入點,@Before、@After是連接點,而下面的代碼就是對應通知。

@Before("cutMethod()")
public void begin() {
    System.out.println("==@Before== lingyejun blog logger : begin");
}

5、目標對象(Target Object)

被一個或多個切面所通知的對象,即為目標對象。

6、AOP代理對象(AOP Proxy Object)

AOP代理是AOP框架所生成的對象,該對象是目標對象的代理對象。代理對象能夠在目標對象的基礎上,在相應的連接點上調用通知。

7、織入(Weaving)

將切面切入到目標方法之中,使目標方法得到增強的過程被稱之為織入。

實例代碼

相關依賴包

<dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.6</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>1.3.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.2.8.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

定義和實現日志切面

package com.lingyejun.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author: Lingye
 * @Date: 2018/11/11
 * @Describe:
 * 定義日志切面
 * @Lazy 注解:容器一般都會在啟動的時候實例化所有單實例 bean,如果我們想要 Spring 在啟動的時候延遲加載 bean,需要用到這個注解
 * value為true、false 默認為true,即延遲加載,@Lazy(false)表示對象會在初始化的時候創建
 *
 * @Modified By:
 */
@Aspect
@Component
@Lazy(false)
public class LoggerAspect {

    /**
     * 定義切入點:對要攔截的方法進行定義與限制,如包、類
     *
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution(* set*(..)) 以set開頭的所有的方法
     * 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply這個類里的所有的方法
     * 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的類的所有的方法
     * 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的類的所有的方法
     * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的類的有三個參數,第一個參數為String類型,第二個參數為任意類型,第三個參數為Long類型的方法
     * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
    @Pointcut("@annotation(com.lingyejun.annotation.Lingyejun)")
    private void cutMethod() {

    }

    /**
     * 前置通知:在目標方法執行前調用
     */
    @Before("cutMethod()")
    public void begin() {
        System.out.println("==@Before== lingyejun blog logger : begin");
    }

    /**
     * 后置通知:在目標方法執行后調用,若目標方法出現異常,則不執行
     */
    @AfterReturning("cutMethod()")
    public void afterReturning() {
        System.out.println("==@AfterReturning== lingyejun blog logger : after returning");
    }

    /**
     * 后置/最終通知:無論目標方法在執行過程中出現一場都會在它之后調用
     */
    @After("cutMethod()")
    public void after() {
        System.out.println("==@After== lingyejun blog logger : finally returning");
    }

    /**
     * 異常通知:目標方法拋出異常時執行
     */
    @AfterThrowing("cutMethod()")
    public void afterThrowing() {
        System.out.println("==@AfterThrowing== lingyejun blog logger : after throwing");
    }

    /**
     * 環繞通知:靈活自由的在目標方法中切入代碼
     */
    @Around("cutMethod()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 獲取目標方法的名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取方法傳入參數
        Object[] params = joinPoint.getArgs();
        Lingyejun lingyejun = getDeclaredAnnotation(joinPoint);
        System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
        // 執行源方法
        joinPoint.proceed();
        // 模擬進行驗證
        if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth success");
        } else {
            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth failed");
        }
    }

    /**
     * 獲取方法中聲明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public Lingyejun getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        // 獲取方法名
        String methodName = joinPoint.getSignature().getName();
        // 反射獲取目標類
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法對應的參數類型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根據類、方法、參數類型(重載)獲取到方法的具體信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        // 拿到方法定義的注解信息
        Lingyejun annotation = objMethod.getDeclaredAnnotation(Lingyejun.class);
        // 返回
        return annotation;
    }
}

自定義一個注解

package com.lingyejun.annotation;

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

/**
 * @Author: Lingye
 * @Date: 2018/11/11
 * @Describe: 
 * @Modified By:
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lingyejun {

    /**
     * 何種場景下的通用日志打印
     *
     * @return
     */
    String module();
}  

調用切面類

package com.lingyejun.annotation;

import org.springframework.stereotype.Component;

/**
 * @Author: Lingye
 * @Date: 2018/11/11
 * @Describe:
 * @Modified By:
 */
@Component
public class LoggerApply {

    @Lingyejun(module = "http://www.cnblogs.com/lingyejun/")
    public void lingLogger(String event) throws Exception {
        System.out.println("lingLogger(String event) : lingyejun will auth by blog address");
        throw new Exception();
    }
}  

測試代碼

package com.lingyejun.annotation;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class AnnotationTest {

    @Autowired
    private LoggerApply loggerApply;

    @Test
    public void testAnnotationLogger() {
        try {
            loggerApply.lingLogger("Blog Home");
        } catch (Exception e) {
            System.out.println("a exception be there");
        }
    }
}

效果展示

 


免責聲明!

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



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