测试人员提出,对所有接口的请求参数去空格处理。 因为接口比较多,所以考虑使用spring mvc的拦截器来实现,但是使用拦截器能获取到请求参数,却无法将修改后的参数返给HttpServletRequest 对象。
HttpServletRequest 提供的获取参数的方法:
String getParameter(String name);//键值对参数
Map<String,String[]> getParameterMap();//键值对参数
Enumeration <String> getParameterNames();//键值对参数
String[] getParameterValues(String name);//键值对参数
ServletInputStream getInputStream();// 文本参数,例如类型 application/json 等
BufferedReader getReader(); //文本参数,例如类型 application/json 等
Collection<Part> getParts();//表单提交, multipart/form-data
之后考虑使用过滤器,在网上找到了方案。使用自定义Request对象继承HttpServletRequestWrapper,获取从过滤器传入的HttpServletRequest对象提取参数,并重写HttpServletRequestWrapper获取参数的方法。
springboot添加过滤器有两种方式:
1、通过创建FilterRegistrationBean的方式
2、 使用@WebFilter
在spring中这些过滤器的创建都需要交给spring容器管理
使用 FilterRegistrationBean 注册过滤器
- 封装request对象实体
继承HttpServletRequestWrapper 类用于扩展request。同时如果修改了原来request中参数内容,需要将其修改后的数据返回,所以需要重写获取参数的方法。
package com.demo.springBootProject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.http.entity.ContentType;
import org.apache.poi.util.IOUtils;
/**
*重新封装request,并对请求参数做去空格处理
*/
public class ParameterTrimRequest extends HttpServletRequestWrapper{
private Map<String, String[]> params = new HashMap<String, String[]>();//保存处理后的参数
private byte[] content;
public ParameterTrimRequest(HttpServletRequest request) {
super(request);
this.params.putAll(request.getParameterMap());
this.modifyParameterValues(); //自定义方法,用于参数去重
if(ContentType.APPLICATION_JSON.getMimeType().equals(request.getContentType())){//对application/json数据格式的数据进行去空格处理
this.content=IOUtils.toByteArray(request.getInputStream());//获取文本数据;
// 对json字符串进行处理
//....................
}
}
public void modifyParameterValues() {//将parameter的值去除空格后重写回去
Set<Entry<String,String[]>> entrys=params.entrySet();
for(Entry<String,String[]> entry :entrys) {
String[] values=entry.getValue();
for(int i=0;i<values.length;i++) {
values[i] = values[i].trim();
}
this.params.put(entry.getKey(), values);
}
}
@Override
public Enumeration<String> getParameterNames() {//重写getParameterNames()
return new Vector<String>(params.keySet()).elements();
}
@Override
public String getParameter(String name) {//重写getParameter()
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {//重写getParameterValues()
return params.get(name);
}
@Override
public Map<String,String[]> getParameterMap(){ //重写getParameterMap()
return this.params;
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 这种获取的参数的方式针对于内容类型为文本类型,比如Content-Type:text/plain,application/json,text/html等
//在springmvc中可以使用@RequestBody 来获取 json数据类型
//其他文本类型不做处理,重点处理json数据格式
if(!super.getHeader("Content-Type").equalsIgnoreCase("application/json")){
return super.getInputStream();
}else{
//根据自己的需要重新指定方法
ByteArrayInputStream in =new ByteArrayInputStream(this.content);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public int read(byte[] b) throws IOException {
return in.read(b);
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public boolean isReady() {
return false;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
};
}
}
}
返回结果是一个ServletInputStream它是InputStream的子类,因为没有重写reset方法,所以该字节流只能被获取一次。所以如果需要对表单提交的json参数处理,则getInputStream()方法需要重写。网上比较流行的做法是,将字节数据保存,然后封装成字节流。
ServletRequest 源码中获取字节流和字符流的方法
/**
* Either this method or {@link #getInputStream} may be called to read the body, not both.
* @exception IllegalStateException
* if {@link #getInputStream} method has been called on this request
**/
public ServletInputStream getInputStream() throws IOException;
public BufferedReader getReader() throws IOException;
getInputSteam和getReader()方法是互斥的,只能调用一次。同时调用会抛出异常 IllegalStateException。
getInputSteam 方法重写最好限定数据类型。比如说文本类型(application/json,text/plain)。
对于Multipart/form-data提交类型,Tomcat会先调用了getInputStream方法获取请求体中的数据。
//Request.class parseParameters
if ("multipart/form-data".equals(contentType)) {
parseParts(false);
success = true;
return;
}
这样在后边再使用getInputSteam方法流中的数据就已经被读取完了。如果再不小心调用了getReader 方法 ,就会抛出异常。
java.lang.IllegalStateException: getInputStream() has already been called for this request
:::本地测试的结果,在filter中和spring mvc的拦截器中可以反复获取流数据。但是在controller中无法获取到空数据,很疑惑。 最后发现导错包了 【苦笑】
- 自定义过滤器
package com.demo.springBootProject.filter;
import java.io.IOException;
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;
/**
* 自定义过滤器,用于对请求参数去空格处理。
*/
public class ParameterTrimFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ParameterTrimRequest trimReqeust= new ParameterTrimRequest((HttpServletRequest)request);
chain.doFilter(trimReqeust, response);
}
@Override
public void destroy() {
}
}
- 创建Java配置类
有多个filter就创建多个FilterRegistrationBean ,若需注明filter的执行顺序,可通过registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE )配置,值越大,执行顺序越靠后
package com.demo.springBootProject.config;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.demo.springBootProject.filter.ParameterTrimFilter;
/**
* 用于添加自定义的bean
*/
@Configuration
public class CustomBean {
/**
*该过滤器用于对请求参数做去空格处理
*@return FilterRegistrationBean<ParameterTrimFilter>
*/
@Bean(name="parameterTrimFilter") //不写name属性,默认beanName为方法名
public FilterRegistrationBean<ParameterTrimFilter> parameterTrimFilter() {
FilterRegistrationBean<ParameterTrimFilter> filter=new FilterRegistrationBean<>();
filter.setDispatcherTypes(DispatcherType.REQUEST);
filter.setFilter(new ParameterTrimFilter()); //必须设置
filter.addUrlPatterns("/*"); //拦截所有请求,如果没有设置则默认“/*”
filter.setName("parameterTrimFilter"); //设置注册的名称,如果没有指定会使用Bean的名称。此name也是过滤器的名称
filter.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);//该filter在filterChain中的执行顺序
return filter;
}
}
注意: 以上配置类所在包或者过滤器所在包必须在spring boot注解可以扫描的包或者子包下。如果springboot没有设置扫描包,则springboot会扫描启动类所在的包及其子包。
FilterRegistrationBean提供了多种方式来注册拦截的请求。
void addUrlPatterns(String... urlPatterns)//向filter追加注册url
void setUrlPatterns(Collection<String> urlPatterns)//覆盖之前已注册的url
void addServletNames(String... servletNames)// 添加过滤的servletName
void setServletNames(Collection<String> servletNames)//覆盖之前的拦截的servletName
拦截url和拦截servlet可以一起使用
使用@WebFilter注册过滤器
package com.demo.springBootProject.controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter(filterName="test",urlPatterns= {"/*"})
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//doSomthing
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
参考文献:spring boot 过滤器去除请求参数前后空格
增加JSON字符串的处理
以上处理的是简单的数据类型,如果是json字符串,应当先解析json字符串,将其中的value字符串前后去空格。可以参考以下代码处理。
public static void main(String[] args) {
String str = "[\" 1\" ,\"2 \",\"4 \"]";
// str = "{\"name\":\"张三 \"}";
// str = "{\"name\": {\"first_name\": \"张 \",\"last_name\":\" 三 \"}}";
str = "[{'name':' 张三 '},{'name':' 李四 '},{'name':19}]";
Object jsonObj = JSON.parse(str);
if (jsonObj instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) jsonObj;
parseJsonObject(jsonObject);
} else if (jsonObj instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) jsonObj;
parseJSONArray(jsonArray);
}
System.out.println(jsonObj);
}
public static void parseJsonObject(JSONObject jsonObject) {
Set<String> keySet = jsonObject.keySet();
for (String key : keySet) {
parseObject(jsonObject, key);
}
}
public static void parseJSONArray(JSONArray jsonArray) {
if (jsonArray == null || jsonArray.isEmpty()) {
return;
}
for (int i = 0; i < jsonArray.size(); i++) {
parseArray(jsonArray, i);
}
}
public static void parseObject(JSONObject jsonObject, String key) {
Object keyObj = jsonObject.get(key);
if (keyObj == null) {
return;
}
if (keyObj instanceof String) {
jsonObject.put(key, ((String) keyObj).trim());
} else if (keyObj instanceof JSONObject) {
//解析json 对象
parseJsonObject(((JSONObject) keyObj));
} else if (keyObj instanceof JSONArray) {
//解析json 数组
parseJSONArray(((JSONArray) keyObj));
}
}
public static void parseArray(JSONArray jsonArray, int index) {
Object keyObj = jsonArray.get(index);
if (keyObj == null) {
return;
}
if (keyObj instanceof String) {
jsonArray.set(index, ((String) keyObj).trim());
} else if (keyObj instanceof JSONObject) {
//解析json 对象
parseJsonObject(((JSONObject) keyObj));
} else if (keyObj instanceof JSONArray) {
//解析json 数组
parseJSONArray(((JSONArray) keyObj));
}
}