什麼是註解?

註解是一種特殊的接口,用於為Java代碼提供元數據。它們不會直接影響代碼的執行,但可以被編譯器、開發工具或運行時環境讀取和使用。

Java內置了一些常用的註解,如:

@Override - 表示方法重寫父類方法

@Deprecated - 表示代碼已過時

@SuppressWarnings - 抑制編譯器警告

註解的基本語法

定義註解

使用@interface關鍵字來定義註解:

 

public @interface AutoFill {
}

 

元註解

元註解是用來註解其他註解的註解,Java提供了以下幾種元註解:

@Target - 指定註解可以應用的目標元素類型

@Retention - 指定註解的保留策略

@Documented - 表示註解應該被包含在Javadoc中

@Inherited - 表示註解可以被繼承

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    /**
     * 數據庫操作類型:INSERT、UPDATE
     */
    OperationType value();
}

 

示例代碼展示了一個用於公共字段自動填充的自定義註解,@Target明確註解可在方法上使用,@Retention明確在程序運行時可見。

 

註解元素

 

註解中可以定義元素,這些元素可以有默認值:

 

public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT

}

 

示例自定義註解中的value方法則用來返回上示枚舉類型數據,明確 使用該註解的方法 的作用,使用方式如下:

 

/**
      * 更新員工信息
      * @param employee
      */
     @AutoFill(OperationType.UPDATE) 
     void updateById(Employee employee);

 

當註解只有一個方法且方法名為 value 時,使用時可以省略方法名,如果方法不叫 value,就必須明確指定方法名:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    /**
     * 數據庫操作類型:INSERT、UPDATE
     */
    OperationType type();
}
/**
      * 更新員工信息
      * @param employee
      */
     @AutoFill(type = OperationType.UPDATE) 
     void updateById(Employee employee);

自定義註解的使用

通過反射處理註解

我們可以使用反射機制在運行時讀取和處理註解:

 

@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 公共字段自動填充切入點
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {}

    /**
     * 公共字段自動填充
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) throws Throwable {
        log.info("公共字段自動填充通知開始");
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        AutoFill autoFill= signature.getMethod().getAnnotation(AutoFill.class);
        // 獲取數據庫操作類型
        Enum operationType = autoFill.value();
        // 從ThreadLocal中獲取當前登錄用户的id
        Long currentId = BaseContext.getCurrentId();
        // 獲取當前時間
        LocalDateTime now = LocalDateTime.now();
        // 從joinPoint中獲取參數
        Object[] args = joinPoint.getArgs();
        if(args==null || args.length==0){
            return;
        }
        // 從參數中獲取實體對象
        Object entity = args[0];
        // 調用實體對象的方法,設置創建時間、創建人、更新時間、更新人
        if(operationType==OperationType.INSERT){
            try{
                Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
                setCreateTime.invoke(entity, now);
                setUpdateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateUser.invoke(entity, currentId);
            }catch (Exception e){
                log.error("公共字段自動填充通知異常", e);
            }
        }else if(operationType==OperationType.UPDATE){
            try{
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            }catch (Exception e){
                log.error("公共字段自動填充通知異常", e);
            }
        }

    }
}

 

上述示例展示的是@AutoFill註解進行公共字段自動填充必要的切面類。

@Aspect

該註解用於聲明切面類。

/**
     * 公共字段自動填充切入點
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {}

這一部分用於聲明哪些部分需要攔截。(在com.sky.mapper包下的所有類及其所有方法  被自定義註解(@AutoFill)所標註)

/**
     * 公共字段自動填充
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) throws Throwable

@Before: 表示這是一個前置通知,在目標方法執行之前運行。

autoFillPointCut(): 引用一個切點表達式,定義了哪些方法需要被攔截。

 

JoinPoint: AOP 連接點對象,可以獲取被攔截方法的詳細信息:方法名、參數、目標對象等。通過 joinPoint.getArgs() 獲取方法參數。

 

MethodSignature signature = (MethodSignature)joinPoint.getSignature();
AutoFill autoFill= signature.getMethod().getAnnotation(AutoFill.class);
// 獲取數據庫操作類型
Enum operationType = autoFill.value();

 

joinPoint.getSignature(): 獲取連接點的簽名信息。

(MethodSignature): 強制轉換為 MethodSignature 類型,因為 Spring AOP 只攔截方法。

signature.getMethod(): 獲取被攔截的 Method 對象。

.getAnnotation(AutoFill.class): 從方法上獲取 @AutoFill 註解。

autoFill.value(): 調用註解的 value() 方法,獲取註解中定義的枚舉值。

 

// 從joinPoint中獲取參數
        Object[] args = joinPoint.getArgs();
        if(args==null || args.length==0){
            return;
        }
        // 從參數中獲取實體對象
        Object entity = args[0];

 

利用從joinPoint中獲取的參數獲得原本方法中傳遞的實體對象。

Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);

利用反射為對象設置公共字段。

最終效果

 

JAVA自定義註解_字段

JAVA自定義註解_字段_02

JAVA自定義註解_自動填充_03

JAVA自定義註解_字段_04

JAVA自定義註解_字段_05

@AutoFill註解能有效替代原本冗長且重複的公共字段設置代碼。