Spring AOP 实现日志记录功能
Posted hanruikai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP 实现日志记录功能相关的知识,希望对你有一定的参考价值。
最近项目中需要记录日志,跟大家想的一样 ,利用spring aop去实现,之前也参考了一些代码,自己实现了一套设计,供大家参考。
之前看到网上很多是基于切面类Aspect去实现了,在切面类中定义before after around等逻辑以及要拦截等方法。本文利用注解实现了一套可以扩展等日志记录模块。
1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public abstract @interface RequiredLogInterceptor
boolean required() default true;
String targetGenerator() default "";
OperateType operateType() default OperateType.GET;
requried:注解是否生效
targetGenerator: 每个模块记录等内容不同,入口参数不同,所以需要个性化定制日志等记录内容,每个模块的日志生成有自己定义的generator类,并且重写generateContent方法。
operateType:当前方法是增加,删除,还是修改
public abstract class ContentGerator
public static String SPLIT="/";
public static String CONTENT_SPLIT=",";
public static String VALUE_SPLIT=":";
abstract List<UserOperateLog> generateContent(Object returnValue, Object[] args, OperateType operateType);
2. 定义拦截器
本模块主要是后置通知,主要逻辑如下:
1.拦截方法,判断是否有注解loginterceptor
2. 如果有判断是否执行成功,成功则记录log,失败不记录
3. 获取注解中配置的generator类,利用反射调用generateContent方法,生成个性化日志内容
5.在日志中添加其他公共属性,比如用户id,创建时间等等。所有个性化定制的日志信息都是在generator类中产生。
public class LogAfterInterceptor implements AfterReturningAdvice
@Autowired
private LogService logService;
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable
RequiredLogInterceptor requiredLogInterceptor = AnnotationUtils.findAnnotation(method, RequiredLogInterceptor.class);
if (requiredLogInterceptor != null)
if(returnValue!=null&&returnValue instanceof Response)
Response response=(Response)returnValue;
String code=response.getCode();
String code200= MegCodeEnums.ResponseCodeEnum.C200.getCode();
String code201= MegCodeEnums.ResponseCodeEnum.C201.getCode();
if (!Strings.isNullOrEmpty(code)&&!code.equalsIgnoreCase(code200)&&!code.equalsIgnoreCase(code201))
return;
String targetGeneratorName=requiredLogInterceptor.targetGenerator();
OperateType operateType=requiredLogInterceptor.operateType();
Class targetGeneratorclass=Class.forName("com.puhui.flowplatform.manage.log."+targetGeneratorName);
Method executeMethod=targetGeneratorclass.getMethod("generateContent",Object.class,Object[].class,OperateType.class);
ContentGerator targetGeneratorBean=(ContentGerator)SpringContextHolder.getBean(targetGeneratorclass);
List<UserOperateLog> userOperateLogList=(List<UserOperateLog>)executeMethod.invoke(targetGeneratorBean,returnValue,args,operateType);
if(CollectionUtils.isNotEmpty(userOperateLogList))
userOperateLogList.forEach(userOperateLog ->
userOperateLog.setCreateTime(new Date());
//token
long userId=0L;
if (args.length>0&&args[0] instanceof String)
userId = CommonUtils.getManageCurUserId(args[0].toString());
userOperateLog.setUserId(userId);
);
logService.batchInsertLog(userOperateLogList);
3 Generator类
继承统一的ContentGenerator类,便于共享一些常量。根据当前操作类型,生成对应的日志内容就可以了。如果需要新增模块, 先定义自己的日志generator类,然后添加注解到对应模块就可以。
@Service
public class ContentGeneratorForRoleMgt extends ContentGerator
@Autowired
private MenuService menuService;
private String generateMenus(VoRole voRole)
List<Menus> menusList=voRole.getMenusList();
StringBuffer stringBuffer=new StringBuffer();
if (CollectionUtils.isNotEmpty(menusList))
menusList.forEach(menus ->
Long menuId=menus.getId();
Menus menusTemp=menuService.queryMenuByMenuId(menuId);
stringBuffer.append(menusTemp.getDisplayTitle()+CONTENT_SPLIT);
);
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
return stringBuffer.toString();
@Override
public List<UserOperateLog> generateContent(Object returnValue, Object[] args, OperateType operateType)
List<UserOperateLog> userOperateLogList=new ArrayList<>();
UserOperateLog userOperateLog=new UserOperateLog();
if (operateType==OperateType.ADD||operateType==OperateType.UPDATE)
VoRole voRole=(VoRole)args[1];
String menus=generateMenus(voRole);
userOperateLog.setOperateContent("角色名称"+VALUE_SPLIT+voRole.getDisplayName()+SPLIT+"权限"+VALUE_SPLIT+menus);
userOperateLog.setOperateType(operateType==OperateType.ADD?LogOperateTypeEnum.ADD_ROLE.getCode():LogOperateTypeEnum.UPDATE_ROLE.getCode());
if (operateType==OperateType.DELETE)
if(returnValue!=null)
Response response=(Response) returnValue;
String roleName=response.getData().toString();
userOperateLog.setOperateContent(roleName);
userOperateLog.setOperateType(LogOperateTypeEnum.DELETE_ROLE.getCode());
userOperateLogList.add(userOperateLog);
return userOperateLogList;
4. 注解应用
@PutMapping(value = "roles/roleId")
@RequiredLogInterceptor(targetGenerator = "ContentGeneratorForRoleMgt",operateType= OperateType.UPDATE)
@ApiOperation(value = "修改角色", httpMethod = "PUT", response = Response.class, notes = "修改角色")
public Response<Object> updateRole(@RequestHeader String token,@RequestBody VoRole voRole, @PathVariable("roleId") String roleId)
LOGGER.info("updateRole入参:", JSONObject.toJSONString(voRole));
5. Configuration
public class SpringMvcConfig extends WebMvcConfigurerAdapter
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
super.configureMessageConverters(converters);
// 初始化转换器
FastJsonHttpMessageConverter fastConvert = new FastJsonHttpMessageConverter();
// 初始化一个转换器配置
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
// 将配置设置给转换器并添加到HttpMessageConverter转换器列表中
fastConvert.setFastJsonConfig(fastJsonConfig);
converters.add(fastConvert);
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
registry.addResourceHandler("/swagger-ui.html").addResourceLocations(
ResourceUtils.CLASSPATH_URL_PREFIX + "/META-INF/resources/");
registry.addResourceHandler("/static/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/static/",
ResourceUtils.CLASSPATH_URL_PREFIX + "/dist/static/");
registry.addResourceHandler("/page/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/dist/");
super.addResourceHandlers(registry);
@Bean
public ViewResolver viewResolver()
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix(ResourceUtils.CLASSPATH_URL_PREFIX + "templates/");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html; charset=UTF-8");
return resolver;
// 创建Advice或Advisor
@Bean
public BeforeAdvice beforeControllerInterceptor()
return new BeforeControllerInterceptor();
@Bean
public AfterAdvice logAfterInterceptor()
return new LogAfterInterceptor();
// 创建Advice或Advisor
@Bean
public BeforeAdvice logBeforeInterceptor()
return new LogBeforeInterceptor();
// 使用BeanNameAutoProxyCreator来创建代理
@Bean
public BeanNameAutoProxyCreator beanAutoProxyCreator()
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true); // 设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("*Controller"); //
// 设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("logAfterInterceptor");
return beanNameAutoProxyCreator;
@Bean
public BeanNameAutoProxyCreator beanBeforeAutoProxyCreator()
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true);
// 设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("*Controller");
// 设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("beforeControllerInterceptor");
beanNameAutoProxyCreator.setInterceptorNames("logBeforeInterceptor");
6.写在结尾
本来实现都代码版本中,所有都日志生成代码都在后置拦截器中,并且根据当前执行都方法都classname和methodname去判断当前都方法,出现很多if 判断,且method name都不一样,有的是addXXX,有的是createXXX,显然设计不合理。后来重新进行了设计,有什么不足,希望大家可以指出。
7.非自定义注解实现方式
package com.puhui.flowplatform.manage.interceptor;
import com.puhui.flowplatform.common.model.manage.UserOperateLog;
import com.puhui.flowplatform.manage.service.LogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
public class LogAspect
public Long id=null;
@Autowired
LogService logService;
/**
* 添加业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.add*(..))")
public void insertCell()
/**
* 修改业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.update*(..))")
public void updateCell()
/**
* 删除业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.delete*(..))")
public void deleteCell()
/**
* 添加操作日志(后置通知)
*
* @param joinPoint
* @param object
*/
@AfterReturning(value = "insertCell()", argNames = "object", returning = "object")
public void insertLog(JoinPoint joinPoint, Object object) throws Throwable
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) // 没有参数
return;
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取操作内容
String opContent = optionContent(joinPoint.getArgs(), methodName);
UserOperateLog log = new UserOperateLog();
log.setOperateContent(opContent);
log.setUserId(id);;
log.setOperateType(1);//enum 增加
log.setCreateTime(new Date());
logService.insertLog(log);
/**
* 管理员修改操作日志(后置通知)
*
* @param joinPoint
* @param object
* @throws Throwable
*/
@AfterReturning(value = "updateCell()", argNames = "object", returning = "object")
public void updateLog(JoinPoint joinPoint, Object object) throws Throwable
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) // 没有参数
return;
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取操作内容
String opContent = optionContent(joinPoint.getArgs(), methodName);
// 创建日志对象
UserOperateLog log = new UserOperateLog();
log.setOperateContent(opContent);
log.setUserId(id);;
log.setOperateType(2);//enum 修改
log.setCreateTime(new Date());
logService.insertLog(log);
/**
* 删除操作
*
* @param joinPoint
* @param object
*/
@AfterReturning(value = "deleteCell()", argNames = "object", returning = "object")
public void deleteLog(JoinPoint joinPoint, Object object) throws Throwable
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) // 没有参数
return;
// 获取方法名
String methodName = joinPoint.getSignature().getName();
StringBuffer rs = new StringBuffer();
rs.append(methodName);
String className = null;
for (Object info : joinPoint.getArgs())
// 获取对象类型
className = info.getClass().getName();
className = className.substring(className.lastIndexOf(".") + 1);
rs.append("[参数,类型:" + className + ",值:(id:"
+ joinPoint.getArgs()[0] + ")");
// 创建日志对象
UserOperateLog log = new UserOperateLog();
log.setOperateContent(rs.toString());
log.setUserId(id);;
log.setOperateType(3);//删除
log.setCreateTime(new Date());
logService.insertLog(log);
/**
* 使用Java反射来获取被拦截方法(insert、update)的参数值, 将参数值拼接为操作内容
*
* @param args
* @param mName
* @return
*/
public String optionContent(Object[] args, String mName)
if (args == null)
return null;
StringBuffer rs = new StringBuffer();
rs.append(mName);
String className = null;
int index = 1;
// 遍历参数对象
for (Object info : args)
// 获取对象类型
className = info.getClass().getName();
className = className.substring(className.lastIndexOf(".") + 1);
rs.append("[参数" + index + ",类型:" + className + ",值:");
// 获取对象的所有方法
Method[] methods = info.getClass().getDeclaredMethods();
// 遍历方法,判断get方法
for (Method method : methods)
String methodName = method.getName();
// 判断是不是get方法
if (methodName.indexOf("get") == -1) // 不是get方法
continue;// 不处理
Object rsValue = null;
try
// 调用get方法,获取返回值
rsValue = method.invoke(info);
catch (Exception e)
continue;
// 将值加入内容中
rs.append("(" + methodName + ":" + rsValue + ")");
rs.append("]");
index++;
return rs.toString();
以上是关于Spring AOP 实现日志记录功能的主要内容,如果未能解决你的问题,请参考以下文章
从头认识Spring-3.8 简单的AOP日志实现(注解版)-扩展增加检查订单功能,以便记录并检测输入的参数