Spring AOP 实现日志记录功能
2018-02-12 12:00
716 查看
最近项目中需要记录日志,跟大家想的一样 ,利用spring aop去实现,之前也参考了一些代码,自己实现了一套设计,供大家参考。
之前看到网上很多是基于切面类Aspect去实现了,在切面类中定义before after around等逻辑以及要拦截等方法。本文利用注解实现了一套可以扩展等日志记录模块。
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);
}
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);
}
}
}
}
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;
}
}
}
之前看到网上很多是基于切面类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类,然后添加注解到对应模块就可以。@Servicepublic 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日志实现(注解版)-扩展增加检查订单功能,以便记录并检测输入的参数
- 从头认识Spring-3.4 简单的AOP日志实现-扩展增加检查订单功能,以便记录并检测输入的参数
- 从头认识Spring-3.8 简单的AOP日志实现(注解版)-扩展添加检查订单功能,以便记录并检測输入的參数
- 从头认识Spring-3.4 简单的AOP日志实现-扩展添加检查订单功能,以便记录并检測输入的參数
- Spring AOP自定义注解实现系统日志记录管理
- springMVC +Mybatis +spring aop 实现用户系统操作日志记录
- spring2.x使用aop实现声明式日志记录
- spring的AOP实现记录操作日志
- spring AOP的 操作日志记录功能
- 关于Spring Aop,日志功能简单的实现
- 不使用spring的aop功能实现日志输出
- springAOP面向切面编程之日志记录功能
- Spring+SpringMVC+Mybatis 利用AOP自定义注解实现可配置日志快照记录
- spring aop 面向切面编程 如何来做一个强大的日志记录功能
- 基于Spring AOP和Groovy日志模板配置的日志记录框架的二次实现与使用案例
- 从头认识Spring-3.3 简单的AOP日志实现-增加检查订单功能
- 从头认识Spring-3.2 简单的AOP日志实现-需要记录方法的运行时间
- Spring AOP进行日志记录,管理 (使用Spring的拦截器功能获取对action中每个方法的调用情况,在方法调用前
- Spring AOP 实现系统操作日志记录