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;
    }