SpringBoot:使用SpringBoot2.0开发Web应用时如何处理全局异常

19 篇文章 0 订阅
9 篇文章 0 订阅

       目录

一、如何知道客户端可以处理什么格式的数据

二、使用@ControllerAdvice来统一处理全局异常

三、定义处理/error的Controller


      在使用Spring开发WEB应用时,一般需要定义全局异常处理器,对应用中抛出的异常统一进行处理。但此时面临的一个问题就是,生成的异常信息需要是客户端能够处理的格式:比如:

  • 客户端只能处理json,那异常信息就应该是json格式。
  • 客户端需要一个错误页面,那异常信息就应该生成在一个错误页面上,再返回给客户端。

一、如何知道客户端可以处理什么格式的数据

HTTP中定义了Accept请求头,客户端就是通过传递Accept的值,来告诉Web服务器它可以处理什么格式的数据的。

下面准备一个测试页面,此处使用freemarker作为模板引擎,测试页面为login.ftl

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="${request.contextPath}/static/bootstrap/3.4.1/css/bootstrap.min.css"
	integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body style="background: #d2d6de; text-align: center;">
	<div class="container" style="max-width: 450px; margin: 5% auto;">
		<div class="row">
			<div class="col-md-12">
				<h1 style="margin: 20px;">登录测试系统</h1>
			</div>
		</div>
		<form id="submitForm" action="${request.contextPath}/sys/login" method="post" style="background: #fff; padding: 20px;">
			<div class="row">
				<div class="col-md-12">
					<p>登录您的账号</p>
					<div class="form-group has-feedback">
						<input type="text" name="username" class="form-control" placeholder="账户"> <span
							class="glyphicon glyphicon-envelope form-control-feedback"></span>
					</div>
					<div class="form-group has-feedback">
						<input type="password" name="password" class="form-control" placeholder="密码"> <span
							class="glyphicon glyphicon-lock form-control-feedback"></span>
					</div>
				</div>
			</div>
			<div class="row">
				<div class="col-5 col-md-5">
					<button id="submitBtn" type="button" class="btn btn-primary btn-block btn-flat">登录</button>
				</div>
			</div>
		</form>
	</div>
	<script src="${request.contextPath}/static/jquery/3.4.1/jquery-3.4.1.min.js"></script>
	<script src="${request.contextPath}/static/bootstrap/3.4.1/js/bootstrap.min.js"
		integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>
<script type="text/javascript">
$("#submitBtn").click(function(){
	$.ajax({
	    url: "${request.contextPath}/sys/login",
	    type: "POST",
	    dataType: "json",
	    data: $("#submitForm").serialize(),
	    processData: false,
	    success: function (data) {
    		alert(JSON.stringify(data));
		// alert((new XMLSerializer()).serializeToString(data));
		// alert(data);
	    },
	    error: function () {
	        alert("error");
	    }
	});
});
</script>
</html>

可以通过修改dataType的值,告诉WEB服务器应该返回什么格式的数据,常用值:json、text、html、xml、script等。以下是使用chrome发起请求时ACCEPT的值:

TypeAccept
xmlapplication/xml, text/xml, */*; q=0.01
jsonapplication/json, text/javascript, */*; q=0.01
texttext/plain, */*; q=0.01
htmltext/html, */*; q=0.01
scripttext/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01

以上页面是通过JQuery做异常提交,也可以改成form直接提交:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="${request.contextPath}/static/bootstrap/3.4.1/css/bootstrap.min.css"
	integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body style="background: #d2d6de; text-align: center;">
	<div class="container" style="max-width: 450px; margin: 5% auto;">
		<div class="row">
			<div class="col-md-12">
				<h1 style="margin: 20px;">登录测试系统</h1>
			</div>
		</div>
		<form id="submitForm" action="${request.contextPath}/sys/login" method="post" style="background: #fff; padding: 20px;">
			<div class="row">
				<div class="col-md-12">
					<p>登录您的账号</p>
					<div class="form-group has-feedback">
						<input type="text" name="username" class="form-control" placeholder="账户"> <span
							class="glyphicon glyphicon-envelope form-control-feedback"></span>
					</div>
					<div class="form-group has-feedback">
						<input type="password" name="password" class="form-control" placeholder="密码"> <span
							class="glyphicon glyphicon-lock form-control-feedback"></span>
					</div>
				</div>
			</div>
			<div class="row">
				<div class="col-5 col-md-5">
					<button id="submitBtn" type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
				</div>
			</div>
		</form>
	</div>
	<script src="${request.contextPath}/static/jquery/3.4.1/jquery-3.4.1.min.js"></script>
	<script src="${request.contextPath}/static/bootstrap/3.4.1/js/bootstrap.min.js"
		integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>
</html>

使用chrome发起form提交时ACCEPT的值:

TypeAccept
formtext/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

如何针对不同的ACCEPT值做不同的处理,下面举例说明如何解决这个问题。

二、使用@ControllerAdvice来统一处理全局异常

import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class GlobalControllerAdvice extends ResponseEntityExceptionHandler {
	@ExceptionHandler
	public String errorController(HttpServletRequest request, Throwable ex) {
		request.setAttribute("exception", ex);

		return "forward:/error";
	}
}

@ExceptionHandler默认会处理所有Throwable异常,在方法errorController中,将捕获的异常存放到HttpServletRequest中,然后使用forward:/error将请求转到可以处理/error的Controller中,forward表示将HttpServletRequest中的所有信息一起带入可以处理/error的Controller中。

三、定义处理/error的Controller

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@RequestMapping("/")
@Controller
public class BaseController {
	private static final Logger LOG = LoggerFactory.getLogger(BaseController.class);

	@GetMapping("index.html")
	public String doIndex() {
		return "login";
	}

	@RequestMapping(value = "error", produces = { "text/html", "text/plain" })
	public String handleErrorPage(@RequestAttribute("exception") Throwable ex) {
		LOG.error("handlePage", ex);

		return "index";
	}

	@RequestMapping(value = "error", produces = { "application/json" })
	@ResponseBody
	public Map<String, Object> handleErrorJson(@RequestAttribute("exception") Throwable ex) {
		LOG.error("handleJson", ex);

		Map<String, Object> retMap = new HashMap<>();
		retMap.put("code", 200);
		retMap.put("msg", ex.getMessage());

		return retMap;
	}
	@RequestMapping(value = "error", produces = { "application/xml", "text/xml" })
	public HttpEntity<Map<String, Object>> handleErrorXml(@RequestAttribute("exception") Throwable ex) {
		LOG.error("handleErrorXml", ex);

		Map<String, Object> retMap = new HashMap<>();
		retMap.put("code", 200);
		retMap.put("msg", ex.getMessage());

		return ResponseEntity.status(HttpStatus.OK).body(retMap);
	}
}

其中,@RequestMapping中的produces表示客户端请求报文头中Accept的值,只有满足条件的才能由对应的异常处理方法处理。@RequestAttribute("exception")表示从HttpServletRequest中取出异常。

  • handleErrorPage可以针对text、html两种类型的请求,通过index.ftl,生成返回页面。
  • handleErrorJson可以针对json类型的请求,生成json返回
  • handleErrorXml可以针对xml类型的请求,生成xml返回

以上代码即可以针对form提交进行处理,也可以针对JQuery发起的异步请求做处理。如果需要生成xml或其它类型的数据,则可以添加相应的处理方法。

页面index.ftl的内容如下:

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="${request.contextPath}/bootstrap/4.3.1/css/bootstrap.min.css">
<title>Hello, world!</title>
</head>
<body>
	<h1>Hello, world!</h1>
	<script src="${request.contextPath}/jquery/3.4.1/jquery-3.4.1.min.js"></script>
	<script src="${request.contextPath}/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
	<script src="${request.contextPath}/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>

 App.java内容如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
@EnableAutoConfiguration(exclude = { ErrorMvcAutoConfiguration.class })
public class App {

	private static final Logger LOG = LoggerFactory.getLogger(App.class);

	public static void main(String[] args) {
		ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
		ctx.registerShutdownHook();

		LOG.info("=========app started!=========");
	}
}

参考文档

Spring Web MVC - 1.1.7. Exceptions
Spring Web MVC - 1.3.6. Exceptions
Spring Web MVC - 1.3.7. Controller Advice
Spring Boot Reference Guide - 29.2.5 Error Handling
jQuery ajax - ajax() 方法
Spring MVC - @ExceptionHandler based on Accept header

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值