要求:记录项目中所抛出的异常信息,并保存该次请求的信息。
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;
}
}
}