关于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结果同上

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

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


相关文章
|
7天前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
43 5
|
1月前
|
机器学习/深度学习 人工智能 NoSQL
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
随着大模型的越来越盛行,现在很多企业开始接入大模型的接口,今天我从java开发角度来写一个demo的示例,用于接入DeepSeek大模型,国内的大模型有很多的接入渠道,今天主要介绍下阿里云的百炼模型,因为这个模型是免费的,只要注册一个账户,就会免费送百万的token进行学习,今天就从一个简单的可以执行的示例开始进行介绍,希望可以分享给各位正在学习的同学们。
234 3
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
|
23天前
|
人工智能 Java 物联网
没有好的学历,Java开发未来的路应该怎么走?
在数字化时代,Java开发者即使没有高学历,也能通过拥抱新兴技术(如大模型应用与鸿蒙系统开发)、积累实战经验、持续学习新技能等途径实现职业突破。从参与开源项目到关注行业动态,再到规划技术专家或管理路线,建立人脉网络并利用教育平台提升能力,开发者可拓宽技术边界,适应日新月异的技术需求,在未来发展中占据一席之地。
|
1天前
|
人工智能 Java 定位技术
Java 开发玩转 MCP:从 Claude 自动化到 Spring AI Alibaba 生态整合
本文以原理与示例结合的形式讲解 Java 开发者如何基于 Spring AI Alibaba 框架玩转 MCP。
|
2月前
|
人工智能 安全 IDE
一天成为Java开发高手:用飞算JavaAI实现十倍提效
“一天成为Java开发高手”曾被视为天方夜谭,但飞算JavaAI的出现改变了这一局面。这款AI开发助手通过智能引导、需求分析、自动化逻辑处理和完整代码工程生成,大幅简化了Java开发流程。它不仅帮助新手快速上手,还让资深开发者提高效率,减少调试时间。现在,参与“飞算JavaAI炫技赛”,展示你的开发实力,赢取丰厚奖品!
|
1月前
|
运维 Java 程序员
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
|
2月前
|
JavaScript 安全 Java
智慧产科一体化管理平台源码,基于Java,Vue,ElementUI技术开发,二开快捷
智慧产科一体化管理平台覆盖从备孕到产后42天的全流程管理,构建科室协同、医患沟通及智能设备互联平台。通过移动端扫码建卡、自助报道、智能采集数据等手段优化就诊流程,提升孕妇就诊体验,并实现高危孕产妇五色管理和孕妇学校三位一体化管理,全面提升妇幼健康宣教质量。
70 12
|
1月前
|
搜索推荐 Java Android开发
课时146:使用JDT开发Java程序
在 Eclipse 之中提供有 JDT环境可以实现java 程序的开发,下面就通过一些功能进行演示。 项目开发流程
|
1月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
2月前
|
人工智能 Java 数据处理
Java高级应用开发:基于AI的微服务架构优化与性能调优
在现代企业级应用开发中,微服务架构虽带来灵活性和可扩展性,但也增加了系统复杂性和性能瓶颈。本文探讨如何利用AI技术,特别是像DeepSeek这样的智能工具,优化Java微服务架构。AI通过智能分析系统运行数据,自动识别并解决性能瓶颈,优化服务拆分、通信方式及资源管理,实现高效性能调优,助力开发者设计更合理的微服务架构,迎接未来智能化开发的新时代。

热门文章

最新文章