1. 程式人生 > >自定義註解以及其結合AOP和反射的使用

自定義註解以及其結合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);
    }
}

這裡面有些方法HttpServletRequestShiroUtils等,都是為了得到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";
    }

這樣,通過一個註解,就可以記錄一條使用者使用的日誌到資料庫中去了。