使用spring aop统一处理异常和打印日志方式

我们很容易写出的代码

我们很容易写出带有很多try catch 和 logger.warn(),logger.error()的代码,这样一个方法本来的业务逻辑只有5行,有了这些,代码就变成了10行或者更多行,如:

public ResultDTO<UserDTO> queryUserByCardId(String cardId) {
        ResultDTO<UserDTO> result = new ResultDTO<UserDTO>();
        StringBuilder log = new StringBuilder();
        log.append("queryUserByCardId:" + cardId);
        try {
            checkCardIdNotNull(cardId);
            StationUserDO userDO = userDAO.queryUserByCardId(cardId);
            UserDTO stationUserDTO = DataTypeConvertUtils.DOToDTO(userDO);
            result.setData(stationUserDTO);
            logger.warn(log.append(" result:").toString() + result);
        } catch (StationErrorCodeException e) {
            //logger.error(log.append("catch StationErrorCodeException!").toString(), e);
            result.setSuccess(false);
            result.setErrorCode(e.getErrorCode().getErrorCode());
            result.setErrorMessage(e.getErrorCode().getErrorMessage());
        } catch (Exception e) {
            logger.error(log.append("catch Exception!").toString(), e);
            result.setSuccess(false);
            result.setErrorCode(StationErrorCodeConstants.STA10001.getErrorCode());
            result.setErrorMessage(StationErrorCodeConstants.STA10001.getErrorMessage());
        }
        return result;
}

实际上,我们的业务逻辑就几行而已,中间却夹杂着那么多的异常处理代码及日志信息代码。

如何改进代码

我们可以使用springaop,做一个切面,这个切面专门做记录日志和异常处理的工作,这样就能减少重复代码。

代码如下:

@Override
public ResultDTO<StationUserDTO>queryUserByCardId(String cardId) {
        ResultDTO<StationUserDTO> result = new ResultDTO<StationUserDTO>();
        checkCardIdNotNull(cardId);
        StationUserDO userDO = stationUserDAO.queryStationUserByCardId(cardId);
        StationUserDTO stationUserDTO = DataTypeConvertUtils.DOToDTO(userDO);
        result.setData(stationUserDTO);
        return result;
}

我们在切面中做异常处理和记录日志:

@Aspect
public class CardServiceAspect {
    private final Logger logger = LoggerFactory.getLogger("card");
    // 切入点表达式按需配置
    @Pointcut("execution(* *.*(..)))")
    private void myPointcut() {
    }
    @Before("execution(* *.*(..)))")
    public void before(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        logger.warn(className + "的" + methodName + "执行了");
        Object[] args = joinPoint.getArgs();
        StringBuilder log = new StringBuilder("入参为");
        for (Object arg : args) {
            log.append(arg + " ");
        }
        logger.warn(log.toString());
    }
    @AfterReturning(value = "execution(* *.*(..)))", returning = "returnVal")
    public void afterReturin(Object returnVal) {
        logger.warn("方法正常结束了,方法的返回值:" + returnVal);
    }
    @AfterThrowing(value = "StationCardServiceAspect.myPointcut()", throwing = "e")
    public void afterThrowing(Throwable e) {
        if (e instanceof StationErrorCodeException) {
            logger.error("通知中发现异常StationErrorCodeException", e);
        } else {
            logger.error("通知中发现未知异常", e);
        }
    }
    @Around(value = "StationCardServiceAspect.myPointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.warn("前置增强...");
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Exception e) {
            ResultDTO resultDTO = new ResultDTO();
            if (e instanceof StationErrorCodeException) {
                StationErrorCodeException errorCodeException = (StationErrorCodeException) e;
                resultDTO.setSuccess(false);
                resultDTO.setErrorCode(errorCodeException.getErrorCode().getErrorCode());
                resultDTO.setErrorMessage(errorCodeException.getErrorCode().getErrorMessage());
            } else {
                resultDTO.setSuccess(false);
                resultDTO.setErrorCode(StationErrorCodeConstants.STA10001.getErrorCode());
                resultDTO.setErrorMessage(StationErrorCodeConstants.STA10001.getErrorMessage());
            }
            return resultDTO;
        }
        return result;
    }
}

然后我们在spring配置文件中配置切面

<!-- 配置切面的类 -->
<bean id="serviceAspect" class="com.lirui.StationCardServiceAspect"/>
<!-- 配置成注解方式寻找要被代理的对象 -->
<aop:aspectj-autoproxy/>

这样,我们就可以统一处理异常和日志了。

不足点

利用这种方式,只能打入参和出参,还有抛出异常时打异常日志,不能打方法运行中的中间值,目前我只能想到,方法中间值的日志,就是用原来的方式打出,不知道大家有没有什么好的方法。

spring aop的其他使用

推荐使用aspectJ来完成面向切面编程。我们还可以利用aop完成其他功能如记录程序运行时间等。

aop实现统一记录请求方法返回值日志及统一异常处理

接到将请求返回值写入到日志方便查问题需求,首先考虑的是用拦截器实现,无奈拦截器postHandle方法里获取不到返回值就此作罢。

继续寻找新的方法,网上查询一番找到一个便捷的方法,利用log4j2,在log4j2.xml配置文件里添加如下配置:

<AsyncLogger name="org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="allLog"/>
 </AsyncLogger>

这样就能将方法返回值记录到日志里了,但是这样记录的日志和系统其它日志不一样不方便查看,此方法pass。最后只能用spring aop来实现此功能了,步骤如下:

1、引入aop依赖的jar包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.0.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.0.0.RELEASE</version>
</dependency>

2、配置xml文件

引入aop命名空间

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
<!-- 开启自动切面代理 -->
<aop:aspectj-autoproxy/>
<!-- 使用annotation 自动注册bean -->
<context:component-scan base-package="com.zzz.dealer.**"/>

3、编写切面类

@Aspect  //指定当前类为切面类
@Component //把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
public class MethodLogAndExceptionAop {

    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        String targetName = jp.getTarget().getClass().getName();
        String methodName = jp.getSignature().getName();
        Object[] arguments = jp.getArgs();
        Object[] args = new Object[arguments.length];
        for (int i = 0; i < arguments.length; i++) {
            if (arguments[i] instanceof ServletRequest || arguments[i] instanceof ServletResponse || arguments[i] instanceof MultipartFile) {
              //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
              //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            args[i] = arguments[i];
        }
        Object result = null;
        try {
            //StopWatch 计时
            StopWatch clock = new StopWatch();
            clock.start();
            result = jp.proceed();
            clock.stop();
            long executeTime = clock.getTime();
            LoggerUtil.info(targetName, methodName, "调用Controller方法返回结果", result, executeTime, args);
        } catch (Exception exception) {
            LoggerUtil.error(targetName, methodName, "统一异常处理", exception, args);
            ResultVo resultVo = new ResultVo(false);
            // 为安全起见,只有业务异常我们对前端可见,否则统一归为系统异常
            if (exception instanceof BusinessException) {
                resultVo.setResultAndCode(false, ((BusinessException) exception).getErrorCode(), ((BusinessException) exception).getErrorMessage());
            } else {
                resultVo.setResultAndCode(false, ErrorCode.DEALER_ERR_100000.getCode(), "系统异常,请联系管理员");
            }
            result = resultVo;
        }
        return result;
    }
}

系统本来的统一异常处理是通过实现HandlerExceptionResolver接口自定义异常处理,实现这个aop后发现,在这里也可以实现系统异常统一处理,于是就把自定义异常处理给干掉了。一举两得。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot配置AOP打印日志的全过程

    前言 在项目开发中,日志系统是必不可少的,用AOP在Web的请求做入参和出参的参数打印,同时对异常进行日志打印,避免重复的手写日志,完整案例见文末源码. 一.Spring AOP AOP(Aspect-Oriented Programming,面向切面编程),它利用一种"横切"的技术,将那些多个类的共同行为封装到一个可重用的模块.便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性. AOP中有以下概念: Aspect(切面):声明类似于Java中的类声明,在

  • 使用SpringBoot AOP 记录操作日志、异常日志的过程

    平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能:我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发生的异常进行统计,从而改进我们的项目,要是能做个功能专门来记录操作日志和异常日志那就好了, 当然我们肯定有方法来做这件事情,而且也不会很难,我们可以在需要的方法中增加记录日志的代码,和在每个方法中增加记录异常的代码,最终把记录的日志存到数据库中.听起来好像很容易,但是我们做起来会发现,做这项工作很繁

  • spring-boot使用AOP统一处理日志

    AOP我想大家都很清楚,有时候我们需要处理一些请求日志,或者对某些方法进行一些监控,如果出现例外情况应该进行怎么样的处理,现在,我们从spring-boot中引入AOP. [开发环境:jdk版本号为1.8,spring boot的版本号为1.4.1]{style="background-color:#FF0000"} 首先,我们先引入jar包, POM文件添加如下内容: <!--引用AOP--> <dependency> <groupId>org.s

  • java基于spring注解AOP的异常处理的方法

    一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...finally对异常进行处理,但是我们真的能在写程序的时候处理掉所有可能发生的异常吗? 以及发生异常的时候执行什么逻辑,返回什么提示信息,跳转到什么页面,这些都是要考虑到的. 二.基于@ControllerAdvice(加强的控制器)的异常处理 @ControllerAdvice注解内部使用@Except

  • 详解Spring Boot中使用AOP统一处理Web请求日志

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframewo

  • 使用spring aop统一处理异常和打印日志方式

    我们很容易写出的代码 我们很容易写出带有很多try catch 和 logger.warn(),logger.error()的代码,这样一个方法本来的业务逻辑只有5行,有了这些,代码就变成了10行或者更多行,如: public ResultDTO<UserDTO> queryUserByCardId(String cardId) { ResultDTO<UserDTO> result = new ResultDTO<UserDTO>(); StringBuilder l

  • 使用spring aop 统一捕获异常和写日志的示例demo

    之前给大家介绍过Spring AOP的基础知识,需要的朋友点击了解下吧,这边我将给您介绍用spring AOP 实现的异常捕获和日志的小demo,我也会详细解释相关配置. 首先给大家看一下我的工程目录: 大家可以先用eclipse中新建一个maven工程,在工程中pom.xml按下面文件添加依赖: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XM

  • Spring AOP统一功能处理示例代码

    目录 1. 什么是Spring AOP? 2. 为什要用 AOP? 3. Spring AOP 应该怎么学习呢? 3.1AOP组成 3.1.1 切面(Aspect) 3.1.2 连接点(Join Point) 3.1.3 切点(Pointcut) 3.1.4 通知(Advice) 3.2 Spring AOP实现 3.2.1 添加 AOP 框架支持 3.2.2 定义切面和切点. 3.2.3 定义相关通知 3.3 Spring AOP 实现原理 3.3.1 动态代理 3.3.2 JDK和CGLIB

  • 阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式

    目录 1.为什么要抛异常? 2.给出解决方案 3.两种抛异常的实例解说 dubbo工程搭建 在网上很多关于dubbo异常统一处理的博文,90%都是抄来抄去.大多都是先上一段dubbo中对于异常的统一处理的原码,然后说一堆的(甚至有12345,五种)不靠谱方案,最后再说“本篇使用的是方案4”,然后再对所谓的方案4写了一段文字,最后还说不清!!! 本篇解决方案不会那么罗里吧嗦也不会贴dubbo源码来凑字数,我就直接从刚结束不久的双11保卫战性能全链路优化中我们的面对10万级别TPS的方案中提取的代码

  • Spring Aop之AspectJ注解配置实现日志管理的方法

    最近项目要做一个日志功能,我用Spring Aop的注解方式来实现. 创建日志注解 package com.wyj.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lan

  • 详解AOP与Filter拦截请求打印日志实用例子

    相信各位同道在写代码的时候,肯定会写一些日志打印,因为这对往后的运维而言,至关重要的. 那么我们请求一个restfull接口的时候,哪些信息是应该被日志记录的呢? 以下做了一个基本的简单例子,这里只是示例说明基本常规实现记录的信息,根据项目的真实情况选用: 1 . Http请求拦截器(Filter) : 从HttpServletRequest获取基本的请求信息 import name.ealen.util.HttpUtil; import org.slf4j.Logger; import org

  • Spring AOP 后置通知修改响应httpstatus方式

    目录 Spring AOP后置通知修改响应httpstatus 1.定义Aspect 2.使用 3.ApiResponse响应体 4.ApiUtil Spring AOP前后置通知最简单案例 1.首先导jar包 2.写applicationContext.xml 3.项目架构 4.Demo类 5.前后置通知 Spring AOP后置通知修改响应httpstatus 1.定义Aspect /** * 响应体切面 * 后置通知修改httpstatus * * @author : CatalpaFla

  • 详解使用Spring MVC统一异常处理实战

    1 描述 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大. 那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的.下面将介绍使用Spring MVC统一处理异常的解决和实现过程. 2 分析 Spring MVC处理异常有3种方

  • Spring AOP注解失效的坑及JDK动态代理

    @Transactional @Async等注解不起作用 之前很多人在使用Spring中的@Transactional, @Async等注解时,都多少碰到过注解不起作用的情况. 为什么会出现这些情况呢?因为这些注解的功能实际上都是Spring AOP实现的,而其实现原理是通过代理实现的. JDK动态代理 以一个简单的例子理解一下JDK动态代理的基本原理: //目标类接口 public interface JDKProxyTestService { void run(); } //目标类 publ

  • Spring AOP实现原理解析

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入封装.继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合.当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力.也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系.例如日志功能.日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无

随机推荐