java通过自定义注解实现切面记录操作日志
- 自定义注解
- java.lang.annotation为开发者提供的元注解
- 日志自定义注解实例
- 日志切面实现
- 在业务逻辑中使用自定义注解记录操作日志
自定义注解
java.lang.annotation为开发者提供的元注解
@Documented – 注解是否将包含在JavaDoc中
@Retention – 定义该注解的生命周期
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解
1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。
4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,
@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了
@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。
日志自定义注解实例
/**
* Created with IntelliJ IDEA.
* User: t123
* Date: 2019/12/24 9:38
* Description: 自定义日志注解
* Version: V1.0
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
String module() default "";
String methods() default "";
}
日志切面实现
package com.zyhd.aop;
import com.zyhd.pojo.LogEntity;
import com.zyhd.service.impl.LogServiceImpl;
import com.zyhd.service.impl.RedisService;
import com.zyhd.utils.UserIp;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
* Created with IntelliJ IDEA.
* User: t123
* Date: 2019/12/24 9:40
* Description: 日志逻辑处理类
* Version: V1.0
*/
@Component
@Aspect
@Order(100)
@Slf4j
public class LogAopAction {
//注入service,用来将日志信息保存在数据库
@Autowired
private LogServiceImpl logservice;
@Autowired
private RedisService redisService;
//配置接入点
@Pointcut("@annotation(com.zyhd.aop.SystemLog)")
//@Pointcut("execution(* com.zyhd.controller.*.*(..))")
private void controllerAspect(){}//定义一个切入点
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//常见日志实体对象
LogEntity logEntity = new LogEntity();
//获取日志ID
String logId = UUID.randomUUID().toString().replaceAll("-","");
logEntity.setLogId(logId);
// //获取登录用户账号
ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
ShiroHttpServletRequest request = (ShiroHttpServletRequest)servletRequestAttributes.getRequest();
RequestFacade request1 = (RequestFacade)request.getRequest();
HttpSession session = request1.getSession();
// 拦截的方法名称。当前正在执行的方法
String methodName = pjp.getSignature().getName();
// 拦截的方法参数
Object[] args = pjp.getArgs();
if(methodName!="UserLogin"){
String token = request1.getHeader("token");
String userAccount =null;
if(token!=null){
userAccount = (String) redisService.get(token);
}
if (userAccount == null){
logEntity.setAdminId(userAccount);
} else {
logEntity.setAdminId(userAccount);
}
}else {
Field[] fields = args[0].getClass().getDeclaredFields();
try {
//得到属性
Field field = fields[2];
//打开私有访问
field.setAccessible(true);
//获取属性值
String value = (String) field.get(args[0]);
logEntity.setAdminId(value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//获取系统时间
String time = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date());
logEntity.setDate(time);
//获取访问者的ip
String ip = UserIp.getIp(request);
System.out.println(ip);
logEntity.setIp(ip);
//获取请求的地址
String url = request.getRequestURI();
logEntity.setUrl(url);
//方法通知前获取时间,用来计算模块执行时间的
long start = System.currentTimeMillis();
// 拦截的实体类,就是当前正在执行的controller
Object target = pjp.getTarget();
// 拦截的放参数类型
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Class[] parameterTypes = msig.getMethod().getParameterTypes();
Object object = null;
// 获得被拦截的方法
Method method = null;
try {
method = target.getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if (null != method) {
// 判断是否包含自定义的注解,说明一下这里的SystemLog就是自定义的注解
if (method.isAnnotationPresent(SystemLog.class)) {
SystemLog systemlog = method.getAnnotation(SystemLog.class);
logEntity.setModule(systemlog.module());
logEntity.setMethod(systemlog.methods());
try {
object = pjp.proceed();
long end = System.currentTimeMillis();
//将计算好的时间保存在实体中
logEntity.setRsponseDate(""+(end-start));
logEntity.setCommite("执行成功!");
//保存进数据库
logservice.saveLog(logEntity);
} catch (Throwable e) {
// TODO Auto-generated catch block
log.error("执行失败",e);
long end = System.currentTimeMillis();
logEntity.setRsponseDate(""+(end-start));
logEntity.setCommite("执行失败");
logservice.saveLog(logEntity);
}
} else {//没有包含注解
object = pjp.proceed();
}
} else { //不需要拦截直接执行
object = pjp.proceed();
}
return object;
}
}
在业务逻辑中使用自定义注解记录操作日志
/**
* 测试方法
*
* @param
* @return
*/
@RequestMapping(value = "test", method = RequestMethod.POST)
@ResponseBody
@SystemLog(module = "测试", methods = "测试日志记录")
public ResponseData testLog() {
ResponseData responseData = new ResponseData();
responseData.setCode(Constant.FAIL_CODE);
responseData.setMsg("测试成功");
return responseData;
}