技术标签: spring boot SpringBoot Spring
异常处理主要分为三类:
基于请求转发的异常处理方式是真正的全局异常处理。
实现方式有:
基于异常处理器的异常处理方式其实并不是真正的全局异常处理,因为它处理不了过滤器等抛出的异常。
实现方式有:
基于过滤器的异常处理方式近似与全局异常处理。它能处理过滤器及之后的环节抛出的异常。
实现方式有:
这是SpringBoot默认处理异常方式:一旦程序中出现了异常SpringBoot就会请求/error的url,在SpringBoot中提供了一个叫BasicExceptionController的类来处理/error请求,然后跳转到默认显示异常的页面来展示异常信息。显示异常的页面也可以自定义,在目录src/main/resources/templates/下定义一个叫error的文件,可以是jsp也可以是html 。
此种方式是通过请求转发实现的,出现异常时,会转发到请求到/error,该接口对异常进行处理返回。是最符合全局异常处理的。
可以自定义Controller继承BasicErrorController异常处理来实现异常处理的自定义。
@Slf4j
@RestController
public class MyErrorController extends BasicErrorController {
public MyErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
/**
* produces 设置返回的数据类型:application/json
* @param request 请求
* @return 自定义的返回实体类
*/
@Override
@RequestMapping(value = "", produces = {
MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
// 获取错误信息
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
String code = body.get("status").toString();
String message = body.get("message").toString();
return new ResponseEntity(ApiUtil.fail(message), HttpStatus.OK);
}
}
需要注意
该种方式能获取到的信息时有限的。一般情况只能获取到下面这几个参数(特殊情况会有补充参数)。
现在一般项目需要的响应信息都是自定义统一格式的JSON(code、msg、data)。对于自定义业务错误码code不好得到,对于错误信息msg有时得到的也不一定是你所想要的(简单说就是一些特殊的异常描述信息不好得到)。
比如:自定义的参数校验信息
@NotNull(message = "主键不能为空")
message参数取到的并不是“主键不能为空”。
当出现抛出两次异常,第一次被异常处理器处理,第二次异常转由BasicExceptionController处理。但能取到的异常信息可能是一次的,具体原因下面有分析。
该种方式只能作用于使用@ExceptionHandler注解的Controller的异常,对于其他Controller的异常就无能为力了,所以并不不推荐使用。
此种方式是通过异常处理器实现的,使用HandlerExceptionResolverComposite异常处理器中的ExceptionHandlerExceptionResolver异常处理器处理的。
@RestController
public class TestController {
@GetMapping("test9")
public FundInfo test9() throws Exception {
throw new Exception("test9 error");
}
@GetMapping("test10")
public FundInfo test10() throws Exception {
throw new IOException("test10 error");
}
@ExceptionHandler(Exception.class)
public ApiResult exceptionHandler(Exception e) {
return ApiUtil.custom(500, e.getMessage());
}
}
注意:如果既在具体Controller使用了@ExceptionHandler,也定义了全局异常处理器类(@ControllerAdvice+@ExceptionHandler),优先使用Controller定义的@ExceptionHandler处理。如果处理不了,才会使用全局异常处理器处理。
使用 @ControllerAdvice+@ExceptionHandler注解能够进行近似全局异常处理,这种方式推荐使用。
一般说它只能处理控制器中抛出的异常,这种说法并不准确,其实它能处理DispatcherServlet.doDispatch方法中DispatcherServlet.processDispatchResult方法之前捕捉到的所有异常,包括:拦截器、参数绑定(参数解析、参数转换、参数校验)、控制器、返回值处理等模块抛出的异常。
此种方式是通过异常处理器实现的,使用HandlerExceptionResolverComposite异常处理器中的ExceptionHandlerExceptionResolver异常处理器处理的。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
......省略代码......
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
......省略代码......
mappedHandler = getHandler(processedRequest);
......省略代码......
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
......省略代码......
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
......省略代码......
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
......省略代码......
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
......省略代码......
}
catch (Throwable err) {
......省略代码......
}
finally {
......省略代码......
}
}
使用方式
定义一个类,使用@ControllerAdvice注解该类,使用@ExceptionHandler注解方法。@RestControllerAdvice注解是@ControllerAdvice注解的扩展(@RestControllerAdvice=@ControllerAdvice+@ResponseBody),返回值自动为JSON的形式。
/**
* 全局异常处理器
*/
@Slf4j
@SuppressWarnings("ALL")
@RestControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.OK)
public ApiResult bindException(HttpServletRequest request,
HttpServletResponse response,
BindException exception) {
return ApiUtil.fail(exception.getBindingResult().getFieldError().getDefaultMessage());
}
@ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public ApiResult methodArgumentNotValidException(HttpServletRequest request,
HttpServletResponse response,
MethodArgumentNotValidException exception) {
return ApiUtil.fail(exception.getBindingResult().getFieldError().getDefaultMessage());
}
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.OK)
public ApiResult methodArgumentNotValidException(HttpServletRequest request,
HttpServletResponse response,
MissingServletRequestParameterException exception) {
return ApiUtil.fail(exception.getMessage());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
public ApiResult methodArgumentNotValidException(HttpServletRequest request,
HttpServletResponse response,
ConstraintViolationException exception) {
System.out.println(exception.getLocalizedMessage());
Iterator<ConstraintViolation<?>> iterator = exception.getConstraintViolations().iterator();
if (iterator.hasNext()) {
ConstraintViolationImpl next = (ConstraintViolationImpl)iterator.next();
return ApiUtil.fail(next.getMessage());
}
return ApiUtil.fail(exception.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public ApiResult exception(HttpServletRequest request,
HttpServletResponse response,
Exception exception) {
return ApiUtil.fail(exception.getMessage());
}
}
@ResponseStatus注解
作用:指定http状态码,正确执行时返回该状态码,但方法执行报错时,该返回啥状态码就是啥状态码,指定的状态码无效。
使用简单映射异常处理器处理异常,通过配置SimpleMappingExceptionResolver类也是进行近似全局异常处理,但该种方式不能得到具体的异常信息,且返回的是视图,不推荐使用。
此种方式是通过异常处理器实现的,使用SimpleMappingExceptionResolver异常处理器处理的。
@Configuration
public class GlobalExceptionConfig {
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
/**
* 参数一:异常的类型,这里必须要异常类型的全名
* 参数二:要跳转的视图名称
*/
Properties mappings = new Properties();
mappings.put("java.lang.ArithmeticException", "error1");
mappings.put("java.lang.NullPointerException", "error1");
mappings.put("java.lang.Exception", "error1");
mappings.put("java.io.IOException", "error1");
// 设置异常与视图的映射信息
resolver.setExceptionMappings(mappings);
return resolver;
}
}
实现HandlerExceptionResolver接口来处理异常,该种方式是近似全局异常处理。
此种方式是通过异常处理器实现的,使用自定义的异常处理器(实现HandlerExceptionResolver接口)处理的。
public class MyExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* 异常解析器的顺序, 数值越小,表示优先级越高
* @return
*/
@Override
public int getOrder() {
return -999999;
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
try {
response.getWriter().write(JSON.toJSONString(ApiUtil.fail(ex.getMessage())));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
AbstractHandlerExceptionResolver类实现了HandlerExceptionResolver接口。
基于过滤器的异常处理方式,比异常处理器处理的范围要大一些(能处理到Filter过滤器抛出的异常),更近似全局异常处理。使用自定义过滤器进行异常处理时,该过滤器应该放到过滤链的第一个位置,这样才能保证能处理到后续过滤器抛出的异常。
@Bean
ExceptionFilter exceptionFilter() {
return new ExceptionFilter();
}
@Bean
public FilterRegistrationBean exceptionFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(exceptionFilter());
registration.setName("exceptionFilter");
//此处尽量小,要比其他Filter靠前
registration.setOrder(-1);
return registration;
}
/**
* 自定义异常过滤器
* 用于处理Controller外抛出的异常(如Filter抛出的异常)
*/
@Slf4j
public class ExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException,IOException {
try {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (IOException e) {
httpServletResponse.getWriter().write(JSON.toJSONString(ApiUtil.fail(e.getMessage())));
}
}
}
上面的写法其实还是有一定问题的,如果进入了catch就会重复写入httpServletResponse,可能会导致产生一些列的问题。
举例一个问题来说明,通过上面的写法,这样的对响应的写入一般是累加的,可能会导致返回的数据格式有问题,比如:当异常处理器处理了Controller抛出的异常,写入了响应,然后过滤器又抛出了异常,被ExceptionFilter给catch到,这就有一次处理了异常,写入了响应,最后的到的响应数据可能是这样的:
{
"code": 500,
"msg": "Controller error"
}{
"code": 505,
"msg": "Filter error"
}
这个时候我们一般会使用代理类来再次封装Response,filterChain.doFilter传递的是封装后的代理类。
Response代理类
/**
* Response代理类
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private PrintWriter printWriter = new PrintWriter(outputStream);
public ResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new ServletOutputStream() {
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
outputStream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
outputStream.write(b, off, len);
}
@Override
public void flush() throws IOException {
outputStream.flush();
}
};
}
@Override
public PrintWriter getWriter() throws IOException {
return printWriter;
}
public void flush(){
try {
printWriter.flush();
printWriter.close();
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public byte[] getContent() {
flush();
return outputStream.toByteArray();
}
}
自定义过滤器类修改为
@Slf4j
public class ExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException,IOException {
try {
// 封装Response,得到代理对象
ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse);
// 使用代理对象
filterChain.doFilter(httpServletRequest, responseWrapper);
// 读取响应内容
byte[] bytes = responseWrapper.getContent();
// 这里可以对响应内容进行修改等操作
// 模拟Filter抛出异常
if (true) {
throw new IOException("Filter error");
}
// 内容重新写入原响应对象中
httpServletResponse.getOutputStream().write(bytes);
} catch (Exception e) {
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.getOutputStream().write(JSON.toJSONString(ApiUtil.fail(e.getMessage())).getBytes());
}
}
}
要想实现正在的全局异常处理,显然只通过异常处理器的方式处理是不够的,这种方案处理不了过滤器等抛出的异常。
全局异常处理的几种实现方案:
该方案貌似不好获取到特殊的异常描述信息(没仔细研究),如参数校验中的message属性信息:
@NotNull(message = "主键不能为空")
本方案通过自定义错误处理Controller继承BasicExceptionController来实现。
具体实现参考:常用异常处理实现方案1。
(1)自定义异常处理Controller实现BasicExceptionController
具体实现参考:常用异常处理实现方案1。
(2)异常处理器实现
具体实现参考:常用异常处理实现方案6。
创建自定义过滤器bean
@Bean
ExceptionFilter exceptionFilter() {
return new ExceptionFilter();
}
@Bean
public FilterRegistrationBean exceptionFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(exceptionFilter());
registration.setName("exceptionFilter");
//此处尽量小,要比其他Filter靠前
registration.setOrder(-1);
return registration;
}
方式1:@ControllerAdvice+@ExceptionHandler+Filter(推荐使用)
@Slf4j
public class ExceptionFilter extends OncePerRequestFilter {
/**
* 遇到的坑,ExceptionFilter对象的创建没有交给Spring容器(直接new的),导致@Autowired注入不会生效
*/
@Autowired
private HandlerExceptionResolver handlerExceptionResolver;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException,IOException {
try {
// 封装Response,得到代理对象
ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse);
// 使用代理对象
filterChain.doFilter(httpServletRequest, responseWrapper);
// 读取响应内容
byte[] bytes = responseWrapper.getContent();
// 这里可以对响应内容进行修改等操作
// 模拟Filter抛出异常
if (true) {
throw new IOException("Filter error");
}
// 内容重新写入原响应对象中
httpServletResponse.getOutputStream().write(bytes);
} catch (Exception e) {
handlerExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null, e);
}
}
}
注入的HandlerExceptionResolver其实是HandlerExceptionResolverComposite异常处理器,最终是使用异常处理器中的ExceptionHandlerExceptionResolver异常处理器处理的。方式2:HandlerExceptionResolver+Filter
@Slf4j
public class ExceptionFilter extends OncePerRequestFilter {
@Autowired
private MyExceptionResolver myExceptionResolver;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException,IOException {
try {
// 封装Response,得到代理对象
ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse);
// 使用代理对象
filterChain.doFilter(httpServletRequest, responseWrapper);
// 读取响应内容
byte[] bytes = responseWrapper.getContent();
// 这里可以对响应内容进行修改等操作
// 模拟Filter抛出异常
if (true) {
throw new IOException("Filter error");
}
// 内容重新写入原响应对象中
httpServletResponse.getOutputStream().write(bytes);
} catch (Exception e) {
myExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null, e);
}
}
}
2、4两种通过组合的方式进行异常处理需要考虑到的问题:对于一个请求,如果两个地方都捕捉到了异常,要考虑两次异常处对response响应信息的重复写入问题。
比如:异常处理器处理了控制器抛出的异常,写入响应;过滤器处理了过滤器抛出的异常,写入响应。这就会出现响应被写入了两次的问题或者第二次写入响应直接报错。
一些处理思路:考虑使用Response代理类。第一次处理时,异常处理器写入的响应信息是写入到Response代理对象的,并可以从Response代理类中得到写入的响应信息;第二次处理,过滤器等写入的响应写入到Response原对象中的。
过程中发现一个问题:通过BasicExceptionController+异常处理器处理异常的方式时。Controller抛出了异常,被异常处理器处理,返回的过程中,Filter又抛出了一个异常,被BasicExceptionController处理,但BasicExceptionController的到的异常信息却是Controller产生的异常信息,而不是Filter产生的异常信息。但是调到BasicExceptionController去处理异常又却是是因为Filter抛出异常产生的。
个人猜想:异常处理器在处理异常时,不仅是把响应内容部部分写入了Response,还把异常信息写入了Response。当因为异常跳转到BasicExceptionController进行处理,BasicExceptionController在获取异常信息时,会先从Response获取异常信息,获取不到才会从异常中获取异常信息。
请求转发(推荐
)。完全统一的全局异常处理,自定义异常处理Controller能达到自定义统一响应信息格式目的。
但是,现在一般项目需要的响应信息都是自定义统一格式的JSON(code、msg、data)。但对于自定义业务错误码code不好得到,对于错误信息msg有时得到的也不一定是你所想要的。
但感觉通过自定义的扩展是能得到业务状态码和特殊异常描述信息的(没详细研究)。
异常处理+请求转发补充(个人最推荐
)。推荐使用@ControllerAdvice+@ExceptionHandler+BasicExceptionController
的方式。
异常处理器能自定义处理大多异常(包括特殊的异常),剩余处理不到的异常交给异常处理控制器处理。
过滤器(不推荐
)。异常处理全需要手写代码实现,自己的代码肯定不会太完美,可能有没考虑到的情况,容易出问题;且过滤器之前抛出的异常处理不到。
异常处理器+过滤器补充(不太推荐
)。推荐使用@ControllerAdvice+@ExceptionHandler+Filter(借助异常处理器处理异常)
的方式,但过滤器之前抛出的异常处理不到。
文章浏览阅读753次。最近一直碰到苹果的内存释放的问题:ViewController关闭后没有被释放,导致内存噌噌的上涨,于是检查发现存在以下问题1、ViewController和TableCell互相之间strong引用2、ViewController和delegate互相之间strong引用这些都会导致很严重的ViewController内存泄露,解决方法是第一:ViewController已经_zzphotolistviewcontroller出现 内存泄漏啦
文章浏览阅读2.1k次。logrotate 程序是一个日志文件管理工具。用来把旧的日志文件删除,并创建新的日志文件,我们把它叫做“转储”。我们可以根据日志文件的大小,也可以根据其天数来转储,这个过程一般通过 cron 程序来执行安装后系统会定时运行logrotate,一般是每天一次。系统是这么实现按天执行的。crontab会每天定时执行/etc/cron.daily目录下的脚本,而这个目录下有个文件叫logrotate。在centos上脚本内容是这样的:/usr/sbin/logrotate /etc/logrotate.co_logrotate按天切割日志
文章浏览阅读1w次。using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;name_窗体characters中有两个文本框
文章浏览阅读239次。int bold = 5;((Graphics2D)g).setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));//((Graphics2D)g).setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROU..._java drawline drawstroke
文章浏览阅读490次。C++菜鸟学习笔记系列(19)本期主题:返回类型和return语句return语句用于终止当前正在执行的函数并将控制权返回调用该函数的地方,在C++语言中return语句有两种形式:return;return 表达式;(1)无返回值函数第一种格式为无返回值的return语句,只能用在返回值类型为void的函数中。其实在返回值类型为void的函数中并不要求必须有return语句,因为在..._c++语言return
文章浏览阅读478次。我写了以下脚本:#!/bin/bashhost="www.myhost.com"IFS=$" " ;for x in $(cat foo.list) ; dosrcURI=$(echo $x | awk '{print $1}') ;destURI=$(echo $x | awk '{print $2}') ;srcCURL=$(curl -s -H "Host: ${host}" -I "qua..._linux中脚本调用curl
文章浏览阅读8.1k次。首先声明,看清题目,是VS2012(或更低版本),缺少的是MSVCP120D.dll,至于原因,后面说。当然,其余的VS和缺少其余的dll也可以参考1. 问题原因这个很重要,很多网上的解决方案都是找个vcredist_x86之类的C++运行库安装一下,有的还直接下载MSVCP120D.dll,将其拖入系统文件夹再进行regsvr32命令操作之类的.....这些方案至少对于我的问题是_opencv msvcp120d.dl
文章浏览阅读490次。当前位置:我的异常网» Java Web开发»怎么在表格中自动增加行,并对输入的数据作判断怎么在表格中自动增加行,并对输入的数据作判断www.myexceptions.net网友分享于:2013-12-27浏览:279次如何在表格中自动增加行,并对输入的数据作判断本人刚开始做jsp。现有一个问题,就是一个表格:第一行 显示表头如 单号 名..._java 表格 怎么怎么增加多行
文章浏览阅读344次。1.varvar 命令定义变量会发生‘变量提升’的现象。即变量可以在声明之前使用,值为undefined。为了纠正这种现象,在es6中新增了 let和const(块级作用域)。2.letlet相当于之前的var使用let定义变量需要注意:1.let声明的变量只能在let命令所在的代码块内有效(块级作用域) { let a = 12 var b = 5 } consol..._const变量是一个数组或对象的时候用中括号和花括号的区别
文章浏览阅读1.8k次,点赞2次,收藏6次。夜光序言:生活不只是眼前的苟且,生活还有诗和远方正文:2.Floyd算法Floyd算法是一种经典的动态规划算法,用于解决任意两点之间的最短路问题。时间复杂度为T=0(|V|^3),通常适用于稠密图。Opentcs 路径算法方案确定本研究采用两种算法:Dijkstra多源拓展算法和Floyd算法,根据不同..._opentcs算法
文章浏览阅读624次。文章目录1. 奇异矩阵2. 特征向量 eigenvector3. 行列式 determinate4. 极大似然估计通过Andrew Ng的机器学习,复习了一些忘记的数学知识,比较好的博客记录在这里。1. 奇异矩阵在机器学习中,如果特征数量大于数据量,会产生奇异矩阵(吴恩达建议最好数据量超过特征数量的十倍,这样更好的避免出现奇异矩阵的情况)2. 特征向量 eigenvector3. 行列式..._什么是矩阵奇异向量
文章浏览阅读375次。1881: 蛤玮的机房Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 287 Solved: 101SubmitStatusWeb BoardDescription蛤玮成为了实验室主任,现在学校要求他建好一个机房里的通信网络.这个网络中有n台主机,现在已知建设好了m条线路,可以让一些主机直接或间接通信,为了使这n台主机_机房日子oj