关于java异常处理与开发体验和用户体验的思考(下)

简介: 关于java异常处理与开发体验和用户体验的思考

响应数据结构和异常类型统一后,我们需要统一处理controller的返回数据,全部包装成CommonResponse类型的数据。

import com.zx.eagle.annotation.IgnoreResponseAdvice;
import com.zx.eagle.vo.CommonResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Objects;
/** @author zouwei */
@RestControllerAdvice
public class CommonResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(
            MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        boolean ignore = false;
        IgnoreResponseAdvice ignoreResponseAdvice =
                returnType.getMethodAnnotation(IgnoreResponseAdvice.class);
        if (Objects.nonNull(ignoreResponseAdvice)) {
            ignore = ignoreResponseAdvice.value();
            return !ignore;
        }
        Class<?> clazz = returnType.getDeclaringClass();
        ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class);
        if (Objects.nonNull(ignoreResponseAdvice)) {
            ignore = ignoreResponseAdvice.value();
        }
        return !ignore;
    }
    @Override
    public Object beforeBodyWrite(
            Object body,
            MethodParameter returnType,
            MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response) {
        if (Objects.isNull(body)) {
            return CommonResponse.successInstance();
        }
        if (body instanceof CommonResponse) {
            return body;
        }
        CommonResponse commonResponse = CommonResponse.successInstance(body);
        return commonResponse;
    }
}
复制代码

很明显,并不是所有的返回对象都需要包装的,比如controller已经返回了CommonResponse,那么就不需要包装

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** @author zouwei */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface IgnoreResponseAdvice {
    /**
     * 是否需要被CommonResponseAdvice忽略
     *
     * @return
     */
    boolean value() default true;
}
复制代码

其次,我们还需要统一处理异常

import com.google.common.collect.Lists;
import com.zx.eagle.common.config.ExceptionTipsStackConfig;
import com.zx.eagle.common.exception.EagleException;
import com.zx.eagle.common.exception.handler.ExceptionNotifier;
import com.zx.eagle.common.vo.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/** @author zouwei */
@Slf4j
@RestControllerAdvice
public class ExceptionResponseAdvice {
    @Autowired private ExceptionTipsStackConfig exceptionStack;
    @Autowired(required = false)
    private List<ExceptionNotifier> exceptionNotifierList;
    /**
     * 用户行为导致的错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(EagleException.class)
    public CommonResponse handleEagleException(
            EagleException e, HttpServletRequest request, HttpServletResponse response) {
        String massage = handleExceptionMessage(e);
        CommonResponse commonResponse =
                CommonResponse.exceptionInstance(e.getCode(), massage, e.getTips());
        sendNotify(e, request, response);
        return commonResponse;
    }
    /**
     * 处理未知错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public CommonResponse handleRuntimeException(
            RuntimeException e, HttpServletRequest request, HttpServletResponse response) {
        String error = handleExceptionMessage(e);
        EagleException unknownException = EagleException.unknownException(error);
        CommonResponse commonResponse =
                CommonResponse.exceptionInstance(
                        unknownException.getCode(), error, unknownException.getTips());
        sendNotify(unknownException, request, response);
        return commonResponse;
    }
    /**
     * 处理参数验证异常
     *
     * @param e
     * @param request
     * @param response
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public CommonResponse handleValidException(
            ConstraintViolationException e,
            HttpServletRequest request,
            HttpServletResponse response) {
        String error = handleExceptionMessage(e);
        Set<ConstraintViolation<?>> set = e.getConstraintViolations();
        Iterator<ConstraintViolation<?>> iterator = set.iterator();
        List<EagleException.ValidMessage> list = Lists.newArrayList();
        while (iterator.hasNext()) {
            EagleException.ValidMessage validMessage = new EagleException.ValidMessage();
            ConstraintViolation<?> constraintViolation = iterator.next();
            String message = constraintViolation.getMessage();
            Path path = constraintViolation.getPropertyPath();
            Object fieldValue = constraintViolation.getInvalidValue();
            String tipsKey = constraintViolation.getMessageTemplate();
            validMessage.setTipsKey(tipsKey);
            validMessage.setFieldName(path.toString());
            validMessage.setFieldValue(fieldValue);
            validMessage.setDefaultMessage(message);
            list.add(validMessage);
        }
        EagleException validException = EagleException.validException(list);
        sendNotify(validException, request, response);
        return CommonResponse.exceptionInstance(validException, error);
    }
    /**
     * 发送请求
     *
     * @param exception
     * @param request
     * @param response
     */
    private void sendNotify(
            EagleException exception, HttpServletRequest request, HttpServletResponse response) {
        if (!CollectionUtils.isEmpty(exceptionNotifierList)) {
            for (ExceptionNotifier notifier : exceptionNotifierList) {
                if (notifier.support(exception.getTipsKey())) {
                    notifier.handle(exception, request, response);
                }
            }
        }
    }
    /**
     * 处理异常信息
     *
     * @param e
     * @return
     */
    private String handleExceptionMessage(Exception e) {
        String massage = e.getMessage();
        String stackInfo = toStackTrace(e);
        String messageStackInfo = massage + "{" + stackInfo + "}";
        // 无论是否让客户端显示堆栈信息,后台都要记录
        log.error(messageStackInfo);
        if (exceptionStack.isShowMessage() && exceptionStack.isShowStack()) {
            return messageStackInfo;
        } else if (exceptionStack.isShowMessage()) {
            return massage;
        } else if (exceptionStack.isShowStack()) {
            return stackInfo;
        }
        return StringUtils.EMPTY;
    }
    /**
     * 获取异常堆栈信息
     *
     * @param e
     * @return
     */
    private static String toStackTrace(Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            e.printStackTrace(pw);
            return sw.toString();
        } catch (Exception e1) {
            return StringUtils.EMPTY;
        }
    }
}
复制代码

为了解决有一些异常需要额外处理的,例如调用第三方接口,接口返回异常并告知费用不够需要充值,这个时候就需要额外通知到相关人员及时充值。为此,特地添加一个接口:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 异常通知器
 *
 * @author zouwei
 */
public interface ExceptionNotifier {
    /**
     * 是否支持处理该异常
     *
     * @param exceptionKey
     * @return
     */
    boolean support(String exceptionKey);
    /**
     * 处理该异常
     *
     * @param e
     * @param request
     */
    void handle(EagleException e, HttpServletRequest request, HttpServletResponse response);
}
复制代码

为了满足返回的异常信息可配置化,通过配置决定不同的环境返回指定的字段信息

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/** @author zouwei */
@Data
@Component
@ConfigurationProperties(prefix = "exception-tips-stack")
public class ExceptionTipsStackConfig {
    /** 是否显示堆栈信息 */
    private boolean showStack = false;
    /** 是否显示exception message */
    private boolean showMessage = false;
}
复制代码

application.yaml中配置示例(根据环境配置):

exceptionTipsStack:
  #异常堆栈是否需要显示
  showStack: true
  #开发提示信息是否需要显示
  showMessage: true
复制代码

为了保证返回的数据是指定的json格式,需要配置HttpMessageConverter

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/** @author zouwei */
@Configuration
public class CustomWebConfigure implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.clear();
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}
复制代码

4.测试

先将application.yaml调整为:

exceptionTipsStack:
  #异常堆栈是否需要显示
  showStack: true
  #开发提示信息是否需要显示
  showMessage: true
复制代码

编写TestController:

import com.zx.eagle.exception.EagleException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
/** @author zouwei */
@Validated
@RestController
@RequestMapping("/test")
public class TestController {
    /**
     *
     * @return
     * @throws EagleException
     */
    @GetMapping("/user_repeat")
    public String userRepeat() throws EagleException {
        throw EagleException.newInstance("USER_REPEAT_REGIST", "用户重复注册了,正常提示");
    }
    /**
     * 对于用户来说,不应该直接看到NoSuchAlgorithmException,因为这并不是用户造成的,所以应该使用未知错误
     *
     * @return
     */
    @GetMapping("/unknownException")
    public String unknownException() throws EagleException {
        final MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD4");
        } catch (final NoSuchAlgorithmException e) {
            throw EagleException.unknownException("显然是因为程序没有获取MD5算法导致的异常,这是完全可以避免的");
        }
        return "success";
    }
  @GetMapping("/valid")
    public String validException(
            @NotNull(message = "USER_NAME_NOT_NULL")
                    @Length(min = 5, max = 10, message = "USER_NAME_LENGTH_LIMIT")
                    String username,
            @NotNull(message = "年龄不能为空")
                    @Min(value = 18, message = "年龄必须大于18岁")
                    @Max(value = 70, message = "年龄不能超过70岁")
                    int age)
            throws EagleException {
        // return "success";
        throw EagleException.newInstance("USER_NO_EXIST", "用户不存在,这个地方要注意");
    }
  @PostMapping("/valid4Post")
    public String validException2(@Valid @RequestBody User user, BindingResult result) {
        return "success";
    }
    @Data
    private static class User {
        @Length(min = 5, max = 10, message = "USER_NAME_LENGTH_LIMIT")
        private String username;
        @Min(value = 18, message = "年龄必须大于18岁")
        @Max(value = 70, message = "年龄不能超过70岁")
        private int age;
    }
}
复制代码

测试结果:

url: /test/user_repeat

{
  //错误码
  "code":"11023",
  //显示给开发人员看,方便调试
  //这个信息里面包括修复信息和异常的堆栈信息,包括异常出在InsuranceController.java:23,也就是这个类的第23行userRepeat方法里面
  "error":"用户重复注册了,正常提示{EagleException(code=11023, tips=重复注册)\n\tat com.zx.eagle.exception.EagleException.newInstance(EagleException.java:37)\n\tat com.zx.eagle.controller.InsuranceController.userRepeat(InsuranceController.java:23)\n}",
  //显示给用户看,明确错误,提示纠正措施
  "message":"重复注册",
  "data":null
}
复制代码

url: /test/unknownException

{
  //错误码
  "code":"-1",
  //告知出错原因,并给出修复提示,包含堆栈信息,帮助定位异常位置
  "error":"显然是因为程序没有获取MD5算法导致的异常,这是完全可以避免的{EagleException(code=-1, tips=未知错误)\n\tat com.zx.eagle.exception.EagleException.newInstance(EagleException.java:37)\n\tat com.zx.eagle.exception.EagleException.unknownException(EagleException.java:47)\n\tat com.zx.eagle.controller.InsuranceController.unknownException(InsuranceController.java:37)}",
  //显示给用户看,相对于无反应或直接展示异常信息更好
  "message":"未知错误",
  "data":null
}
复制代码

url:/test/valid?username=z2341d&age=10

{
code: "-2",
error: "test.age: 年龄必须大于18岁{javax.validation.ConstraintViolationException: test.age: 年龄必须大于18岁 at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) }",
message: "参数验证错误",
validMessage: [
{
fieldName: "test.age",
fieldValue: 10,
code: "-2",
tips: "年龄必须大于18岁",
tipsKey: "年龄必须大于18岁",
defaultMessage: "年龄必须大于18岁"
}
],
data: null
}
复制代码

url:/test/valid4Post结果同上

至此,关于异常处理的相关思考和实现阐述完毕。小伙伴们可以依据类似的思考方式实现符合自身实际情况的异常处理方式。

欢迎有过类似思考的小伙伴一起讨论。


相关文章
|
1月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
22天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
8天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
118 13
|
1月前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
82 10
|
1月前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
66 2
|
1月前
|
Java
Java 异常处理:11 个异常处理最佳实践
本文深入探讨了Java异常处理的最佳实践,包括早抛出晚捕获、只捕获可处理异常、不忽略异常、抛出具体异常、正确包装异常、记录或抛出异常但不同时执行、不在finally中抛出异常、避免用异常控制流程、使用模板方法减少重复代码、抛出与方法相关的异常及异常处理后清理资源等内容,旨在提升代码质量和可维护性。
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
140 1
|
1月前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
60 2