栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java > SpringBoot

实战Spring Boot 2.0系列(二)

SpringBoot 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

实战Spring Boot 2.0系列(二)

前言

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

正文 1. 创建项目

利用 Spring Initializer 创建一个 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. 配置实体类

首先安装 Intellij Idea 的 lombok 插件这里不做详细的介绍。切记需要在设置中将 Enable annotation 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 {
    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. 配置全局异常通知

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

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(SessionNotFoundException.class)
    @ResponseBody
    public ErrorMessage sessionNotFoundExceptionHandler(HttpServletRequest request, SessionNotFoundException exception) throws Exception {
 return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(NullOrEmptyException.class)
    @ResponseBody
    public ErrorMessage nullOrEmptyExceptionHandler(HttpServletRequest request, NullOrEmptyException exception) throws Exception {
 return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(IllegalPropertiesException.class)
    @ResponseBody
    public ErrorMessage illegalPropExceptionHandler(HttpServletRequest request, IllegalPropertiesException exception) throws Exception {
 return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ErrorMessage exceptionHandler(HttpServletRequest request, Exception exception) throws Exception {
 return handleErrorInfo(request, exception.getMessage(), exception);
    }

    private ErrorMessage handleErrorInfo(HttpServletRequest request, String message, Exception exception) {
 ErrorMessage 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测试类

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

@RunWith(SpringJUnit4ClassRunner.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> map = new ImmutableMap.Builder>()
     .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"));
    }

    
    @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();
    }

    
    @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();

    }

    
    @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();
    }

    
    @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 处理不了由 Spring Boot 默认的 异常处理机制 处理。


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

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号