项目中,我们经常会遇到这样的需求,记录用户一些特殊的操作日志,我遇到过好几次,所以今天我分享下自己的实现。
实现方式有很多种,我是依据SpringAop+SpEL表达式实现的,下面看下代码:
注解解析类和切面配置
实现方法可以是在spring配置文件中配置切面层,也可以是自定义标有Aspect注解的类,接下来我先采用配置方式实现
解析处理类 SysLogAspect.java
package com.smqi.common.log;
import com.smqi.common.aop.OperInfo;
import com.smqi.common.aop.OperationType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 系统操作日志记录
*
* Created by smqi on 2016/11/4.
*/
public class SysLogAspect {
/**
* 日志记录器
*/
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 缓存标注有自定义日志注解的方法参数名称
*/
private static Map parameterNameCaches = new ConcurrentHashMap();
/**
* 缓存SPEL Expression
*/
private static Map spelExpressionCaches = new ConcurrentHashMap();
/**
* Spel表达式语法分析式
*/
private static ExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数名称数组
*/
private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere =
new LocalVariableTableParameterNameDiscoverer();
public Object doAroundMethodForCtLog(ProceedingJoinPoint pjp)
throws Throwable {
//获取当前注解
Method theMethod = getMethod(pjp);
if (theMethod != null
&& theMethod.isAnnotationPresent(OperInfo.class)) {
OperInfo annotation = theMethod.getAnnotation(OperInfo.class); // 获取指定注解
String descpTemp = annotation.description();
String descption = executeTemplate(descpTemp, pjp);
OperationType type = annotation.type();
logger.info("操作类型:【{}】,日志信息:【{}】",type.getType(),descption);
//TODO 调用数据存储层进行入库,此处省略……
}
return pjp.proceed();
}
/**
* 解析执行description中的SPEL模板。
*
* @param template
* @param joinPoint
* @return
*/
private String executeTemplate(String template, ProceedingJoinPoint joinPoint) {
//获取原始对象方法名
String methodLongName = joinPoint.getSignature().toLongString();
String[] parameterNames = parameterNameCaches.get(methodLongName);
if (parameterNames == null) {
Method method = getMethod(joinPoint);
parameterNames = parameterNameDiscovere.getParameterNames(method);
parameterNameCaches.put(methodLongName, parameterNames);
}
StandardEvaluationContext context = new StandardEvaluationContext();
Object[] args = joinPoint.getArgs();
if (args.length == parameterNames.length) {
for (int i = 0, len = args.length; i < len; i++)
context.setVariable(parameterNames[i], args[i]);
}
Expression expression = spelExpressionCaches.get(template);
if (expression == null) {
expression = parser.parseExpression(template, new TemplateParserContext());
spelExpressionCaches.put(template, expression);
}
return expression.getValue(context, String.class);
}
/**
* 获取当前执行的方法
*
* @param joinPoint
* @return
*/
private Method getMethod(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
try {
method = target.getClass().getMethod(method.getName(),
method.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return method;
}
}
xml文件配置如下:
<!--系统操作日志记录-->
<bean id="sysLogAspect" class="com.smqi.common.log.SysLogAspect"/>
这里我集成到了数据库读写分离的aop配置里,不要的话可以把第一个aop:aspect配置删掉
下面我在controller层调用demoService.addDemo(user)方法,看下运行结果:
上述是基于配置的实现,下面我们再尝试下自定义标有Aspect注解的类
先将原先的配置删除,只需要添加类SysLogAspect2的bean即可
SysLogAspect2.java
package com.smqi.common.log;
import com.smqi.common.aop.OperInfo;
import com.smqi.common.aop.OperationType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* Created by smqi on 2016/11/4.
*/
@Aspect
public class SysLogAspect2 {
/**
* 日志记录器
*/
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 缓存标注有自定义日志注解的方法参数名称
*/
private static Map<String, String[]> parameterNameCaches = new ConcurrentHashMap<String, String[]>();
/**
* 缓存SPEL Expression
*/
private static Map<String, Expression> spelExpressionCaches = new ConcurrentHashMap<String, Expression>();
/**
* Spel表达式语法分析式
*/
private static ExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数名称数组
*/
private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere =
new LocalVariableTableParameterNameDiscoverer();
@Around("execution(* com.smqi.modules..service.impl.*Impl.*(..)) && @annotation(annotation)")
public Object doAroundMethodForCtLog(ProceedingJoinPoint pjp, OperInfo annotation)
throws Throwable {
//获取当前注解
String descpTemp = annotation.description();
String descption = executeTemplate(descpTemp, pjp);
OperationType type = annotation.type();
logger.info("操作类型:【{}】,日志信息:【{}】", type.getType(), descption);
//TODO 调用数据存储层进行入库,此处省略……
return pjp.proceed();
}
/**
* 解析执行description中的SPEL模板。
*
* @param template
* @param joinPoint
* @return
*/
private String executeTemplate(String template, ProceedingJoinPoint joinPoint) {
//获取原始对象方法名
String methodLongName = joinPoint.getSignature().toLongString();
String[] parameterNames = parameterNameCaches.get(methodLongName);
if (parameterNames == null) {
Method method = getMethod(joinPoint);
parameterNames = parameterNameDiscovere.getParameterNames(method);
parameterNameCaches.put(methodLongName, parameterNames);
}
StandardEvaluationContext context = new StandardEvaluationContext();
Object[] args = joinPoint.getArgs();
if (args.length == parameterNames.length) {
for (int i = 0, len = args.length; i < len; i++)
context.setVariable(parameterNames[i], args[i]);
}
Expression expression = spelExpressionCaches.get(template);
if (expression == null) {
expression = parser.parseExpression(template, new TemplateParserContext());
spelExpressionCaches.put(template, expression);
}
return expression.getValue(context, String.class);
}
/**
* 获取当前执行的方法
*
* @param joinPoint
* @return
*/
private Method getMethod(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
try {
method = target.getClass().getMethod(method.getName(),
method.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return method;
}
}