自定义starter(实现HttpServletRequest重复读取)

一、概述

公司里面的祖传代码很多,然后对拦截器进行修改,在拦截器中添加一些参数校验,然而对于payload请求,不可以通过getParameter()方法直接获取body里面的参数,需要读取流,然后转换成json对象,然而,抽取出来了就不能自动装配了,这个之前有写过一篇文章描述如何解决该问题的。

目前又遇到了一个问题,这种参数校验的项目较多,每个项目添加几个类很麻烦,然后想着用spring-boot的starter来封装之前的解决方法,然后每次要用的时候,我只需要pom引入我的starter就行。

二、启动器starter基本规范

  1. 启动器只用来做依赖导入(maven工程)
  2. 写一个启动器自动配置模块(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,\
  1. 自定义启动器名***-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文件引入进来。


<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.0modelVersion>

    <groupId>com.infosecgroupId>
    <artifactId>multiplyuse-spring-boot-starterartifactId>
    <version>1.0-SNAPSHOTversion>
    <dependencies>
        <dependency>
            <groupId>com.infosecgroupId>
            <artifactId>multiplyuse-spring-boot-starter-autoconfigurerartifactId>
            <version>0.0.1-SNAPSHOTversion>
        dependency>
        
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.6version>
        dependency>
    dependencies>
    
project>

2.2 自动配置模块

  1. 编写一个属性配置类,用来读取配置属性,这里是用来配置过滤器的过滤条件
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties(prefix = "httpservlet.mulitiplyuse")
public class MultiplyUseProperties {

    private List urlParten;

    public List getUrlParten() {
        return urlParten;
    }

    public void setUrlParten(List urlParten) {
        this.urlParten = urlParten;
    }
}
  1. 编写用于实现重复读取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() {

    }
}
  1. 编写配置类
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;
    }
}
  1. 设置自动配置类启动加载,配置在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在拦截器中被调用之后,自动装配也是成功的,所以实现了重复读取的功能。

你可能感兴趣的:(springboot,springboot)