记一次用户操作日志功能完成-SpringAOP拦截Controller实现日志管理(自定义注解的方式
)
一、背景
在做项目的时候,领导对我们说需要记录用户的各种操作形成一个用户习惯的数据库。因为是一个新手,以前没有做过日志系统,经过分析我们要完成这个功能需要记录用户操作的操作模块,操作描述,方法类,以及用户Id,用户名等详细信息。
二、收集实现方案
经过度娘的千百遍虐,总结出大致有三种方法,可能不全不过也是足够用了。
- 第一种最简单也是最繁琐且效率低的方法
即写一个方法记录操作,每个入口方法去调用这个方法。因为会产生重复操作,所以没有进行考虑。
- 第二种是采用spring的拦截器进行方法拦截
新建一个拦截器的class继承spring web的HandlerInterceptorAdapter类,在spring4中该类有四个方法可以进行重写,如:
preHandle:它会在处理方法之前执行,可以用来做一些编码处理、安全限制之类的操作。
postHandle:它是在方法执行后开始返回前执行,可以进行日志记录、修改ModelView之类的操作。
afterCompletion:最后执行,无论出错与否都会执行这个方法,可以用来记录异常信息和一些必要的操作记录。
afterConcurrentHandlingStarted:controller方法异步开始执行时就开始执行这个方法,而postHandle需要等到controller异步执行完成后再执行。
因为spring的拦截器无法获取处理函数的参数值,无法直接判断这个操作是哪个用户执行的,所以也没考虑。
- 第三种就是采用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);
}