简介

  • 举个例子:如果一个系统有多种功能,但是大部分功能都要登录才能操作,否则跳登录页面,那么每个控制器都判断下不方便,即便抽出来作为共有的校验方法也不方便。
  • 过滤器即可将共有的逻辑提取出来,并通过路径来校验,就比调用逻辑方法方便

    • ,它在HTTP请求到达Servlet(控制器)之前,可以被一个或多个Filter预处理,类似打印日志、登录检查等逻辑,完全可以放到Filter中。

      Servlet实现Filter

  • 过滤器类需要实现Filter接口,并实现doFilter方法

    • 其实**Filter**就是一个特殊的**httpservlet**对象,但是它不能对请求和响应进行处理。所以它的作用就是预处理(存疑,设置重定向难道不是对响应进行处理)
  • @WebFilter设置过滤链接,所有经过该设置参数的请求都会被进行校验
  • FilterChain是过滤器链,即过滤器可以有很多个,被过滤的请求会根据配置顺序被每一个过滤器筛选
    • doFilter(request, response)表示继续处理,即走下一个过滤。
      • 如果重写的doFilter什么都不做,那么将看到一个空白页,因为请求没有继续处理,默认响应是200+空白输出
  • springboot中因为**@WebFilter**非springboot注解,所以需要添加被扫描。
    • 在main上增加**@ServletComponentScan** ```java @WebFilter(urlPatterns = “/*”) //对所有请求都进行过滤 public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(“EncodingFilter:doFilter”);
      1. //将输出和输入都定位utf-8
      request.setCharacterEncoding(“UTF-8”); response.setCharacterEncoding(“UTF-8”); chain.doFilter(request, response); } }

@WebFilter(“/user/*”) public class AuthFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(“AuthFilter: check authentication”); HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getSession().getAttribute(“user”) == null) { // 未登录,自动跳转到登录页。不再走剩下的过滤器和servlet控制器方法 System.out.println(“AuthFilter: not signin!”); resp.sendRedirect(“/signin”); } else { // 已登录,继续处理: chain.doFilter(request, response); } } }

<a name="N2AwB"></a>
# Springboot实现Filter

- spring中实现bean和servlet实现差不多,不同的是需要配置xml将spring的bean注册到servlet中。而在springboot中凭借自动配置就更加简单
   - 也可以继续采用上面这种方法,但是需要添加`@ServletComponentScan`,**而如下方式除了简化外,还可以指定过滤器的顺序**

---

- 首先还是创建Filter的具体实现过滤器,无需再加`@WebFilter`
- 创建一个过滤器工厂,继承`FilterRegistrationBean`即可。该接口可以看成是Filter的工厂,在工厂可**以对传入的bean进行额外配置,最终所以过滤器bean都可以被**`**FilterRegistrationBean**`**统一管理**
   - **一个过滤器就创建一个对应工厂类**
   - Spring Boot会自动扫描所有的`FilterRegistrationBean`类型的Bean,然后,将它们返回的Filter自动注册到Servlet容器中,无需任何配置。
- **不设置过滤路径时过滤一切url,过滤器order越小优先级越高**
```java
@Component
public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {
    @PostConstruct
    public void init() {
        setOrder(20);  //设置优先级
        setFilter(new ApiFilter());
        List<String>   list= Arrays.asList("/xx/*");
        setUrlPatterns(list);  //设置过滤路径
    }
}
或者--------------------------------------------
    public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {
    @Override
    public Filter getFilter() {
        setOrder(30);
        setFilter(new ApiFilter());
        List<String>   list= Arrays.asList("/xx/*");
        return new AuthFilter();
    }
}
@Bean
    public FilterRegistrationBean xx() {
        FilterRegistrationBean registration = new FilterRegistrationBean(new MyFilter());    
        registration.addUrlPatterns("/test/*");         //可选
        registration.setName("MyFilter1");  //可选
        registration.setOrder(1);  //可选
        //添加不过滤路径
        registration.addInitParameter("excludedUris","/app/login,/app/test/");
        return registration;
    }
...

xml配置过滤顺序

  • FilterChain是存放很多过滤器的集合,不同的过滤顺序可能会影响程序的运行效果,如本意先过滤/x1通后再过滤x1/y ,如果x1/y在在x1前就很有可能不一样
    • 配置过滤器顺序只能通过web.xml配置
  • springboot中可以通过setOrder设置过滤器顺序

    过滤器修改请求与设置响应

  • 过滤器要修改请求/响应,最好的方法是创建一个HttpServletRequestWrapper/HttpServletResponseWrapper的子类。这2者是ServletRespon/ServletRequest间接子类,并实现了HttpServletRespon/HttpServletRequest

    • 因此...Wrapper可以使用HttpServlet...的很多方法,如设置请求头,获取请求头
    • ...Wrapper不需要过于了解,它使用起来更像是个工具类
  • 下面的响应修改有点问题,使用**HttpServletResponseWrapper**构建输出流返回的内容是空的
  • 我们可以使用缓存将固定的响应缓存起来,键为url。即在过滤器中创建个ConcurrentHashMap<Map<String, byte[]> >,对于固定内容的响应内容就存到该map中,之后直接从map取,内容即wrapper.getContent() ```java //—————修改请求————————————— public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){ RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); String a= requestWrapper.getHeader(“?”); //获取请求头 requestWrapper.addHeader(“?”,”?”); //修改或增加请求头 //对于删除请求头Wrapper增加map元素删除方法即可 chain.doFilter(requestWrapper, servletResponse); }

//——————-修改响应,这个没啥好说的,一般就构建响应后直接返回给前端(跟Servlet返回是一样的),而修改响应很多时候是修改后给后续操作继续使用—————————- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){ // 构建响应主体,这里也可以使用PrintWriter构建响应,但是注意getOutputStream与getWriter()不能同时使用,否则会报响应getOutputStream() has already been called for this response ServletOutputStream out =servletResponse.getOutputStream();
resp.setContentType(“text/html”); //设置响应类型 resp.setCharacterEncoding(“UTF-8”); out.write(“错误请求”.getBytes(“UTF-8”)); out.flush(); out.close(); }

```java
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
//存储请求头信息的map
    private Map<String,String> headers=new HashMap<>();
//存储请求体信息
    private String tempBody;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        tempBody = getBodyString(request);
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(tempBody.getBytes(Charset.forName("UTF-8")));
        return new ServletInputStream() {
            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public int readLine(byte[] b, int off, int len) throws IOException {
                return super.readLine(b, off, len);
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }
        };
    }

    public void addHeader(String name,String value){
        headers.put(name, value);
    }

    @Override
    public String getHeader(String name) {
        String value=super.getHeader(name);

        if (headers.containsKey(name)){
            value=headers.get(name);
        }

        return value;
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.tempBody;
    }

    public void setBody(String body) {
        this.tempBody = body;
    }


    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }

        return sb.toString();
    }
}
public class ResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream buffer;
    private ServletOutputStream out;

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        buffer = new ByteArrayOutputStream();
        out = new WrapperOutputStream(buffer);
    }

    @Override
    public ServletOutputStream getOutputStream()
            throws IOException
    {
        return out;
    }


    @Override
    public void flushBuffer()
            throws IOException
    {
        if (out != null)
        {
            out.flush();
        }
    }

    public byte[] getContent()
            throws IOException
    {
        flushBuffer();
        return buffer.toByteArray();
    }

    class WrapperOutputStream extends ServletOutputStream
    {
        private ByteArrayOutputStream bos;
        public WrapperOutputStream(ByteArrayOutputStream bos)
        {
            this.bos = bos;
        }
        @Override
        public void write(int b)
                throws IOException
        {
            bos.write(b);
        }
        @Override
        public boolean isReady()
        {
            // TODO Auto-generated method stub
            return false;
        }
        @Override
        public void setWriteListener(WriteListener arg0)
        {
            // TODO Auto-generated method stub
        }
    }
}