zoukankan      html  css  js  c++  java
  • Servlet过滤器简单探索

    过滤器的工作时机介于浏览器和Servlet请求处理之间,可以拦截浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应。

    其工作模式大概是这样的:

    一、Filter的原理

    在Servlet API中,过滤器接口Filter会依赖于FilterChain和FilterConfig两个接口,都位于package javax.servlet;包中。

    在Tomcat容器中,ApplicationFilterChain和ApplicationFilterConfig两个类分别实现了FilterChain和FilterConfig两个接口。

    Filter接口和FilterChain接口都有一个叫doFilter()的方法,Filter的doFilter()在web应用中编写具体的过滤器时,由继承Filter的实现类来重写;FilterChain接口的doFilter()方法由Tomcat容器实现了。

    相关接口和类的结构关系如下图:

    以下简单探索Filter的原理。

    ApplicationFilterChain类在Tomcat源码中位于包org.apache.catalina.core中。

    用ApplicationFilterConfig类型的数组filters保存了web应用中设置的若干个ApplicationFilterConfig实例,由ApplicationFilterConfig实例可以获得web中定义的Filter实例。因此在这种意义上,ApplicationFilterConfig实例也就的映射成Filter实例。

    pos指针记录filters数组中当前的ApplicationFilterConfig实例索引。

    n指针存储所有的ApplicationFilterConfig实例数量。

    在ApplicationFilterChain中,主要实现了FilterChain的doFilter()方法。

       //package org.apache.catalina.core;
        //继承javax.servlet.FilterChain(request,response)方法
        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
            if( Globals.IS_SECURITY_ENABLED ) {//判断是否设置安全管理器
                final ServletRequest req = request;
                final ServletResponse res = response;
                try {
                    java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedExceptionAction<Void>() {
                            @Override
                            public Void run()
                                throws ServletException, IOException {
                                internalDoFilter(req,res);//调用内部私有的internalDoFilter()方法
                                return null;
                            }
                        }
                    );
                } catch( PrivilegedActionException pe) {
                    Exception e = pe.getException();
                    if (e instanceof ServletException)
                        throw (ServletException) e;
                    else if (e instanceof IOException)
                        throw (IOException) e;
                    else if (e instanceof RuntimeException)
                        throw (RuntimeException) e;
                    else
                        throw new ServletException(e.getMessage(), e);
                }
            } else {
                internalDoFilter(request,response);//调用内部私有的internalDoFilter()方法
            }
        }

     内部私有的internalDoFilter()方法如下:

        private void internalDoFilter(ServletRequest request,ServletResponse response)
            throws IOException, ServletException {
            if (pos < n) {
                ApplicationFilterConfig filterConfig = filters[pos++];
                try {
                    Filter filter = filterConfig.getFilter();
                    //其他处理省略
                    if( Globals.IS_SECURITY_ENABLED ) {
                        //其他处理省略
                        //通过invoke()调用filter对象的doFilter()方法
                        SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                    } else {
                        //直接调用filter对象的doFilter()方法
                        filter.doFilter(request, response, this);
                    }
                } catch (IOException | ServletException | RuntimeException e) {
                    //异常处理省略
                }
                return;
            }
            try {
                //其他代码省略
                if ((request instanceof HttpServletRequest) &&
                        (response instanceof HttpServletResponse) &&
                        Globals.IS_SECURITY_ENABLED ) {
                    //其他代码省略
                    //通过invoke()调用servlet对象的servicer()方法
                    SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);
                } else {
                    //直接调用servlet对象的service()方法
                    servlet.service(request, response);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                //异常处理省略
            }
        }

    逻辑已经很清楚,当web应用设置了多了过滤器时(比如同时设置字符过滤器、请求参数编码过滤和静态资源过滤器三个过滤器)。会依次调用这三个Filter实例的doFilter方法,就相当于是一个任务链,完成之后,在调用servlet的service()方法正式处理请求逻辑。

    总结起来,过滤器的工作流程大致如下:

    请求到来,陆续调用各个Filter实例的doFilter()方法,然后在调用servlet的service()处理请求,之后在执行位于doFilter()方法后的代码处理逻辑,最后才返回给浏览器。

                //service()前的处理逻辑
                chain.doFilter(req, res);
                //service()后的处理逻辑

    二、简单应用

    常见的设置过滤器的场景为:

    1、特殊字符过滤器(如html页面元素:<,>等);

    2、请求参数编码过滤器(GET请求参数和POST请求参数编码设置);

    3、登录检测过滤器(判定用户是否登录)等等。

    如下针对2和3的场景实现一个过滤器:

    package com.tms.web;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.log4j.Logger;
    
    public class TMSFilter implements Filter {
        private static Logger logger = Logger.getLogger(TMSFilter.class);  
        public void init(FilterConfig arg0) throws ServletException {
            // TODO Auto-generated method stub
        }
        public void destroy() {
            // TODO Auto-generated method stub
        }
        public void doFilter(ServletRequest resquest, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
            // TODO Auto-generated method stub
            HttpServletRequest req = (HttpServletRequest)resquest;
            HttpServletResponse res = (HttpServletResponse)response;
            logger.info(req.getRequestURL());
            //用户登录检测
            if (req.getSession().getAttribute("hasLoginedUsername") != null) {
                //请求参数编码设置
                HttpServletRequest wreq = new TMSRequestWrapper(req,"UTF-8");
                chain.doFilter(wreq, res);
                //service()后的处理逻辑
            }else {
                res.sendRedirect("/tms/login");
            }
        }
    }

     通过继承HttpServletRequestWrapper类,覆盖其getParameter(String name)方法,将请求对象包装处理,使其针对GET和POST请求都能够正确的处理编码问题,这样的好处是集中在一个地方处理编码问题和登录检测,避免了在每个Servlet的doXXX()方法中去写这部分重复的代码,利于重构和维护,体现了DRY原则。

    package com.tms.web;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    
    public class TMSRequestWrapper extends HttpServletRequestWrapper {
        private String encoding = ""; 
        public TMSRequestWrapper(HttpServletRequest req,String encoding){
            super(req);
            this.encoding = encoding;
        }
        public String getParameter(String name){
            HttpServletRequest req = (HttpServletRequest)getRequest(); 
            String value = "";
    
            try {
                //POST请求编码设置,直接通过setCharacterEncoding()来设置编码方式
                req.setCharacterEncoding(encoding);
                value = req.getParameter(name);
                 //如果为GET请求,通过获取字节数据再转化为String对象来设置编码方式
                if("GET".equals(req.getMethod())){
                    if (value != null) {
                        byte[] b = value.getBytes("ISO-8859-1");//ISO-8859-1
                        value = new String(b, encoding);
                    }
                }
            } catch (Exception e) {
                // TODO: handle exception
                new RuntimeException(e);
            }
            return value;
        }
    }

     参考资料:

    1、《深入分析java web技术内幕》

    2、《Servlet和JSP笔记》

    3、apache-tomcat-9.0.1-src

  • 相关阅读:
    java基础学习
    形参和返回值
    内部类
    常用API(String、StringBuilder)【1】
    什么是servlet
    servlet2.5和3.0的区别,servlet4.0注解
    什么是事务
    jdk环境配置(转载)
    idea中运行Tomcat后控制台出现乱码(统一设置成UTF-8)
    java数组
  • 原文地址:https://www.cnblogs.com/qcblog/p/8047639.html
Copyright © 2011-2022 走看看