实战Spring Boot 2.0系列(二) - 全局异常处理和测试
2021-02-05 21:54
966 查看
前言
在日常
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.8 repositories { 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. 配置入口类
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3. 配置实体类
首先安装
IntellijIdea的
lombok插件,这里不做详细的介绍。切记,需要在设置中将
Enableannotation processing勾选上,否则 测试代码 在 编译时会无法对
lombok插件配置的 注解 进行处理。
使用
lombok工具提供的 注解 配置一个实体类
import lombok.Data; @Data public 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 @ToString public 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的控制器中。
@ControllerAdvice public 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, IllegalPropertiesExcep 8000 tion 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种异常处理路径。
@RestController public 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默认的 异常处理机制 处理。
欢迎关注技术公众号: 零壹技术栈
本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。
相关文章推荐
- SpringBoot2.0系列教程(五)Springboot框架添加全局异常处理
- SpringBoot2.0 基础案例(03):配置系统全局异常映射处理
- SpringBoot秒杀系统实战10-JSR303参数校验+全局异常处理
- springboot(三)一实战(一)之全局异常处理
- SpringBoot系列五:SpringBoot错误处理(数据验证、处理错误页、全局异常)
- SpringBoot2.x系列第七章 (全局异常处理)
- SpringBoot系列之三全局异常的捕获处理
- Spring Boot干货系列:(十三)Spring Boot全局异常处理整理
- Spring Boot 快速入门实战(三)全局异常处理
- SpringBoot系列教程web篇之全局异常处理
- SpringBoot系列教程web篇之全局异常处理
- SpringBoot2.0 基础案例(03):配置系统全局异常映射处理
- Springboot全局异常处理
- Spring Boot全局异常捕获处理
- SpringBoot2.X Kotlin系列之数据校验和异常处理详解
- SpringBoot全局异常处理
- Springboot项目中异常的全局处理及特殊异常处理
- springboot结合全局异常处理之登录注册验证
- 第二十一章:SpringBoot项目中的全局异常处理
- SpringBoot之全局异常处理