要求:记录项目中所抛出的异常信息,并保存该次请求的信息。
springboot中自带的注解@ControllerAdvice,可以帮助捕获所有controller抛出的信息,所以使用该种方式(看到有文章说带有该注解的类必须放在controller同包下,我试了,放在其他包下同样生效)。
在使用时需要记录请求参数(方便找出错原因),但是在处理异常时获取requestBody遇到问题:requestBody流只能读一次(
httpServletRequest中的流只能读取一次的原因
),通过接口再到这里,就获取不到了。于是想使用@ModelAttribute注解方法,在进入接口之前把requestBody取出来,加一个自定义参数,结果接口中读取不到requestBody了。于是在网上找了个方法,通过过滤器把requestBody读取后再放回去。
首先创建工具类:
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
* 用于重复读取requestBody
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private BufferedReader reader;
private ServletInputStream inputStream;
public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
loadBody(request);
private void loadBody(HttpServletRequest request) throws IOException {
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
public byte[] getBody() {
return body;
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
return super.getInputStream();
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
return reader;
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
@Override
public int read() throws IOException {
return inputStream.read();
@Override
public boolean isFinished() {
return inputStream.available() == 0;
@Override
public boolean isReady() {
return true;
@Override
public void setReadListener(ReadListener readlistener) {
过滤器filter:
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class LedgerReportFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
@Override
public void destroy() {
异常统一处理:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lenovo.lsump.ledger.ledgerpostingreportservice.domain.document.ExceptionLog;
import com.lenovo.lsump.ledger.ledgerpostingreportservice.domain.repository.mongo.ExceptionLogRepository;
import com.lenovo.lsump.ledger.ledgerpostingreportservice.filter.ContentCachingRequestWrapper;
import feign.FeignException;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
private static String REQUESTBODY = "requestBodyMessage";
@Autowired
ExceptionLogRepository exceptionLogRepository;
@ExceptionHandler({Exception.class})
public Map<String, Object> handleException(HttpServletRequest req, Exception e) throws Exception {
try {
String uri = req.getRequestURI();
ExceptionLog exceptionLog = new ExceptionLog();
exceptionLog.setCreateDate(new Date());
Object body = req.getAttribute(REQUESTBODY);
if (body != null) exceptionLog.setRequestBody(body.toString());
exceptionLog.setUri(uri);
Map<String, String[]> parameterMap = req.getParameterMap();
if (!parameterMap.isEmpty()) {
ObjectMapper objectMapper = new ObjectMapper();
exceptionLog.setRequestParams(objectMapper.writeValueAsString(parameterMap));
Enumeration<String> headerNames = req.getHeaderNames();
Map<String, String> headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, req.getHeader(headerName));
if (!headers.isEmpty()) {
ObjectMapper objectMapper = new ObjectMapper();
exceptionLog.setRequestHeaders(objectMapper.writeValueAsString(headers));
exceptionLog.setMessage(e.toString());
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
exceptionLog.setStackTrace(sw.toString());
exceptionLogRepository.save(exceptionLog);
} catch (Exception ex) {
throw e;
* 由于body在接口读取后无法获取,这里把body提前取出放到参数中,在上面处理异常时使用
@ModelAttribute
public void getBobyInfo(HttpServletRequest request) {
//获取requestBody
try {
ContentCachingRequestWrapper requestWapper = null;
if (request instanceof HttpServletRequest) {
requestWapper = (ContentCachingRequestWrapper) request;
String body = IOUtils.toString(requestWapper.getBody(), request.getCharacterEncoding());
request.setAttribute(REQUESTBODY, body);
} catch (Exception e) {
@ExceptionHandler(FeignException.class)
public Map<String, Object> handleException(FeignException feignException, HttpServletResponse response) {
ObjectMapper objectMapper = new ObjectMapper();
try {
String message = feignException.getMessage();
if (!message.contains("{")) {
message = feignException.contentUTF8();
String originalBody = message;
Map<String, Object> map = objectMapper.readValue(originalBody, Map.class);
response.setStatus((Integer) map.get("status"));
return map;
} catch (Exception e) {
throw feignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframe...
使用统一异常处理,将这些重复的try-catch块抽取出来,这样使我们可以更专注于业务逻辑的处理,同时能够使得异常的处理有一个统一的控制。
一、HandlerExceptionResolver全局异常处理
使用全局异常处理器只需要两步:
1.实现HandlerExceptionResolver接口。
2.将实现类作为Spring Bean,这样Spring就能扫描到它并作为全局异常处理器加载。
二、Controller局部异常处理
三、@ControllerAdvice
如果单使用2中的@Exception
Spring Boot后端接口规范Spring在3.2版本增加了一个注解,可以与、、等注解注解配套使用。不过跟异常处理相关的只有注解,从字面上看,就是异常处理器的意思**Assert(断言)**是Spring 家族的,在我们写测试用例的时候经常会用到,使用断言能让我们编码的时候有一种非一般丝滑的感觉Assert的部分源码,可以看到,Assert 其实就是帮我们把 if {…} 封装了一下,抛出的异常是} } }
为什么拦截器会重复调两遍呢?
ServletInputStream(CoyoteInputStream) 输入流无法重复调用
自定义 HttpServletRequestWrapper
总结一下 展望一下
获取http请求参数是一种刚需
我想有的小伙伴肯定有过获取http请求的需要,比如想
前置获取参数,统计请求数据
做服务的接口签名校验..
在项目的开发中,在某些情况下,比如非业务的操作,日志记录,权限认证和异常处理等。我们需要对客户端发出的请求进行拦截,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect。先简单介绍一下不同的拦截方式。
代码中一堆try-catch块非常冗余和啰嗦,对阅读也造成了一定的干扰,使注意力很难集中到核心业务上来。
因此我们使用统一异常处理,将这些重复的try-catch块抽取出来,这样使我们可以更专注于业务逻辑的处理,同时能够使得异常的处理有一个统一的控制。
1.HandlerExceptionResolver全局异常处理
使用全局异常处理器只需要两步:
1.实现HandlerExceptionResolver接口。
2.将实现类作为Spring Bean,这样Spring就能扫描到它并作为全局异常处理器加载。
文章目录1 统一数据返回格式2 全局异常处理概述2.1 请求参数校验异常全局处理2.2 自定义异常全局处理3 测试代码
本文源码地址:https://github.com/nieandsun/NRSC-STUDY
1 统一数据返回格式
在实际项目开发中为了与前端更好的交互,我们一般都会固定参数的返回格式,如下图:
为了达到这一目的我比较喜欢的一种做法是定义三个类来完成这件事:
代码很简单,...
前景描述: 最近在做项目时发现后台程序的异常抛到前端页面上报出一大段sql异常,因此考虑需要对异常进行全局统一处理,并将日志进行分类入库以及记录接口请求的日志信息等,记录日志信息在之前的文章已经有记录了这里不再重复有需要的请移步到Spring Boot 使用AOP切面实现后台日志管理模块。
因为项目是基于Springboot做的前后端分离的项目,需要结合项目本身的一些特殊需求做些许改造。
在网...
import com.techsun.industry.common.enums.ResultCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
impo...
SpringBoot全局异常捕获处理及参数校验
为什么要用全局异常处理?
在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,参数校验逻辑业务逻辑还长,不利于后期维护。
为解决该问题,可以将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端。
如何进行全局异常捕获和处理?
一共有两种方法:
Spring的AOP
@ControllerAdvice结合@Exc