什麼是註解?
註解是一種特殊的接口,用於為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);
利用反射為對象設置公共字段。
最終效果
@AutoFill註解能有效替代原本冗長且重複的公共字段設置代碼。