zoukankan      html  css  js  c++  java
  • spring boot 异常(exception)处理


    Spring Boot 集成教程


    概述

    异常处理注解

    spring中处理异常可以通过2个注解:

    • @ControllerAdvice 全局,处理所有控制器中的异常
    • @ExceptionHandler 局部,只针对某个控制器中的异常

    先有ExceptionHandler,再有ControllerAdviceExceptionHandler不能集中处理异常,ControllerAdvice为弥补此缺点引入,推荐使用ControllerAdvice。本文介绍ControllerAdvice的用法,对ExceptionHandler不作介绍,如需了解可参考相关资料。

    错误处理页面:ErrorController

    ErrorController的作用是为servlet设置错误页,默认错误页是Whitelabel,访问不存在的页面就会显示此错误页。

    image

    通过继承ErrorController接口可以设置自定义的错误页。

    @RestController
    public class MyErrorController implements ErrorController {
    	
    	@RequestMapping(value = "/error")
    	public ResponseEntity<Result> error() {
            Result res = new Result(404, "页面未找到");
            return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
        }
    
    	@Override
    	public String getErrorPath() {
    		return "/error";
    	}
    }
    

    如果ControllerAdvice中没有直接返回http响应,继续抛出异常,将会调用ErrorController显示错误页。如果在ControllerAdvice中捕获异常并直接返回http响应,就没必要配置ErrorController中的错误页了。

    404 异常

    spring boot默认不会抛出404异常(NoHandlerFoundException),所以在ControllerAdvice中捕获不到该异常,导致404总是跳过ContollerAdvice,直接显示ErrorController的错误页。需要改变配置,让404错误抛出异常(NoHandlerFoundException),这样便可在ControllerAdvice中捕获此异常。改变配置,在application.properties中添加:

    spring.mvc.throw-exception-if-no-handler-found=true
    spring.resources.add-mappings=false
    

    过滤器异常

    ContollerAdvice不能捕获过滤器抛出的异常,对于此类异常需要特别处理。

    如 [spring boot rest 接口集成 spring security(2) – JWT配置] 章节中的jwt过滤器,异常处理需要设置特别的处理类。

    使用ContollerAdvice可以实现对所有控制器异常的集中处理,下面通过一个实际项目介绍此过程。

    项目内容

    模拟一个用户注册的接口,抛出各类异常让ContollerAdvice处理。

    要求

    • JDK1.8或更新版本
    • Eclipse开发环境

    如没有开发环境,可参考前面章节 [spring boot 开发环境搭建(Eclipse)]。

    项目创建

    创建spring boot项目

    打开Eclipse,创建spring boot的spring starter project项目,选择菜单:File > New > Project ...,弹出对话框,选择:Spring Boot > Spring Starter Project,在配置依赖时,勾选web,完成项目创建。

    image

    项目配置

    application.properties配置

    ## 服务器端口,默认是8080
    server.port=8096 
    
    ## 让404错误抛出异常,需要同时设置spring.resources.add-mappings为false
    #  让404错误抛出异常
    spring.mvc.throw-exception-if-no-handler-found=true
    # 禁用静态资源的自动映射,如不禁用,不存在的url将被映射到/**,servlet不有机会抛出异常
    spring.resources.add-mappings=false
    
    ## log级别设置为debug, 通过log.debug打印异常信息
    logging.level.root=DEBUG
    

    如果使用静态资源的自动映射,不存在的url将被映射到/**,servlet不有机会抛出异常。在rest api的项目中没有静态资源的处理,完全可以禁止。

    代码实现

    项目目录结构如下图,我们添加了几个类,下面将详细介绍。

    image

    异常处理类 ErrorHandler.java

    这个类就是加ControllerAdvice注解的异常处理类,所有控制器的异常,都在这里集中处理。这里我们实现了2类特殊异常的处理函数,以及1个缺省的异常处理函数。

    • 输入参数校验异常处理
    • 404异常处理
    • 缺省的异常处理函数,处理所有其他异常
    
    @ControllerAdvice //表明这是控制器的异常处理类
    public class ErrorHandler {
    	
    	private static final org.slf4j.Logger log = LoggerFactory.getLogger(ErrorHandler.class);
    	
    	/**
    	 * 输入参数校验异常
    	 */
    	@ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ResponseEntity<Result> NotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception {
    		
    		log.debug("异常详情", e);
    		BindingResult bindingResult = e.getBindingResult();
    		
    		//rfc4918 - 11.2. 422: Unprocessable Entity			 
            Result res = MiscUtil.getValidateError(bindingResult);
            return new ResponseEntity<Result>(res, HttpStatus.UNPROCESSABLE_ENTITY);
        }
    	
    	/**
    	 * 404异常处理
    	 */
    	@ExceptionHandler(value = NoHandlerFoundException.class)
        public ResponseEntity<Result> NoHandlerFoundExceptionHandler(HttpServletRequest req, Exception e) throws Exception {
    		
    		log.debug("异常详情", e);
    				
    		Result res = new Result(404, "页面不存在");
            return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
        }
    	
    	/**
    	 *  默认异常处理,前面未处理
    	 */
    	@ExceptionHandler(value = Throwable.class)
        public ResponseEntity<Result> defaultHandler(HttpServletRequest req, Exception e) throws Exception {
    		        
            Result res = new Result(500, "服务器内部错误");
            log.debug("异常详情", e);
            
            return new ResponseEntity<Result>(res, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    

    输入参数校验异常处理,在 [spring boot输入数据校验(validation)] 章节里,是直接在控制器返回bindingResult,现在放在这里统一处理,好处是无需再在每个接口里重复写返回bindingResult的代码。

    控制器 AuthController

    AuthController实现了一个模拟用户注册的接口,数据校验返回bindingResult的代码被去除,spring boot将直接抛出MethodArgumentNotValidException,然后由ErrorHandler捕获处理。

    @RestController
    @RequestMapping("/auth")
    public class AuthController {
    	
        @RequestMapping(value = "/register", method = RequestMethod.POST, produces="application/json")
        public ResponseEntity<Result> register(
        	@Valid @RequestBody RegisterRequest register
    //    	, BindingResult bindingResult
        ) throws Exception {
        	
    //		if(bindingResult.hasErrors()) {	
    //			
    //			Result res1 = MiscUtil.getValidateError(bindingResult);
    //			return new ResponseEntity<Result>(res1, HttpStatus.UNPROCESSABLE_ENTITY);
    //		}
        	
    		Result res = new Result(200, "ok");
            return ResponseEntity.ok(res);
        }
    }
    

    RegisterRequest (DTO/数据传输对象)

    RegisterRequest类接受并校验用户注册时输入的信息。关于数据校验可参考教程 [spring boot输入数据校验(validation)]。

    
    public class RegisterRequest {
    	
    	@SuppressWarnings("unused")
    	private static final org.slf4j.Logger log = LoggerFactory.getLogger(RegisterRequest.class);
    	
    	@NotNull(message="手机号必须填")
    	@Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message="账号请输入11位手机号") // 手机号
    	private String mobile;
    	
    	@NotNull(message="昵称必须填")
    	@Size(min=1, max=20, message="昵称1~20个字")
    	private String nickname;
    	
        @NotNull(message="密码必须填")
        @Size(min=6, max=16, message="密码6~16位")
    	private String password;
    
    	public String getMobile() {
    		return mobile;
    	}
    
    	public void setMobile(String mobile) {
    		this.mobile = mobile;
    	}
    
    	public String getNickname() {
    		return nickname;
    	}
    
    	public void setNickname(String nickname) {
    		this.nickname = nickname;
    	}
    
    	public String getPassword() {
    		return password;
    	}
    
    	public void setPassword(String password) {
    		this.password = password;
    	}
        	
    }
    

    辅助类

    • Result 结果封装类
    • MiscUtil 杂项功能

    运行

    Eclipse左侧,在项目根目录上点击鼠标右键弹出菜单,选择:run as -> spring boot app 运行程序。 打开Postman访问接口,运行结果如下:

    用户注册,输入错误的信息

    image

    访问不存在的网址

    image

    总结

    完整代码

  • 相关阅读:
    javascript异步编程系列【十】—Jscex+Easeljs制作坦克大战
    博客园分页JQuery打造的分页无刷新的Repeater
    参赛作品
    摄像机、投影、3D旋转、缩放
    javascript异步编程系列【八】Jscex版火拼俄罗斯
    javascript异步编程系列【七】扫盲,我们为什么要用Jscex
    javascript异步编程系列【五】Jscex制作愤怒的小鸟
    javascript异步编程系列【六】Jscex版愤怒的小鸟之冲锋陷阵鸟
    每周优秀代码赏析—Jscex内核【一】
    javascript异步编程系列【一】用Jscex画圆
  • 原文地址:https://www.cnblogs.com/jinbuqi/p/11038717.html
Copyright © 2011-2022 走看看