美文网首页
spring boot自定义starter

spring boot自定义starter

作者: 秋水畏寒 | 来源:发表于2021-01-30 22:43 被阅读0次

    代码未经生产验证,仅供参考

    1 前言

    尽管spring boot官方为我们提供了一系列的功能丰富的starter组件(官方starter),但有时候结合业务场景,我们也需要自定义一些starter,来满足我们的需求。本文将自定义一个starter,来实现去除web请求参数中的前后空格的功能。

    2 项目结构

    • space-trim-spring-boot-starterspring boot工程,自定义的starter,实现去除web请求参数中的前后空格
    • demo-spring-boot-starterspring boot工程,集成space-trim-spring-boot-starter,用来验证对应的功能

    3 space-trim-spring-boot-starter

    3.1 引入依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.7</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.67</version>
            </dependency>
        </dependencies>
    

    核心在于引入spring-boot-autoconfigurespring-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<String,String[]> map = request.getParameterMap();
                Set<String> keys = map.keySet();
                keys.forEach(key -> removeSpaceLetter(getRequestWrapper, map, key));
                filterChain.doFilter(getRequestWrapper,servletResponse);
            }
        }
    
        private void removeSpaceLetter(GetParameterRequestWrapper getRequestWrapper, Map<String, String[]> map, String key) {
            Object value = map.get(key);
            if(value != null) {
                String[] values = (String[]) value;
                List<String> 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<String , String[]> params = new HashMap<>();
    
    
        public GetParameterRequestWrapper(HttpServletRequest request) {
            super(request);
            this.params.putAll(request.getParameterMap());
        }
    
    
        public GetParameterRequestWrapper(HttpServletRequest request , Map<String , Object> 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(Map<String , Object>otherParams) {
            for(Map.Entry<String , Object>entry : 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<String> 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<String> 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: 复制输入流</br>
         *
         * @param inputStream
         * @return</br>
         */
        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> trimFilter() {
            FilterRegistrationBean<TrimFilter> 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至本地仓库

    mvn install

    4 demo-spring-boot-starter

    4.1 引入依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
            <dependency>
                <groupId>com.kungyu</groupId>
                <artifactId>hello-spring-boot-starter</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>com.kungyu</groupId>
                <artifactId>space-trim-spring-boot-starter</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    

    这里引入了上个步骤install后的依赖:

    <dependency>
      <groupId>com.kungyu</groupId>
      <artifactId>space-trim-spring-boot-starter</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    

    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

    结果


    /test
    /testPost
    /testPost1

    可以看到,对于已配置的路径/test/testPost,参数空格去除成功,而对于未配置的路径/testPost1,则原样输出

    5 总结

    全文看起来比较冗长,但其实涵盖了创建starter、实现参数去空格的功能和测试等,总计起来,实现一个自定义starter的步骤如下:

    • 引入自动配置依赖spring-boot-autoconfigurespring-boot-configuration-processor
    • 创建配置项实体,并用@ConfigurationProperties修饰
    • 创建配置类,并用@EnableConfigurationProperties@Configuration修饰
    • spring.factories中声明自定义的配置类

    相关文章

      网友评论

          本文标题:spring boot自定义starter

          本文链接:https://www.haomeiwen.com/subject/cqrktltx.html