自定義註解以及其結合AOP和反射的使用
用一個詞就可以描述註解,那就是元資料,即一種描述資料的資料。Annotations僅僅是元資料,和業務邏輯無關。
元註解
說自定義註解之前,肯定要說元註解,因為自定義註解是由元註解來定義的。
@Documented :註解是否將包含在JavaDoc中
@Inherited :是否允許子類繼承該註解
@Retention :什麼時候使用該註解,即註解的生命週期
RetentionPolicy.SOURCE
:在編譯階段丟棄。這些註解在編譯結束之後就不再有任何意義,所以它們不會寫入位元組碼。@Override
,@SuppressWarnings
都屬於這類註解。RetentionPolicy.CLASS
RetentionPolicy.RUNTIME
:始終不會丟棄,執行期也保留該註解,因此可以使用反射機制讀取該註解的資訊。我們自定義的註解通常使用這種方式。
@Target :註解用於什麼地方
ElementType.TYPE
:用於描述類、介面或enum宣告ElementType.FIELD
:用於描述例項變數ElementType.METHOD
:用於方法- ElementType.PARAMETER
- ElementType.CONSTRUCTOR
- ElementType.LOCAL_VARIABLE
- ElementType.ANNOTATION_TYPE :另一個註釋
- ElementType.PACKAGE :用於記錄java檔案的package資訊
自定義註解
用例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}
自定義註解類編寫的一些規則:
1. Annotation
型定義為@interface
所有的Annotation
會自動繼承java.lang.Annotation
這一介面,並且不能再去繼承別的類或是介面.
2. 引數成員只能用public
或預設(default)
這兩個訪問權修飾
3. 引數成員只能用基本型別byte,short,char,int,long,float,double,boolean
八種基本資料型別和String、Enum、Class、annotations
等資料型別,以及這一些型別的陣列。
4. 如果只有一個成員,成員名稱最好用value
,因為預設就是它。
4. 一般定義引數成員後,都會給一個預設值,注意String
一般不用null
做預設值,可以用字串或空串(”“)
5. 要獲取類方法和欄位的註解資訊,必須通過Java
的反射技術來獲取 Annotation
物件,因為你除此之外沒有別的獲取註解物件的方法
6. 註解也可以沒有定義成員, 不過這樣註解就沒啥用了
使用:
public class BusinessLogic {
public BusinessLogic() {
super();
}
public void compltedMethod() {
System.out.println("This method is complete");
}
@Todo(priority = Todo.Priority.HIGH)
public void notYetStartedMethod() {
// No Code Written yet
}
@Todo(priority = Todo.Priority.MEDIUM, author = "Uday", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But its not complete yet
}
@Todo(priority = Todo.Priority.LOW, status = Todo.Status.STARTED )
public void incompleteMethod2() {
//Some business logic is written
//But its not complete yet
}
}
Annotations
僅僅是元資料,和業務邏輯無關,並沒有做邏輯處理。那麼接下來問題來了,怎麼做這個邏輯處理?
AOP+反射+自定義註解
自定義註解可以用來做許可權控制、欄位校驗,最常見的就是Log記錄。下面我們就來看看AOP+反射+自定義註解做的Log記錄吧
1、新建Log記錄實體類
public class LogDO {
private Long id;
private Long userId;
private String username;
private String operation;
private Integer time;
private String method;
private String params;
private String ip;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date gmtCreate;
//省去getter、sertter方法
}
這裡持久層dao就不寫了。不是重點。
2.、新建一個註解Log
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
3、新建一個LogAspect來監聽Log物件
@Aspect
@Component
public class LogAspect {
@Autowired
LogDao logMapper; //由於篇幅原因,持久層dao省略了
@Pointcut("@annotation(com.bootdo.common.annotation.Log)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 執行方法
Object result = point.proceed();
// 執行時長(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 儲存日誌
saveLog(point, time);
return result;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogDO sysLog = new LogDO();
//通過註解,獲取註解物件
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 註解上的描述
sysLog.setOperation(syslog.value());
}
// 請求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 請求的引數
Object[] args = joinPoint.getArgs();
try {
String params = JSONUtils.beanToJson(args[0]).substring(0, 4999);
sysLog.setParams(params);
} catch (Exception e) {
}
// 獲取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 設定IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
// 使用者名稱 這裡用的是shiro管理使用者資訊,要是其他的框架,也可以
UserDO currUser = ShiroUtils.getUser();
//UserDO currUser = null; //如果不會shiro,用這個,同樣不影響日誌的列印
if (null == currUser) {
if (null != sysLog.getParams()) {
sysLog.setUserId(-1L);
sysLog.setUsername(sysLog.getParams());
} else {
sysLog.setUserId(-1L);
sysLog.setUsername("獲取使用者資訊為空");
}
} else {
sysLog.setUserId(ShiroUtils.getUserId());
sysLog.setUsername(ShiroUtils.getUser().getUsername());
}
sysLog.setTime((int) time);
// 系統當前時間
Date date = new Date();
sysLog.setGmtCreate(date);
// 儲存系統日誌
logMapper.save(sysLog);
}
}
這裡面有些方法HttpServletRequest
、ShiroUtils
等,都是為了得到LogDO
物件欄位值的一種手段,這裡就不貼程式碼了。總之,如上面所示,可以通過AOP
來監聽註解Log
這個切面,然後,再利用反射來處理方法上的註解。
4、使用
@Log("請求訪問主頁")
@GetMapping({ "/index" })
String index(Model model) {
List<Tree<MenuDO>> menus = menuService.listMenuTree(getUserId());
model.addAttribute("menus", menus);
model.addAttribute("name", getUser().getName());
model.addAttribute("username", getUser().getUsername());
return "index_v1";
}
這樣,通過一個註解,就可以記錄一條使用者使用的日誌到資料庫中去了。