preface
When exceptions occur in daily Web development, a unified exception handling is often required to ensure that the client can receive a friendly prompt. This article introduces global unified exception handling in Spring Boot.
This series of articles
- Spring Boot 2.0 series 1 – Build Docker images using Gradle
- Spring Boot 2.0 Series ii – Global exception handling and testing
- Spring Boot 2.0 series (3) – Using @async to make asynchronous calls
- Spring Boot 2.0 series 4 – Use WebAsyncTask to handle asynchronous tasks
- Spring Boot 2.0 series 5 – Listener, Servlet, Filter, and Interceptor
- Spring Boot 2.0 series (vi) – Several implementations of single machine timing tasks
The body of the
1. Create projects
Create a Gradle project spring-boot-global-exception-handle using Spring Initializer. Add dependencies when creating gradle. The resulting initial build.gradle looks like this:
buildscript {
ext {
springBootVersion = '. The 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 - the SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
compile('org.apache.com mons: the Commons - lang3:3.1')
compile(: 'com. Google. Guava guava: 19.0')
testCompile('org.springframework.boot:spring-boot-starter-test')}Copy the code
2. Configure the entry class
@SpringBootApplication
public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code
3. Configure the entity class
First, install the Lombok plugin for Intellij Idea, which is not covered in detail here. Keep in mind that Enable Annotation Processing needs to be checked in the Settings, otherwise the test code will not be able to handle annotations configured by the Lombok plug-in at compile time.
Configure an entity class using the annotations provided by the Lombok tool
import lombok.Data;
@Data
public class User implements Serializable {
private Long id;
private String username;
private String accountName;
}
Copy the code
4. Configure the exception response entity
The ErrorMessage entity is used to record specific exception information and respond to the client.
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;
}
Copy the code
5. Configure related exception classes
SessionNotFoundException.java
public class SessionNotFoundException extends Exception {
@Getter
@Setter
protected String message;
public SessionNotFoundException(a) {
setMessage("Session is not found!");
}
public SessionNotFoundException(String message) {
this.message = message; }}Copy the code
NullOrEmptyException.java
public class NullOrEmptyException extends Exception {
@Getter
@Setter
protected String message;
public NullOrEmptyException(a) {
setMessage("Parameter is null or empty!");
}
public NullOrEmptyException(String message) {
this.message = message; }}Copy the code
IllegalPropertiesException.java
public class IllegalPropertiesException extends Exception {
@Getter
@Setter
protected String message;
public IllegalPropertiesException(a) {
setMessage("Prop is illegal!");
}
public IllegalPropertiesException(String message) {
this.message = message;
setMessage(String.format("Prop: %s is illegal!", message)); }}Copy the code
6. Configure global exception notification
As of Spring 3.2, the @ControllerAdvice annotation is added, which can be used to define @ExceptionHandler and applied to controllers configured with @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, 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());
returnerrorMessage; }}Copy the code
The code above specifies three specific exception handlers and one default exception handler. When an exception occurs in the request processing, an exception matching and processing attempt is made according to the configuration order of the exception handler.
When the exception is not SessionNotFoundException, NullOrEmptyException, IllegalPropertiesException, Spring will entrust the default exceptionHandler process.
7. Configure the controller
According to the difference of request data, the controller can override the above three exception processing paths.
@RestController
public class UserController {
@PostMapping("user")
publicResponseEntity<? > 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"); }}Copy the code
8. Configure the Mock test class
The configuration of Spring Mock is not covered here, but the following test classes cover all the execution paths of 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<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(a) 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 SessionNotFoundException *@throws Exception
*/
@Test
public void testSessionNotFoundException(a) throws Exception {
session.clearAttributes();
// simulate sending a request
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 NullOrEmptyException *@throws Exception
*/
@Test
public void testNullOrEmptyException(a) 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 IllegalPropException *@throws Exception
*/
@Test
public void testIllegalPropException(a) 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 normal operation *@throws Exception
*/
@Test
public void testNormal(a) 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(); }}Copy the code
9. Test results
Run the tests in batches. The test results are as follows. All test cases pass.
summary
Handling exceptions using @controllerAdvice has some limitations. Only errors that enter the Controller layer are handled by @ControllerAdvice. Errors thrown by interceptors, and cases of accessing wrong addresses that @ControllerAdvice cannot handle, are handled by Spring Boot’s default exception handling mechanism.
Welcome to pay attention to the technical public number: Zero one Technology Stack
This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.