前言
日常開發中用到了各式各樣的註解,常用的註解@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] - 後置通知, 在方法執行之後執行...
最後
在文章的最後作者爲大家整理了很多資料!包括java核心知識點+全套架構師學習資料和視頻+一線大廠面試寶典+面試簡歷模板+阿里美團網易騰訊小米愛奇藝快手嗶哩嗶哩面試題+Spring源碼合集+Java架構實戰電子書等等!
歡迎關注公衆號:前程有光,領取!