關于注解的作用、以及自定義注解的使用和場景
- 一、注解的作用
- 二、建立自定義注解
- 2.1 基本定義
- 2.2 示例 -- 類級别的注解
- 2.3 示例 -- 方法級别的注解
- 2.4 示例 -- 字段屬性級别的注解
- 三、注解的使用場景
- 3.1 JDK中的常用注解
- 3.2 Spring中的常用注解
- 3.3 自定義注解的使用場景
- 3.3.1 AOP + @CusLog,通過自定義注解實作 >>記錄檔 --> DB資料庫
- 四、[Spring下所有的注解,及其用法,持續更補](javascript:void(0))
一、注解的作用
- 注解是一種中繼資料形式。即注解是屬于java的一種資料類型,和類、接口、數組、枚舉類似。
- 注解用來修飾,類、方法、變量、參數、包。
- 注解不會對所修飾的代碼産生直接的影響。
二、建立自定義注解
2.1 基本定義
- 首先使用 @interface聲明注解名稱
- 然後,使用@Retention,@Target等元注解标注注解的生命周期和作用元素
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface FirstAnnotation{
}
2.2 示例 – 類級别的注解
定義注解
@Target(ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Custom1 {
String[] values();
}
測試注解
@Custom1(values = {"V1","V2"})
public class MainTest {
@Test
public void t1(){
MainTest t = new MainTest();
boolean present = t.getClass().isAnnotationPresent(Custom1.class);
if (present) {
Custom1 ca = t.getClass().getAnnotation(Custom1.class);
if (ca != null) {
for (String value : ca.values()){
System.out.println(value);
}
}
}
}
}
控制台列印
V1
V2
2.3 示例 – 方法級别的注解
定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
}
使用注解,方法之上
public class AnnotationRunner {
public void method1() {
System.out.println("method1");
}
@CanRun
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
@CanRun
public void method5() {
System.out.println("method4");
}
}
測試注解
public class MainTest {
@Test
public void t1(){
AnnotationRunner runner = new AnnotationRunner();
Method[] methods = runner.getClass().getMethods();
for (Method method : methods) {
CanRun annos = method.getAnnotation(CanRun.class);
if (annos != null) {
try {
method.invoke(runner);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
控制台列印
method2
method4
2.4 示例 – 字段屬性級别的注解
定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
}
使用注解,方法之上
public class AnnotationRunner {
public void method1() {
System.out.println("method1");
}
@CanRun
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
@CanRun
public void method5() {
System.out.println("method4");
}
}
測試注解
public class MainTest {
@Test
public void t1(){
AnnotationRunner runner = new AnnotationRunner();
Method[] methods = runner.getClass().getMethods();
for (Method method : methods) {
CanRun annos = method.getAnnotation(CanRun.class);
if (annos != null) {
try {
method.invoke(runner);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
控制台列印
method2
method4
三、注解的使用場景
3.1 JDK中的常用注解
@Override: 表示注解修飾的方法必須滿足重寫的規則
@Deprecated: 表示成員過時,編譯器可以在程式運作的時候擷取到該注解
@SupressWarnings: 表示忽略編譯器的警告
@FunctionalInterface: 表示該接口是一個函數式接口,并且可以作為Lambda表達式參數傳入
還有,幾個元注解,用于定義注解的
@Retention: 表示對它所标記的元素的生命周期(參考的範圍看RetentionPolicy枚舉類)
@Target: 表示标記定義的注解可以和什麼目标元素綁定
@Inherited: 表示該注解可以被繼承
@Document: 表示該注解可以被生成API文檔
3.2 Spring中的常用注解
@RestController
@RequestMapping
@PostMapping
@RequestBody
…
這裡是Spring中所有的注解連結
3.3 自定義注解的使用場景
3.3.1 AOP + @CusLog,通過自定義注解實作 >>記錄檔 --> DB資料庫
(1) - 首先是有日志的Model定義,Dao層的save實作,這裡不介紹太多了…
@Data
@Entity
@Table(name = "operate_log")
public class OperateLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String recordId; // 操作資料id
private String module;// 子產品名稱
private String business;// 業務方法描述
private String opType;// 操作類型
private Long userId;// 操作人
private String userName;// 操作人姓名
private String params;// 操作資料
/**
* 建立時間
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SystemLog {
/**
* 操作描述 業務名稱business
*
* @return
*/
String description() default "";
/**
* 操作子產品
*
* @return
*/
OperateModule module();
/**
* 操作類型 create modify delete
*
* @return
*/
OperateType opType();
/**
* 主鍵入參參數名稱,入參中的哪個參數為主鍵
*
* @return
*/
String primaryKeyName() default "";
/**
* 主鍵在參數中的順序,從0開始,預設0
*/
int primaryKeySort() default 0;
/**
* 主鍵所屬類
*
* @return
*/
Class<?> primaryKeyBelongClass();
}
@Aspect
@Component
@Slf4j
public class WebLogAspect {
@Autowired
private OperateLogRepository logRepository;
@Pointcut("execution(public * demo1.controller..*.*(..)) "
+ " && @annotation(demo1.log.SystemLog)")
public void webLog() {
}
@Around("webLog()")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
// log.info("環繞通知開始........");
// String username = SecurityUtils.getCurrentUserName();
String username = "ss";
// 入參 value
Object[] args = joinPoint.getArgs();
// 入參名稱
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> params = new HashMap<>();
// 擷取所有參數對象
for (int i = 0; i < args.length; i++) {
if (null != args[i]) {
if (args[i] instanceof BindingResult) {
params.put(paramNames[i], "bindingResult");
} else {
params.put(paramNames[i], args[i]);
}
} else {
params.put(paramNames[i], "無");
}
}
Map<String, Object> values = getControllerAnnotationValue(joinPoint);
String opType = values.get("opType").toString();
String module = values.get("module").toString();
String business = values.get("business").toString();
String primaryKeyName = values.get("primaryKeyName").toString();
int primaryKeySort = Integer.parseInt(values.get("primaryKeySort").toString());
Class<?> primaryKeyBelongClass = (Class<?>) values.get("primaryKeyBelongClass");
Object primaryKeyValue = null;
if (StringUtils.isNotBlank(primaryKeyName) && OperateType.valueOf(opType) == OperateType.delete) {
primaryKeyValue = args[primaryKeySort];
}
// 切面傳回值
Object returnValue = joinPoint.proceed();
if (OperateType.valueOf(opType) != OperateType.delete) {
// 主要目的是為了擷取方法儲存成功的傳回資料格式,以擷取儲存成功的資料id
// 此處要限制新增傳回成功的格式,return ok("具體操作資訊", new MapBean("此處為實體主鍵屬性名稱",
// primaryKeyValue));
// 不然自己也可定義格式,進行拆分擷取主鍵
primaryKeyName = getPrimaryKeyName(primaryKeyBelongClass).toString();
primaryKeyValue = ReflectUtils.dynamicGet(returnValue, primaryKeyName);
if (primaryKeyValue == null || primaryKeyValue.toString().equals("")) {// 處理service層傳回ResultBean
Object result = ReflectUtils.dynamicGet(returnValue, "result");
if (result != null) {
primaryKeyValue = ReflectUtils.dynamicGet(result, primaryKeyName);
} else {
primaryKeyValue = args[primaryKeySort];
}
}
}
OperateLog operateLog = new OperateLog();
//
if (JSONUtil.toJsonStr(params).length() <= 2000) {
operateLog.setData(JSONUtil.toJsonStr(params));
}
operateLog.setUserId(1L);
operateLog.setUserName(username == null ? "系統" : username);
operateLog.setModule(module);
operateLog.setOpType(opType);
operateLog.setBusiness(business);
String recordId = null;
if (primaryKeyValue instanceof Object[]) {
recordId = Arrays.toString((Object[]) primaryKeyValue);
} else {
recordId = primaryKeyValue.toString();
}
operateLog.setRecordId(recordId);
operateLog.setCreateTime(new Date());
logRepository.save(operateLog);
log.info(">>>記錄檔:{}", operateLog);
return returnValue;
}
/**
* 擷取class的主鍵字段名 主鍵值
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private static Object getPrimaryKeyName(Class<?> clazz) throws Exception {
Object param = null;
// 遞歸擷取父子類所有的field
Class tempClass = clazz;
// 當父類為null的時候說明到達了最上層的父類(Object類
while (tempClass != null && !StringUtils.equals(tempClass.getName().toLowerCase(), "java.lang.object")) {
Field[] fields = tempClass.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
// boolean類型不必判斷,因實體裡包含boolean類型的屬性,getter方法是以is開頭,不是get開頭
if (field.getType().equals(Boolean.class) || field.getType().getName().equals("boolean")) {
continue;
}
if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
continue;
}
String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = tempClass.getDeclaredMethod(getterMethod);
// 字段上是否存在@Id注解
Object primaryAnnotation = field.getAnnotation(Id.class);// for hibernate
if (primaryAnnotation == null)
primaryAnnotation = field.getAnnotation(org.springframework.data.annotation.Id.class);// for spring
// data
// getter方法上是否存在@Id注解
if (primaryAnnotation == null)
primaryAnnotation = method.getAnnotation(Id.class);
if (primaryAnnotation == null)
primaryAnnotation = method.getAnnotation(org.springframework.data.annotation.Id.class);
// 存在@Id注解,則說明該字段為主鍵
if (primaryAnnotation != null) {
/* String primaryKeyName = field.getName(); */
param = field.getName();
break;
}
}
if (param != null && StringUtils.isNotBlank(param.toString())) {
break;
}
// 得到父類指派給tempClass
tempClass = tempClass.getSuperclass();
}
if (param == null) {
throw new Exception(clazz.getName() + "實體,未設定主鍵");
}
return param;
}
/**
* 擷取@SystemLog 注解上資訊
*
* @param joinPoint
* @return map
* @throws Exception
*/
@SuppressWarnings("rawtypes")
public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
Map<String, Object> map = new HashMap<>();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] classes = method.getParameterTypes();
if (classes.length == arguments.length) {
// 取入參資料
String description = method.getAnnotation(SystemLog.class).description();
String module = method.getAnnotation(SystemLog.class).module().name();
String opType = method.getAnnotation(SystemLog.class).opType().name();
String primaryKeyName = method.getAnnotation(SystemLog.class).primaryKeyName();
int primaryKeySort = method.getAnnotation(SystemLog.class).primaryKeySort();
Class<?> clazz = method.getAnnotation(SystemLog.class).primaryKeyBelongClass();
map.put("module", module);
map.put("opType", opType);
map.put("business", description);
map.put("primaryKeyName", primaryKeyName);
map.put("primaryKeySort", primaryKeySort);
map.put("primaryKeyBelongClass", clazz);
break;
}
}
}
return map;
}
@AfterReturning("webLog()")
public void doAfterReturning(JoinPoint joinPoint) {
// 處理完請求,傳回内容
// log.info("WebLogAspect.doAfterReturning()");
}
}