SpringAOP拦截Controller实现用户操作日志记录(自定义注解的方式)


)

一、背景

在做项目的时候,领导对我们说需要记录用户的各种操作形成一个用户习惯的数据库。因为是一个新手,以前没有做过日志系统,经过分析我们要完成这个功能需要记录用户操作的操作模块,操作描述,方法类,以及用户Id,用户名等详细信息。

二、收集实现方案

经过度娘的千百遍虐,总结出大致有三种方法,可能不全不过也是足够用了。

  1. 第一种最简单也是最繁琐且效率低的方法

即写一个方法记录操作,每个入口方法去调用这个方法。因为会产生重复操作,所以没有进行考虑。

  1. 第二种是采用spring的拦截器进行方法拦截

新建一个拦截器的class继承spring web的HandlerInterceptorAdapter类,在spring4中该类有四个方法可以进行重写,如:
preHandle:它会在处理方法之前执行,可以用来做一些编码处理、安全限制之类的操作。
postHandle:它是在方法执行后开始返回前执行,可以进行日志记录、修改ModelView之类的操作。
afterCompletion:最后执行,无论出错与否都会执行这个方法,可以用来记录异常信息和一些必要的操作记录。
afterConcurrentHandlingStarted:controller方法异步开始执行时就开始执行这个方法,而postHandle需要等到controller异步执行完成后再执行。
因为spring的拦截器无法获取处理函数的参数值,无法直接判断这个操作是哪个用户执行的,所以也没考虑。

  1. 第三种就是采用spring的AOP配置注解进行拦截,也是我采用的方式

使用Spring的切面AOP的思想,可以很好且高效实现第一点的作用,由于用户操作的实现方法并不在同一个类,每个方法的操作说明也不同,所以需要自定义注解,来记录用户不同操作及其操作说明。

三、使用Spring AOP实现用户操作日志具体步骤

1. 环境配置
1.1导入相应的jar包
  • spring-aop.jar
  • aspectjrt.jar
  • aspectjweaver.jar(如果运行失败,请更新其版本。注:我遇到过此问题,经过百度发现其版本太低,下载aspectjweaver-1.8.13.jar就解决)
  • aopalliance-1.0.jar
    注:如果没有这些jar包,可以去Maven仓库下载:Maven库
1.2 配置Spring的ApplicationContext.xml文件
1.2.1 在头文件中添加aop的标签
 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.1.xsd
1.2.2 配置注解扫描以及开启AOP代理

<!-- 使用annotation 自动注册bean,并检查@Required,@Autowired的属性已被注入 -->
	<context:component-scan base-package="com.xxx">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
<!-- 启动对@AspectJ注解的支持  通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller-->   
	<aop:aspectj-autoproxy/>  
	<aop:aspectj-autoproxy proxy-target-class="true"/>  
2. 创建自定义注解
import java.lang.annotation.*;


@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Documented
public @interface UserOperate {
 
	//模块名  
    String moduleName() default "";
  	//操作名  
    String optionName() default "";
    //操作内容  
    String option() default ""; 
    //用户
    String userId() default ""; 
}

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

3.创建Spring AOP切面类


/**
 * 处理用户操作日志切面类
 * 
 * @author liuyong
 *
 */
@Aspect
@Component
public class LogAspect {

	private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

	@Autowired
	private UserOperateLogService userOperateLogService;
	@Autowired
	LeaderService leaderService;
	@Autowired
	TokenManageService tokenmanageService;

	@Pointcut("@annotation(xxx.UserOperate)") 
	public void logPointCut() {
	}
	
	/** 
     * 前置通知 用于拦截Controller层记录用户的操作 
     * 
     * @param joinPoint 切点 
     */  
    @Before("logPointCut()")  
    public void doBefore(JoinPoint joinPoint) {  
    	handleLog(joinPoint, null);
    }  

	/**
	 * 后置通知 用于拦截操作,在方法返回后执行,可以获取返回结果
	 * 
	 * @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);
	}
	/**
	 * 数据获取以及入库
	 * @param joinPoint
	 * @param e
	 */
	private void handleLog(JoinPoint joinPoint, Exception e) {
		 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();  
	        //请求的IP  
	        String  ip = this.getIpAddr(request);
	        // 引入下面获取注解的方法对象
	        AnnotationResolver annotationResolver = AnnotationResolver.newInstance();
	        String userId=null;
	        
	        try {  
	        	UserOperate userOperate = getAnnotationLog(joinPoint);
	        	// 模块名
	 			String moduleName = userOperate.moduleName();
	 			// 操作内容
	 			String option = userOperate.option();
	 			// 操作名
	 			String optionName  = userOperate.optionName();
	 			
	 			//*========获取用户模块=========*// 
	 			// 从注解中获取参数
	 			userId=annotationResolver.resolver(joinPoint, userOperate.userId()).toString();
	 			
	            //*========控制台输出=========*//  
	            System.out.println("=====前置通知开始=====");  
	            System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
	            System.out.println("操作模块:" + moduleName);
	            System.out.println("方法描述:" + userOperate.option());  
	            System.out.println("请求人:"+ userId);  
	            System.out.println("请求IP:" + ip);  
	            //*========数据库日志=========*//  
	            	
	 			// 访问类名
	 			String className = (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
	 			// 访问方法名
	 			String methodName = joinPoint.getSignature().getName();
	 			// 获取用户名称
	 			MyworkLeader leader = leaderService.getLeaderByAd(userId);
	 			UserOperateLog uol = new UserOperateLog();
	 			
	 			uol.setUserId(userId);
	 			uol.setOptionName(optionName);
	 			uol.setUserName(leader.getName());
	 			uol.setOperateDesc(option);
	 			uol.setClassName(className);
	 			uol.setModuleName(moduleName);
	 			uol.setIpAddress(ip);
	 			Date now = DateUtil.getNow();
	 			uol.setCreateTime(new Timestamp(now.getTime()));
	 			if(!userId.toString().equals("") || !userId.toString().equals(null)){
	 				userOperateLogService.saveOrUpdate(uol);
	 			}
	 			
	            System.out.println("=====前置通知结束=====");  

		} catch (Exception exp) {
			// 记录本地异常日志
			// 记录本地异常日志
			log.error("\n====================================== 异常信息通知 ======================================");
			log.error("异常信息:{}", exp.getMessage());
	log.error("\n====================================================================================\n");
			exp.printStackTrace();
		}
	}

	/**
	 * 是否存在注解,如果存在就获取
	 */
	private static UserOperate getAnnotationLog(JoinPoint joinPoint) throws Exception {
		Signature signature = joinPoint.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method method = methodSignature.getMethod();
		if (method != null) {
			return method.getAnnotation(UserOperate.class);
		}
		return null;
	}

	/**
	 * 获取IP地址
	 * 
	 * @param request
	 * @return
	 */
	private String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

}
4.因为需要从注解获取方法参数值

经过各种方式进行尝试以及百度,终于在一篇博客中发现有一个完美解决方案《java在注解中绑定方法参数的解决方案》的博客中的AnnotationResolver,便可获取前端页面传来的参数值。

package com.cnooc.common;
 
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;
    }
	
}
5.在Controller中添加自定义注解
	@RequestMapping(value = "/validateLeader")
	@UserOperate(moduleName = "登录模块",optionName="账号密码登录",option = "/web/leader/validateLeader",userId="#{userid}")
	public ResponseEntity<String> validateLeader(Model model, HttpServletRequest request, String userid,
			String password, String type) throws Exception {
		boolean flag = false;
		boolean result = false;
		
		//  代码省略。。。。。。。。
		return renderData(false, "对不起!您输入的用户名和密码错误!", null);
	}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值