五分鐘!帶你重新瞭解自定義註解,看完還不懂算我輸! 前言 註解作用 什麼是內置註解,元註解,自定義註解? 自定義註解+切面實現統一日誌處理 最後

前言

日常開發中用到了各式各樣的註解,常用的註解@Override、@param、@Autowired、@Service等等,這些都是JDK或者Spring這類框架自帶。在類,方法,變量,參數,包都可以用註解來註釋。很多小夥伴可能還停留在使用層面,知道怎麼用,但並不知道實現原理,更沒親自寫過自定義註解運用在實際項目中解決問題。

接下來聊聊註解的基礎,再聊聊自定義註解在實際項目中的使用。

註解作用

1、生成文檔,早期最常見的@return,@param

2、在編譯時進行檢查,例如@Override,檢查是否重寫父類

3、簡化配置文件,使得代碼更清晰

什麼是內置註解,元註解,自定義註解?

內置註解

@Override:作用在方法上,聲明重寫父類的方法

@Deprecated:作用方法,屬性,或者類上,標識已過時

@SuppressWarings:用於抑制編譯器警告,告知編譯器,忽略它們產生了特殊警告

@SafeVarargs:是jdk1.7引入的註解,作用抑制在使用泛型和可變參數搭配使用產生的編譯器告警

元註解:

元註解是由JDK5.0開始提供的,不能更改,用於定義其他註解,也就是對我們自定義註解進行定義


@Target:聲明註解的作用範圍可以是類,方法,方法參數變量等,也可以通過枚舉類ElementType表達作用類型(可以查看源碼)

@Retention:聲明註解保留時長的作用域,可以理解爲運行環境,

SOURCE(在源文件中有效)

CLASS(源文件編譯成Class類文件中有效)

RUNTIME(在運行時有效)

@Documented:文檔化註解,作用可以被javadoc此類工具文檔化

@Inherited:聲明此類能否被繼承

自定義註解

/**
 * 元註解
 * public @interface 註解名稱 {
 * 類型 屬性名() default 默認值
 * }
 */
//用來聲明自定義註解作用範圍,變量、方法、類、包...
@Target({ElementType.METHOD, ElementType.TYPE})
//聲明註解在運行時有效
@Retention(RetentionPolicy.RUNTIME)
//文檔化
@Documented
//此類能否被繼承
@Inherited
public @interface TestAnnotation {
    String value() default "";
}

自定義註解的簡單使用

/**
 * 定義用戶註解
 */
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface UserAnnontion {
    String name();
    int age() default 18;
    String[] familyMembers() default {};
}
 
 
/**
 * 方法註解
 */
@User(name="阿杰",age = 18,familyMembers = {"xxx","xxx","xxx"})
public void getUser() {
 
}

通過反射獲取註解信息

public class TestAnnotationReflex {
    public static void main(String[] args) {
        try {
            Class stuClass = Class.forName("com.example.demo.User");
            Method method = stuClass.getMethod("getUser");
            if(method.isAnnotationPresent(UserAnnontion.class)){
                UserAnnontion userAnnontion = method.getAnnotation(UserAnnontion.class);
                System.out.println("name: " + userAnnontion.name() 
                + ", age: " + userAnnontion.age() + ", familyMembers: " + userAnnontion.familyMembers()[0]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以上的只是基礎的知識,可能很多人還沒搞明白自定義註解到底有什麼作用,好像只是簡單的註釋說明,或者是通過反射獲取註解信息。

接下來,使用自定義註解結合Spring AOP來講解一下平時工作中使用的場景,Spring AOP常用於攔截器,事務,日誌,權限,AOP就不再講解了,舉個例子講解一下日誌記錄。在實際項目中,在調試或者排查異常信息時,日誌是否記錄着請求入參信息,是否都是使用log.info("請求參數")這樣的寫法記錄日誌,如果每次都需要這樣手寫,很容易遺漏,並且重複代碼也比較多,在這種場景下,可以做一個統一日誌處理的方案,通過使用自定義註解和切面來實現這個功能。

自定義註解+切面實現統一日誌處理

自定義日誌註解

/**
 * 自定義操作日誌註解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLog {
    /**
     * 業務
     * @return
     */
    String business();
 
    /**
     * 操作類型,增刪改查
     * @return
     */
    OptType optType();
}

聲明日誌切面組件

import com.alibaba.fastjson.JSONObject;
import com.example.demo.annotation.OptLog;
import com.example.demo.annotation.OptType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
 
@Aspect
@Component
public class OptLogAspect {
 
    private static final Logger LOG = LoggerFactory.getLogger(OptLogAspect.class);
 
    /**
     * 聲明切入點,凡是使用該註解都經過攔截
     */
    @Pointcut("@annotation(com.example.demo.annotation.OptLog)")
    public void OptLog() {
 
    }
 
    @Before("OptLog()")
    public void doOptLogBefore(JoinPoint proceedingJoinPoint) {
        LOG.info("前置通知, 在方法執行之前執行...");
    }
 
    @After("OptLog()")
    public void doOptLogAfter(JoinPoint proceedingJoinPoint) {
        LOG.info("後置通知, 在方法執行之後執行...");
    }
 
    @AfterReturning("OptLog()")
    public void doOptLogAfterReturning(JoinPoint proceedingJoinPoint) {
        LOG.info("返回通知, 在方法返回結果之後執行...");
    }
 
    @AfterThrowing("OptLog()")
    public void doOptLogAfterThrowing(JoinPoint proceedingJoinPoint) {
        LOG.info("異常通知, 在方法拋出異常之後執行...");
    }
 
    /**
     * 設置環繞通知,圍繞着方法執行
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("OptLog()")
    public Object optLogAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        if (method == null) {
            return null;
        }
        // 獲取方法名稱
        String methodName = proceedingJoinPoint.getSignature().getName();
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        // 請求參數名稱
        String[] parameterNames = discoverer.getParameterNames(method);
        // 請求參數值
        Object[] paramValues = proceedingJoinPoint.getArgs();
 
        OptLog optLog = method.getAnnotation(OptLog.class);
        this.handle(optLog.optType(), optLog.business(), methodName, parameterNames, paramValues);
        return proceedingJoinPoint.proceed();
    }
 
    /**
     * 日誌處理
     *
     * @param optType
     * @param business
     * @param methodName
     * @param parameterNames
     * @param paramValues
     */
    public void handle(OptType optType, String business, String methodName, 
                       String[] parameterNames, Object[] paramValues) {
        JSONObject jsonObject = new JSONObject();
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                jsonObject.put(parameterNames[i], paramValues[i]);
            }
        }
        LOG.info("optType:" + optType + ",business:" + business + ", methodName:" + methodName + ", params:" + jsonObject);
    }
 
}

控制層運行結果

@RestController
@RequestMapping("/user/")
public class UserController {
 
    @OptLog(optType = OptType.CREATE,business = "用戶信息")
    @RequestMapping("create")
    public String createUser(String userName,int age,String address) {
        System.out.println("方法執行中...");
        return "success";
    }
}

運行結果

15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [handle,91] - optType:CREATE,business:用戶信息, methodName:createUser, params:{"address":"廣州市","userName":"阿杰","age":18}
15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogBefore,32] - 前置通知, 在方法執行之前執行...
方法執行中...
15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfterReturning,42] - 返回通知, 在方法返回結果之後執行...
15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfter,37] - 後置通知, 在方法執行之後執行...

最後

在文章的最後作者爲大家整理了很多資料!包括java核心知識點+全套架構師學習資料和視頻+一線大廠面試寶典+面試簡歷模板+阿里美團網易騰訊小米愛奇藝快手嗶哩嗶哩面試題+Spring源碼合集+Java架構實戰電子書等等!
歡迎關注公衆號:前程有光,領取!

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