--Catch exceptions that occur in controller class in Spring Boot application -In the class with @ControllerAdvice, catch each exception class with the method with @ExceptionHandler --Exceptions that are not handled by the method with @ExceptionHandler are caught by the class that implements HandlerExceptionResolver.
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── my
│ ├── MyApplication.java
│ ├── MyController.java
│ ├── MyControllerAdvice.java
│ ├── MyException.java
│ └── MyHandlerExceptionResolver.java
└── resources
└── templates
└── myview.html
MyApplication.java
Spring Boot startup class.
package com.example.my;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
MyException.java
A simple exception class prepared for this operation check.
package com.example.my;
public class MyException extends Exception {
}
MyController.java
A controller class that handles routing. Raise a MyException exception when accessing http: // localhost: 8080 / myexception. Raise an Exception exception when accessing http: // localhost: 8080 / exception.
package com.example.my;
import org.springframework.boot.SpringApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
public static void main(String[] args) {
SpringApplication.run(MyController.class, args);
}
@RequestMapping("/")
public ModelAndView handleTop(ModelAndView mav) {
mav.addObject("mymessage", "Hello, world.");
mav.setViewName("myview");
return mav;
}
@RequestMapping("/myexception")
public ModelAndView handleMyException(ModelAndView mav) throws MyException {
throw new MyException();
}
@RequestMapping("/exception")
public ModelAndView handleException(ModelAndView mav) throws Exception {
throw new Exception();
}
}
MyControllerAdvice.java
MyException A class for catching exceptions. Annotate the class with @ControllerAdvice. The @ExceptionHandler annotation is added to the method for catching the exception, and MyException.class is specified. Reference: [ExceptionHandler \ (Spring Framework 5 \ .1 \ .9 \ .RELEASE API )](https://docs.spring.io/spring/docs/5.1.9.RELEASE/javadoc-api/org/springframework /web/bind/annotation/ExceptionHandler.html)
package com.example.my;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler({MyException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleMyException(Exception e, WebRequest req) {
System.out.println("MyControllerAdvice#handleMyException");
ModelAndView mav = new ModelAndView();
mav.addObject("myerror", "MyControllerAdvice#handleMyException");
mav.setViewName("myview");
return mav;
}
}
MyHandlerExceptionResolver.java
A class for catching exceptions that @ExceptionHandler does not handle. Implements the HandlerExceptionResolver interface. Annotate the class with @Component to register it in the DI container as a bean. Reference: [HandlerExceptionResolver \ (Spring Framework 5 \ .1 \ .9 \ .RELEASE API )](https://docs.spring.io/spring/docs/5.1.9.RELEASE/javadoc-api/org/springframework /web/servlet/HandlerExceptionResolver.html)
package com.example.my;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("MyHandlerExceptionResolver#resolveException");
System.out.println(handler.getClass());
System.out.println(handler);
ModelAndView mav = new ModelAndView();
mav.addObject("myerror", "MyHandlerExceptionResolver#resolveException");
mav.setViewName("myview");
mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
return mav;
}
}
myview.html
Thymeleaf template file for HTML output. Display information such as errors.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:if="${myerror}">
<div>Error: <span th:text="${myerror}"></span></div>
</div>
<div th:if="${mymessage}">
<div>Message: <span th:text="${mymessage}"></span></div>
</div>
</body>
</html>
pom.xml
Configuration file for building with Maven.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>my</artifactId>
<version>0.0.1</version>
<name>my</name>
<description>My project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Generate a JAR file with Maven's mvn package command.
$ mvn package
Specify the generated JAR file and start the Web server by Spring Boot with the java command.
$ java -jar target/my-0.0.1.jar
http://localhost:8080/myexception
Access with curl. You can see that it is handled by the handleMyException method of the MyControllerAdvice class.
$ curl http://localhost:8080/myexception
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<div>Error: <span>MyControllerAdvice#handleMyException</span></div>
</div>
</body>
</html>
Spring Boot Server side output.
MyControllerAdvice#handleMyException
Access with curl. You can see that it is handled by the resolveException method of the MyHandlerExceptionResolver class.
$ curl http://localhost:8080/exception
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<div>Error: <span>MyHandlerExceptionResolver#resolveException</span></div>
</div>
</body>
</html>
Spring Boot Server side output.
MyHandlerExceptionResolver#resolveException
class org.springframework.web.method.HandlerMethod
public org.springframework.web.servlet.ModelAndView com.example.my.MyController.handleException(org.springframework.web.servlet.ModelAndView) throws java.lang.Exception
The DispatcherServlet class is handling multiple HandlerExceptionResolver objects.
ExceptionHandlerExceptionResolver class, ResponseStatusExceptionResolver class, DefaultHandlerExceptionResolver class are prepared, and these classes handle exceptions with their respective roles.
Among them, the ExceptionHandlerExceptionResolver class is processing to call the method with @ExceptionHandler annotation.
DispatcherServlet (Spring Framework 5.1.9.RELEASE API)
The dispatcher's exception resolution strategy can be specified via a HandlerExceptionResolver, for example mapping certain exceptions to error pages. Default are ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver. These HandlerExceptionResolvers can be overridden through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).
View the source code of the Spring Web MVC DispatcherServlet class.
spring-framework/DispatcherServlet.java at v5.1.9.RELEASE · spring-projects/spring-framework
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
In the processHandlerException method, get the HandlerExceptionResolver object one by one from this.handlerExceptionResolvers and call the resolveException method. The resolveException method of each object handles the exception and returns a ModelAndView object.
Let's take a look at the object being processed by the IntelliJ IDEA debugger.
You can see that the HandlerExceptionResolverComposite object manages the ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver.
The ExceptionHandlerExceptionResolver object has an instance variable called exceptionHandlerAdviceCache, which contains an object of the class annotated with @ControllerAdvice.
If you look at the source code of ExceptionHandlerExceptionResolver, you can see that it is registered in exceptionHandlerAdviceCache.
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
Recommended Posts