- 用于记录前端发送请求的body参数和query参数,当报错无法将
json转为java对象的时候,通过查看日志能快速定位问题。
创建ParamsRequestWrapper类
-
HttpServletRequest的输入流只能读取一次不能重复读取,如果直接在拦截器中读取request的body参数,会导致controller中无法获取的错误,所有需要增加HttpServletRequest类,用于将HttpServletRequest中的body参数存储,方便多次读取。
/**
* @author klan
**/
public class ParamsRequestWrapper extends HttpServletRequestWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamsRequestWrapper.class);
/**
* 存储body数据
*/
private final byte[] body;
public ParamsRequestWrapper(HttpServletRequest request) {
super(request);
// 将body数据存储起来
String bodyStr = getBodyString(request);
body = bodyStr.getBytes(Charset.defaultCharset());
}
/**
* Save the body data.
*
* @param request The request.
* @return The body.
*/
public String getBodyString(final ServletRequest request) {
try {
return inputStreamToString(request.getInputStream());
} catch (IOException e) {
LOGGER.error("Error information:", e);
throw new RuntimeException(e);
}
}
/**
* Get body.
*
* @return The body.
*/
public String getBodyString() {
final InputStream inputStream = new ByteArrayInputStream(body);
return inputStreamToString(inputStream);
}
/**
* 将inputStream里的数据读取出来并转换成字符串.
*
* @param inputStream The input stream.
* @return String.
*/
private String inputStreamToString(InputStream inputStream) {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, Charset.defaultCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
LOGGER.error("Error information:", e);
throw new RuntimeException(e);
}
return sb.toString();
}
/**
* The get reader.
*
* @return The buffered reader.
*/
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* 获取input stream,解决ServletInputStream只能读取一次的问题.
*
* @return The servlet input stream.
*/
@Override
public ServletInputStream getInputStream() {
// 每次获取都取保存在body常量中的数据.
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return inputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
增加过滤器Filter
- 在过滤器中将
HttpServletRequest包装成为ParamsRequestWrapper,方便多次读取。
/**
* The core filter.
*
* @author klan
*/
@Component
public class CoreFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);
/**
* Filter初始化.
*
* @param filterConfig The filter config.
*/
@Override
public void init(FilterConfig filterConfig) {
LOGGER.info("CoreFilter init...");
}
/**
* 实现过滤功能.
*
* @param request The request.
* @param response The response.
* @param chain The chain.
* @throws IOException The io exception.
* @throws ServletException The servlet exception.
*/
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 将HttpServletRequest包装成wrapper对象,后续可重复读取.
ServletRequest requestWrapper = new ParamsRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
}
/**
* 用于Filter 销毁前,完成某些资源的回收.
*/
@Override
public void destroy() {
LOGGER.info("CoreFilter destroy...");
}
}
增加拦截器Interceptor
- 增加拦截器用于记录日志,在拦截器中获取到的
HttpServletRequest已经是包装过之后的ParamsRequestWrapper
/**
* The params interceptor.
*
* @author klan
*/
public class ParameterInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);
private static final String LOCALHOST = "127.0.0.1";
private static final String LOCALHOST_REMOTE = "0:0:0:0:0:0:0:1";
private static final String UNKNOWN = "unknown";
private static final String X_FORWARDED_FOR = "x-forwarded-for";
private static final String PROXY_CLIENT_IP = "Proxy-Client-IP";
private static final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
private static final String HTTP_CLIENT_IP = "HTTP_CLIENT_IP";
private static final String HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR";
private static final String SPLIT_WORD = ",";
/**
* 在业务处理器处理请求之前被调用.
*
* @param request The request.
* @param response The response.
* @param handler The handler.
* @return false表示流程中断.
*/
@Override
public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler) {
String bodyParam = new ParamsRequestWrapper(request).getBodyString();
String requestURI = request.getRequestURI();
String requestParameter = JSON.toJSONString(request.getParameterMap());
String methodType = request.getMethod();
String ipAddress = getIpAddress(request);
String remoteAddress = request.getRemoteAddr();
LOGGER.info("[ParameterInterceptor]->[preHandle],ipAddress:{},remoteAddress:{},uri:{},methodType:{},bodyParam:{},requestParam:{}",
ipAddress, remoteAddress, requestURI, methodType, bodyParam, requestParameter);
return true;
}
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前).
*
* @param request The request.
* @param response The response.
* @param handler The handler.
* @param modelAndView The model and view.
*/
@Override
public void postHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler, ModelAndView modelAndView) {
LOGGER.info("[ParameterInterceptor]->[postHandle]");
}
/**
* 整个请求处理完毕回调方法,即在视图渲染完毕时回调整个请求处理完毕回调方法.
*
* @param request The request.
* @param response The response.
* @param handler The handler.
* @param ex The exception.
*/
@Override
public void afterCompletion(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler, Exception ex) {
LOGGER.info("[ParameterInterceptor]->[afterCompletion]");
}
/**
* Get real request ip address.
*
* @param request The request.
* @return The ip address.
*/
private String getIpAddress(HttpServletRequest request) {
// 只有在通过了HTTP代理或者负载均衡服务器时才会添加该项.
String ip = request.getHeader(X_FORWARDED_FOR);
if (isIPAddressNotEmpty(ip)) {
// 用apache http做代理时一般会加上Proxy-Client-IP请求头.
ip = request.getHeader(PROXY_CLIENT_IP);
}
if (isIPAddressNotEmpty(ip)) {
// 用apache http做代理时web logic插件加上的头.
ip = request.getHeader(WL_PROXY_CLIENT_IP);
}
if (isIPAddressNotEmpty(ip)) {
// 是代理服务器发送的HTTP头.
ip = request.getHeader(HTTP_CLIENT_IP);
}
if (isIPAddressNotEmpty(ip)) {
// 简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP.
ip = request.getHeader(HTTP_X_FORWARDED_FOR);
}
if (isIPAddressNotEmpty(ip)) {
// 未代理直接获取remoteAddr.
ip = request.getRemoteAddr();
}
// localhost情况.
if (LOCALHOST_REMOTE.equals(ip)) {
ip = LOCALHOST;
}
// 真实ip为第一个非unknown的有效IP字符串.
if (ip.split(SPLIT_WORD).length > 1) {
ip = ip.split(SPLIT_WORD)[0];
}
return ip;
}
/**
* 判断获取的ip不为空.
*
* @param ip The ip address.
* @return if ip address is empty return false.
*/
private boolean isIPAddressNotEmpty(String ip) {
return (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip));
}
}
增加配置Configuration
- 将拦截器注入到容器中
/**
* The core interceptor config.
*
* @author klan
*/
@Configuration
public class CoreInterceptorConfig extends WebMvcConfigurationSupport {
@Bean
public ParameterInterceptor parameterInterceptor() {
// 把自定义拦截器注入到spring容器里.
return new ParameterInterceptor();
}
/**
* 解决redirect:无法识别问题.
*
* @return The internal resource view resolver.
*/
@Bean
public InternalResourceViewResolver defaultViewResolver() {
return new InternalResourceViewResolver();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截的path,忽略swagger页面.
registry.addInterceptor(parameterInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/");
super.addInterceptors(registry);
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 忽略swagger,解决无法访问swagger页面问题.
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
遇到的问题
- 在添加拦截器要拦截的url之后导致项目的swagger无法访问,需要在集成了
WebMvcConfigurationSupport的configuration配置类中添加静态资源配置和忽略的path。
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截的path,忽略swagger页面.
registry.addInterceptor(parameterInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/");
super.addInterceptors(registry);
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 忽略swagger,解决无法访问swagger页面问题.
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
- swagger页面可以正常访问了但是访问项目根目录会报错
:redirect找不到的错误,在网上搜索后发现是缺少试图解析器,所以在配置类中将默认的试图解析器指定为了InternalResourceViewResolver
/**
* 解决redirect:无法识别问题.
*
* @return The internal resource view resolver.
*/
@Bean
public InternalResourceViewResolver defaultViewResolver() {
return new InternalResourceViewResolver();
}
原因是在
WebMvcAutoConfiguration类源码中有一个注解@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}),只有在没有WebMvcConfigurationSupport这个bean的时候才会触发WebMvcAutoConfiguration这个配置类的自动配置,在WebMvcAutoConfiguration中对defaultViewResolver这个bean进行了初始化,默认为InternalResourceViewResolver,而我们的CoreInterceptorConfig配置类继承了WebMvcConfigurationSupport,所以就不会触发WebMvcAutoConfiguration的自动配置,导致项目中没有defaultViewResolver这个bean,无法处理:redirect跳转,如下:
// 在WebMvcAutoConfiguration这个配置类中对defaultViewResolver这个bean进行了如下初始化
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}