8288分类目录 8288分类目录 8288分类目录
  当前位置:海洋目录网 » 站长资讯 » 站长资讯 » 文章详细 订阅RssFeed

实战Spring Boot 2.0系列(二) - 全局异常处理和测试

来源:本站原创 浏览:33次 时间:2023-05-17
前言

在日常 web 开发中发生了异常,往往需要通过一个统一 异常处理,来保证客户端能够收到友好的提示。本文将会介绍 SpringBoot 中的 全局统一异常处理。

正文1. 创建项目

利用 SpringInitializer 创建一个 gradle 项目 spring-boot-global-exception-handle,创建时添加相关依赖。得到的初始 build.gradle 如下:

buildscript {    ext {        springBootVersion = '2.0.3.RELEASE'    }    repositories {        mavenCentral()    }    dependencies {        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")    }}apply plugin: 'java'apply plugin: 'eclipse'apply plugin: 'org.springframework.boot'apply plugin: 'io.spring.dependency-management'group = 'io.ostenant.springboot.sample'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8repositories {    mavenCentral()}dependencies {    compile('org.springframework.boot:spring-boot-starter-web')    compile('org.projectlombok:lombok')    compile('org.apache.commons:commons-lang3:3.1')    compile('com.google.guava:guava:19.0')    testCompile('org.springframework.boot:spring-boot-starter-test')}
2. 配置入口类
@SpringBootApplicationpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}
3. 配置实体类

首先安装 IntellijIdealombok 插件,这里不做详细的介绍。切记,需要在设置中将 Enableannotation processing 勾选上,否则 测试代码 在 编译时会无法对 lombok 插件配置的 注解 进行处理。

使用 lombok 工具提供的 注解 配置一个实体类

import lombok.Data;@Datapublic class User implements Serializable {    private Long id;    private String username;    private String accountName;}
4. 配置异常响应实体

ErrorMessage 实体用于记录具体的 异常信息,并响应 客户端。

import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import lombok.ToString;@NoArgsConstructor@Setter@Getter@ToStringpublic class ErrorMessage<T> {    public static final Integer OK = 0;    public static final Integer ERROR = 100;    private Integer code;    private String message;    private String url;    private T data;}
5. 配置相关异常类

SessionNotFoundException.java

public class SessionNotFoundException extends Exception {    @Getter    @Setter    protected String message;    public SessionNotFoundException() {        setMessage("Session is not found!");    }    public SessionNotFoundException(String message) {        this.message = message;    }}

NullOrEmptyException.java

public class NullOrEmptyException extends Exception {    @Getter    @Setter    protected String message;    public NullOrEmptyException() {        setMessage("Parameter is null or empty!");    }    public NullOrEmptyException(String message) {        this.message = message;    }}

IllegalPropertiesException.java

public class IllegalPropertiesException extends Exception {    @Getter    @Setter    protected String message;    public IllegalPropertiesException() {        setMessage("Prop is illegal!");    }    public IllegalPropertiesException(String message) {        this.message = message;        setMessage(String.format("Prop: %s is illegal!", message));    }}
6. 配置全局异常通知

spring3.2 开始,新增了 @ControllerAdvice 注解,可以用于定义 @ExceptionHandler,并应用到配置了 @RequestMapping 的控制器中。

@ControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(SessionNotFoundException.class)    @ResponseBody    public ErrorMessage<String> sessionNotFoundExceptionHandler(HttpServletRequest request, SessionNotFoundException exception) throws Exception {        return handleErrorInfo(request, exception.getMessage(), exception);    }    @ExceptionHandler(NullOrEmptyException.class)    @ResponseBody    public ErrorMessage<String> nullOrEmptyExceptionHandler(HttpServletRequest request, NullOrEmptyException exception) throws Exception {        return handleErrorInfo(request, exception.getMessage(), exception);    }    @ExceptionHandler(IllegalPropertiesException.class)    @ResponseBody    public ErrorMessage<String> illegalPropExceptionHandler(HttpServletRequest request, IllegalPropertiesException exception) throws Exception {        return handleErrorInfo(request, exception.getMessage(), exception);    }    @ExceptionHandler(Exception.class)    @ResponseBody    public ErrorMessage<String> exceptionHandler(HttpServletRequest request, Exception exception) throws Exception {        return handleErrorInfo(request, exception.getMessage(), exception);    }    private ErrorMessage<String> handleErrorInfo(HttpServletRequest request, String message, Exception exception) {        ErrorMessage<String> errorMessage = new ErrorMessage<>();        errorMessage.setMessage(message);        errorMessage.setCode(ErrorMessage.ERROR);        errorMessage.setData(message);        errorMessage.setUrl(request.getRequestURL().toString());        return errorMessage;    }}

上述代码指定了 3特定 的异常处理器和 1默认 的异常处理器。当请求处理出现异常时,会根据 异常处理器 的 配置顺序 依次尝试 异常匹配和 处理。

当异常不在 SessionNotFoundException、NullOrEmptyException、IllegalPropertiesException 中时, Spring 会委托 默认 的 exceptionHandler进行处理。

7. 配置控制器

根据请求数据的差异,控制器能覆盖以上 3 种异常处理路径。

@RestControllerpublic class UserController {    @PostMapping("user")    public ResponseEntity<?> save(HttpServletRequest request, HttpSession session) throws Exception {        String sessionId = (String) session.getAttribute("sessionId");        if (StringUtils.isBlank(sessionId)) {            throw new SessionNotFoundException();        }        String userPlainText = request.getParameter("user");        if (StringUtils.isBlank(userPlainText) || StringUtils.equalsIgnoreCase("{}", userPlainText)) {            throw new NullOrEmptyException();        }        ObjectMapper objectMapper = new ObjectMapper();        User user = objectMapper.readValue(userPlainText, User.class);        if (StringUtils.isBlank(user.getUsername())) {            throw new IllegalPropertiesException("username");        }        if (StringUtils.isBlank(user.getAccountName())) {            throw new IllegalPropertiesException("accountName");        }        return ResponseEntity.ok("Successful");    }}
8. 配置Mock测试类

SpringMock 的相关配置这里就不详细介绍了,以下测试类覆盖了 UserController 的所有执行路径。

@RunWith(SpringJUnit4Cla***unner.class)@SpringBootApplication@WebAppConfiguration@Slf4j(topic = "UserControllerTester")public class ApplicationTests {    @Autowired    private WebApplicationContext context;    private MockMvc mockMvc;    private MockHttpSession session;    @Autowired    private UserController userController;    private ImmutableMap<Long, Pair<String, String>> map = new ImmutableMap.Builder<Long, Pair<String, String>>()            .put(0x00001L, Pair.of("user", ""))            .put(0x00002L, Pair.of("user", "{}"))            .put(0x00003L, Pair.of("user", "{\"username\": \"\", \"accountName\": \"\"}"))            .put(0x00004L, Pair.of("user", "{\"username\": \"Harrison\", \"accountName\": \"\"}"))            .put(0x00005L, Pair.of("user", "{\"username\": \"Harrison\", \"accountName\": \"ostenant\"}"))            .build();    @Before    public void setUp() throws Exception {        boolean singleRunner = false;        if (singleRunner) {            this.mockMvc = MockMvcBuilders.standaloneSetup(userController).build();        } else {            this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();        }        session = new MockHttpSession();        session.setAttribute("sessionId", StringUtils.replace(UUID.randomUUID().toString(), "-", ""));        log.debug("sessionId: {}", session.getAttribute("sessionId"));    }    /**     * 测试SessionNotFoundException     * @throws Exception     */    @Test    public void testSessionNotFoundException() throws Exception {        session.clearAttributes();        // 模拟发送请求        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00005L).getKey(), map.get(0x00005L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();    }    /**     * 测试NullOrEmptyException     * @throws Exception     */    @Test    public void testNullOrEmptyException() throws Exception {        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00001L).getKey(), map.get(0x00001L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00002L).getKey(), map.get(0x00002L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();    }    /**     * 测试IllegalPropException     * @throws Exception     */    @Test    public void testIllegalPropException() throws Exception {        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00003L).getKey(), map.get(0x00003L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00004L).getKey(), map.get(0x00004L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();    }    /**     * 测试正常运行的情况     * @throws Exception     */    @Test    public void testNormal() throws Exception {        mockMvc.perform(                MockMvcRequestBuilders.post("/user")                        .param(map.get(0x00005L).getKey(), map.get(0x00005L).getValue())                        .session(session))                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))                .andDo(MockMvcResultHandlers.print())                .andReturn();    }}
9. 测试结果

批量运行测试,测试结果如下,所有的测试用例全部通过。


小结

使用 @ControllerAdvice 处理异常也有一定的 局限性。只有进入 Controller层的错误,才会由 @ControllerAdvice 处理。拦截器 抛出的错误,以及 访问错误地址 的情况 @ControllerAdvice 处理不了,由 SpringBoot 默认的 异常处理机制 处理。


欢迎关注技术公众号: 零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。


  推荐站点

  • At-lib分类目录At-lib分类目录

    At-lib网站分类目录汇集全国所有高质量网站,是中国权威的中文网站分类目录,给站长提供免费网址目录提交收录和推荐最新最全的优秀网站大全是名站导航之家

    www.at-lib.cn
  • 中国链接目录中国链接目录

    中国链接目录简称链接目录,是收录优秀网站和淘宝网店的网站分类目录,为您提供优质的网址导航服务,也是网店进行收录推广,站长免费推广网站、加快百度收录、增加友情链接和网站外链的平台。

    www.cnlink.org
  • 35目录网35目录网

    35目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向35目录推荐、提交优秀网站。

    www.35mulu.com
  • 就要爱网站目录就要爱网站目录

    就要爱网站目录,按主题和类别列出网站。所有提交的网站都经过人工审查,确保质量和无垃圾邮件的结果。

    www.912219.com
  • 伍佰目录伍佰目录

    伍佰网站目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向伍佰目录推荐、提交优秀网站。

    www.wbwb.net