过滤器的实现及其原理责任链设计模式

Filter过滤器

过滤器的应用

DeptServlet,EmpServlet,OrderServlet三个业务类的业务方法执行之前都需要编写判断用户是否登录和解决的中文乱码的代码,代码没有得到重复利用

Filter是过滤器可以用来编写请求的过滤规则和多个Servlet都会执行的公共代码,Filter中的业务代码既可以在目标Servlet程序执行之前也可以在其之后执行

  • Filter的生命周期和Servlet对象生命周期一致,先实例化(一次即单例)–>执行初始化方法(一次)–>处理请求方法(每次请求都会调用)–>销毁方法(一次)
  • Filter默认情况下在服务器启动阶段就会实例化 , Servlet默认在用户发起对应请求的时候才会实例化
  • Filter的优先级天生的就比Servlet优先级高,若Filter和Servlet都可以处理/a.do请求,那么一定是先执行Filter然后再执行Servlet

在这里插入图片描述

/**
 * 处理部门相关的业务类
 */
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 低版本的Tomcat还需要解决post请求乱码问题
        request.setCharacterEncoding("UTF-8");
        // 响应中文乱码问题
        response.setContentType("text/html;charset=UTF-8");
        // 获取当前session,这个session是不需要新建的,获取不到就返回null
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("username") != null){
            String servletPath = request.getServletPath();
            if("/dept/list".equals(servletPath)){
                doList(request, response);
            }else if("/dept/detail".equals(servletPath)){
                doDetail(request, response);
            }else if("/dept/delete".equals(servletPath)){
                doDel(request, response);
            }else if("/dept/save".equals(servletPath)){
                doSave(request, response);
            }else if("/dept/modify".equals(servletPath)){
                doModify(request, response);
            }
        }else{
            // 获取不到session或session中没有存储用户的信息就跳转到登录页面
            response.sendRedirect(request.getContextPath() + "/index.jsp"); 
        }
    }
}
/**
 * 处理员工相关的业务类
 */
public class EmpServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 解决post请求乱码问题
        request.setCharacterEncoding("UTF-8");
        // 响应中文乱码问题
        response.setContentType("text/html;charset=UTF-8");
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("username") != null){
            String servletPath = request.getServletPath();
            //...
        }else{
            response.sendRedirect(request.getContextPath() + "/index.jsp");
        }
    }
}
/**
 * 处理订单相关的业务类
 */
public class OrderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 解决post请求乱码问题
        request.setCharacterEncoding("UTF-8");
        // 解决响应中文乱码问题
        response.setContentType("text/html;charset=UTF-8");
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("username") != null){
            String servletPath = request.getServletPath();
            //...
        }else{
            response.sendRedirect(request.getContextPath() + "/index.jsp");
        }
    }
}

责任链设计模式

传统模式方法调用时在编译阶段已经完全确定了方法的调用顺序,如果想改变方法的调用顺序必须修改源代码,显然违背了OCP原则

public class Test {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }

    private static void m1() {
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }

    private static void m2() {
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }

    private static void m3() {
        System.out.println("目标正在执行中。。。。");
    }
}

责任链设计模式: 由于方法的调用顺序在程序编译阶段无法确定,需要根据实际情况在程序运行阶段动态的组合方法的调用顺序,实现代码的灵活性

  • Filter使用了责任链设计模式,将Filter的调用顺序配置到了web.xml文件中,想要调整Filter的执行顺序只需要修改配置文件中filter-mapping标签的顺序
<filter>
    <filter-name>filter</filter-name>
    <filter-class>com.bjpowernode.javaweb.servlet.Filter2</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

实现过滤器

第一步:编写一个类实现jarkata.servlet.Filter接口,由于接口中的方法都有默认的方法体,我们可以根据需求实现方法

方法名 功能
init(FilterConfig filterConfig) 在Filter对象第一次被创建之后调用,并且只调用一次
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 编写过滤规则和公共代码的方法,只要用户发送一次请求就执行一次 , 发送N次请求执行N次
destroy() 在Filter对象被释放/销毁之前调用,并且只调用一次

第二步: 执行FilterChain类的doFilter方法放行,过滤器只有放行了请求,目标Servlet才有机会执行到

方法名 功能
doFilter(request, response) 执行下一个过滤器,如果下面没有过滤器了才执行最终处理对应请求路径的Servlet
public class Filter implements Filter {
    public Filter1(){
        System.out.println("无参数构造方法执行");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init方法执行。");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 在请求的时候添加过滤规则
        System.out.println("Filter1 doFilter方法开始执行");
        // 执行下一个过滤器,如果下一个不是过滤器则执行目标程序Servlet
        chain.doFilter(request, response);
        // 在响应的时候添加过滤规则
        System.out.println("Filter1 doFilter方法执行结束");
    }

    @Override
    public void destroy() {
        System.out.println("destroy方法执行");
    }
}

第三步: 编写目标Servlet处理用户的请求

@WebServlet("/a.do")
public class AServlet  extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("AServlet中的doGet方法执行了");
    }
}

@WebServlet("/b.do")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("BServlet中的doGet方法执行了");
    }
}

第四步: 配置Filter拦截的请求路径和执行顺序(Filter执行时遵循栈的数据结构),配置过滤器拦截的请求路径时可以使用通配符*

  • 在web.xml文件中配置(常用): Filter的执行顺序取决于filter-mapping标签的配置位置,越靠上优先级越高,和filter标签的位置无关
  • 在@WebFilter注解中配置(修改过滤器执行顺序不方便): Filter的执行顺序取决于Filter的类名(按字典序排序) , 比如在FilterA和FilterB中先执行FilterA
匹配方式 举例
精确匹配 /a.do、/b.do、/dept/save
模糊匹配* /*表示拦截所有路径
后缀匹配*,不能以/开始 *.do表示拦截所有以.do结尾的路径
前缀匹配*,以/开始 /dept/*表示拦截/dept/路径及其子路径
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <filter>
        <filter-name>filter</filter-name>
        <filter-class>com.bjpowernode.javaweb.servlet.Filter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
        <!--<url-pattern>a.do</url-pattern>
  		<url-pattern>b.do</url-pattern>-->
        <url-pattern>*.do</url-pattern>
    </filter-mapping>
</web-app>
//@WebFilter({"/a.do", "/b.do"})
@WebFilter({"*.do"})
public class Filter implements Filter {
    public Filter1(){
        System.out.println("无参数构造方法执行");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init方法执行。");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 在请求的时候添加过滤规则
        System.out.println("Filter1 doFilter方法开始执行");
        // 执行下一个过滤器,如果下一个不是过滤器则执行目标程序Servlet
        chain.doFilter(request, response);
        // 在响应的时候添加过滤规则
        System.out.println("Filter1 doFilter方法执行结束");
    }

    @Override
    public void destroy() {
        System.out.println("destroy方法执行");
    }
}

使用过滤器改造oa项目

第一步: 编写一个过滤器LoginCheckFilter同时指定不需要拦截的路径

  • 当用户访问index.jsp首页,用户已经登录了(session中有用户信息),用户想要登录或退出,用户访问项目根目录即欢迎页的请求不能拦截
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        //强制类型转化
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse) resp;
        // 获取请求路径
        String servletPath = request.getServletPath();
        HttpSession session = request.getSession(false);
        if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) ||
                "/user/login".equals(servletPath) || "/user/exit".equals(servletPath)
                || (session != null && session.getAttribute("user") != null)){
            // 继续往下走
            chain.doFilter(request, response);
        }else{
            response.sendRedirect(request.getContextPath() + "/index.jsp");
        }
    }
}

第二步: 在web.xml中注册过滤器,/*表示拦截所有的路径

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <!--登录检查的过滤器,过滤所有的路径-->
    <filter>
        <filter-name>loginFilter</filter-name>
        <filter-class>com.bjpowernode.oa.web.filter.LoginCheckFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>loginFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

第三步: 业务类相关的Servlet就不需要再编写代码判断用户是否登录,如果请求能到目标Servlet说明用户一定是登录过了,目标Servlet只需要转发请求即可

@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String servletPath = request.getServletPath();
        if("/dept/list".equals(servletPath)){
            doList(request, response);
        }else if("/dept/detail".equals(servletPath)){
            doDetail(request, response);
        }else if("/dept/delete".equals(servletPath)){
            doDel(request, response);
        }else if("/dept/save".equals(servletPath)){
            doSave(request, response);
        }else if("/dept/modify".equals(servletPath)){
            doModify(request, response);
        }
    }