Spring MVC unified exception handling

Posted by stevehaysom on Sat, 25 Dec 2021 06:00:53 +0100

Spring MVC unified exception handling

In spring MVC application development, whether it is the operation of the underlying database, or the operation of the business layer or the control layer, it is inevitable to encounter all kinds of predictable and unpredictable exceptions to deal with. If each process needs to handle exceptions separately, the code coupling of the system is high, the workload is large and difficult to unify, and the workload of future maintenance is also large. If all types of exceptions can be decoupled from each layer, it can not only ensure the functional unity of relevant processing processes, but also realize the unified processing and maintenance of exception information.
The spring MVC framework provides three exception handling methods:
1 - simple exception handling SimpleMappingExceptionResolver;
2 - implement the custom exception of the HandlerExceptionResolver interface;
3 - implement @ ExceptionHandler annotation for exception handling

In order to verify the effect of three exception handling methods of spring MVC framework, we need to develop a test application, throw different exceptions from Dao layer, Service layer and Controller layer, and then integrate three methods for exception handling, so as to compare the advantages and disadvantages.

Similar parts of the three exception handling methods are dao layer, Service layer, View layer, myexception, testexception controller and web xml
1 - create the web application ch16-1 in the IDEA, create the lib directory under the WEB-INF directory, and import the spring MVC related jar package under the lib directory.

2 - create a custom exception class, create an exception package in the src directory, and create a custom exception class MyException in the package. The specific code is as follows:

public class MyException extends Exception {
    private static final long serialVersionID = 1L;
    public MyException(){
        super() ;
    }
    public MyException(String message){
        super(message) ;
    }
}

3 - create Dao layer, create Dao package under src directory, and create TestExceptionDao class in this package. Define three methods in this class, and throw "database exception", "custom exception" and "unknown exception" respectively.

import java.sql.SQLException;

/**
 * dao Three methods are defined in the class in the layer to throw database exceptions, custom exceptions and unknown exceptions respectively
 */
@Repository
public class TestExceptionDao {
    public void daodb() throws Exception{
        throw new SQLException("Dao Database exception in") ;
    }
    public void daomy() throws Exception{
        throw new MyException("Dao Custom exception in") ;
    }
    public void daono() throws Exception{
        throw new MyException("Dao Unknown exception in") ;
    }
}

4 - create the Service layer, and create the TestExceptionService interface and TestExceptionServiceImpl implementation class in the src directory. Six methods are defined in the interface, of which three are the methods of the Service layer, three are the methods of calling the Dao layer, and the methods of the Service layer are to show the "database exception" and "custom exception" of the Service layer, "Unknown exception".

TestExceptionService interface:

/**
 * Six interface methods are defined in the interface, of which three define Dao layer methods and three are Service layer methods
 * Service The layer method is to demonstrate database exceptions, custom exceptions and unknown exceptions of the Service layer
 */
public interface TestExceptionService {
    public void servicemy() throws Exception ;
    public void servicedb() throws Exception ;
    public void serviceno() throws Exception ;
    public void daomy() throws Exception ;
    public void daodb() throws Exception ;
    public void daono() throws Exception ;
}

TestExceptionServiceImpl implementation class:

import exception.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
@Service
public class TestExceptionServiceImpl implements TestExceptionService {
    @Autowired
    private TestExceptionService testExceptionService ;
    @Override
    public void servicemy() throws Exception {
        throw new MyException("Service Custom exception in") ;
    }

    @Override
    public void servicedb() throws Exception {
        throw new SQLException("Service Database exception in") ;
    }

    @Override
    public void serviceno() throws Exception {
        throw new Exception("Service Unknown exception in") ;
    }

    @Override
    public void daomy() throws Exception {
        testExceptionService.daomy();
    }

    @Override
    public void daodb() throws Exception {
        testExceptionService.daodb();
    }

    @Override
    public void daono() throws Exception {
        testExceptionService.daono();
    }
}

5 - create the controller class, create the controller package in the src directory, and create the controller class TestExceptionController in the controller package. The code is as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import service.TestExceptionService;

import java.sql.SQLException;

@Controller
public class TestExceptionController {
    @Autowired
    private TestExceptionService testExceptionService ;
    @RequestMapping("/db")
    public void db() throws Exception{
        throw new SQLException("Database exception in controller") ;
    }
    @RequestMapping("/my")
    public void my() throws Exception{
        throw new Exception("Custom exception in controller") ;
    }
    @RequestMapping("/no")
    public void no() throws Exception{
        throw new Exception("Unknown exception in controller") ;
    }

    @RequestMapping("/servicedb")
    public void servicedb() throws Exception{
        testExceptionService.servicedb();
    }
    @RequestMapping("/servicemy")
    public void servicemy() throws Exception{
        testExceptionService.servicemy();
    }
    @RequestMapping("/serviceno")
    public void serviceno() throws Exception{
        testExceptionService.serviceno();
    }
    @RequestMapping("/daodb")
    public void daodb() throws Exception{
        testExceptionService.daodb();
    }
    @RequestMapping("/daomy")
    public void daomy() throws Exception{
        testExceptionService.daomy();
    }
    @RequestMapping("/daono")
    public void daono() throws Exception{
        testExceptionService.daono();
    }
}

6 - create a View layer with 5 JSP pages in total.
They are the test application homepage index JSP, and create a JSP directory under the WEB-INF directory, and create 404. 0 in this directory jsp,error.jsp,my-error.jsp,sql-error.jsp.

index.jsp page:

<%--
  Created by IntelliJ IDEA.
  User: nuist__NJUPT
  Date: 2021/8/14
  Time: 11:24
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
<h1>Examples of all demonstrations</h1>
  <h3><a href = "${pageContext.request.contextPath}/daodb">
    handle dao Database exception in
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/daomy">
    handle dao Custom exception in
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/daono">
    handle dao Unknown exception in
</a></h3>
<hr>
  <h3><a href = "${pageContext.request.contextPath}/servicedb">
    handle service Database exception of layer
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/servicemy">
    handle service Custom exception for layer
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/serviceno">
    handle service Unknown exception for layer
 </a></h3>
<hr>
  <h3><a href = "${pageContext.request.contextPath}/db">
    handle controller Database exception of layer
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/my">
    handle controller Custom exception for layer
  </a></h3>
  <h3><a href = "${pageContext.request.contextPath}/no">
      handle controller Unknown exception for layer
  </a></h3>
  <hr>
  <!--stay web.xml Configuration 404 in-->
  <h3><a href = "${pageContext.request.contextPath}/404">
    404 error
  </a></h3>
  </body>
</html>

Unknown exception error JSP page:

<%--
  Created by IntelliJ IDEA.
  User: nuist__NJUPT
  Date: 2021/8/14
  Time: 12:22
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<H1>Unknown error:</H1> <%=exception%>>
<H2>Error content:</H2>
<%
    exception.printStackTrace(response.getWriter());
%>
</body>
</html>

Custom exception page my error jsp:

<%--
  Created by IntelliJ IDEA.
  User: nuist__NJUPT
  Date: 2021/8/14
  Time: 12:25
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java"  isErrorPage="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<H1>Custom exception error:</H1> <%=exception%>
<H2>Error content:</H2>
<%
    exception.printStackTrace(response.getWriter());
%>
</body>
</html>

The corresponding page of SQL exception is SQL error jsp:

<%--
  Created by IntelliJ IDEA.
  User: nuist__NJUPT
  Date: 2021/8/14
  Time: 12:27
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<H1>Database exception error:</H1> <%=exception%>
<H2>Error content:</H2>
<%
    exception.printStackTrace(response.getWriter());
%>
</body>
</html>

7 - on the web DispatcherServlet is deployed in XML and characterencoding filter is configured to solve the problem of Chinese garbled code. For uncheckedexceptions, because the code does not force capture, they are often ignored. If uncheckedexceptions are generated at runtime and no corresponding capture and processing are carried out in the code, you may have to face 404500 and other server internal error prompt pages, So you also need to be on the web Add global exception 404 handling to the XML file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         id = "WebApp_ID" version="4.0">
    <!--deploy DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/springmvc-servlet.xml</param-value>
        </init-param>
        <!--Represents the load when the container starts servlet-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--Any request passed DispatcherServlet-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- to configure CharacterEncodingFilter Solve the problem of Chinese garbled code-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

        <!-- The configuration encoding format is UTF-8 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/jsp/404.jsp</location>
    </error-page>
</web-app>

From the above codes of Dao layer, Service layer and Controller layer, they just throw exceptions through how and throws and do not handle them.

SimpleMappingExceptionResolver class:
1 - when using this class to handle exceptions, you need to configure the relationship between the exception class and View in advance in the configuration file, springmvc servlet The specific code of XML is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--Using the scanning mechanism, scan the controller class,Make annotations effective-->
    <context:component-scan base-package="controller"/>
    <context:component-scan base-package="service"/>
    <context:component-scan base-package="dao"/>

    <!--Configure view parser-->
    <bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" id = "internalResourceViewResolver">
        <!--prefix-->
        <property name = "prefix" value = "/WEB-INF/jsp/"/>
        <!--suffix-->
        <property name = "suffix" value = ".jsp"/>
    </bean>

    <!--to configure SimpleMappingExceptionResolver,Exception class and View Correspondence of-->
    <bean class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--Define default exception handling page,Used when the exception type is registered-->
        <property name = "defaultErrorView" value="error"></property>
        <!--Define the variable name of the exception handling page to get exception information,The default name is exception-->
        <property name = "exceptionAttribute" value="ex"/>
        <!--Define exceptions that need special handling, using class name or full pathname as key,Exception page as value-->
        <property name = "exceptionMappings">
            <props>
                <prop key = "exception.MyException">my-error</prop>
                <prop key = "java.sql.SQLException">sql-error</prop>
                <!--You can also extend the handling of different types of exceptions here-->
            </props>
        </property>
    </bean>
</beans>

HandlerExceptionResolver interface:
This interface is used to resolve the exceptions generated during request processing. Developers can carry out spring MVC unified exception handling by developing the implementation class of this interface.
1 - create an implementation class MyExceptionHandler of the HandlerExceptionResolver interface in the exception package. The specific code is as follows:

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) {
        Map<String, Object> model = new HashMap<>();
        model.put("ex", arg3);
        //Turn to different pages according to different errors and handle them uniformly. There is a corresponding relationship between exceptions and views
        if (arg3 instanceof MyException) {
            return new ModelAndView("my-error", model);
        } else if (arg3 instanceof SQLException) {
            return new ModelAndView("sql-error", model);
        } else
            return new  ModelAndView("error", model) ;
        }
}

MyExceptionHandler needs to be hosted in the configuration file to the spring MVC framework for unified exception handling.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--Using the scanning mechanism, scan the controller class,Make annotations effective-->
    <context:component-scan base-package="controller"/>
    <context:component-scan base-package="service"/>
    <context:component-scan base-package="dao"/>

    <!--Configure view parser-->
    <bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" id = "internalResourceViewResolver">
        <!--prefix-->
        <property name = "prefix" value = "/WEB-INF/jsp/"/>
        <!--suffix-->
        <property name = "suffix" value = ".jsp"/>
    </bean>

 <!--Will implement class MyExceptionHandler Entrusted to SpringMVC-->
    <bean class = "exception.MyExceptionHandler"/>
</beans>

@ExceptionHandler annotation:
Create a BaseController class in which the @ ExceptionHandler annotation is used to declare exception handling methods.

import exception.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;

@Controller
public abstract class BaseController {
    @ExceptionHandler
    public String exception(HttpServletRequest request, Exception ex){
        request.setAttribute("ex", ex);
        //Turn to different pages according to different errors
        if(ex instanceof SQLException){
            return "sql-error" ;
        }
        else if(ex instanceof MyException){
            return "my-error" ;
        }
        else{
            return "error" ;
        }
    }
}

All controllers requiring exception handling inherit the BaseController class. The code is as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import service.TestExceptionService;

import java.sql.SQLException;

@Controller
public class TestExceptionController extends BaseController {
    @Autowired
    private TestExceptionService testExceptionService ;
    @RequestMapping("/db")
    public void db() throws Exception{
        throw new SQLException("Database exception in controller") ;
    }
    @RequestMapping("/my")
    public void my() throws Exception{
        throw new Exception("Custom exception in controller") ;
    }
    @RequestMapping("/no")
    public void no() throws Exception{
        throw new Exception("Unknown exception in controller") ;
    }

    @RequestMapping("/servicedb")
    public void servicedb() throws Exception{
        testExceptionService.servicedb();
    }
    @RequestMapping("/servicemy")
    public void servicemy() throws Exception{
        testExceptionService.servicemy();
    }
    @RequestMapping("/serviceno")
    public void serviceno() throws Exception{
        testExceptionService.serviceno();
    }
    @RequestMapping("/daodb")
    public void daodb() throws Exception{
        testExceptionService.daodb();
    }
    @RequestMapping("/daomy")
    public void daomy() throws Exception{
        testExceptionService.daomy();
    }
    @RequestMapping("/daono")
    public void daono() throws Exception{
        testExceptionService.daono();
    }

}

Publish the application to Tomcat, and the effect is as follows:

By studying the three methods of unified exception handling in spring MVC framework, we can see that using @ ExceptionHandler annotation to realize exception handling has the advantages of simple integration and good scalability, but this method is invasive to existing code.

Topics: Java Spring Tomcat Spring MVC intellij-idea