SpringBoot实战之处理异常案例详解

前段时间写了一篇关于实现统一响应信息的博文,根据文中实战操作,能够解决正常响应的一致性,但想要实现优雅响应,还需要优雅的处理异常响应,所以有了这篇内容。

作为后台服务,能够正确的处理程序抛出的异常,并返回友好的异常信息是非常重要的,毕竟我们大部分代码都是为了 处理异常情况。而且,统一的异常响应,有助于客户端理解服务端响应,并作出正确处理,而且能够提升接口的服务质量。

SpringBoot提供了异常的响应,可以通过/error请求查看效果:

这是从浏览器打开的场景,也就是请求头不包括content-type: applicaton/json,大白板一个,和友好完全不搭边。

这是请求头包括content-type: applicaton/json时的响应,格式还行,但是我们还需要加工一下,实现自定义的异常码和异常信息。

本文主要是针对RESTful请求的统一响应,想要实现的功能包括:

  1. 自动封装异常,返回统一响应
  2. 异常信息国际化

定义异常响应类

当程序发送错误时,不应该将晦涩的堆栈报告信息返回给API客户端,从某种意义讲,这是一种不礼貌的和不负责任的行为。

我们在SpringBoot 实战:一招实现结果的优雅响应中,定义了一个响应类,为什么还要再定义一个异常响应类呢?其实是为了语义明确且职责单一。类图如下:

具体代码如下:

基础类BaseResponse

@Data
public abstract class BaseResponse {
    private Integer code;
    private String desc;
    private Date timestamp = new Date();
    private String path;

    public BaseResponse() {
    }

    public BaseResponse(final Integer code, final String desc) {
        this.code = code;
        this.desc = desc;
    }

    public BaseResponse(final Integer code, final String desc, final String path) {
        this.code = code;
        this.desc = desc;
        this.path = path;
    }
}

异常类ErrorResponse

@EqualsAndHashCode(callSuper = true)
@Data
public class ErrorResponse extends BaseResponse {
    public ErrorResponse(final Integer code, final String desc) {
        super(code, desc);
    }

    public ErrorResponse(final Integer code, final String desc, final WebRequest request) {
        super(code, desc, extractRequestURI(request));
    }

    public ErrorResponse(final HttpStatus status, final Exception e) {
        super(status.value(), status.getReasonPhrase() + ": " + e.getMessage());
    }

    public ErrorResponse(final HttpStatus status, final Exception e, final WebRequest request) {
        super(status.value(), status.getReasonPhrase() + ": " + e.getMessage(), extractRequestURI(request));
    }

    private static String extractRequestURI(WebRequest request) {
        final String requestURI;
        if (request instanceof ServletWebRequest) {
            ServletWebRequest servletWebRequest = (ServletWebRequest) request;
            requestURI = servletWebRequest.getRequest().getRequestURI();
        } else {
            requestURI = request.getDescription(false);
        }
        return requestURI;
    }
}

定义异常枚举类

为了能够规范响应码和响应信息,我们可以定义一个枚举类。

枚举接口ResponseEnum

public interface ResponseEnum {
    Integer getCode();

    String getMessage();

    default String getLocaleMessage() {
        return getLocaleMessage(null);
    }

    String getLocaleMessage(Object[] args);
}

枚举类CommonResponseEnum

public enum CommonResponseEnum implements ResponseEnum {
    BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Bad Request"),
    NOT_FOUND(HttpStatus.NOT_FOUND.value(), "Not Found"),
    METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "Method Not Allowed"),
    NOT_ACCEPTABLE(HttpStatus.NOT_ACCEPTABLE.value(), "Not Acceptable"),
    REQUEST_TIMEOUT(HttpStatus.REQUEST_TIMEOUT.value(), "Request Timeout"),
    UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "Unsupported Media Type"),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server Error"),
    SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service Unavailable"),
    ILLEGAL_ARGUMENT(4000, "Illegal Argument"),
    DATA_NOT_FOUND(4004, "Data Not Found"),
    USER_NOT_FOUND(4104, "User Not Found"),
    MENU_NOT_FOUND(4204, "Menu Not Found"),
    INTERNAL_ERROR(9999, "Server Error"),
    ;

    private final Integer code;
    private final String message;
    private MessageSource messageSource;

    CommonResponseEnum(final Integer code, final String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String getLocaleMessage(Object[] args) {
        return messageSource.getMessage("response.error." + code, args, message, LocaleContextHolder.getLocale());
    }

    public void setMessageSource(final MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Component
    public static class ReportTypeServiceInjector {
        private final MessageSource messageSource;

        public ReportTypeServiceInjector(final MessageSource messageSource) {
            this.messageSource = messageSource;
        }

        @PostConstruct
        public void postConstruct() {
            for (final CommonResponseEnum anEnum : CommonResponseEnum.values()) {
                anEnum.setMessageSource(messageSource);
            }
        }
    }
}

需要注意的是,我们在异常枚举类中定义了ReportTypeServiceInjector类,这个类的作用是为枚举类注入MessageSource对象,是为了实现异常信息的国际化。这部分功能Spring已经封装好了,我们只需要在resources目录中定义一组messages.properties文件就可以了,比如:

message.properties定义默认描述:

response.error.4000=[DEFAULT] Illegal Arguments
response.error.4004=[DEFAULT] Not Found

messages_zh_CN.properties定义中文描述:

response.error.4004=对应数据未找到
response.error.9999=系统异常,请求参数: {0}

messages_en_US.properties定义英文描述:

response.error.4004=Not Found

自定义异常类

Java和Spring中提供了很多可用的异常类,可以满足大部分场景,但是有时候我们希望异常类可以携带更多信息,所以还是需要自定义异常类:

  • 可以携带我们想要的信息;
  • 有更加明确语义;
  • 附带效果,可以知道这是手动抛出的业务异常。

上代码:

@Data
@EqualsAndHashCode(callSuper = true)
public class CodeBaseException extends RuntimeException {
    private final ResponseEnum anEnum;
    private final Object[] args;// 打印参数
    private final String message;// 异常信息
    private final Throwable cause;// 异常栈

    public CodeBaseException(final ResponseEnum anEnum) {
        this(anEnum, null, anEnum.getMessage(), null);
    }

    public CodeBaseException(final ResponseEnum anEnum, final String message) {
        this(anEnum, null, message, null);
    }

    public CodeBaseException(final ResponseEnum anEnum, final Object[] args, final String message) {
        this(anEnum, args, message, null);
    }

    public CodeBaseException(final ResponseEnum anEnum, final Object[] args, final String message, final Throwable cause) {
        this.anEnum = anEnum;
        this.args = args;
        this.message = message;
        this.cause = cause;
    }
}

自定义异常信息处理类

前期准备工作完成,接下来定义异常信息处理类。

Spring自带的异常信息处理类往往不能满足我们实际的业务需求,这就需要我们定义符合具体情况的异常信息处理类,在自定义异常信息处理类中,我们可以封装更为详细的异常报告。我们可以扩展Spring提供的ResponseEntityExceptionHandler类定义自己的异常信息处理类,站在巨人的肩膀上,快速封装自己需要的类。

通过源码可以看到,ResponseEntityExceptionHandler类的核心方法是public final ResponseEntity<Object> handleException(Exception ex, WebRequest request),所有的异常都在这个方法中根据类型进行处理,我们只需要实现具体的处理方法即可:

@RestControllerAdvice
@Slf4j
public class UnifiedExceptionHandlerV2 extends ResponseEntityExceptionHandler {
    private static final String ENV_PROD = "prod";
    private final MessageSource messageSource;
    private final Boolean isProd;

    public UnifiedExceptionHandlerV2(@Value("${spring.profiles.active:dev}") final String activeProfile, final MessageSource messageSource) {
        this.messageSource = messageSource;
        this.isProd = new HashSet<>(Arrays.asList(activeProfile.split(","))).contains(ENV_PROD);
    }

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(final Exception e, final Object body, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
        log.info("请求异常:" + e.getMessage(), e);
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, e, WebRequest.SCOPE_REQUEST);
        }
        return new ResponseEntity<>(new ErrorResponse(status, e), headers, HttpStatus.OK);
    }

    @Override
    protected ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
        log.info("参数绑定异常", ex);
        final ErrorResponse response = wrapperBindingResult(status, ex.getBindingResult());
        return new ResponseEntity<>(response, headers, HttpStatus.OK);
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
        log.info("参数校验异常", ex);
        final ErrorResponse response = wrapperBindingResult(status, ex.getBindingResult());
        return new ResponseEntity<>(response, headers, HttpStatus.OK);
    }

    @ExceptionHandler(value = CodeBaseException.class)
    @ResponseBody
    public ErrorResponse handleBusinessException(CodeBaseException e) {
        log.error("业务异常:" + e.getMessage(), e);
        final ResponseEnum anEnum = e.getAnEnum();
        return new ErrorResponse(anEnum.getCode(), anEnum.getLocaleMessage(e.getArgs()));
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorResponse handleExceptionInternal(Exception e) {
        log.error("未捕捉异常:" + e.getMessage(), e);
        final Integer code = INTERNAL_SERVER_ERROR.getCode();
        return new ErrorResponse(code, getLocaleMessage(code, e.getMessage()));
    }

    /**
     * 包装绑定异常结果
     *
     * @param status        HTTP状态码
     * @param bindingResult 参数校验结果
     * @return 异常对象
     */
    private ErrorResponse wrapperBindingResult(HttpStatus status, BindingResult bindingResult) {
        final List<String> errorDesc = new ArrayList<>();
        for (ObjectError error : bindingResult.getAllErrors()) {
            final StringBuilder msg = new StringBuilder();
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
            errorDesc.add(msg.toString());
        }
        final String desc = isProd ? getLocaleMessage(status.value(), status.getReasonPhrase()) : String.join(", ", errorDesc);
        return new ErrorResponse(status.value(), desc);
    }

    private String getLocaleMessage(Integer code, String defaultMsg) {
        try {
            return messageSource.getMessage("" + code, null, defaultMsg, LocaleContextHolder.getLocale());
        } catch (Throwable t) {
            log.warn("本地化异常消息发生异常: {}", code);
            return defaultMsg;
        }
    }
}

如果感觉Spring的ResponseEntityExceptionHandler类不够灵活,也可以完全自定义异常处理类:

@RestControllerAdvice
@Slf4j
public class UnifiedExceptionHandler {
    private static final String ENV_PROD = "prod";
    private final MessageSource messageSource;
    private final Boolean isProd;

    public UnifiedExceptionHandler(@Value("${spring.profiles.active:dev}") final String activeProfile, final MessageSource messageSource) {
        this.messageSource = messageSource;
        this.isProd = new HashSet<>(Arrays.asList(activeProfile.split(","))).contains(ENV_PROD);
    }

    @ExceptionHandler({
            MissingServletRequestParameterException.class,// 缺少servlet请求参数异常处理方法
            ServletRequestBindingException.class,// servlet请求绑定异常
            TypeMismatchException.class,// 类型不匹配
            HttpMessageNotReadableException.class,// 消息无法检索
            MissingServletRequestPartException.class// 缺少servlet请求部分

    })
    public ErrorResponse badRequestException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(BAD_REQUEST.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            NoHandlerFoundException.class// 没有发现处理程序异常
    })
    public ErrorResponse noHandlerFoundException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(NOT_FOUND.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            HttpRequestMethodNotSupportedException.class// 不支持的HTTP请求方法异常信息处理方法
    })
    public ErrorResponse httpRequestMethodNotSupportedException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(METHOD_NOT_ALLOWED.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            HttpMediaTypeNotAcceptableException.class// 不接受的HTTP媒体类型异常处方法
    })
    public ErrorResponse httpMediaTypeNotAcceptableException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(NOT_ACCEPTABLE.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            HttpMediaTypeNotSupportedException.class// 不支持的HTTP媒体类型异常处理方法
    })
    public ErrorResponse httpMediaTypeNotSupportedException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(UNSUPPORTED_MEDIA_TYPE.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            AsyncRequestTimeoutException.class// 异步请求超时异常
    })
    public ErrorResponse asyncRequestTimeoutException(Exception e, WebRequest request) {
        log.info(e.getMessage(), e);
        return new ErrorResponse(SERVICE_UNAVAILABLE.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            MissingPathVariableException.class,// 请求路径参数缺失异常处方法
            HttpMessageNotWritableException.class,// HTTP消息不可写
            ConversionNotSupportedException.class,// 不支持转换
    })
    public ErrorResponse handleServletException(Exception e, WebRequest request) {
        log.error(e.getMessage(), e);
        return new ErrorResponse(INTERNAL_SERVER_ERROR.getCode(), e.getMessage(), request);
    }

    @ExceptionHandler({
            BindException.class// 参数绑定异常
    })
    @ResponseBody
    public ErrorResponse handleBindException(BindException e, WebRequest request) {
        log.error("参数绑定异常", e);
        return wrapperBindingResult(e.getBindingResult(), request);
    }

    /**
     * 参数校验异常,将校验失败的所有异常组合成一条错误信息
     */
    @ExceptionHandler({
            MethodArgumentNotValidException.class// 方法参数无效
    })
    @ResponseBody
    public ErrorResponse handleValidException(MethodArgumentNotValidException e, WebRequest request) {
        log.error("参数校验异常", e);
        return wrapperBindingResult(e.getBindingResult(), request);
    }

    /**
     * 包装绑定异常结果
     */
    private ErrorResponse wrapperBindingResult(BindingResult bindingResult, WebRequest request) {
        final List<String> errorDesc = new ArrayList<>();
        for (ObjectError error : bindingResult.getAllErrors()) {
            final StringBuilder msg = new StringBuilder();
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
            errorDesc.add(msg.toString());
        }
        final String desc = isProd ? getLocaleMessage(BAD_REQUEST.getCode(), "") : String.join(", ", errorDesc);
        return new ErrorResponse(BAD_REQUEST.getCode(), desc, request);
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(value = CodeBaseException.class)
    @ResponseBody
    public ErrorResponse handleBusinessException(CodeBaseException e, WebRequest request) {
        log.error("业务异常:" + e.getMessage(), e);
        final ResponseEnum anEnum = e.getAnEnum();
        return new ErrorResponse(anEnum.getCode(), anEnum.getLocaleMessage(e.getArgs()), request);
    }

    /**
     * 未定义异常
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorResponse handleExceptionInternal(Exception e, WebRequest request) {
        log.error("未捕捉异常:" + e.getMessage(), e);
        final Integer code = INTERNAL_SERVER_ERROR.getCode();
        return new ErrorResponse(code, getLocaleMessage(code, e.getMessage()), request);
    }

    private String getLocaleMessage(Integer code, String defaultMsg) {
        try {
            return messageSource.getMessage("" + code, null, defaultMsg, LocaleContextHolder.getLocale());
        } catch (Throwable t) {
            log.warn("本地化异常消息发生异常: {}", code);
            return defaultMsg;
        }
    }
}

从上面两个类可以看出,比较核心的是这么几个注解:

  • @ExceptionHandle:负责处理controller标注的类中抛出的异常的注解
  • @RestControllerAdvice:能够将@ExceptionHandler标注的方法集中到一个地方进行处理的注解,这个注解是复合注解,实现了@ControllerAdvice@ResponseBody的功能。

借用谭朝红博文中的图片(蓝色箭头表示正常的请求和响应,红色箭头表示发生异常的请求和响应):

写个Demo测试一下

接下来我们写个demo测试一下是否能够实现异常的优雅响应:

@RestController
@RequestMapping("index")
@Slf4j
public class IndexController {
    private final IndexService indexService;

    public IndexController(final IndexService indexService) {
        this.indexService = indexService;
    }

    @GetMapping("hello1")
    public Response<String> hello1() {
        Response<String> response = new Response<>();
        try {
            response.setCode(200);
            response.setDesc("请求成功");
            response.setData(indexService.hello());
        } catch (Exception e) {
            log.error("hello1方法请求异常", e);
            response.setCode(500);
            response.setDesc("请求异常:" + e.getMessage());
        } finally {
            log.info("执行controller的finally结构");
        }
        return response;
    }

    @GetMapping("hello2")
    public Response<String> hello2(@RequestParam("ex") String ex) {
        switch (ex) {
            case "ex1":
                throw new CodeBaseException(CommonResponseEnum.USER_NOT_FOUND, "用户信息不存在");
            case "ex2":
                throw new CodeBaseException(CommonResponseEnum.MENU_NOT_FOUND, "菜单信息不存在");
            case "ex3":
                throw new CodeBaseException(CommonResponseEnum.ILLEGAL_ARGUMENT, "请求参数异常");
            case "ex4":
                throw new CodeBaseException(CommonResponseEnum.DATA_NOT_FOUND, "数据不存在");
        }
        throw new CodeBaseException(INTERNAL_ERROR, new Object[]{ex}, "请求异常", new RuntimeException("运行时异常信息"));
    }
}

启动服务之后,传入不同参数获取不同的异常信息:

// 请求 /index/hello2?ex=ex1
{
"code": 4104,
"desc": "User Not Found",
"timestamp": "2020-10-10T05:58:39.433+00:00",
"path": "/index/hello2"
}
// 请求 /index/hello2?ex=ex2
{
"code": 4204, "desc": "Menu Not Found",
"timestamp": "2020-10-10T06:00:34.141+00:00",
"path": "/index/hello2"
}
// 请求 /index/hello2?ex=ex3
{
"code": 4000,
"desc": "[DEFAULT] Illegal Arguments",
"timestamp": "2020-10-10T06:00:44.233+00:00",
"path": "/index/hello2"
}
// 请求 /index/hello2?ex=ex4 
{
"code": 4004,
"desc": "对应数据未找到",
"timestamp": "2020-10-10T06:00:54.178+00:00",
"path": "/index/hello2"
}

附上文中的代码:https://github.com/howardliu-cn/effective-spring/tree/main/spring-exception-handler,收工。

推荐阅读

  • SpringBoot 实战:一招实现结果的优雅响应
  • SpringBoot 实战:如何优雅的处理异常
  • SpringBoot 实战:通过 BeanPostProcessor 动态注入 ID 生成器
  • SpringBoot 实战:自定义 Filter 优雅获取请求参数和响应结果
  • SpringBoot 实战:优雅的使用枚举参数
  • SpringBoot 实战:优雅的使用枚举参数(原理篇)
  • SpringBoot 实战:在 RequestBody 中优雅的使用枚举参数

到此这篇关于SpringBoot实战之处理异常案例详解的文章就介绍到这了,更多相关SpringBoot实战之处理异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot打印详细启动异常信息

    SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现,我们如果想自定义该怎么去做? FailureAnalyzer SpringBoot提供了启动异常分析接口FailureAnalyzer,该接口位于org.springframework.boot.diagnosticspack

  • 解决Springboot 2 的@RequestParam接收数组异常问题

    目录 Springboot 2 的@RequestParam接收数组异常 所以这里给出解决方式: Springboot 的 用数组接参方法 Post接参 RequestParam 其中value 的值随传参改变 有几点需要注意: Springboot 2 的@RequestParam接收数组异常 最近Vue 开发前端,然后向后台springboot 2 传递数组,发现springboot 2 接收数组方式无法使用 -- @RequestParam("ids[]") List<St

  • springBoot2.X配置全局捕获异常的操作

    springBoot2.X配置全局捕获异常 先来看一段代码:当传入的id是0的时候,就会报异常. @RestController public class HelloController { @GetMapping("/getUser") public String getUser(int id) { int j = 1 / id; return "SUCCESS" + j; } } 访问时: 我们知道这个页面要是给用户看到,用户可能不知道这是什么. 方法一:将异常

  • SpringBoot之自定义启动异常堆栈信息打印方式

    在SpringBoot项目启动过程中,当一些配置或者其他错误信息会有一些的规范的提示信息 *************************** APPLICATION FAILED TO START *************************** Description: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that's liste

  • springboot 实现记录业务日志和异常业务日志的操作

    日志记录到redis展现形式 1.基于注解的方式实现日志记录,扫描对应的方法实现日志记录 @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface BussinessLog { /** * 业务的名称,例如:"修改菜单" */ String value() default ""; /** * 被修改的实体的唯一标识,例如:菜单实体的唯一

  • SpringBoot异常: nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletContext解决方案

    今天在使用SpringBoot创建了一个项目出现如下异常 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'documentationPluginsBootstrapper': Resolution of declared constructors on bean Class [springfox.documentation.spring.web.plugins.Docu

  • SpringBoot实战之处理异常案例详解

    前段时间写了一篇关于实现统一响应信息的博文,根据文中实战操作,能够解决正常响应的一致性,但想要实现优雅响应,还需要优雅的处理异常响应,所以有了这篇内容. 作为后台服务,能够正确的处理程序抛出的异常,并返回友好的异常信息是非常重要的,毕竟我们大部分代码都是为了 处理异常情况.而且,统一的异常响应,有助于客户端理解服务端响应,并作出正确处理,而且能够提升接口的服务质量. SpringBoot提供了异常的响应,可以通过/error请求查看效果: 这是从浏览器打开的场景,也就是请求头不包括content

  • SpringBoot之使用枚举参数案例详解

    接口开发过程中不免有表示类型的参数,比如 0 表示未知,1 表示男,2 表示女.通常有两种做法,一种是用数字表示,另一种是使用枚举实现. 使用数字表示就是通过契约形式,约定每个数字表示的含义,接口接收到参数,就按照约定对类型进行判断,接口维护成本比较大. 在 Spring 体系中,使用枚举表示,是借助 Spring 的 Converter 机制,可以将数字或字符串对应到枚举的序号或者 name,然后将前端的输入转换为枚举类型. 在场景不复杂的场景中,枚举可以轻松胜任. 于是,迅速实现逻辑,准备提

  • Java java.lang.InstantiationException异常案例详解

      java.lang.InstantiationException 是指不能实例化某个对象,一般在我们使用java反射机制去创建某个对象的时候实例化到了一个抽象类或者接口(java中抽象类和接口是不能被实例化),而今天我遇到的则是我在使用反射机制实例化某个持久类的时候爆出这个异常,后来发现是因为iBATIS在对象建立中,会使用不带参数的构造函数来建立对象,而自己的持久化类中含有带参数的构造方法,将默认无参构造方法覆盖,导致在实例化过程出现异常.所以在定义一个无参构造方法可解决. 异常 持久类没

  • Maven的porfile与SpringBoot的profile结合使用案例详解

    使用maven的profile功能,我们可以实现多环境配置文件的动态切换,可参考我的上一篇博客.但随着SpringBoot项目越来越火,越来越多人喜欢用SpringBoot的profile功能.但是用SpringBoot的profile功能时,一般我们默认激活的profile肯定是开发环境的profile.当我们打成jar包后,如果在生产环境下运行,就需要在运行这个jar包的命令后面加个命令行参数来指定切换的profile.虽然这样很方便,但是容易忘记加这个参数. 我们可以通过maven的pro

  • SpringBoot实战之高效使用枚举参数(原理篇)案例详解

    找入口 对 Spring 有一定基础的同学一定知道,请求入口是DispatcherServlet,所有的请求最终都会落到doDispatch方法中的ha.handle(processedRequest, response, mappedHandler.getHandler())逻辑.我们从这里出发,一层一层向里扒. 跟着代码深入,我们会找到org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest的

  • SpringBoot实战之实现结果的优雅响应案例详解

    今天说一下 Spring Boot 如何实现优雅的数据响应:统一的结果响应格式.简单的数据封装. 前提 无论系统规模大小,大部分 Spring Boot 项目是提供 Restful + json 接口,供前端或其他服务调用,格式统一规范,是程序猿彼此善待彼此的象征,也是减少联调挨骂的基本保障. 通常响应结果中需要包含业务状态码.响应描述.响应时间戳.响应内容,比如: { "code": 200, "desc": "查询成功", "tim

  • SpringBoot在RequestBody中使用枚举参数案例详解

    前文说到 优雅的使用枚举参数 和 实现原理,本文继续说一下如何在 RequestBody 中优雅使用枚举. 本文先上实战,说一下如何实现.在 优雅的使用枚举参数 代码的基础上,我们继续实现. 确认需求 需求与前文类似,只不过这里需要是在 RequestBody 中使用.与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 json 或 xml 格式,Spring 默认借助 Jackson 反序列化为对象. 同样的,我们需要在枚举中定义 int 类型的 id.String

  • SpringBoot之通过BeanPostProcessor动态注入ID生成器案例详解

    在分布式系统中,我们会需要 ID 生成器的组件,这个组件可以实现帮助我们生成顺序的或者带业务含义的 ID. 目前有很多经典的 ID 生成方式,比如数据库自增列(自增主键或序列).Snowflake 算法.美团 Leaf 算法等等,所以,会有一些公司级或者业务级的 ID 生成器组件的诞生.本文就是通过 BeanPostProcessor 实现动态注入 ID 生成器的实战. 在 Spring 中,实现注入的方式很多,比如 springboot 的 starter,在自定义的 Configuratio

  • SpringBoot之自定义Filter获取请求参数与响应结果案例详解

    一个系统上线,肯定会或多或少的存在异常情况.为了更快更好的排雷,记录请求参数和响应结果是非常必要的.所以,Nginx 和 Tomcat 之类的 web 服务器,都提供了访问日志,可以帮助我们记录一些请求信息. 本文是在我们的应用中,定义一个Filter来实现记录请求参数和响应结果的功能. 有一定经验的都知道,如果我们在Filter中读取了HttpServletRequest或者HttpServletResponse的流,就没有办法再次读取了,这样就会造成请求异常.所以,我们需要借助 Spring

  • Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    在优雅的使用枚举参数(原理篇)中我们聊过,Spring对于不同的参数形式,会采用不同的处理类处理参数,这种形式,有些类似于策略模式.将针对不同参数形式的处理逻辑,拆分到不同处理类中,减少耦合和各种if-else逻辑.本文就来扒一扒,RequestBody参数中使用枚举参数的原理. 找入口 对 Spring 有一定基础的同学一定知道,请求入口是DispatcherServlet,所有的请求最终都会落到doDispatch方法中的ha.handle(processedRequest, respons

随机推荐