一、概述
公司里面的祖传代码很多,然后对拦截器进行修改,在拦截器中添加一些参数校验,然而对于payload请求,不可以通过getParameter()方法直接获取body里面的参数,需要读取流,然后转换成json对象,然而,抽取出来了就不能自动装配了,这个之前有写过一篇文章描述如何解决该问题的。
目前又遇到了一个问题,这种参数校验的项目较多,每个项目添加几个类很麻烦,然后想着用spring-boot的starter来封装之前的解决方法,然后每次要用的时候,我只需要pom引入我的starter就行。
二、启动器starter基本规范
- 启动器只用来做依赖导入(maven工程)
- 写一个启动器自动配置模块(spring-boot工程)
1. @Configuration //指定这个类是一个配置类 @ConditionalOnXXX //在指定条件成立的情况下自动配置类生效 @AutoConfigureAfter //指定自动配置类的顺序 @Bean //给容器中添加组件 2. @ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置 @EnableConfigurationProperties //让xxxProperties生效加入到容器中 3. 自动配置类要能加载 将需要启动就加载的自动配置类,配置在META‐INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
- 自定义启动器名***-spring-boot-starter
推荐使用以下命名规约; • 官方命名空间 – 前缀:“spring-boot-starter-” – 模式:spring-boot-starter-模块名 – 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc • 自定义命名空间 – 后缀:“-spring-boot-starter ” – 模式:模块-spring-boot-starter – 举例:mybatis-spring-boot-starter
2.1 启动器模块
将自动配置类需要的jar包在starter中的pom文件引入进来。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.infosec</groupId> <artifactId>multiplyuse-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.infosec</groupId> <artifactId>multiplyuse-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies> </project>
2.2 自动配置模块
- 编写一个属性配置类,用来读取配置属性,这里是用来配置过滤器的过滤条件
import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; @ConfigurationProperties(prefix = "httpservlet.mulitiplyuse") public class MultiplyUseProperties { private List<String> urlParten; public List<String> getUrlParten() { return urlParten; } public void setUrlParten(List<String> urlParten) { this.urlParten = urlParten; } }
- 编写用于实现重复读取HttpServletRequest请求的代码
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.io.IOUtils; public class ContentCachingRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; private BufferedReader reader; private ServletInputStream inputStream; public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException{ super(request); loadBody(request); } private void loadBody(HttpServletRequest request) throws IOException{ body = IOUtils.toByteArray(request.getInputStream()); inputStream = new RequestCachingInputStream(body); } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { if (inputStream != null) { return inputStream; } return super.getInputStream(); } @Override public BufferedReader getReader() throws IOException { if (reader == null) { reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding())); } return reader; } private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream(byte[] bytes) { inputStream = new ByteArrayInputStream(bytes); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
添加过滤器
import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class SignValidateFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request); String body = IOUtils.toString(requestWrapper.getBody(),request.getCharacterEncoding()); chain.doFilter(requestWrapper, response); } @Override public void destroy() { } }
- 编写配置类
import com.infosec.multiplyusespringbootstarterautoconfigurer.MultiplyUseProperties; import com.infosec.multiplyusespringbootstarterautoconfigurer.SignValidateFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnWebApplication @ConditionalOnClass(name = "org.apache.commons.io.IOUtils") @EnableConfigurationProperties(value = MultiplyUseProperties.class) public class MultiplyUseAutoConfiguration { @Autowired private MultiplyUseProperties multiplyUseProperties; @Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new SignValidateFilter()); filterRegistrationBean.setUrlPatterns(multiplyUseProperties.getUrlParten()); return filterRegistrationBean; } }
- 设置自动配置类启动加载,配置在META‐INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.infosec.multiplyusespringbootstarterautoconfigurer.configuration.MultiplyUseAutoConfiguration
三、测试
拦截器
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.example.testforauto.annotatin.LoginRequired; import com.infosec.multiplyusespringbootstarterautoconfigurer.ContentCachingRequestWrapper; import org.apache.commons.io.IOUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; public class UserInceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("========================"); ContentCachingRequestWrapper requestWapper = null; if(request instanceof HttpServletRequest){ requestWapper = (ContentCachingRequestWrapper) request; } System.out.println(123456); String body = IOUtils.toString(requestWapper.getBody(),request.getCharacterEncoding()); JSONObject obj = JSON.parseObject(body); System.out.println(obj); return true; } }
请求
import com.example.testforauto.annotatin.LoginRequired; import com.example.testforauto.entity.User; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @LoginRequired @PostMapping(value = "/user") public String User(@RequestBody User user){ System.out.println(123); System.out.println(user); return user.getName(); } }
测试结果:
======================== 123456 {"name":"ljl","id":1} 123 User{id=1, name='ljl'}
从结果上看,httpServletRequest在拦截器中被调用之后,自动装配也是成功的,所以实现了重复读取的功能。