Servlet笔记(8):过滤器Filter详解

版权声明:本博客所有文章均采用 CC BY 3.0 CN 许可协议。转载请注明出处! https://blog.csdn.net/u012228718/article/details/86688909

过滤器详解

概述

Filter,过滤器,用于在servlet之外对request 和response 进行修改。Filter 有一个 FilterChain 的概念,一个FilterChain 包括多个 Filter。客户端请求 request在抵达servlet 之前会经过 FilterChain 里面所有的 Filter,服务器响应 response 从servlet 抵达客户端浏览器之前也会经过 FilterChain 里面所有的 Filter 。过程如图所示:

Filter 处理过程

Filter 的实现

实现自定义的 Filter 需要满足一下条件:

  1. 实现 javax.servlet.Filter 接口,实现其 init、doFilter、destroy 三个方法
  2. 实现在web.xml中的配置

javax.servlet.Filter 接口

  1. Filter 接口有三个方法:这三个方法反应了 Filter 的生命周期
  • init:只会在 web 程序加载的时候调用,即启动如tomcat等服务器时调用。一般负责加载配置的参数
  • destroy:web程序卸载的时候调用。一般负责关闭某些容器等
  • doFilter:每次客户端请求都会调用一次。Filter 的所有工作基本都集中在该方法中进行
  1. 自定义 Filter 实现
public class CustomFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String name = filterConfig.getInitParameter("name");
        System.out.println("获取过滤器的初始化参数: " + name);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        //上下文路径
        String contextPath = request.getContextPath();
        //得到访问的servlet或者jsp的路径
        String servletPath = request.getServletPath();

        System.out.println("上下文路径:" + contextPath);
        System.out.println("访问的servlet或者jsp的路径 : " + servletPath);

        chain.doFilter(req, resp);
    }

    @Override
    public void destroy() {

    }
}

Filter 的配置

  1. 配置 Filter:每个过滤器需要配置在web.xml中才能生效,一个 Filter 需要配置filterfilter-mapping标签

  2. Filter标签:配置 Filter 名称,实现类以及初始化参数。可以同时配置多个初始化参数

  3. filter-mapping标签:配置什么规则下使用这个Filter

  • url-pattern :配置url的规则,可以配置多个,也可以使用通配符 *。例如 /jsp/* 适用于本ContextPath下以“/jsp/ ”开头的所有servlet路径, *.do 适用于所有以“ .do”结尾的servlet路径

  • dispatcher :配置到达servlet的方式,可以同时配置多个。有四种取值:默认为REQUEST。它们的区别是

    • REQUEST :表示仅当直接请求servlet时才生效
    • FORWARD :表示仅当某servlet通过forward转发到该servlet时才生效
    • INCLUDE :Jsp中可以通过<jsp:include/>请求某 servlet, 只有这种情况才有效
    • ERROR :Jsp中可以通过<%@page errorPage="error.jsp" %>指定错误处理页面,仅在这种情况下才生效
  • url-patterndispatcher 是且的关系,只有满足两个条件配置,该Filter 才能生效

  1. 总结:一个Web程序可以配置多个Filter,访问有先后顺序,filter-mapping 配置在前面的Filter 执行要早于配置在后面的Filter
<!--Filter-->
<filter>
    <filter-name>CustomFilter</filter-name>
    <filter-class>com.learning.servlet2x.filter.CustomFilter</filter-class>
    <init-param>
        <param-name>name</param-name>
        <param-value>Sam</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CustomFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

常用过滤器

字符编码的 Filter

  1. 代码如下
public class CharacterEncodingFilter implements Filter {

    private String characterEncoding;
    /**
     * 是否启用
     */
    private boolean enabled;

    @Override
    public void init(FilterConfig config) throws ServletException {

        // 获取配置好的参数,
        // 配置好的字符编码
        characterEncoding = config.getInitParameter("characterEncoding");
        //是否启用
        enabled = "true".equalsIgnoreCase(config.getInitParameter("enabled"));
    }

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

        //设置字符编码
        if (enabled && characterEncoding != null) {
            request.setCharacterEncoding(characterEncoding);
            response.setCharacterEncoding(characterEncoding);
        }
        //调用下一个过滤器
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        //注销的时候,设为空
        characterEncoding = null;
    }
}

  1. web.xml 配置
    <!-- 编码过滤器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>com.learning.servlet2x.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>characterEncoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>enabled</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

防盗链 Filter

  1. 防盗链需要使用到请求头 Referer ,该Filter的配置仅对 /referer/ 下面的所有资源有效。
  2. 代码如下
public class RefererFilter implements Filter {

    @Override
    public void init(FilterConfig config) {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        // 必须的
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 禁止缓存
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);

        // 链接来源地址,通过获取请求头 referer 得到
        String referer = request.getHeader("referer");
        System.out.println("获取的来源--->: " + referer);

        // 本站点访问,则有效
        if (referer == null || !referer.contains(request.getServerName())) {
            //如果 链接地址来自其他网站,则返回错误信息
            request.getRequestDispatcher("/error.gif").forward(request, response);
        } else {
            // 正常访问
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
    }
}

  1. 配置如下
<!--防盗链过滤器  -->
<filter>
    <filter-name>RefererFilter</filter-name>
    <filter-class>com.learning.servlet2x.filter.RefererFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>RefererFilter</filter-name>
    <url-pattern>/referer/*</url-pattern>
</filter-mapping>

权限校验 Filter

  1. 简单实现如下
public class PrivilegeFilter implements Filter {


    private Properties properties = new Properties();

    @Override
    public void init(FilterConfig config) throws ServletException {

        // 从 初始化参数 中获取权 限配置文件 的位置
        String file = config.getInitParameter("file");
        String realPath = config.getServletContext().getRealPath(file);
        try {
            properties.load(new FileInputStream(realPath));
        } catch (Exception e) {
            config.getServletContext().log("读取权限控制文件失败。", e);
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;

        // 获取访问的路径,例如:admin.jsp
        String requestURI = request.getRequestURI().replace(request.getContextPath() + "/", "");

        // 获取 action 参数,例如:add
        String action = req.getParameter("action");
        action = action == null ? "" : action;

        // 拼接成 URI。例如:log.do?action=list
        String uri = requestURI + "?action=" + action;

        // 从 session 中获取用户权限角色。
        String role = (String) request.getSession(true).getAttribute("role");
        role = role == null ? "guest" : role;

        boolean authEntificated = false;
        // 开始检查该用户角色是否有权限访问 uri
        for (Object obj : properties.keySet()) {
            String key = ((String) obj);
            // 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下
            if (uri.matches(key.replace("?", "\\?").replace(".", "\\.")
                    .replace("*", ".*"))) {
                // 如果 role 匹配
                if (role.equals(properties.get(key))) {
                    authEntificated = true;
                    break;
                }
            }
        }
        if (!authEntificated) {
            System.out.println("您无权访问该页面。请以合适的身份登陆后查看。");
        }
        // 继续运行
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        properties = null;
    }
}

  1. 配置
<!-- 权限过滤器 -->
<filter>
    <filter-name>privilegeFilter</filter-name>
    <filter-class>com.learning.servlet2x.filter.PrivilegeFilter</filter-class>
    <init-param>
        <param-name>file</param-name>
        <param-value>/filter/privilege.properties</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>privilegeFilter</filter-name>
    <url-pattern>/privilege/*</url-pattern>
</filter-mapping>
  1. 权限暂时配置在文件中,方便测试

GZIP 压缩 Filter

Servlet 实现压缩返回

public class GzipServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //实现压缩
        String tDate = "准备被压缩的数据";
        System.out.println("压缩前的数据大小:  " + tDate.getBytes().length);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout);
        gout.write(tDate.getBytes());
        gout.flush();
        gout.finish();
        gout.close();//写到字节数组流中
        // 得到压缩后的数据
        byte[] gzip = bout.toByteArray();
        System.out.println("压缩后的数据大小:  " + gzip.length);

        // 通知浏览器数据采用压缩格式
        // 压缩格式
        resp.setHeader("Content-Encoding", "gzip");
        // 压缩数据的长度
        resp.setHeader("Content-Length", gzip.length + "");
        resp.getOutputStream().write(gzip);
    }
}

GZIP 压缩 Filter

  1. GZIP 压缩的核心是 JDK 自带的压缩数据的类,GZIPOutputStream
  2. 响应头:Content-Encoding 和 Content-Length
  3. GzipResponseWrapper 类为自定义的 Response 类,内部对输出的内容进行 GZIP 的压缩
  4. 代码如下
public class GzipFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 获取浏览器支持的压缩格式
        String acceptEncoding = request.getHeader("Accept-Encoding");
        System.out.println("Accept-Encoding: " + acceptEncoding);

        if (acceptEncoding != null && acceptEncoding.toLowerCase().contains("gzip")) {

            // 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据
            GzipResponseWrapper gzipResponse = new GzipResponseWrapper(response);
            chain.doFilter(request, gzipResponse);

            // 输出压缩数据
            gzipResponse.getOutputStream();
            gzipResponse.finishResponse();

        } else {
            // 否则, 不压缩
            chain.doFilter(request, response);
        }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }


    @Override
    public void destroy() {
    }
}

public class GzipResponseWrapper extends HttpServletResponseWrapper {

    /**
     * 默认的 response
     */
    private HttpServletResponse response;

    /**
     * 自定义的 outputStream, 执行close()的时候对数据压缩,并输出
     */
    private GzipOutputStream gzipOutputStream;

    /**
     * 自定义 printWriter,将内容输出到 GZipOutputStream 中
     */
    private PrintWriter writer;

    public GzipResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (gzipOutputStream == null) {
            gzipOutputStream = new GzipOutputStream(response);
        }
        return gzipOutputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (writer == null) {
            writer = new PrintWriter(new OutputStreamWriter(new GzipOutputStream(response), "UTF-8"));
        }
        return writer;
    }

    /**
     * 压缩后数据长度会发生变化 因此将该方法内容置空
     *
     * @param contentLength
     */
    @Override
    public void setContentLength(int contentLength) {
    }

    @Override
    public void flushBuffer() throws IOException {
        gzipOutputStream.flush();
    }

    public void finishResponse() throws IOException {
        if (gzipOutputStream != null) {
            gzipOutputStream.close();
        }
        if (writer != null) {
            writer.close();
        }
    }
}

public class GzipOutputStream extends ServletOutputStream {

    private HttpServletResponse response;

    /**
     * JDK 自带的压缩数据的类
     */
    private GZIPOutputStream gzipOutputStream;

    /**
     * 将压缩后的数据存放到 ByteArrayOutputStream 对象中
     */
    private ByteArrayOutputStream byteArrayOutputStream;

    public GzipOutputStream(HttpServletResponse response) throws IOException {
        this.response = response;
        byteArrayOutputStream = new ByteArrayOutputStream();
        gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
    }

    @Override
    public void write(int b) throws IOException {
        gzipOutputStream.write(b);
    }

    @Override
    public void close() throws IOException {

        // 压缩完毕 一定要调用该方法
        gzipOutputStream.finish();

        // 将压缩后的数据输出到客户端
        byte[] content = byteArrayOutputStream.toByteArray();

        // 设定压缩方式为 GZIP, 客户端浏览器会自动将数据解压
        response.addHeader("Content-Encoding", "gzip");
        response.addHeader("Content-Length", Integer.toString(content.length));

        // 输出
        ServletOutputStream out = response.getOutputStream();
        out.write(content);
        out.close();
    }

    @Override
    public void flush() throws IOException {
        gzipOutputStream.flush();
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        gzipOutputStream.write(b, off, len);
    }

    @Override
    public void write(byte[] b) throws IOException {
        gzipOutputStream.write(b);
    }
}

  1. 配置
    <!-- 压缩过滤器 -->
    <filter>
        <filter-name>GzipFilter</filter-name>
        <filter-class>com.learning.servlet2x.filter.gzip.GzipFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>GzipFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

参考

  1. 源码地址
  2. Servlet学习笔记(八):过滤器Filter详解

Fork me on Gitee

猜你喜欢

转载自blog.csdn.net/u012228718/article/details/86688909
今日推荐