使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常

一、前言

   在进行项目开发的过程中,我们不可避免地都要对代码中可能存在的异常进行处理和捕获,而我们通常的做法有如下两种:

 

方式一:

1、对于在Service层的异常,使用try-catch进行捕获处理,其Service层捕获异常代码如下所示:

@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserMapper userMapper;

    @Override
    public User selectUserById(Integer id) {

        User user = null;

        try {
             user = userMapper.selectUserById(id);
        } catch (Exception e) {
            logger.error("user信息有误,需要回滚");
            e.printStackTrace();
        }
        
        logger.info("代码继续往下执行");

        return user;
    }
}

 

   对于如上在Service层使用try-catch对异常进行捕获并处理的代码,其存在如下缺陷:

      1、由于Service层是与Dao层进行交互的,我们通常都会把事务配置在Service层,当操作数据库失败时,会抛出异常并由Spring事务管理器帮我们进行回滚,而当我们使用try-catch对异常进行捕获处理时,这时Spring事务管理器并不会帮我们进行回滚,而代码也会继续往下执行,这时我们就有可能读取到脏数据。

      2、由于我们在Service层对异常进行了捕获处理,在Controller层调用该Service层的相关方法时,并不能把其相关异常信息抛给Controller层,也就无法把异常信息展示给前端页面,而当用户进行相关操作失败时,也就无法得知其操作失败的缘由。

 

方式二:

1、在Service层将异常抛出,并在Controller层对Service层抛出的异常使用try-catch进行捕获处理,其Controller层捕获异常代码如下所示:

@RestController
public class UserController {

    @Autowired
    private UserService userService;
    
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping(value = "/User/{id}")
    public User selectUserById(@PathVariable(value = "id") Integer id) {

        User user = null;

        try {
            user = userService.selectUserById(id);
            
        } catch (UserNotExistException e) {
            logger.error("用户不存在");
            e.printStackTrace();

        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
        }

        return user;
    }
}

 

 对于如上在Controller层使用try-catch对异常进行捕获并处理的代码,其存在如下缺陷:

      1、由于Controller层是与Service层进行交互的,这样一来,在Controller层调用Service层抛出异常的方法时,我们都需要在Controller层的方法体中,写一遍try-catch代码来捕获处理Service层抛出的异常,就会使得代码很难看而且也很难维护。

      2、对于Service层抛出的不同异常,那么Controller层的方法体中需要catch多个异常分别进行处理。

   这里就会有人说了,我直接在Controller层的方法上使用 throws关键字 把Service层的异常继续往上抛不就行了,还不需要使用try-catch来进行捕获处理,确实是可以,但是这样会把大量的异常信息带到前端页面,对用户来说是非常不友好的,例如如下界面显示:

使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常_第1张图片

 

二、简介

  使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解能够实现全局处理Controller层的异常,并能够自定义其返回的信息(前提:Controller层不使用try-catch对异常进行捕获处理)

优缺点:

    优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。

    缺点:能处理 Controller 层的异常 (未使用try-catch进行捕获) 和 @Validated 校验器注解的异常,但是对于 Interceptor(拦截器)层的异常 和 Spring 框架层的异常,就无能为力了。

 

三、使用

1、导入相关依赖jar包


    5.1.5.RELEASE




    
    
      org.projectlombok
      lombok
      1.16.20
      provided
    

    
    
      javax.servlet
      javax.servlet-api
      3.1.0
      provided
    

    
    
      org.springframework
      spring-context
      ${spring.version}
    
    
      org.springframework
      spring-beans
      ${spring.version}
    
    
      org.springframework
      spring-webmvc
      ${spring.version}
    
    
      org.springframework
      spring-aspects
      ${spring.version}
    
    
      org.springframework
      spring-jms
      ${spring.version}
    
    
      org.springframework
      spring-context-support
      ${spring.version}
    

    
    
      org.slf4j
      slf4j-api
      1.7.25
    

 

2、编写applicationContext-spring.xml配置文件




    
    

 

3、编写web.xml配置文件




  Archetype Created Web Application

  
  
    CharacterEncodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
      encoding
      utf-8
    
  
  
    CharacterEncodingFilter
    /*
  


  
  
    ExceptionHandlerProject
    org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      classpath:spring/applicationContext-spring.xml
    
    1
  

  
    ExceptionHandlerProject
    
    /
  

 

4、自定义异常信息的枚举类

package com.exception.pojo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;


@Getter
@ToString
@AllArgsConstructor
public enum ReturnEnum {

    /**
     * 请求成功返回信息
     **/
    SERVICE_SUCCESS("200", "success", "请求成功"),

    /**
     * 请求失败返回信息
     **/
    SERVICE_ERROR("500", "error", "请求失败"),

    /**
     * 用户不存在返回信息
     **/
    USER_NOT_EXIST("404", "user not exist", "用户不存在");

    /**
     * 自定义异常状态码
     **/
    private String code;

    /**
     * 异常的英文信息,便于国际化切换使用
     **/
    private String detailsInEnglish;

    /**
     * 异常的中文信息
     **/
    private String detailsInChinese;

}

 

5、自定义封装异常信息返回前端的JSON类

package com.exception.pojo;

import lombok.*;
import java.io.Serializable;


@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class JsonResult implements Serializable {

    private String code;
    private String message;
    private T data;

    public JsonResult(ReturnEnum returnEnum, T data){
         this.code = returnEnum.getCode();
         this.message = returnEnum.getDetailsInChinese();
         this.data = data;
    }
}

 

6、自定义异常类

package com.exception;

import com.exception.pojo.ReturnEnum;
import lombok.Getter;
import lombok.Setter;


@Getter
@Setter
public class UserNotExistException extends Exception {

    /**
     * 异常信息的枚举类
     **/
    private ReturnEnum returnEnum;

    public UserNotExistException(ReturnEnum returnEnum){
        super(returnEnum.getDetailsInChinese());
        this.returnEnum = returnEnum;
    }
}

 

7、创建User实体类

package com.exception.pojo;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;


@Getter
@Setter
@NoArgsConstructor
public class User {
    
    private String name;
    private String password;

}

 

8、编写UserService接口 (这里我就不给出Dao层了,直接在Service层模拟调用Dao层)

package com.exception.service;

import com.exception.UserNotExistException;
import com.exception.pojo.User;


public interface UserService {

    /**
     * 通过id查询用户信息
     *
     * @param id
     * @return com.exception.pojo.User
     * @throws UserNotExistException
     **/
    User selectUserById(Integer id) throws UserNotExistException;
}

 

9、编写UserServiceImpl实现类

package com.exception.service.impl;

import com.exception.UserNotExistException;
import com.exception.pojo.ReturnEnum;
import com.exception.pojo.User;
import com.exception.service.UserService;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl implements UserService {

    @Override
    public User selectUserById(Integer id) throws UserNotExistException {
        
        // id等于0,表示没找到用户
        if(0 == id){
            throw new UserNotExistException(ReturnEnum.USER_NOT_EXIST);
        }

        return new User();
    }
}

 

10、编写UserController

package com.exception.controller;

import com.exception.UserNotExistException;
import com.exception.pojo.User;
import com.exception.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class UserController {

    @Autowired
    private UserService userService;
    
    @GetMapping(value = "/user/{id}")
    public User selectUserById(@PathVariable(value = "id") Integer id) throws UserNotExistException {
        
        //根据id查找用户信息,并继续抛出Service层的异常
        User user = userService.selectUserById(id);
        
        return user;
    }
}

 

11、编写全局异常处理类,其中@ExceptionHandler(value = UserNotExistException.class)注解中的value用于指定需要捕获的异常

package com.exception.handler;

import com.exception.UserNotExistException;
import com.exception.pojo.JsonResult;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 用于对UserNotExistException异常进行处理
     *
     * @param ex
     * @return com.exception.pojo.JsonResult
     * @throws
     **/
    @ExceptionHandler(value = UserNotExistException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public JsonResult handleUserNotExistException(UserNotExistException ex){
        
        //封装异常信息
        JsonResult jsonResult = new JsonResult(ex.getReturnEnum(),null);
        return jsonResult;
    }

}

 

12、启动项目,访问 http://localhost:8080/user/0

使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常_第2张图片

 

        这时我们就会有疑惑了,为什么我对异常进行了处理,界面还是会把大量的异常信息带到前端页面呢?相信使用过Spring的开发人员都用过@RequestBody、@ResponseBody注解,可以直接将输入解析成Json、将输出解析成Json,但HTTP 请求和响应是基于文本的,意味着浏览器和服务器需要通过交换原始文本才能进行通信,而这里其实就是通过HttpMessageConverter消息转换器发挥着作用,所以我们需要配置HttpMessageConverter消息转换器。

 

 

解决方式一:使用SpringMVC默认的HttpMessageConverter消息转换器

1、在pom.xml文件中 添加 jackson-databind jar包



    com.fasterxml.jackson.core
    jackson-databind
    2.9.8

 

2、在applicationContext-spring.xml配置文件中 添加如下配置

 2.1、在标签中添加 mvc命名空间

xmlns:mvc="http://www.springframework.org/schema/mvc"

http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd

 

2.2、开启SpringMVC注解驱动

        关于更多的默认配置,请点击到底帮我们做了什么

        关于更多的介绍,请点击的简单介绍

    

    
    

 

3、运行结果如下所示

使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常_第3张图片

 

4、可以看到页面上返回了我们 自定义封装异常信息返回前端的JSON类,但是可以看到data:null也返回到了前端页面上,有时候我们需要把为null的字段 不展示到前端页面中,这时我们需要在 自定义封装异常信息返回前端的JSON类上添加这么一个注解@JsonInclude(JsonInclude.Include.NON_NULL) 即可,运行结果如下所示:

使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常_第4张图片

 

 

解决方式二:使用FastJson自定义HttpMessageConverter消息转换器

1、在pom.xml文件中 添加 fastjson jar包



    com.alibaba
    fastjson
    1.2.47

 

2、在applicationContext-spring.xml配置文件中 添加如下配置

 2.1、在标签中添加 mvc命名空间

xmlns:mvc="http://www.springframework.org/schema/mvc"

http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd

 

2.2、自定义HttpMessageConverter消息转换器,注意:序列化特征配置对泛型字段不起作用

        关于更多serializerFeatures 序列化特性配置 和日期转换格式的介绍,请点击常用FastJSON的SerializerFeature特性及日期转换格式


    
        
        
            
                
                
                    
                        text/html
                        application/json;charset=UTF-8
                    
                
                
            
        
    

    
        
        
            
                
                PrettyFormat
                
                WriteMapNullValue
                
                WriteNullListAsEmpty
                
                WriteNullStringAsEmpty
                
                WriteNullBooleanAsFalse
                
                DisableCircularReferenceDetect
            
        
        
        
    

 

 

                   如果有遇到不懂或者有问题时,可以扫描下方二维码,欢迎进群交流与分享,希望能够跟大家交流学习!

                                                               使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常_第5张图片

 

你可能感兴趣的:(使用@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler注解实现全局处理Controller层的异常)