天天都在使用的 Java 注解,你真的了解它嗎?

Hello,大家好,我是阿粉,Java 的注解相信大家天天都在用,但是關(guān)于注解的原理,大家都了解嗎?這篇文章通過(guò)意見(jiàn)簡(jiǎn)單的示例給大家演示一下注解的使用和原理。
Java 元注解
注解(Annotation)是一種可以放在 Java 類上,方法上,屬性上,參數(shù)前面的一種特殊的注釋,用來(lái)注釋注解的注解叫做元注解。元注解我們平常不會(huì)編寫(xiě),只需要添加到我們自己編寫(xiě)的注解上即可,。
Java 自帶的常用的元注解有@Target,@Retention,@Documented,@Inherited 分別有如下含義
@Target:標(biāo)記這個(gè)注解使用的地方,取值范圍在枚舉java.lang.annotation.ElementType:TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE。@Retention:標(biāo)識(shí)這個(gè)注解的生命周期,取值范圍在枚舉java.lang.annotation.RetentionPolicy,SOURCE,CLASS,RUNTIME,一般定義的注解都是在運(yùn)行時(shí)使用,所有要用@Retention(RetentionPolicy.RUNTIME);@Documented:表示注解是否包含到文檔中。@Inherited:使用@Inherited定義子類是否可繼承父類定義的Annotation。@Inherited僅針對(duì)@Target(ElementType.TYPE)類型的annotation有效,并且僅針對(duì)class的繼承,對(duì)interface的繼承無(wú)效。
定義注解
上面介紹了幾個(gè)元注解,下面我們定義一個(gè)日志注解來(lái)演示一下,我們通過(guò)定義一個(gè)名為OperationLog ?的注解來(lái)記錄一些通用的操作日志,比如記錄什么時(shí)候什么人查詢的哪個(gè)表的數(shù)據(jù)或者新增了什么數(shù)據(jù)。編寫(xiě)注解我們用的是 @interface 關(guān)鍵字,相關(guān)代碼如下:
package?com.api.annotation;
import?java.lang.annotation.*;
/**
?*?
?*?Function:
?*?Author:@author?子悠
?*?Date:2020-11-17 22:10
?*?Desc:用于記錄操作日志
?*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@interface?OperationLog?{
????/**
?????*?操作類型
?????*
?????*?@return
?????*/
????String?type()?default?OperationType.SELECT;
????/**
?????*?操作說(shuō)明
?????*
?????*?@return
?????*/
????String?desc()?default?"";
????/**
?????*?請(qǐng)求路徑
?????*
?????*?@return
?????*/
????String?path()?default?"";
????/**
?????*?是否記錄日志,默認(rèn)是
?????*
?????*?@return
?????*/
????boolean?write()?default?true;
????/**
?????*?是否需要登錄信息
?????*
?????*?@return
?????*/
????boolean?auth()?default?true;
???/**
?????*?當(dāng)?type?為?save?時(shí)必須
?????*
?????*?@return
?????*/
????String?primaryKey()?default?"";
????/**
?????*?對(duì)應(yīng)?service?的?Class
?????*
?????*?@return
?????*/
????Class>?defaultServiceClass()?default?Object.class;
}
說(shuō)明
上面的注解,我們?cè)黾恿?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">@Target({ElementType.METHOD}) , @Retention(RetentionPolicy.RUNTIME), @Documented 三個(gè)元注解,表示我們這個(gè)注解是使用在方法上的,并且生命周期是運(yùn)行時(shí),而且可以記錄到文檔中。然后我們可以看到定義注解采用的u是@interface ?關(guān)鍵字,并且我們給這個(gè)注解定義了幾個(gè)屬性,同時(shí)設(shè)置了默認(rèn)值。主要注意的是平時(shí)我們編寫(xiě)的注解一般必須設(shè)置@Target和@Retention,而且 @Retention一般設(shè)置為RUNTIME,這是因?yàn)槲覀冏远x的注解通常要求在運(yùn)行期讀取,另外一般情況下,不必寫(xiě)@Inherited。
使用
上面的動(dòng)作只是把注解定義出來(lái)了,但是光光定義出來(lái)是沒(méi)有用的,必須有一個(gè)地方讀取解析,才能提現(xiàn)出注解的價(jià)值,我們就采用 Spring 的 AOP 攔截這個(gè)注解,將所有攜帶這個(gè)注解的方法所進(jìn)行的操作都記錄下來(lái)。
package?com.api.config;
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.beans.factory.annotation.Autowired;
import?org.springframework.core.annotation.Order;
import?org.springframework.stereotype.Component;
import?org.springframework.web.bind.annotation.GetMapping;
import?org.springframework.web.bind.annotation.PostMapping;
import?org.springframework.web.bind.annotation.RequestMapping;
import?javax.servlet.http.HttpServletRequest;
import?java.lang.reflect.Field;
import?java.lang.reflect.Method;
import?java.util.*;
/**
?*?
?*?Function:
?*?Author:@author?子悠
?*?Date:2020-11-17 14:40
?*?Desc:aspect for operation log
?*/
@Aspect
@Component
@Order(-5)
@Slf4j
public?class?LogAspect?{
????/**
?????*?Pointcut?for?methods?which?need?to?record?operate?log
?????*/
????@Pointcut("within(com.xx.yy.controller..*)?&&?@annotation(com.api.annotation.OperationLog)")
????public?void?logAspect()?{
????}
????/**
?????*?record?log?for?Admin?and?DSP
?????*
?????*?@param?joinPoint?parameter
?????*?@return?result
?????*?@throws?Throwable
?????*/
????@Around("logAspect()")
????public?Object?around(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
????????Object?proceed?=?null;
????????String?classType?=?joinPoint.getTarget().getClass().getName();
????????Class>?targetCls?=?Class.forName(classType);
????????MethodSignature?ms?=?(MethodSignature)?joinPoint.getSignature();
????????Method?targetMethod?=?targetCls.getDeclaredMethod(ms.getName(),?ms.getParameterTypes());
????????OperationLog?operation?=?targetMethod.getAnnotation(OperationLog.class);
????????if?(null?!=?operation?&&?operation.write())?{
????????????SysMenuOpLogEntity?opLogEntity?=?new?SysMenuOpLogEntity();
????????????StringBuilder?change?=?new?StringBuilder();
????????????if?(StrUtil.isNotBlank(operation.type()))?{
????????????????switch?(operation.type())?{
????????????????????case?OperationType.ADD:
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????String?addString?=?genAddData(targetCls,?operation.defaultServiceClass(),?joinPoint.getArgs());
????????????????????????opLogEntity.setAfterJson(addString);
????????????????????????change.append(OperationType.ADD);
????????????????????????break;
????????????????????case?OperationType.DELETE:
????????????????????????String?deleteString?=?autoQueryDeletedData(targetCls,?operation.primaryKey(),?operation.defaultServiceClass(),?joinPoint.getArgs());
????????????????????????opLogEntity.setBeforeJson(deleteString);
????????????????????????change.append(OperationType.DELETE);
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????break;
????????????????????case?OperationType.EDIT:
????????????????????????change.append(OperationType.EDIT);
????????????????????????setOpLogEntity(opLogEntity,?targetCls,?operation.primaryKey(),?operation.defaultServiceClass(),?joinPoint.getArgs());
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????break;
????????????????????case?OperationType.SELECT:
????????????????????????opLogEntity.setBeforeJson(getQueryString(targetCls,?operation.defaultServiceClass(),?joinPoint.getArgs()));
????????????????????????change.append(operation.type());
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????break;
????????????????????case?OperationType.SAVE:
????????????????????????savedDataOpLog(opLogEntity,?targetCls,?operation.primaryKey(),?operation.defaultServiceClass(),?joinPoint.getArgs());
????????????????????????change.append(operation.type());
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????break;
????????????????????case?OperationType.EXPORT:
????????????????????case?OperationType.DOWNLOAD:
????????????????????????change.append(operation.type());
????????????????????????proceed?=?joinPoint.proceed();
????????????????????????break;
????????????????????default:
????????????????}
????????????????opLogEntity.setExecType(operation.type());
????????????}
????????????StringBuilder?changing?=?new?StringBuilder();
????????????if?(StrUtil.isNotBlank(opLogEntity.getExecType()))?{
????????????????if?(operation.auth())?{
????????????????????LoginUserVO?loginUser?=?getLoginUser();
????????????????????if?(null?!=?loginUser)?{
????????????????????????opLogEntity.setUserId(loginUser.getUserId());
????????????????????????opLogEntity.setUserName(loginUser.getUserName());
????????????????????????changing.append(loginUser.getUserName()).append("-");
????????????????????}?else?{
????????????????????????log.error("用戶未登錄");
????????????????????}
????????????????}
????????????????opLogEntity.setCreateTime(DateUtils.getCurDate());
????????????????opLogEntity.setRemark(getOperateMenuName(targetMethod,?operation.desc()));
????????????????opLogEntity.setPath(getPath(targetMethod,?targetMethod.getName()));
????????????????opLogEntity.setChanging(changing.append(change).toString());
????????????????menuOpLogService.save(opLogEntity);
????????????}
????????}
????????return?proceed;
????}
????/**
?????*?query?data?by?userId
?????*
?????*?@param?targetCls???????????class
?????*?@param?defaultServiceClass?default?service?class
?????*?@return
?????*?@throws?Exception
?????*/
????private?String?queryByCurrentUserId(Class>?targetCls,?Class>?defaultServiceClass)?throws?Exception?{
????????BaseService?baseService?=?getBaseService(targetCls,?defaultServiceClass);
????????LoginUserVO?loginUser?=?dspBaseService.getLoginUser();
????????if?(null?!=?loginUser)?{
????????????Object?o?=?baseService.queryId(loginUser.getUserId());
????????????return?JsonUtils.obj2Json(o);
????????}
????????return?null;
????}
????/**
?????*?return?query?parameter
?????*
?????*?@param?targetCls???????????class
?????*?@param?args????????????????parameter
?????*?@param?defaultServiceClass?default?service?class
?????*?@return
?????*?@throws?Exception
?????*/
????private?String?getQueryString(Class>?targetCls,?Class>?defaultServiceClass,?Object[]?args)?{
????????if?(args.length?>?0)?{
????????????Class>?entityClz?=?getEntityClz(targetCls,?defaultServiceClass);
????????????for?(Object?arg?:?args)?{
????????????????if?(arg.getClass().equals(entityClz)?||?arg?instanceof?BaseModel)?{
????????????????????return?JsonUtils.obj2Json(arg);
????????????????}
????????????}
????????}
????????return?null;
????}
????/**
?????*?save?record?log?while?OperatorType?is?SAVE
?????*
?????*?@param?opLogEntity?????????entity
?????*?@param?targetCls???????????class
?????*?@param?primaryKey??????????primaryKey
?????*?@param?defaultServiceClass?default?service?class
?????*?@param?args????????????????parameter
?????*?@throws?Exception
?????*/
????private?void?savedDataOpLog(SysMenuOpLogEntity?opLogEntity,?Class>?targetCls,?String?primaryKey,?Class>?defaultServiceClass,?Object[]?args)?throws?Exception?{
????????Class>?entityClz?=?getEntityClz(targetCls,?defaultServiceClass);
????????BaseService?baseService?=?getBaseService(targetCls,?defaultServiceClass);
????????for?(Object?arg?:?args)?{
????????????if?(arg.getClass().equals(entityClz))?{
????????????????if?(StrUtil.isNotBlank(primaryKey))?{
????????????????????Field?declaredField?=?entityClz.getDeclaredField(primaryKey);
????????????????????declaredField.setAccessible(true);
????????????????????Object?primaryKeyValue?=?declaredField.get(arg);
????????????????????//if?primary?key?is?not?null?that?means?edit,?otherwise?is?add
????????????????????if?(null?!=?primaryKeyValue)?{
????????????????????????//query?data?by?primary?key
????????????????????????Object?o?=?baseService.queryId(primaryKeyValue);
????????????????????????opLogEntity.setBeforeJson(JsonUtils.obj2Json(o));
????????????????????}
????????????????}
????????????????opLogEntity.setAfterJson(JsonUtils.obj2Json(arg));
????????????}
????????}
????}
????/**
?????*?set?parameter?which?edit?data
?????*
?????*?@param?opLogEntity?????????entity
?????*?@param?targetCls???????????class
?????*?@param?primaryKey??????????primaryKey
?????*?@param?defaultServiceClass?default?service?class
?????*?@param?args????????????????parameter
?????*?@throws?Exception
?????*/
????private?void?setOpLogEntity(SysMenuOpLogEntity?opLogEntity,?Class>?targetCls,?String?primaryKey,?Class>?defaultServiceClass,?Object[]?args)?throws?Exception?{
????????Map?saveMap?=?autoQueryEditedData(targetCls,?primaryKey,?defaultServiceClass,?args);
????????if?(null?!=?saveMap)?{
????????????if?(saveMap.containsKey(ASPECT_LOG_OLD_DATA))?{
????????????????opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_OLD_DATA));
????????????}
????????????if?(saveMap.containsKey(ASPECT_LOG_NEW_DATA))?{
????????????????opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_NEW_DATA));
????????????}
????????}
????}
????/**
?????*?query?data?for?edit?and?after?edit?operate
?????*
?????*?@param?targetCls???????????class
?????*?@param?primaryKey??????????primaryKey
?????*?@param?defaultServiceClass?default?service?class
?????*?@param?args????????????????parameter
?????*?@return?map?which?data
?????*?@throws?Exception
?????*/
????private?Map?autoQueryEditedData(Class>?targetCls,?String?primaryKey,?Class>?defaultServiceClass,?Object[]?args)?throws?Exception? {
????????if?(StrUtil.isBlank(primaryKey))?{
????????????throw?new?Exception();
????????}
????????Map?map?=?new?HashMap<>(16);
????????Class>?entityClz?=?getEntityClz(targetCls,?defaultServiceClass);
????????BaseService?baseService?=?getBaseService(targetCls,?defaultServiceClass);
????????for?(Object?arg?:?args)?{
????????????if?(arg.getClass().equals(entityClz))?{
????????????????Field?declaredField?=?entityClz.getDeclaredField(primaryKey);
????????????????declaredField.setAccessible(true);
????????????????Object?primaryKeyValue?=?declaredField.get(arg);
????????????????//query?the?data?before?edit
????????????????if?(null?!=?primaryKeyValue)?{
????????????????????//query?data?by?primary?key
????????????????????Object?o?=?baseService.queryId(primaryKeyValue);
????????????????????map.put(ASPECT_LOG_OLD_DATA,?JsonUtils.obj2Json(o));
????????????????????map.put(ASPECT_LOG_NEW_DATA,?JsonUtils.obj2Json(arg));
????????????????????return?map;
????????????????}
????????????}
????????}
????????return?null;
????}
????/**
?????*?return?JSON?data?which?add?operate
?????*
?????*?@param?targetCls???????????class
?????*?@param?args????????????????parameter
?????*?@param?defaultServiceClass?default?service?class
?????*?@return?add?data?which?will?be?added
?????*?@throws?Exception
?????*/
????private?String?genAddData(Class>?targetCls,?Class>?defaultServiceClass,?Object[]?args)?throws?Exception?{
????????List 上面的代碼中我們定義了一個(gè)切面指定需要攔截的包名和注解,因?yàn)樯婕暗胶芏鄻I(yè)務(wù)相關(guān)的代碼,所以不能完整的提供出來(lái),但是整個(gè)思路就是這樣的,在每種操作類型前后將需要記錄的數(shù)據(jù)查詢出來(lái)進(jìn)行記錄。代碼很長(zhǎng)主要是用來(lái)獲取相應(yīng)的參數(shù)值的,大家使用的時(shí)候可以根據(jù)自己的需要進(jìn)行取舍。比如在新增操作的時(shí)候,我們將新增的數(shù)據(jù)進(jìn)行記錄下來(lái);編輯的時(shí)候?qū)⒕庉嬊暗臄?shù)據(jù)查詢出來(lái)和編輯后的數(shù)據(jù)一起保存起來(lái),刪除也是一樣的,在刪除前將數(shù)據(jù)查詢出來(lái)保存到日志表中。
同樣導(dǎo)出和下載都會(huì)記錄相應(yīng)信息,整個(gè)操作類型的代碼如下:
package?com.api.annotation;
/**
?*?
?*?Function:
?*?Author:@author 子悠
?*?Date:2020-11-17 22:11
?*?Desc:無(wú)
?*/
public?interface?OperationType?{
????/**
?????*?新增
?????**/
????String?ADD?=?"add";
????/**
?????*?刪除
?????**/
????String?DELETE?=?"delete";
????/**
?????*?使用實(shí)體參數(shù)修改
?????**/
????String?EDIT?=?"edit";
????/**
?????*?查詢
?????**/
????String?SELECT?=?"select";
????/**
?????*?新增和修改的保存方法,使用此類型時(shí)必須配置主鍵字段名稱
?????**/
????String?SAVE?=?"save";
????/**
?????*?導(dǎo)出
?????**/
????String?EXPORT?=?"export";
????/**
?????*?下載
?????**/
????String?DOWNLOAD?=?"download";
}
后續(xù)在使用的時(shí)候只需要在需要的方法上加上注解,填上相應(yīng)的參數(shù)即可@OperationLog(desc = "查詢單條記錄", path = "/data")
總結(jié)
注解一個(gè)我們天天再用的東西,雖然不難,但是我們卻很少自己去寫(xiě)注解的代碼,通過(guò)這篇文章能給大家展示一下注解的使用邏輯,希望對(duì)大家有幫助。Spring 中的各種注解本質(zhì)上也是這種邏輯都需要定義使用和解析。很多時(shí)候我們可以通過(guò)自定義注解去解決很多場(chǎng)景,比如日志,緩存等。
喜歡就三連呀
關(guān)注 Stephen,一起學(xué)習(xí),一起成長(zhǎng)。
