搜索
首页Javajava教程如何在Java SpringBoot项目中优雅地实现操作日志记录?

    一、AOP是什么?

    AOP(Aspect-Oriented Programming:⾯向切⾯编程),说起AOP,几乎学过Spring框架的人都知道,它是Spring的三大核心思想之一(IOC:控制反转,DI:依赖注入,AOP:面向切面编程)。能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

    二、AOP做了什么?

    简单说来,AOP主要做三件事:

    • 1、在哪里切入,也就是日志记录等非业务代码在哪些业务代码中执行。

    • 2、在什么时候切入,是在业务代码执行前还是后。

    • 3、切入后做什么事情,比如权限校验,日志记录等。

    可以用一张图来理解:

    如何在Java SpringBoot项目中优雅地实现操作日志记录?

    图上的一个核心术语的说明:

    • Pointcut切点,决定在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。execution方式:可以用路径表达式指定哪些类织入切面,annotation方式:可以指定被哪些注解修饰的代码织入切面。

    • Advice处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。

    • Aspect切面,即Pointcut和Advice。

    • Joint point连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。

    • Weaving织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

    三、实现步骤

    (1)自定义一个注解@Log (2)创建一个切面类,切点设置为拦截标注@Log的方法,截取传参,进行日志记录 (3)将@Log标注在接口上

    具体的实现步骤如下:

    1. 添加AOP依赖

    代码如下(示例):

     <dependency>
       	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    2. 自定义一个日志注解

    日志一般使用的是注解类型的切点表达式,我们先创建一个日志注解,当spring容器扫描到有此注解的方法就会进行增强。

    代码如下(示例):

    @Target({ ElementType.PARAMETER, ElementType.METHOD }) // 注解放置的目标位置,PARAMETER: 可用在参数上  METHOD:可用在方法级别上
    @Retention(RetentionPolicy.RUNTIME)    // 指明修饰的注解的生存周期  RUNTIME:运行级别保留
    @Documented
    public @interface Log {
    
        /**
         * 模块
         */
        String title() default "";
    
        /**
         * 功能
         */
        public BusinessType businessType() default BusinessType.OTHER;
    
        /**
         * 是否保存请求的参数
         */
        public boolean isSaveRequestData() default true;
    
        /**
         * 是否保存响应的参数
         */
        public boolean isSaveResponseData() default true;
    }

    3. 切面声明

    申明一个切面类,并交给Spring容器管理。

    代码如下(示例):

    @Aspect
    @Component
    @Slf4j
    public class LogAspect {
        @Autowired
        private IXlOperLogService operLogService;
        /**
         * 处理完请求后执行
         * @param joinPoint 切点
         */
        @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
        public void doAfterReturnibng(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
            handleLog(joinPoint, controllerLog, null, jsonResult);
        }
        protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
            try {
                // 获取当前的用户
                JwtUser loginUser = SecurityUtils.getLoginUser();
    
                // 日志记录
                XlOperLog operLog = new XlOperLog();
                operLog.setStatus(0);
                // 请求的IP地址
                String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
                if ("0:0:0:0:0:0:0:1".equals(iP)) {
                    iP = "127.0.0.1";
                }
                operLog.setOperIp(iP);
                operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
                if (loginUser != null) {
                    operLog.setOperName(loginUser.getUsername());
                }
                if (e != null) {
                    operLog.setStatus(1);
                    operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
                }
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                operLog.setMethod(className + "." + methodName + "()");
                operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
                operLog.setOperTime(new Date());
                // 处理设置注解上的参数
                getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
                // 保存数据库
                operLogService.save(operLog);
    
            } catch (Exception exp) {
                log.error("异常信息:{}", exp.getMessage());
                exp.printStackTrace();
            }
        }
        /**
         * 获取注解中对方法的描述信息 用于Controller层注解
         * @param log 日志
         * @param operLog 操作日志
         * @throws Exception
         */
        public void getControllerMethodDescription(JoinPoint joinPoint, Log log, XlOperLog operLog, Object jsonResult) throws Exception {
            // 设置操作业务类型
            operLog.setBusinessType(log.businessType().ordinal());
            // 设置标题
            operLog.setTitle(log.title());
            // 是否需要保存request,参数和值
            if (log.isSaveRequestData()) {
                // 设置参数的信息
                setRequestValue(joinPoint, operLog);
            }
            // 是否需要保存response,参数和值
            if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
                operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
            }
        }
        /**
         * 获取请求的参数,放到log中
         * @param operLog 操作日志
         * @throws Exception 异常
         */
        private void setRequestValue(JoinPoint joinPoint, XlOperLog operLog) throws Exception {
            String requsetMethod = operLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
                String parsams = argsArrayToString(joinPoint.getArgs());
                operLog.setOperParam(StringUtils.substring(parsams,0,2000));
            } else {
                Map<?,?> paramsMap = (Map<?,?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                operLog.setOperParam(StringUtils.substring(paramsMap.toString(),0,2000));
            }
        }
        /**
         * 参数拼装
         */
        private String argsArrayToString(Object[] paramsArray) {
            String params = "";
            if (paramsArray != null && paramsArray.length > 0) {
                for (Object object : paramsArray) {
                    // 不为空 并且是不需要过滤的 对象
                    if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
                        Object jsonObj = JSON.toJSON(object);
                        params += jsonObj.toString() + " ";
                    }
                }
            }
            return params.trim();
        }
        /**
         * 判断是否需要过滤的对象。
         * @param object 对象信息。
         * @return 如果是需要过滤的对象,则返回true;否则返回false。
         */
        @SuppressWarnings("rawtypes")
        public boolean isFilterObject(final Object object) {
            Class<?> clazz = object.getClass();
            if (clazz.isArray()) {
                return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
            } else if (Collection.class.isAssignableFrom(clazz)) {
                Collection collection = (Collection) object;
                for (Object value : collection) {
                    return value instanceof MultipartFile;
                }
            } else if (Map.class.isAssignableFrom(clazz)) {
                Map map = (Map) object;
                for (Object value : map.entrySet()) {
                    Map.Entry entry = (Map.Entry) value;
                    return entry.getValue() instanceof MultipartFile;
                }
            }
            return object instanceof MultipartFile || object instanceof HttpServletRequest
                    || object instanceof HttpServletResponse || object instanceof BindingResult;
        }
    }

    4. 标注在接口上

    将自定义注解标注在需要记录操作日志的接口上,代码如下(示例):

    	@Log(title = "代码生成", businessType = BusinessType.GENCODE)
        @ApiOperation(value = "批量生成代码")
        @GetMapping("/download/batch")
        public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
            String[] tableNames = Convert.toStrArray(tables);
            byte[] data = genTableService.downloadCode(tableNames);
            genCode(response, data);
        }

    5. 实现的效果

    执行相关操作就会记录日志,记录了一些基础信息存在数据表里。

    如何在Java SpringBoot项目中优雅地实现操作日志记录?

    以上是如何在Java SpringBoot项目中优雅地实现操作日志记录?的详细内容。更多信息请关注PHP中文网其他相关文章!

    声明
    本文转载于:亿速云。如有侵权,请联系admin@php.cn删除

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    AI Hentai Generator

    AI Hentai Generator

    免费生成ai无尽的。

    热门文章

    R.E.P.O.能量晶体解释及其做什么(黄色晶体)
    1 个月前By尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O.最佳图形设置
    1 个月前By尊渡假赌尊渡假赌尊渡假赌
    威尔R.E.P.O.有交叉游戏吗?
    1 个月前By尊渡假赌尊渡假赌尊渡假赌

    热工具

    记事本++7.3.1

    记事本++7.3.1

    好用且免费的代码编辑器

    禅工作室 13.0.1

    禅工作室 13.0.1

    功能强大的PHP集成开发环境

    SecLists

    SecLists

    SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

    Dreamweaver CS6

    Dreamweaver CS6

    视觉化网页开发工具

    ZendStudio 13.5.1 Mac

    ZendStudio 13.5.1 Mac

    功能强大的PHP集成开发环境