Java 工具 —— 包装Servlet
标签(空格分隔): java 工具类 servlet
前言:在日常的接口开发中,经常需要对接口参数生成签名,以验证请求是否被修改。但目前通用的请求格式都以json数据作为参数,这种需要获取流对象来读取数据,这样,如果在拦截器中进行读取验证,在之后Controller中的参数封装就会报错,因为流数据只能读取一次。
request的请求参数获取方式
一、只适用于GET
getQueryString()
只适用于GET,比如客户端发送http://localhost/testServlet?a=b&c=d&e=f,通过request.getQueryString()得到的是a=b&c=d&e=f
二、共用方法
getParameter()
GET和POST都可以用。request的getparameter方法的作用是获取到客户端通过表单或url请求参数所发送过来的参数值。application/x-www-form-urlencoded对于这种数据格式是有效的,对于json或是application/form-data都是获取不到数据的。
getInputStream()、getReader()
针对上面情况,在getParameter()获取不到数据时,我们可以调用流对象来获取body数据。
包装ServletRequest以多次读取数据
由于流对象只能读取一次,这样,如果在拦截器中进行读取验证,在之后Controller中的参数封装就会报错。我们可以在第一次读取流对象内容时将其存在一个变量中,以实现多次读取。
- 首先创建包装类
/**
* 请求体参数包装类,用于解决二次读取body数据问题
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**用于存储body内容的变量 */
private byte[] requestBody = null;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
//将原requestbody数据copy到requestBody中
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
//模拟构建一个流对象,用于每次的数据读取,每次都会创建一个新的
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
//用新创建的字节流对象来构建字符流对象
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
- 其次,我们需要将原有的request替换为我们创建的包装类,这会用到Filter过滤器,,在过滤器中完成对象的替代工作
@WebFilter(filterName = "bodyReaderFilter", urlPatterns = "/*")
public class BodyReaderFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(BodyReaderFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("进入过滤器,替换request对象");
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest){
requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest)servletRequest);
}
if (null == requestWrapper){
filterChain.doFilter(servletRequest,servletResponse);
}else {
filterChain.doFilter(requestWrapper,servletResponse);
}
}
}
- 最后,需要在bean容器里去注册Filter过滤器实例
@Bean
public FilterRegistrationBean<BodyReaderFilter> Filters() {
FilterRegistrationBean<BodyReaderFilter> registrationBean = new FilterRegistrationBean<BodyReaderFilter>();
registrationBean.setFilter(new BodyReaderFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("tokenFilter");
return registrationBean;
}
Filter与Interceptor的区别
- 规范不同:Filter是Servlet规范规定的,依赖于Servlet容器;Interceptor是Spring规定的,依赖于Spring框架。
- 实现原理不同:Filter基于函数回调;Interceptor基于java反射。
- 使用范围不同:Filter依赖于Servlet容器,只能用于Web程序;Interceptor既可用于Web程序,也可以用于Application、Swing等程序中。
- 使用的资源不同:Interceptor是Spring的一个组件,归Spring管理,配置在Spring文件中,可以使用Spring里的任何资源和对象,例如Service对象、数据源、事务管理等,通过IoC注入到Interceptor既可;Filter则不能。
- 深度不同:Filter只在Servlet前后起作用,Interceptor能够深入到方法前后,异常抛出前后等。在Spring中应该优先使用Interceptor。
Filter的运行时间是在Interceptor之前。
Interceptor中有三个方法,preHandler(), postHandle(), afterCompletion()这三个方法分别在进入DispatcherServlet之前,在Controller执行之后,在页面渲染完成返回给客户端之前执行。