spring boot 自定义参数过滤器,把传入的空字符转换成null方式

时间:2022-06-21 05:12:51

spring boot 滤器,把传入的空字符转换成null

废话不多说直接上代码

自定义参数处理器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyStringArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        return  new NamedValueInfo("", false, ValueConstants.DEFAULT_NONE);
    }
    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        String[] param = request.getParameterValues(name);
        if(param==null){
            return null;
        }
        if(StringUtils.isEmpty(param[0])){
            return null;
        }
        return param[0];
    }
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(String.class);
    }
}

应用启动类

?
1
2
3
4
5
6
7
8
9
10
public class Applicaction extends WebMvcConfigurerAdapter {
    public static void main(String[] ags) {
        SpringApplication.run(Applicaction.class, ags);
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        super.addArgumentResolvers(argumentResolvers);
        argumentResolvers.add(new MyStringArgumentResolver());
    }
}

springboot过滤器对请求参数去空格处理

测试人员提出,对所有接口的请求参数去空格处理。 因为接口比较多,所以考虑使用spring mvc的拦截器来实现,但是使用拦截器能获取到请求参数,却无法将修改后的参数返给HttpServletRequest 对象。

HttpServletRequest 提供的获取参数的方法:

?
1
2
3
4
5
6
7
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 注册过滤器

1、封装request对象实体

继承HttpServletRequestWrapper 类用于扩展request。同时如果修改了原来request中参数内容,需要将其修改后的数据返回,所以需要重写获取参数的方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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 源码中获取字节流和字符流的方法

?
1
2
3
4
5
6
7
/**
*  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方法获取请求体中的数据。

?
1
2
3
4
5
6
//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中无法获取到空数据,很疑惑。 最后发现导错包了 【苦笑】

2、自定义过滤器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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() {
    }
}

3、创建Java配置类

有多个filter就创建多个FilterRegistrationBean ,若需注明filter的执行顺序,可通过registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE )配置,值越大,执行顺序越靠后

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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提供了多种方式来注册拦截的请求。

?
1
2
3
4
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注册过滤器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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() {
    }
}

增加JSON字符串的处理

以上处理的是简单的数据类型,如果是json字符串,应当先解析json字符串,将其中的value字符串前后去空格。可以参考以下代码处理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
     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));
        }
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/RXY5021/article/details/82857761