Spring Filter 学习


title: Spring Filter 学习
date: 2022-6-30
tags:

  • spring
  • springboot
  • Filter
  • Java
  • Interceptor
    categories:
  • spring
  • springboot
  • springMVC
  • Filter

一、简介

1.1 执行顺序

二、使用

2.1 创建 Filter

实现 Filter接口后实现其三个方法,业务逻辑写在doFilter()方法内即可。

public class MyFilter implements Filter {
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(httpServletRequest, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

2.2 将 Filter 注入到 Spring 中

2.2.1 方式一:通过@WebFilter + @ServletComponentScan注入

步骤一:

在自定义 Filter 中添加 @WebFilter注解

该注解注释的类必须实现 Filter 接口

@WebFilter(filterName = "filter", value = "/filter/path2")
public class MyFilter implements Filter {
 ....
{

@WebFilter 常用属性属性:

  1. 不指定属性:例如@WebFilter("/filter/path2/**"),等价于value,表示只过滤请求路径为/filter/path2/**的请求
  2. value:@WebFilter(value = "/filter/path2/**"),表示只过滤请求路径为/filter/path2/**的请求
  3. urlPatterns:@WebFilter(urlPatterns = "/filter/path2/**"),作用与value基本相同,两个属性至少声明一个,但不能同时声明两者,当URL模式是唯一设置的属性时,建议使用value属性,否则应该使用urlPattern属性
  4. filterName:@WebFilter(filterName= "filter"),指定过滤器的名称,不指定则为类的完全限定名

步骤二:

在启动类上加上 @ServletComponentScan 注解,使 Spring 可以扫描到自己定义的 Filter。

@SpringBootApplication
@ServletComponentScan(basePackages = "com.xxx")
public class JavaStudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(JavaStudyApplication.class, args);
    }
}

可通过注解的 basePackagesbasePackageClasses 属性指定需要扫描到的 Filter,可以不指定,默认扫描启动类下的包。

2.2.2 方式二:通过@Component + @Configuration的方式自己注入Filter

步骤一:

在 Filter 上添加 @Component 注解。

@Component
public class MyFilter implements Filter {
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(httpServletRequest, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

如果你的这个过滤器是想过滤所有请求,方式二到步就够了。

步骤二:将该过滤器注入到 Spring 容器中

@Configuration
public class FilterConfiguration {
    
    @Bean
    public FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean(MyFilter myFilter) {
        FilterRegistrationBean<MyFilter> myFilterRegistrationBean = new FilterRegistrationBean<>();
        // 设置需要注如的过滤器
        myFilterRegistrationBean.setFilter(myFilter);
        // 设置过滤器优先级
        myFilterRegistrationBean.setOrder(1);
        // 设置过滤器过滤的请求路径
        myFilterRegistrationBean.addUrlPatterns("/filter/path2");
        return myFilterRegistrationBean;
    }

}

踩坑:方式一和方式二,使用其中一种注入自定一过滤器即可,如果过滤器上同时加了 @WebFilter@Component 注解,@WebFilter注解的配置信息会失效,并且该过滤器会被执行多次。

三、OncePerRequestFilter

3.1 OncePerRequestFilterFilter 的区别

Spring 的过滤器都继承了OncePerRequestFilter 过滤器,这两个过滤器总体没有区别,但是更推荐OncePerRequestFilter,因为当使用OncePerRequestFilter过滤器时,一个请求只会走一遍该过滤器,但Filter可能会走多次,比如说转发、重定向。(但是分 servlet 版本,比如 servlet2.3 有可能OncePerRequestFilter也会走多次,所以在 spring 环境下用第一个过滤器就完事了)

OncePerRequestFilter源码中实现了 doFilter() 方法并做了某些校验,使得某些请求直接放行了,不在重复走我们写的过滤器逻辑,为的就是请求只走一边我们的过滤器逻辑。

详细区别可查看这几篇博客:

  • https://blog.51cto.com/u_3631118/3121386
  • https://www.jianshu.com/p/de66fc745da8
  • https://blog.csdn.net/yy_diego/article/details/110482447
  • https://blog.csdn.net/zl1zl2zl3/article/details/79270664

3.2 使用

使用上和 Filter 一样,两种方式都可以,这里只演示第一种,唯一的区别就是,由于 OncePerRequestFilter 实现了 doFilter() 并在方法中调用了 doFilterInternal() 抽象方法,所以我们只需要实现该放法即可。

步骤一:

@WebFilter(value = "/filter/path1")
public class MyOncePerRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("MyOncePerRequestFilter:filter/path1");
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

步骤二:

@SpringBootApplication
@ServletComponentScan(basePackages = "com.xxx")
public class JavaStudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(JavaStudyApplication.class, args);
    }
}

四、Filter 的应用

4.1 自定义 HttpServletRequest

有时候我们希望在拦截器中对请求的请求体做一些处理后,将处理过的请求体在放回 request 中,但是由于流的特点能读一次,导致拦截器中读取完毕后,Controller 中就的 @RequestBody 就读取不到了,所以我们可以创建一个 HttpServletRequest 的子类,并重写其 getInputStream() 方法,使得请求体的流可以重复调用,HttpServletRequestWrapper 就可以满足我们的需求。

步骤一:自定义 HttpServletRequest

这里我们的思路时,在构造方法中将原 HttpServletRequest request 中的 body 流保存在 byte[] 类型的属性中,因为拦截器中获取请求体,和 Controller 中的 @RequestBody 获取请求体都是通过 HttpServletRequest requestgetInputStream() 获取的,所以我们重写getInputStream() ,每此调用该方法都从 byte[] body 属性中获取,这样就保证了多次调用该方法也可以获取到流。

public class MyServletRequestWrapper extends HttpServletRequestWrapper {
    private byte[] body;
    
    // 将流保存在byte[]类型的属性中
    public MyServletRequestWrapper(HttpServletRequest request) {
        super(request);

        try (ServletInputStream inputStream = request.getInputStream();
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            if (inputStream != null) {
                IOUtils.copy(inputStream, byteArrayOutputStream);
                body = byteArrayOutputStream.toByteArray();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

	// 重写getInputStream() 方法,使其从 byte[] body 属性中获取流
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }
        };
    }

    public String getBody() {
        return new String(body);
    }

    public void setBody(byte[] body) {
        this.body = body;
    }
}

步骤二:在 Filter 中替换原 HttpServletRequest

这里的操作就是将原来的 HttpServletRequest request 替换为我们刚刚写的他的子类HttpServletRequestWrapper

这里做了一些判断,使我们只将 Json 格式的请求体,转成我们的子类,如果你知道你过滤的请求体使什么类型的,可以不做这些判断直接将 ServletRequest request 转成 HttpServletRequestWrapper 然后放行即可。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String method = httpServletRequest.getMethod();
    String contentType = httpServletRequest.getContentType();
    if (StrUtil.isNotEmpty(contentType)) {
        contentType = contentType.toLowerCase();
    }

    // 该方法处理 POST请求并且contentType为application/json格式的
    if (HttpMethod.POST.name().equalsIgnoreCase(method) && StrUtil.isNotEmpty(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
        // 强转
        MyServletRequestWrapper myServletRequestWrapper = new MyServletRequestWrapper(httpServletRequest);
        httpServletRequest = myServletRequestWrapper;
    }

    chain.doFilter(httpServletRequest, response);
}

步骤三:在拦截器中使用

拦截器中直接将 HttpServletRequest request 强转为我们的 HttpServletRequestWrapper 子类,然后使用即可。

这里我做的业务逻辑就是,将 body 中的被 Base64 编码的属性值转换为原值,在放回去。

@Component
public class MyHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String body = ((MyServletRequestWrapper) request).getBody();

        //System.out.println(body);

        JSONObject jsonObject = JSON.parseObject(body);
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            String key = entry.getKey();
            String value = (String) entry.getValue();

            if (Base64.isBase64(value)) {
                jsonObject.put(key, Base64.decodeStr(value));
            }
        }


        byte[] bytes = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
        ((MyServletRequestWrapper) request).setBody(bytes);

        //System.out.println(((MyServletRequestWrapper) request).getBody());

        return true;
    }
}

配置拦截器:

@Order(1)
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    @Resource
    private MyHandlerInterceptor myHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myHandlerInterceptor).addPathPatterns("/filter/path2");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值