代码未经生产验证,仅供参考
1 前言
尽管spring boot
官方为我们提供了一系列的功能丰富的starter
组件(官方starter),但有时候结合业务场景,我们也需要自定义一些starter
,来满足我们的需求。本文将自定义一个starter
,来实现去除web
请求参数中的前后空格的功能。
2 项目结构
- space-trim-spring-boot-starter:
spring boot
工程,自定义的starter
,实现去除web
请求参数中的前后空格 - demo-spring-boot-starter:
spring boot
工程,集成space-trim-spring-boot-starter
,用来验证对应的功能
3 space-trim-spring-boot-starter
3.1 引入依赖
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-configuration-processor
org.springframework.boot
spring-boot-starter-web
org.apache.commons
commons-lang3
3.7
com.alibaba
fastjson
1.2.67
核心在于引入spring-boot-autoconfigure
和spring-boot-configuration-processor
3.2 常见配置属性类SpaceTrimProperties
@ConfigurationProperties(prefix = "space.trim")
public class SpaceTrimProperties {
private boolean enable;
private String urlPattern;
private Integer order;
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public String getUrlPattern() {
return urlPattern;
}
public void setUrlPattern(String urlPattern) {
this.urlPattern = urlPattern;
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
this.order = order;
}
}
使用@ConfigurationProperties(prefix = "space.trim")
注解修饰,其中的space.trim
为配置项的前缀,该类提供以下几个属性:
- enable:是否启用去除参数空格的功能
- urlPattern:后文中使用一个自定义
Filter
实现去除参数空格的功能,该参数用于指定该Filter
的生效url
- order:指定
Filter
的优先级
3.3 实现去除参数空格功能
3.3.1 TrimFilter
public class TrimFilter implements Filter {
private SpaceTrimProperties spaceTrimProperties;
public TrimFilter(SpaceTrimProperties spaceTrimProperties) {
this.spaceTrimProperties = spaceTrimProperties;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// init
}
@Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
if (!spaceTrimProperties.isEnable()) {
filterChain.doFilter(servletRequest,servletResponse);
return;
}
HttpServletRequest request = (HttpServletRequest) servletRequest;
String method = request.getMethod();
ServletRequestWrapper requestWrapper = null;
if(StringUtils.equalsIgnoreCase("POST",method)){
requestWrapper = new PostParameterRequestWrapper(request);
filterChain.doFilter(requestWrapper,servletResponse);
}else if(StringUtils.equalsIgnoreCase("GET",method)){
GetParameterRequestWrapper getRequestWrapper = new GetParameterRequestWrapper(request);
Map map = request.getParameterMap();
Set keys = map.keySet();
keys.forEach(key -> removeSpaceLetter(getRequestWrapper, map, key));
filterChain.doFilter(getRequestWrapper,servletResponse);
}
}
private void removeSpaceLetter(GetParameterRequestWrapper getRequestWrapper, Map map, String key) {
Object value = map.get(key);
if(value != null) {
String[] values = (String[]) value;
List newValueArray = new ArrayList<>();
if (values.length > 0) {
for (String singleValue : values) {
if(StringUtils.isNotBlank(singleValue)) {
singleValue = StringUtils.stripToEmpty(singleValue);
newValueArray.add(singleValue);
}
}
newValueArray.toArray(values);
getRequestWrapper.addParameter(key,values);
}
}
}
@Override
public void destroy() {
//destroy
}
}
提供一个入参为 spaceTrimProperties
的构造函数,并将入参赋值给类成员变量,在doFilter
方法中使用spaceTrimProperties
来实现一些逻辑控制,具体细节不是本文讨论重点,不展开
3.3.2 GetParameterRequestWrapper
public class GetParameterRequestWrapper extends HttpServletRequestWrapper {
private Map params = new HashMap<>();
public GetParameterRequestWrapper(HttpServletRequest request) {
super(request);
this.params.putAll(request.getParameterMap());
}
public GetParameterRequestWrapper(HttpServletRequest request , Map extendParams) {
this(request);
addAllParameters(extendParams);
}
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
public String[] getParameterValues(String name) {
return params.get(name);
}
public void addAllParameters(MapotherParams) {
for(Map.Entryentry : otherParams.entrySet()) {
addParameter(entry.getKey() , entry.getValue());
}
}
public void addParameter(String name , Object value) {
if(value != null) {
if(value instanceof String[]) {
params.put(name , (String[])value);
}else if(value instanceof String) {
params.put(name , new String[] {(String)value});
}else {
params.put(name , new String[] {String.valueOf(value)});
}
}
}
}
3.3.3 PostParameterRequestWrapper
public class PostParameterRequestWrapper extends HttpServletRequestWrapper {
private static final Logger log = LoggerFactory.getLogger(PostParameterRequestWrapper.class);
private byte[] body;
public PostParameterRequestWrapper(HttpServletRequest request) {
super(request);
//获取request域json类型参数
String param = getBodyString(request);
log.info("contentType:{}",request.getContentType());
if (StringUtils.equalsIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
JSONObject newParamJson = new JSONObject();
if (StringUtils.isNotBlank(param)) {
JSONObject origParamJson = JSON.parseObject(param);
List keys = new ArrayList<>(origParamJson.keySet());
for (String key : keys) {
generateKeyValuePair(newParamJson, origParamJson, key);
}
body = newParamJson.toJSONString().getBytes(StandardCharsets.UTF_8);
}
} else {
body = param.getBytes(StandardCharsets.UTF_8);
}
}
private static void generateKeyValuePair(JSONObject newParamJson, JSONObject origParamJson, String key) {
Object value = origParamJson.get(key);
if(value == null) {
newParamJson.put(key,value);
}
if(value instanceof String) {
// 处理 {"key":"value"}
newParamJson.put(key, StringUtils.stripToEmpty((String) value));
} else if(value instanceof JSONArray) {
// 处理 {"key":[{"key1":"value1"},{"key2":"value2"}]}
generateJsonArray(newParamJson,key,value);
} else {
// 处理 {"key":1}
newParamJson.put(key,value);
}
}
private static void generateJsonArray(JSONObject newParamJson, String key, Object value) {
JSONArray array = (JSONArray) value;
JSONArray newArray = new JSONArray();
for(int i = 0;i < array.size(); i++) {
Object arrayItemValue = array.get(i);
if(arrayItemValue instanceof JSONObject) {
// 处理 [{"key1":"value1"},{"key2":"value2"}]
JSONObject origInnerJson = array.getJSONObject(i);
Set keySet = origInnerJson.keySet();
JSONObject newInnerJson = new JSONObject();
for (String innerKey : keySet) {
generateKeyValuePair(newInnerJson, origInnerJson, innerKey);
}
newArray.add(newInnerJson);
} else if(arrayItemValue instanceof String) {
// 处理 ["string1",'string2"]
newArray.add(StringUtils.stripToEmpty((String) arrayItemValue));
} else {
// 处理 [1,2,3,4]等
newArray.add(arrayItemValue);
}
}
newParamJson.put(key,newArray);
}
/**
* 获取请求Body
*
* @param request
* @return
*/
public String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
try (InputStream inputStream = cloneInputStream(request.getInputStream())) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("获取请求实体异常", e);
}
return sb.toString();
}
/**
* Description: 复制输入流
*
* @param inputStream
* @return
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
log.error("复制流异常", e);
}
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
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) {
//setReadListener
}
};
}
}
3.4 实现配置类
@EnableConfigurationProperties(SpaceTrimProperties.class)
@Configuration
@ConditionalOnWebApplication
public class SpaceTrimConfiguration {
private SpaceTrimProperties spaceTrimProperties;
public SpaceTrimConfiguration(SpaceTrimProperties spaceTrimProperties) {
this.spaceTrimProperties = spaceTrimProperties;
}
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean trimFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean<>();
TrimFilter trimFilter = new TrimFilter(spaceTrimProperties);
bean.setFilter(trimFilter);
bean.setName("trimFilter");
if (StringUtils.isBlank(spaceTrimProperties.getUrlPattern())) {
bean.setUrlPatterns(Collections.singletonList("/*"));
} else {
bean.setUrlPatterns(Arrays.asList(StringUtils.split(spaceTrimProperties.getUrlPattern(),",")));
}
if (spaceTrimProperties.getOrder() == null) {
bean.setOrder(1);
} else {
bean.setOrder(spaceTrimProperties.getOrder());
}
return bean;
}
}
使用@EnableConfigurationProperties
来启动自定义配置,@ConditionalOnWebApplication
表示只在web
工程启用该配置类,trimFilter
方法创建了一个Filter
,并读取自定义配置项来配置该Filter
3.5 配置自动装配功能
创建文件/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kungyu.config.SpaceTrimConfiguration
3.6 install至本地仓库
4 demo-spring-boot-starter
4.1 引入依赖
org.springframework.boot
spring-boot-starter
com.kungyu
hello-spring-boot-starter
0.0.1-SNAPSHOT
com.kungyu
space-trim-spring-boot-starter
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
这里引入了上个步骤install后的依赖:
com.kungyu
space-trim-spring-boot-starter
1.0-SNAPSHOT
4.2 创建配置类application.properties
space.trim.enable=true
space.trim.order=2
space.trim.url-pattern=/test,/testPost/*
server.port=8085
4.3 测试
@RestController(value = "/demo")
public class TestController {
@GetMapping(value = "/test")
public void test(@RequestParam("name") String name) {
System.out.println(name);
}
@PostMapping(value = "/testPost")
public void testPost(@RequestBody PostBody postBody) {
System.out.println(postBody.getName());
System.out.println(postBody.getAddress());
System.out.println(postBody.getDesc());
}
@PostMapping(value = "/testPost1")
public void testPost1(@RequestBody PostBody postBody) {
System.out.println(postBody.getName());
System.out.println(postBody.getAddress());
System.out.println(postBody.getDesc());
}
}
请求
结果
可以看到,对于已配置的路径/test
和/testPost
,参数空格去除成功,而对于未配置的路径/testPost1
,则原样输出
5 总结
全文看起来比较冗长,但其实涵盖了创建starter
、实现参数去空格的功能和测试等,总计起来,实现一个自定义starter
的步骤如下:
- 引入自动配置依赖
spring-boot-autoconfigure
和spring-boot-configuration-processor
- 创建配置项实体,并用
@ConfigurationProperties
修饰 - 创建配置类,并用
@EnableConfigurationProperties
和@Configuration
修饰 - 在
spring.factories
中声明自定义的配置类