过滤器Filter
首先创建过滤器类,继承Filter。例如创建一个记录http请求和响应日志的过滤器
package edu.duan.springboot.http;
import edu.duan.springboot.util.ApiLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Order(Ordered.HIGHEST_PRECEDENCE)
// 添加@WebFilter注解,就不用FilterRegistrationBean配置
@WebFilter(urlPatterns = "/*", filterName = "LogFilter")
@Slf4j
public class LogFilter implements Filter {
private final String CLASS_NAME = LogFilter.class.getSimpleName();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("===========================过滤器开始工作===========================");
BodyCachingHttpServletRequestWrapper requestWrapper = new BodyCachingHttpServletRequestWrapper((HttpServletRequest) servletRequest);
BodyCachingHttpServletResponseWrapper responseWrapper = new BodyCachingHttpServletResponseWrapper((HttpServletResponse) servletResponse);
ApiLogUtil.logRequest(requestWrapper);
filterChain.doFilter(requestWrapper, responseWrapper);
ApiLogUtil.logResponse(responseWrapper);
}
@Override
public void destroy() {
}
}
中间BodyCachingHttpServletRequestWrapper和 BodyCachingHttpServletResponseWrapper 是刻意包装的,目的是多次读取http请求中的数据(spring原生的http请求类只允许读取一次数据);以及ApiLogUtil是打印日志的工具类。详见附录
要使过滤器生效,有两种办法。
Ⅰ:添加spring配置类,实现@bean注入
package edu.duan.springboot.config;
import edu.duan.springboot.http.LogFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 过滤器配置
* 若添加@ServletComponentScan和@WebFilter注解,就不用当前配置类
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new LogFilter());
registration.addUrlPatterns("/*");
registration.setName("LogFilter");
registration.setOrder(1);
return registration;
}
}
Ⅱ:通过注解
在Filter类添加注解@Order(多个过滤器时决定执行顺序,单个时可省略),@WebFilter
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter(urlPatterns = "/*", filterName = "LogFilter")
并在启动类添加注解@ServletComponentScan,目的是扫描@WebFilter注解的类
@ServletComponentScan
@SpringBootApplication
public class Application implements CommandLineRunner
若@WebFilter所在类不在Application 启动类的所在包或子包中,则@ServletComponentScan需要写明路径
两种方法功能重叠,取其一即可
实现的效果:
2020-01-05 16:05:20.867 INFO 15448 --- [nio-8080-exec-1] edu.duan.springboot.http.LogFilter : ===========================过滤器开始工作===========================
2020-01-05 16:05:20.953 INFO 15448 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 请求:127.0.0.1,HTTP/1.1, POST, http://localhost:8080/api/user, headers={"content-length":"9","host":"localhost:8080","content-type":"application/json;charset=UTF-8","connection":"Keep-Alive","cache-control":"no-cache","accept-encoding":"gzip,deflate","accept":"*/*","user-agent":"Apache-HttpClient/4.5.2 (Java/1.8.0_152-release)"}, params={}, body={"uid":1}
2020-01-05 16:05:22.007 INFO 15448 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 响应:response={"code":200,"msg":null,"body":[{"uid":1,"uname":"张三","uage":15}]}
拦截器Interceptor
创建拦截器类,继承HandlerInterceptor。创建一个与上述过滤器同样功能的拦截器
package edu.duan.springboot.http;
import edu.duan.springboot.util.ApiLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
log.info("===========================拦截器开始任务===========================");
BodyCachingHttpServletRequestWrapper requestWrapper = (BodyCachingHttpServletRequestWrapper) httpServletRequest;
ApiLogUtil.logRequest(requestWrapper);
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
// 只能强制类型转换,不能new一个新对象!
BodyCachingHttpServletResponseWrapper responseWrapper = (BodyCachingHttpServletResponseWrapper) httpServletResponse;
ApiLogUtil.logResponse(responseWrapper);
log.info("===========================拦截器本次任务结束===========================");
}
}
增加配置文件使拦截器生效
package edu.duan.springboot.config;
import edu.duan.springboot.http.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* 拦截器配置
*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
实现的效果:
2020-01-05 16:11:03.765 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.http.LogFilter : ===========================过滤器开始工作===========================
2020-01-05 16:11:04.030 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 请求:127.0.0.1,HTTP/1.1, POST, http://localhost:8080/api/user, headers={"content-length":"9","host":"localhost:8080","content-type":"application/json;charset=UTF-8","connection":"Keep-Alive","cache-control":"no-cache","accept-encoding":"gzip,deflate","accept":"*/*","user-agent":"Apache-HttpClient/4.5.2 (Java/1.8.0_152-release)"}, params={}, body={"uid":1}
2020-01-05 16:11:04.039 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.http.LogInterceptor : ===========================拦截器开始任务===========================
2020-01-05 16:11:04.039 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 请求:127.0.0.1,HTTP/1.1, POST, http://localhost:8080/api/user, headers={"content-length":"9","host":"localhost:8080","content-type":"application/json;charset=UTF-8","connection":"Keep-Alive","cache-control":"no-cache","accept-encoding":"gzip,deflate","accept":"*/*","user-agent":"Apache-HttpClient/4.5.2 (Java/1.8.0_152-release)"}, params={}, body={"uid":1}
2020-01-05 16:11:04.750 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 响应:response={"code":200,"msg":null,"body":[{"uid":1,"uname":"张三","uage":15}]}
2020-01-05 16:11:04.751 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.http.LogInterceptor : ===========================拦截器本次任务结束===========================
2020-01-05 16:11:04.756 INFO 7796 --- [nio-8080-exec-1] edu.duan.springboot.util.ApiLogUtil : 响应:response={"code":200,"msg":null,"body":[{"uid":1,"uname":"张三","uage":15}]}
附:
BodyCachingHttpServletRequestWrapper
package edu.duan.springboot.http;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
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;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiConsumer;
@Slf4j
public class BodyCachingHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> parameterMap = new HashMap<>();
private byte[] bytes = new byte[]{};
public BodyCachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
parameterMap.putAll(request.getParameterMap());
try {
bytes = IOUtils.toByteArray(request.getInputStream());
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
public void customize(BiConsumer<HttpServletRequest, BodyCachingHttpServletRequestWrapper> handler) {
handler.accept((HttpServletRequest) getRequest(), this);
}
public void addParameter(String name, String value) {
if (name != null && value != null) {
String[] parameterValues = getParameterValues(name);
List<String> values = parameterValues != null ? Lists.newArrayList(parameterValues) : Lists.newArrayList();
values.add(value);
parameterMap.put(name, values.toArray(new String[0]));
}
}
public void setParameter(String name, String value) {
if (name != null) {
if (value != null) {
parameterMap.put(name, new String[]{value});
} else {
parameterMap.remove(name);
}
}
}
@Override
public String getParameter(String name) {
String[] values = parameterMap.get(name);
return values != null ? values[0] : null;
}
@Override
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(parameterMap.keySet());
}
@Override
public String[] getParameterValues(String name) {
return parameterMap.get(name);
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
}
BodyCachingHttpServletResponseWrapper
package edu.duan.springboot.http;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class BodyCachingHttpServletResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
/**
* Constructs a response adaptor wrapping the given response.
*
* @param response The response to be wrapped
* @throws IllegalArgumentException if the response is null
*/
public BodyCachingHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
}
public byte[] getBytes() {
return byteArrayOutputStream.toByteArray();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
byteArrayOutputStream.write(b);
getResponse().getOutputStream().write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {
}
};
}
}
ApiLogUtil
package edu.duan.springboot.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import edu.duan.springboot.http.BodyCachingHttpServletRequestWrapper;
import edu.duan.springboot.http.BodyCachingHttpServletResponseWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class ApiLogUtil {
public static void logRequest(BodyCachingHttpServletRequestWrapper requestWrapper) throws IOException {
Enumeration<String> headersEnum = requestWrapper.getHeaderNames();
JSONObject headers = new JSONObject();
while (headersEnum.hasMoreElements()) {
String name = headersEnum.nextElement();
headers.put(name, requestWrapper.getHeader(name));
}
Map<String, String> params = requestWrapper.getParameterMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> String.join(",", entry.getValue())));
String body = IOUtils.toString(requestWrapper.getInputStream(), StandardCharsets.UTF_8);
// String body = "";
log.info("请求:{},{}, {}, {}, headers={}, params={}, body={}", NetworkUtil.getIpAddress(requestWrapper), requestWrapper.getProtocol(), requestWrapper.getMethod(), requestWrapper.getRequestURL(), JSON.toJSONString(headers), params, body);
}
public static void logResponse(BodyCachingHttpServletResponseWrapper responseWrapper) throws IOException {
byte[] responseBody = responseWrapper.getBytes();
String content = IOUtils.toString(responseBody, StandardCharsets.UTF_8.name());
log.info("响应:response={}", content);
}
}