前言
在之前的日志记录的写法中,我们大多是写一个工具类,在这个类里面定义日志保存的方法,然后再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;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation == null ? null : operation.trim();
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method == null ? null : method.trim();
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params == null ? null : params.trim();
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip == null ? null : ip.trim();
}
public Date getCreateTime() {
return 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注解:
@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注解:
这个参数有三种,一般默认第三种
1. RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,
编译时就会被忽略
2. RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,
但JVM将会忽略
3. 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";
}
}