自定义注解+Spring AOP实现记录用户操作日志

一、背景

    项目中需要对用户的各种操作做详细的操作日志记录,需要记录用户操作的操作模块、具体操作以及操作的数据记录ID等。
    若写一个方法去保存操作,则需要每次手动去调用。由于是非业务性的操作,并且大量的重复操作,Spring AOP就能很好的解决这个问题。
     由于用户操作的实现方法并不在同一个类中,而且每个操作的说明也不一样,所以用自定义注解作为切入点,来记录用户不同操作及其操作说明。

 二、配置

2.1、导入jar包

  • spring-aop.jar
  • aspectjrt.jar
  • aspectjweaver.jar
  • aopalliance-1.0.jar

Spring AOP相关jar包下载

2.2、Spring的ApplicationContext.xml配置文件

    2.2.1 配置头文件

xmlns:aop="http://www.springframework.org/schema/aop"
<!-- 在xsi:schemaLocation中添加 -->
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"

注:若没添加则会报 ==》
    java错误-The prefix "aop" for element "aop:aspectj-autoproxy" is not bound.


    2.2.2 配置注解扫描和AOP自动代理

<!-- 配置组件扫描功能 -->
<context:component-scan base-package="com.test"/>
<!-- 配置自动代理功能 -->
<aop:aspectj-autoproxy />
<aop:config proxy-target-class="true"></aop:config>

三、创建自定义注解

package com.test.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * 品目操作日志注解
 * @author zhoujin
 */
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME) 
public @interface OperationLogAnno {
	
	/** 模块 */
	String module();
	
	/** 具体操作 */
	String operate();
	
	/** 品目编号 */
	String pmbh();
	
	/** 备注说明 */
	String remarks() default "";

}

注:注解中定义的方法若没有给默认值,则写该注解的时候必须给该方法赋值!

四、Spring AOP切面类

package com.test.common.aop;

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

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.test.common.annotation.OperationLogAnno;
import com.test.modules.sys.entity.User;
import com.test.modules.sys.utils.UserUtils;
import com.test.modules.zxztb.entity.operationLog.OperationLog;
import com.test.modules.zxztb.service.operationLog.OperationLogService;

/**
 * 品目操作日志切面类
 * @author zhoujin
 * @date 2018-10-23
 */
@Aspect
@Component("operationLogAspect")
public class OperationLogAspect {
	
    private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);
    
    @Autowired
    private OperationLogService operationLogService;
	
    // 配置织入点(以OperationLog注解为标志)
    @Pointcut("@annotation(com.test.common.annotation.OperationLogAnno)")
    public void logPointCut() {
    }
 
    /**
     * 后置通知 用于拦截操作,在方法返回后执行
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()")
    public void doAfterReturn(JoinPoint joinPoint) {
    	handleLog(joinPoint, null);
    }
 
    /**
     * 拦截异常操作,有异常时执行
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, Exception e) {
    	handleLog(joinPoint, e);
    }
 
    private void handleLog(JoinPoint joinPoint, Exception e) {
	try {
	    // 获得注解
		OperationLogAnno controllerLog = getAnnotationLog(joinPoint);
	    if (controllerLog == null) {
	    	return;
	    }
	    // 品目编号
	    Object pmbh = controllerLog.pmbh();
	    // 模块详情
	    Object module = controllerLog.module();
	    // 具体操作
	    Object operate = controllerLog.operate();
	    // 备注说明
	    Object remarks = controllerLog.remarks();
	    // 操作用户信息
	    User currentUser = UserUtils.getUser();
	    // 访问类名
	    String className = joinPoint.getTarget().getClass().getName();
	    // 访问方法名
	    String methodName = joinPoint.getSignature().getName();
	    // 保存日志
	    log.info("\n====================================== 保存操作日志  ======================================");
	    OperationLog operationLog = new OperationLog();
	    operationLog.setPmbh(pmbh.toString());
	    operationLog.setModule(module.toString());
	    operationLog.setOperate(operate.toString());
	    operationLog.setOperate(remarks.toString());
	    operationLog.setOperatorLoginname(currentUser.getLoginName());
	    operationLog.setOperatorName(currentUser.getName());
	    operationLog.setOperateDate(new Date());
	    operationLog.setClassname(className);
	    operationLog.setMethodname(methodName);
	    
	    operationLogService.save(operationLog);
	    log.info("\n=====================================================================================\n");
	} catch (Exception exp) {
	    // 记录本地异常日志
	    log.error("\n====================================== 异常信息通知 ======================================");
	    log.error("异常信息:{}", exp.getMessage());
	    log.error("\n====================================================================================\n");
	    exp.printStackTrace();
	}
    }
 
    /**
     * 是否存在注解,如果存在就获取
     */
    private static OperationLogAnno getAnnotationLog(JoinPoint joinPoint) throws Exception {
	Signature signature = joinPoint.getSignature();
	MethodSignature methodSignature = (MethodSignature) signature;
	Method method = methodSignature.getMethod();
	if (method != null) {
            return method.getAnnotation(OperationLogAnno.class);
	}
	return null;
    }
    
}

五、Controller上使用

@ResponseBody
@RequestMapping(value = "kaiQicb")
@RequiresPermissions("zxztb:dljgKbkzt:kqcb")
@OperationLogAnno(module="控制台", operate="开启流程", pmbh="1111111")
public AjaxJson kaiQicb(String id) {
    AjaxJson j = new AjaxJson();
    String message = "开启流程成功";
    Zfcgpmbxm zfcgpmbxm = zfcgpmbxmService.get(id);
    try {
        zfcgpmbxm.setFlowstatus("7");
        zfcgpmbxmService.save(zfcgpmbxm);
    } catch (Exception e) {
        e.printStackTrace();
        j.setSuccess(false);
        message = "开启流程失败";
    }
    j.setMsg(message);
    return j;
}

注:此处注解上的pmbh是写死的,没有什么作用。但实际情况是,此处pmbh应取前端页面传来的参数id的值,那么怎么才能使注解中能获取到参数的值呢?

java在注解中绑定方法参数的解决方案


六、下载上面node2017博客中的AnnotationResolver,便可获取前端页面传来的参数值

6.1 Controller中

@OperationLogAnno(module="控制台", operate="开启流程", pmbh="#{id}")
public AjaxJson kaiQiKaiBiao(String id){
    ...
}
// 这样也可以
@OperationLogAnno(module="控制台", operate="开启流程", pmbh="#{pmxx.id}")
public AjaxJson kaiQiKaiBiao(Pmxx pmxx){
    ...
}

6.2 Spring AOP切面类中

 private void handleLog(JoinPoint joinPoint, Exception e) {
    try {
        // 获得注解
        OperationLogAnno controllerLog = getAnnotationLog(joinPoint);
        if (controllerLog == null) {
            return;
        }
        AnnotationResolver annotationResolver = AnnotationResolver.newInstance();
        // 品目编号
        Object pmbh = annotationResolver.resolver(joinPoint, controllerLog.pmbh());
        // 模块详情
        Object module = annotationResolver.resolver(joinPoint, controllerLog.module());
        // 具体操作
        Object operate = annotationResolver.resolver(joinPoint, controllerLog.operate());
        // 备注说明
        Object remarks = annotationResolver.resolver(joinPoint, controllerLog.remarks());
        // 操作用户信息
        User currentUser = UserUtils.getUser();
        // 访问类名
        String className = joinPoint.getTarget().getClass().getName();
        // 访问方法名
        String methodName = joinPoint.getSignature().getName();
        // 保存日志
        ...

   } catch (Exception exp) {
        exp.printStackTrace();
}

6.4 AnnotationResolver代码(来自:node2017 ==》java在注解中绑定方法参数的解决方案

package com.jeeplus.common.annotation;

import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名}
 * 能解析类似#{user}或者#{user.id}或者{user.createBy.id}
 */
public class AnnotationResolver {

    private static AnnotationResolver resolver ;
	
	
    public static AnnotationResolver newInstance(){
		
        if (resolver == null) {
            return resolver = new AnnotationResolver();
        }else{
            return resolver;
        }
		
    }
	
	
    /**
     * 解析注解上的值
     * @param joinPoint
     * @param str 需要解析的字符串
     * @return
     */
    public Object resolver(JoinPoint joinPoint, String str) {

        if (str == null) return null ;
		
        Object value = null;
        if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
            String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
            if (newStr.contains(".")) { // 复杂类型
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolver(joinPoint, newStr);
            }
        } else { //非变量
            value = str;
        }
        return value;
    }

	
    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }

        return null;

    }

    private Object getValue(Object obj, int index, String[] strs) {

        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }

            return obj;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }

	
    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < names.length; i++) {
            if (str.equals(names[i])) {
                return args[i];
            }
        }
        return null;
    }
	
}

发布了47 篇原创文章 · 获赞 16 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/zorro_jin/article/details/83687264