04_SpringBoot自定義註解

一、源碼地址

https://github.com/dianjiu/spring-boot-learn

https://gitee.com/dianjiu/spring-boot-learn

https://gitee.com/point9/spring-boot-learn

二、目錄結構


三、源碼介紹

Java自定義註解一般使用場景爲:自定義註解+攔截器或者AOP,使用自定義註解來自己設計框架,使得代碼看起來非常優雅。
 

1、常用元註解

Target:描述了註解修飾的對象範圍,取值在java.lang.annotation.ElementType定義,常用的包括:

  • METHOD:用於描述方法
  • PACKAGE:用於描述包
  • PARAMETER:用於描述方法變量
  • TYPE:用於描述類、接口或enum類型

Retention: 表示註解保留時間長短。取值在java.lang.annotation.RetentionPolicy中,取值爲:

  • SOURCE:在源文件中有效,編譯過程中會被忽略
  • CLASS:隨源文件一起編譯在class文件中,運行時忽略
  • RUNTIME:在運行時有效

 

2、引入依賴

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

3、編寫自定義註解

package cn.point9.aspect.annotation;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface MyLog {

}

4、編寫AOP切面

package cn.point9.aspect.annotation;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;

@Slf4j
@Aspect
@Component
public class MyLogAspect {

    // 2. PointCut表示這是一個切點,@annotation表示這個切點切到一個註解上,後面帶該註解的全類名
    // 切面最主要的就是切點,所有的故事都圍繞切點發生
    // logPointCut()代表切點名稱
    @Pointcut("@annotation(cn.point9.aspect.annotation.MyLog)")
    public void logPointCut() {
    }

    ;

    // 3. 環繞通知
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        // 獲取連接點所在類名
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        // 獲取連接點所在方法名稱
        String methodName = joinPoint.getSignature().getName();
        // 下面兩個數組中,參數值和參數名的個數和位置是一一對應的。
        // 參數名
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 參數值
        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        //不爲空時便利組裝
        if (argNames.length > 0 && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                sb.append(argNames[i] + ":" + args[i] + ";");
            }
        }
        long startTime = System.currentTimeMillis();
        log.info("\n進入【" + classname + "." + methodName + "()】方法的時間是:" + sdf.format(startTime) + "\n參數爲:" + sb.toString());

        //執行到這裏開始走進來的方法體(必須聲明)
        try {
            joinPoint.proceed(args);
        } catch (Throwable throwable) {
            log.error("MyLogAspect logAround Exception By Method:" + classname + "." + methodName);
            throwable.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        long periodTime = endTime - startTime;
        log.info("\n離開【" + classname + "." + methodName + "()】方法的時間是:" + sdf.format(endTime));
        log.info("\n在【" + classname + "." + methodName + "()】方法中總共耗時爲:" + periodTime + " 毫秒");

    }
}

5、拓展Aspect的執行順序

package cn.point9.aspect.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Around最先執行
 * 然後在執行proceed方法之前,Before先執行
 * 然後纔是方法體本身
 * 然後是Around再結尾
 * 最後纔是After
 */
@Component
@Aspect
@Order(10)
public class PermisionAspect {

    @Pointcut("@annotation(PermisionAnnotation)")
    private void AnnotationPointCut() { }

    /**
     * 定製一個環繞通知
     * @param joinPoint
     */
    @Around("AnnotationPointCut()&&@annotation(permisionAnnotation)")
    public void around(ProceedingJoinPoint joinPoint, PermisionAnnotation permisionAnnotation) throws Throwable {
        System.out.println("Around Begin");
        String value=permisionAnnotation.value();//獲取註解中的傳值
        System.out.println(value);
        Method method = getMethod(joinPoint);
        PermisionAnnotation permisionAnnotation1 = method.getAnnotation(PermisionAnnotation.class);
        joinPoint.proceed();//執行到這裏開始走進來的方法體(必須聲明)
        System.out.println("Around End");
    }

    private Method getMethod(JoinPoint joinPoint) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        return method;
    }


    //當想獲得註解裏面的屬性,可以直接注入改註解
    //方法可以帶參數,可以同時設置多個方法用&&
    @Before("AnnotationPointCut()")
    public void before(JoinPoint joinPoint) throws Exception {
        Method method = getMethod(joinPoint);
        PermisionAnnotation permisionAnnotation = method.getAnnotation(PermisionAnnotation.class);
        //通過反射可獲得註解上的屬性,然後做日誌記錄相關的操作。
        System.out.println("Before");
    }

    @After("AnnotationPointCut()")
    public void after(JoinPoint joinPoint) throws Exception {
        Method method = getMethod(joinPoint);
        PermisionAnnotation permisionAnnotation = method.getAnnotation(PermisionAnnotation.class);
        System.out.println("After");
    }

}

6、演示的Controller

package cn.point9.aspect.controller;

import cn.point9.aspect.annotation.MyLog;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class DemoController {

    @MyLog
    @GetMapping("/sourceC/{source_name}")
    @ResponseBody
    public String sourceC(@PathVariable("source_name") String sourceName){
        return "你正在訪問sourceC資源";
    }
}

四、效果演示

 五、技術交流

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章