草庐IT

SpringBoot添加过滤器Filter

轻轻敲醒沉睡的心灵 2023-09-14 原文

1. 拦截器和过滤器

先说一下,过滤器和拦截器的区别和联系。

1.1 相同点

首先过滤器和拦截器都能实现请求的筛选(过滤或者拦截),然后根据自己的业务需求,添加自己的逻辑,保证请求往后走的时候数据能满足自己的需求。同时呢,他们又都能终止请求流(过滤器只要不在过滤链中往后传request就形;拦截器返回false)。

1.2 不同点

1.2.1 实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的

1.2.2 使用范围不同

过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Servlet的,生存与Tomcat等服务器容器中,导致它只能在web程序中使用
拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。

1.2.3 触发时机不同

由于过滤器和拦截器基于不同的容器,所以他们的触发时机和请求中经过容器的顺序有关,Filter作用于Servlet,Interceptor作用于Springmvc。


时机,来源于网络
1.2.4 处理范围不同

过滤器处理逻辑都在doFilter方法中,拦截器区分pre、post和after,划分粒度更细了,使用起来更灵活

过滤器

拦截器

1.2.5 拦截器可以注入业务

拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

2. Springboot添加拦截器

项目当中使用过滤器还是拦截器,根据需求来定,一般用哪种都可以,我是要处理requestbody 中的数据(处理特殊字符,加密等)。由于数据还要往下继续传,所以选用filter。
过滤器添加有2种方法:

2.1 通过@WebFilter注解添加

    1. 写过滤器类,并实现Filter接口
    1. 添加@WebFilter注解,属性filterName设置过滤器名称,urlPatterns匹配要过滤的url
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;


@WebFilter(filterName = "testFilter", urlPatterns = "/*")
public class TestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        System.out.println("==过滤逻辑==");
        chain.doFilter(request, response);
        

    }

}
    1. 在启动类上添加@ServletComponentScan注解,添加这个注解后,@WebServlet@WebFilter@WebListener注解都可以被扫描到了。
      启动类注解

2.2 使用FilterRegistrationBean对象,将过滤器添加到spring容器,需配合@Configuration@Bean注解

    1. 写过滤器类,实现Filter接口,并添加 @Component注解
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.http.entity.ContentType;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;

/**
 * gateway转发参数时,有特殊符号如 * +等经过了urlencode,无法正确接收,
 * 加过滤器将 body进行urldecode
 * 
 * 注意的问题:RequestBody注解的数据,
 * 要通过request的getReader和getInputStream()方法来获取流数据,然后转变为字符串的
 * 但是流读取一次后就关闭了,在filter中读取了以后,controller中就拿不到数据了
 * 为此,我们自定义一个HttpServletRequestWrapper对象,将拿到的数据在放到这个对象中,
 * 并将这个对象从chain.doFilter()方法中继续传下去,后续就能继续读取了
 */
@Component
public class RequestBodyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String contextPath = request.getServletContext().getContextPath();
        boolean b1 = HttpMethod.POST.name().equals(req.getMethod());
        boolean b2 = ContentType.APPLICATION_JSON.getMimeType().equals(request.getContentType());
        boolean b3 = request.getParameterMap().isEmpty();
        boolean b4 = contextPath.contains("/test");
        if (b1 && b2 && b3 && b4) {
            MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(req);
            chain.doFilter(requestWrapper, response);
        } else {
            chain.doFilter(request, response);
        }
        
    }

}
    1. 将过滤器添加到FilterRegistrationBean,并交给spring容器
import javax.servlet.Filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyFilterConfig {
    
    @Autowired
    private RequestBodyFilter requestBodyFilter;
    
    /**
     * 将过滤器注册到FilterRegistrationBean中
     * 进行简单配置
     */
    @Bean
    public FilterRegistrationBean<Filter> someFilterRegistration() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(requestBodyFilter);
        registration.addUrlPatterns("/*");
        registration.setName("requestBodyFilter");
        registration.setOrder(99);
        return registration;
    }

}
    1. 踩的坑,因为在过滤器中通过request的getReader和getInputStream()方法来获取了流数据,但是流读取一次后就关闭了,在filter中读取了以后,controller中就拿不到数据了。为此,我们自定义一个HttpServletRequestWrapper对象,将拿到的数据在放到这个对象中, 并将这个对象从chain.doFilter()方法中继续传下去,后续就能继续读取了。
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * 自定义request对象,将body处理后,放到这个对象里面,
 * 一定要重写getReader()和getInputStream()方法,将body放进去,
 * 这样后面的controller才能从这个request的getReader()和getInputStream()
 * 方法中拿到新的body对象
 */
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
    private byte[] body;
    
    public MyHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        
        final StringBuilder builder = new StringBuilder();
        // 缓存按 8192 大小
        final CharBuffer buffer = CharBuffer.allocate(2 << 12);
        BufferedReader reader = null;
        try {
            reader = request.getReader();
            while (-1 != reader.read(buffer)) {
                builder.append(buffer.flip());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        String bodyStr = builder.toString();
        try {
            bodyStr = URLDecoder.decode(bodyStr, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RuntimeException("body解码出错!");
        }
        body = bodyStr.getBytes(Charset.defaultCharset());
    }

    

    public byte[] getBody() {
        return body;
    }



    @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 boolean isFinished() {
                return bais.available() == 0;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
}

总结

  1. 了解过滤器和拦截器的区别
  2. 过滤器推荐使用第2种方法,使用spring管理,可以设置order
  3. 过滤器中如果读取了body数据,需要回写回去,让后续操作能读取到body

有关SpringBoot添加过滤器Filter的更多相关文章

  1. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  2. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  3. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  4. ruby-on-rails - before_filter 运行多个方法 - 2

    是否有可能:before_filter:authenticate_user!||:authenticate_admin! 最佳答案 before_filter:do_authenticationdefdo_authenticationauthenticate_user!||authenticate_admin!end 关于ruby-on-rails-before_filter运行多个方法,我们在StackOverflow上找到一个类似的问题: https://

  5. ruby - 可以通过多少种方法将方法添加到 ruby​​ 对象? - 2

    当谈到运行时自省(introspection)和动态代码生成时,我认为ruby​​没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby​​的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资

  6. ruby - 如何在 Ruby 中向现有方法定义添加语句 - 2

    我注意到类定义,如果我打开classMyClass,并在不覆盖的情况下添加一些东西我仍然得到了之前定义的原始方法。添加的新语句扩充了现有语句。但是对于方法定义,我仍然想要与类定义相同的行为,但是当我打开defmy_method时似乎,def中的现有语句和end被覆盖了,我需要重写一遍。那么有什么方法可以使方法定义的行为与定义相同,类似于super,但不一定是子类? 最佳答案 我想您正在寻找alias_method:classAalias_method:old_func,:funcdeffuncold_func#similartoca

  7. ruby-on-rails - 添加回形针新样式不影响旧上传的图像 - 2

    我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司

  8. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  9. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  10. ruby-on-rails - 在 Controller 中干净地处理多个过滤器(参数) - 2

    我有一个名为Post的类,我需要能够适应以下场景:如果用户选择了一个类别,则只显示该类别的帖子如果用户选择了一种类型,则只显示该类型的帖子如果用户选择了一个类别和类型,则只显示该类别中该类型的帖子如果用户没有选择任何内容,则显示所有帖子我想知道我的Controller是否不可避免地会因大量条件语句而显得粗糙...这是我解决此问题的错误方法-有谁知道我如何才能做到这一点?classPostsController 最佳答案 您最好遵循“胖模型,瘦Controller”的惯例,这意味着您应该将这种逻辑放在模型本身中。Post类应该能够报告

随机推荐