SpringBoot使用@ControllerAdvice和@ExceptionHandler注解实现全局异常处理

1、使用控制器通知

在编写代码时,需要对异常进行处理。进行异常处理的普通的代码是try...catch结构。但在开发业务时,只想关注业务正常的代码,对于catch语句中的捕获异常,希望交给异常捕获来处理,不单独在每个方法中编写,这样不仅可以减少冗余代码,还可以减少因忘记写catch而出现错误的概率。

Spring正好提供了一个非常方便的异常处理方案——控制器通知 (@ControllerAdvice 或 @RestControllerAdvice),它将所有控制器作为一 个切面,利用切面技术来实现。

通过基于@ControllerAdvice或@RestContollerAdvice的注解可以对异常进行全局统一处理,默认对所有的Controller有效。如果要限定生效范围,则可以使用@ControllerAdvice支持的限定范围方式。

  • 按注解:@ControllerAdvice(annotations = RestController .class)。
  • 按包名:@ControllerAdvice("org.example.controller")。
  • 按类型:@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})。

可以利用这一特性在一个 系统实现多个异常处理器,然后Controller可以有选择地决定使用哪个,使得异常处理更加灵活、降低侵入性。

2、使用@ControllerAdvice注解实现全局异常处理

@ControllerAdvice注解是Spring3.2提供的新注解,从名字上可以看出大体意思是控制器通知。@ControllerAdvice注解主要功能有:

  1. 全局数据绑定
  2. 全局数据预处理
  3. 全局异常处理

2.1 全局数据绑定:@ModelAttribute注解的使用

@ModelAttribute注解:在控制器方法被执行前,对所有Controller的Model添加属性进行操作。

【示例】使用@ControllerAdvice注解和@ModelAttribute注解实现全局数据绑定。

(1)创建GlobalController类(全局控制器)。

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;

/**
 * 全局控制器
 * @author pan_junbiao
 **/
@ControllerAdvice
public class GlobalController
{
    @ModelAttribute
    public void addBolgInfo(Model model)
    {
        model.addAttribute("blogName","pan_junbiao的博客");
        model.addAttribute("blogUrl","https://blog.csdn.net/pan_junbiao");
        model.addAttribute("blogInfo","您好,欢迎访问 pan_junbiao的博客");
    }
}

(2)创建BlogController类(博客信息控制器)。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 博客信息控制器
 * @author pan_junbiao
 **/
@Controller
public class BlogController
{
    /**
     * 获取博客信息
     */
    @RequestMapping("/blogInfo")
    public String blogInfo()
    {
        return "blog-info.html";
    }
}

(3)创建blog-info.html页面,并使用Thymeleaf绑定全局数据。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>博客信息</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
    <p th:text="'博客名称:'+ ${blogName}"></p>
    <p th:text="'博客地址:'+ ${blogUrl}"></p>
    <p th:text="'博客信息:'+ ${blogInfo}"></p>
</body>
</html>

执行结果:

2.2 全局数据预处理:@InitBinder注解的使用

@InitBinder注解:对表单数据进行绑定,用于定义控制器参数绑定规则。如转换规则、格式化等。可以通过这个注解的方法得到WebDataBinder对象,它在参数转换之前被执行。

【示例】使用@ControllerAdvice注解和@InitBinder注解现全局数据预处理。

(1)修改GlobalController类(全局控制器)。

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.Date;
import java.text.SimpleDateFormat;

/**
 * 全局控制器
 * @author pan_junbiao
 **/
@ControllerAdvice
public class GlobalController
{
    @InitBinder
    public void globalInitBinder(WebDataBinder binder)
    {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    @ModelAttribute
    public void addBolgInfo(Model model)
    {
        model.addAttribute("blogName","pan_junbiao的博客");
        model.addAttribute("blogUrl","https://blog.csdn.net/pan_junbiao");
        model.addAttribute("blogInfo","您好,欢迎访问 pan_junbiao的博客");
    }
}

(2)修改控制器中的获取博客信息方法,添加日期参数。

/**
 * 获取博客信息
 * @author pan_junbiao
 */
@RequestMapping("/blogInfo")
public String blogInfo(Date date,Model model)
{
    model.addAttribute("date",date);
    return "blog-info.html";
}

(3)修改blog-info.html页面,显示转换后的日期信息。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>博客信息</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
    <p th:text="'博客名称:'+ ${blogName}"></p>
    <p th:text="'博客地址:'+ ${blogUrl}"></p>
    <p th:text="'博客信息:'+ ${blogInfo}"></p>
    <p th:text="'日期转换:'+ ${date}"></p>
</body>
</html>

执行结果:

2.3 全局异常处理:@ExceptionHandler注解的使用

@ExceptionHandler注解:定义控制器发生异常后的操作,可以拦截所有控制器发生的异常。统一异常处理 ,通过@ExceptionHandler(value = Exception.class) 来指定捕获的异常。“@ControllerAdvice + @ExceptionHandle" 可以处理除“404”
以外的运行异常。

【示例】使用@ControllerAdvice注解和@ExceptionHandler注解现全局异常处理。

(1)创建BusinessException类(自定义业务异常),继承RuntimeException类。

/**
 * 自定义业务异常
 * 注意:如果继承的是Exception类,那么Spring的事务管理将会失效,
 * 只有继承RuntimeException类才使Spring的事务管理不会失效
 * @author pan_junbiao
 **/
public class BusinessException extends RuntimeException
{
    private String errorMessage; //异常信息

    public BusinessException(String errorMessage)
    {
        super(errorMessage);
        this.errorMessage = errorMessage;
    }

    public String getErrorMessage()
    {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage)
    {
        this.errorMessage = errorMessage;
    }
}

(2)创建BusinessExceptionHandler类(全局异常处理器)。

import com.pjb.pms.entity.DBEntity.StaffInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * 全局异常处理器
 * @author pan_junbiao
 **/
@ControllerAdvice
public class BusinessExceptionHandler
{
    //日志对象
    private Logger logger = LogManager.getLogger(BusinessExceptionHandler.class);

    /**
     * 自定义业务异常处理
     */
    @ExceptionHandler(BusinessException.class)
    public ModelAndView businessExceptionHandler(BusinessException ex)
    {
        //错误信息
        String errorMessage = ex.getErrorMessage();

        //记录异常日志
        logger.error("[业务异常]" + errorMessage);

        //返回错误页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("errorMessage",errorMessage);
        modelAndView.setViewName("error.html");
        return modelAndView;
    }

    /**
     * Shiro无权限异常
     */
    @ExceptionHandler(UnauthorizedException.class)
    public ModelAndView unauthorizedExceptionHandler(UnauthorizedException ex)
    {
        //获取当前登录人
        StaffInfo currentSaff = (StaffInfo) SecurityUtils.getSubject().getPrincipal();

        //记录异常日志
        String logMeg = String.format("[403无权限] 操作人:%s;异常信息:%s;",currentSaff.getStaffName(),ex.toString());
        logger.error(logMeg);

        //返回403无权限页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("403.html");
        return modelAndView;
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Exception ex)
    {
        //记录异常日志
        logger.error("[系统异常]" + ex.toString());

        //返回错误页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("errorMessage","系统错误,请联系管理员");
        modelAndView.setViewName("error.html");
        return modelAndView;
    }
}

(3)修改控制器中的获取博客信息方法,让其抛出BusinessException自定义业务异常。

/**
 * 获取博客信息
 * @author pan_junbiao
 */
@RequestMapping("/blogInfo")
public String blogInfo(Date date,Model model)
{
    boolean isException = true;
    if(isException)
    {
        throw new BusinessException("这是自定义异常信息");
    }

    model.addAttribute("date",date);
    return "blog-info.html";
}

(4)创建error.html页面,显示异常信息。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>异常页面</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
    <h3>异常页面</h3>
    <p style="color: red" th:text="'异常信息:'+ ${errorMessage}"></p>
</body>
</html>

执行结果:

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pan_junbiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值