新的Spring Aop实现全局日志记录功能

Spring基本篇操作日志
placeholder image
admin 发布于:2019-04-05 20:01:59
阅读:loading

在2015年本站里面的一篇《浅浅的议一下Spring整合EhCache的两种方式》文章中,笔者头一次的认识到了缓存结合自定义注解使用的便捷,但心中小小的疑问是不清除它是怎么实现的,借着这次翻起使用Aop实现全局日志的功能又想起了这茬,又琢磨着去再实现一版,主旨是参考上述文章的自定义注解和表达式引擎实现,如此,首选要去了解的便是spel表达式引擎的使用,看了一下资料感觉与Apache的commons-el高度的相识,在简单了解spel表达式引擎的API后,觉得实现已经不再是问题了,本文将是一版新的全局用户cao作日志记录的实现。

文接前篇,围绕前文说的缺点进行改进:

(1)将更少的切入实际业务逻辑;

(2)增加拦截函数出现错误时的日志记录;

本次实现的原理是使用自定义注解的形式集成spel表达式引擎定义好需记录的log内容,在拦截器中使用反射去获取被拦截函数中的所有参数定义的变量名称,将变量名称与变量值赋值交给表达式引擎去解析,解析后的数据内容则为日志内容,具体实现参考如下:

自定义注解类

package cn.chendd.annotations;

import ...


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {

    
/**
     * 
参数默认为空是表示记录全部的参数作为日志内容
     
参数不为空则只按照表达式引擎进行解析
     
@return 且编码且看
     
*/
    
public String value() default "";
}

记录日志实现类

package cn.chendd.modules.user.service.impl;

import ...

 

@Service
public class UserService implements IUserService {
   

    
@Log(value = "'新增用户,标识为:' + #userToken + ',用户ID' + #user.userId")
    
public Boolean addUser(User user, String userToken) {
        
return this._saveUser(user , userToken);
    }

    
@Log(value = "(#user.userId == null ? '新增' : '修改') + '用户,标识为:' + #userToken + ',用户名称:' + #user.userName")
    
public Boolean saveUser(User user, String userToken) {
        user.setUserName(user.getUserName() + 
"_new");
        
return Boolean.TRUE;
    }

    
@Log(value = "'重写toString函数:' + #qqUser")
    
public void updateUser(QQUser qqUser) {
        qqUser.setUserName(qqUser.getUserName() + 
"_new");
        qqUser.setAge((
short)(qqUser.getAge() + 5));
        qqUser.setSource(
"腾讯用户");
    }

    
@Log //无参数则表示将记录所有参数的值,含本函数中对对象属性的修改

    public void updateUser2(QQUser qqUser) {
        
this.updateUser(qqUser);
    }


    
private Boolean _saveUser(User user, String userToken) {
        
//假设这里是存储数据入库,并返回cao作成功
        
user.setUserId(1001);//数据库返回主键1001
        
return Boolean.TRUE;
    }
}
 

Aop实现类

package cn.chendd.aops;

import ...

@Component
@Aspect
public class LogAop {

    
@Resource
    
private IOperationLogService operationLogService;

    
@Pointcut(value = "execution(* cn.chendd.modules.*.service.impl.*Service.*(..))")
    
public void pointcutDeclare(){}

    
@Before(value = "pointcutDeclare()")
    
public void before(JoinPoint joinPoint){
        MethodInvocationProceedingJoinPoint methodPoint = (MethodInvocationProceedingJoinPoint) joinPoint;
        System.
out.println("before: " + methodPoint.getStaticPart());
    }

    
@Around(value = "pointcutDeclare()")
    
public Object around(ProceedingJoinPoint point) throws Throwable {
        
//方法结构体
        
MethodInvocationProceedingJoinPoint methodPoint = (MethodInvocationProceedingJoinPoint) point;
        MethodSignature signature = (MethodSignature) methodPoint.getSignature();
        Object object = 
null;
        
try{
            object = point.proceed();
            storageOperationLog(methodPoint, signature);
        } 
catch(Exception e){
            storageOperationLog(methodPoint, signature);
            
throw new Throwable(e);
        }
        
return object;
    }

    
@After(value = "pointcutDeclare()")
    
public void after(JoinPoint joinPoint){
        MethodInvocationProceedingJoinPoint methodPoint = (MethodInvocationProceedingJoinPoint) joinPoint;
        System.
out.println(" after: " + methodPoint.getStaticPart());
    }


    
/**
     * 
获得当前上下文的参数集合
     
*/
    
private Map<String , Object> getParams(Object args[], MethodSignature signature) {
        Map<String , Object> paramMap = 
new HashMap<String , Object>();
        String paramNames[] = signature.getParameterNames();
        
for (int i = 0; i < args.length; i++) {
            paramMap.put(paramNames[i], args[i]);
        }
        
return paramMap;
    }

    
/**
     * 
处理日志存储
     
*/
    
private void storageOperationLog(MethodInvocationProceedingJoinPoint methodPoint, MethodSignature signature) {
        
Log log = signature.getMethod().getDeclaredAnnotation(Log.class);
        
//方法参数值
        
Object args[] = methodPoint.getArgs();
        
if(log != null){
            Map<String , Object> params = getParams(args, signature);
            String value = log.value();
            
operationLogService.saveOperationLog(value , params);
        }
        String message = 
"方法名:%s,参数个数:%s,返回类型:%s";
        System.
out.println(String.format(message, signature.getName() , args.length , signature.getReturnType()));
    }

}
 

模拟存储入库实现类(SpelStandUtilq前文有给出过

package cn.chendd.operation.service.impl;

import ...
/**
 * 
记录日志的service处理
 
该类存放路径不能被拦截日志的AOP所拦截,否则会造成死循环
 
*/
@Service
public class OperationLogService implements IOperationLogService {

    
@Override
    
public void saveOperationLog(String eval, Map<String, Object> params) {
        
if(StringUtils.isEmpty(eval)){
            System.
out.println("记录的数据信息为:+ JSONObject.toJSONString(params));
            
return;
        }
        SpelStandUtil spel = 
new SpelStandUtil();
        spel.sets(params);
        Object object = spel.get(eval);
        System.
out.println("记录的数据信息为:+ object);
    }
}
 

运行结果

image.png

(按@Log声明表达式规则生成)

image.png

(按@Log无表达式时默认记录全部参数)

至此本次的功能实现已经完结,本次改进目前只是演示阶段,实际使用中需要考虑记录功能的模块名称和当前用户相关的信息,然而这些也是很容易实现的,如有疑问可以再此文章留言。

示例下载(IDEA源码工程)

新的Spring Aop实现全局日志记录功能.zip

说明:基于此种方式的实现已经在实际应用中发现了更多应优化与改进的地方,如有需要可留言交流。


 点赞


 发表评论

当前回复:作者

 评论列表


留言区