日常開發中用到了各式各樣的注解,常用的注解@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] - 后置通知, 在方法執行之后執行...
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/281570.html
標籤:Java
上一篇:Java 基礎 一文搞懂泛型
