1. 程式人生 > >springboot自定義日誌註解

springboot自定義日誌註解

前言

      在之前的日誌記錄的寫法中,我們大多是寫一個工具類,在這個類裡面定義日誌儲存的方法,然後再controller中執行請求的時候呼叫即可,雖然呼叫僅僅一行程式碼,但是不夠友好;所有可以寫一個類似於@Controller等的註解,在需要儲存日誌的方法上面加上一個註解,這樣不用在每個都寫一端程式碼;話不多說上程式碼

     1、首先一個log的實體類,這個無關緊要

package com.sysmg.system.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.sysmg.common.annotation.ExportConfig;

@Table(name = "t_log")
public class SysLog implements Serializable {

	private static final long serialVersionUID = -8878596941954995444L;

	@Id
	@GeneratedValue(generator = "JDBC")
	@Column(name = "ID")
	private Long id;

	@Column(name = "USERNAME")
	@ExportConfig(value = "操作使用者")
	private String username;

	@Column(name = "OPERATION")
	@ExportConfig(value = "描述")
	private String operation;

	@Column(name = "TIME")
	@ExportConfig(value = "耗時(毫秒)")
	private Long time;

	@Column(name = "METHOD")
	@ExportConfig(value = "操作方法")
	private String method;

	@Column(name = "PARAMS")
	@ExportConfig(value = "引數")
	private String params;

	@Column(name = "IP")
	@ExportConfig(value = "IP地址")
	private String ip;

	@Column(name = "CREATE_TIME")
	@ExportConfig(value = "操作時間", convert = "c:com.sysmg.common.util.poi.convert.TimeConvert")
	private Date createTime;

	@Column(name = "LOCATION")
	@ExportConfig(value = "地點")
	private String location;
	
	// 用於搜尋條件中的時間欄位
	@Transient
	private String timeField;

	/**
	 * @return ID
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @param id
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * @return USERNAME
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * @param username
	 */
	public void setUsername(String username) {
		this.username = username == null ? null : username.trim();
	}

	/**
	 * @return OPERATION
	 */
	public String getOperation() {
		return operation;
	}

	/**
	 * @param operation
	 */
	public void setOperation(String operation) {
		this.operation = operation == null ? null : operation.trim();
	}

	/**
	 * @return TIME
	 */
	public Long getTime() {
		return time;
	}

	/**
	 * @param time
	 */
	public void setTime(Long time) {
		this.time = time;
	}

	/**
	 * @return METHOD
	 */
	public String getMethod() {
		return method;
	}

	/**
	 * @param method
	 */
	public void setMethod(String method) {
		this.method = method == null ? null : method.trim();
	}

	/**
	 * @return PARAMS
	 */
	public String getParams() {
		return params;
	}

	/**
	 * @param params
	 */
	public void setParams(String params) {
		this.params = params == null ? null : params.trim();
	}

	/**
	 * @return IP
	 */
	public String getIp() {
		return ip;
	}

	/**
	 * @param ip
	 */
	public void setIp(String ip) {
		this.ip = ip == null ? null : ip.trim();
	}

	/**
	 * @return CREATE_TIME
	 */
	public Date getCreateTime() {
		return createTime;
	}

	/**
	 * @param createTime
	 */
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public String getTimeField() {
		return timeField;
	}

	public void setTimeField(String timeField) {
		this.timeField = timeField;
	}
	

}

  2、定義一個註解介面

package com.sysmg.common.annotation;

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)
public @interface Log {
    String value() default "";
}

@Target(ElementType.METHOD)代表是方法上的註解,當然也可以是類註解,欄位註解等

@Target(ElementType.TYPE) //介面、類、列舉、註解  @Target(ElementType.FIELD) //欄位、列舉的常量  @Target(ElementType.METHOD) //方法  @Target(ElementType.PARAMETER) //方法引數  @Target(ElementType.CONSTRUCTOR) //建構函式  @Target(ElementType.LOCAL_VARIABLE)//區域性變數  @Target(ElementType.ANNOTATION_TYPE)//註解  @Target(ElementType.PACKAGE) ///包 

@Retention(RetentionPolicy.RUNTIME)代表註解會被jvm保留,這個引數有三種

RetentionPolicy.SOURCE —— 這種型別的Annotations只在原始碼級別保留,編譯時就會被忽略  RetentionPolicy.CLASS —— 這種型別的Annotations編譯時被保留,在class檔案中存在,但JVM將會忽略  RetentionPolicy.RUNTIME —— 這種型別的Annotations將被JVM保留,所以他們能在執行時被JVM或其他使用反射機制的程式碼所讀取和使用。 一般預設第三種

當然也可以寫

@Documented和@Order(優先順序  數字越小優先順序越高)

@Documented 註解表明這個註解應該被 javadoc工具記錄. 預設情況下,javadoc是不包括註解的. 但如果宣告註解時指定了 @Documented,則它會被 javadoc 之類的工具處理, 所以註解型別資訊也會被包括在生成的文件中。

@Order標記定義了元件的載入順序,這個標記包含一個value屬性。屬性接受整形值。如:1,2 等等。值越小擁有越高的優先順序。Ordered.HIGHEST_PRECEDENCE這個屬性值是最高優先順序的屬性,它的值是-2147483648,對應的最低屬性值是Ordered.LOWEST_PRECEDENCE,它的值是2147483647。

String value() default ""

這個代表是要傳遞的引數,類似

@Autowired(required=true)


public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}

springmvc專案還需要開啟切面程式設計

<aop:aspectj-autoproxy proxy-target-class="true"/>

springboot預設是開啟的

3、定義註解的實現類,也就是這個註解要做什麼事

package com.sysmg.common.aspect;

import java.lang.reflect.Method;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sysmg.common.annotation.Log;
import com.sysmg.common.util.AddressUtilsBak;
import com.sysmg.common.util.HttpContextUtils;
import com.sysmg.common.util.IPUtils;
import com.sysmg.system.domain.SysLog;
import com.sysmg.system.domain.User;
import com.sysmg.system.service.LogService;

@Aspect
@Component
public class LogAspect {

	@Autowired
	private LogService logService;

	@Autowired
	ObjectMapper mapper;

	@Pointcut("@annotation(com.sysmg.common.annotation.Log)")
	public void pointcut() {
	}

	@Around("pointcut()")
	public Object around(ProceedingJoinPoint point) {
		Object result = null;
		long beginTime = System.currentTimeMillis();
		try {
			result = point.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		long time = System.currentTimeMillis() - beginTime;
		saveLog(point, time);
		return result;
	}

	private void saveLog(ProceedingJoinPoint joinPoint, long time) {
		User user = (User) SecurityUtils.getSubject().getPrincipal();
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SysLog log = new SysLog();
		Log logAnnotation = method.getAnnotation(Log.class);
		if (logAnnotation != null) {
			log.setOperation(logAnnotation.value());
		}
		String className = joinPoint.getTarget().getClass().getName();
		String methodName = signature.getName();
		log.setMethod(className + "." + methodName + "()");
		Object[] args = joinPoint.getArgs();
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paramNames = u.getParameterNames(method);
		if (args != null && paramNames != null) {
			String params = "";
			for (int i = 0; i < args.length; i++) {
				params += "  " + paramNames[i] + ": " + args[i];
			}
			log.setParams(params);
		}
		HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
		log.setIp(IPUtils.getIpAddr(request));
		log.setUsername(user.getUsername());
		log.setTime(time);
		log.setCreateTime(new Date());
		log.setLocation(AddressUtilsBak.getRealAddressByIP(log.getIp(), mapper));
		this.logService.save(log);
	}
}

這裡的實現類中日誌新增的方法中使用的淘寶的獲取ip服務,後續會加上去,其實這裡面可以隨便寫一個實現方法,因為我是留工具備份,所以記錄的較多

具體的@Aspect、@Pointcut、@Around、@Before、@After等aop相關的註解和引數需要自己去鞏固一下知識

4、然後就可以在你想要記錄日誌的地方使用即可

package com.sysmg.controller;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.sysmg.common.annotation.Log;
import com.sysmg.common.domain.QueryRequest;
import com.sysmg.common.domain.ResponseBo;
import com.sysmg.common.util.FileUtils;

@Controller
public class TestController{

	@Log("規則")
	@RequestMapping("test")
	public String index() {
		return "test";
	}
}	
	

大概這樣就可以使用了,目前裡面缺少aop相關知識的介紹以及獲取訪問ip的工具類,不過跟本課題關係不大,後續會更上去

本文參考路人部落格