Filter过滤器

Filter的使用和经典案例

一、什么是Filter

  • Filter也称之为过滤器。

  • 通过Filter可以拦截所有对Web服务器管理的任意Web资源的请求,从而实现特殊的控制功能。例如实现url权限的控制,过滤敏感信息以及压缩响应格式等一些高级的功能。

  • FilterChain

    • 在一个Web工程中,对同一个Web资源访问时,可以有多个Filter过滤器对其进行拦截,多个过滤器在tomcat服务器启动时,组成过滤器链FilterChain。
  • 每一个Filter必须执行chain.doFilter(request,response)方法,才能将控制权交到下一个过滤器或者目标资源。

二、Filter的生命周期

生命周期方法

* init(FilterConfig):
    在服务器启动时,Filter对象会被创建,init方法被执行,Filter对象只有一个,该方法只执行一次。
    这里与Servlet不同,servlet在第一次访问时,执行servlet对象的初始化。

    
* doFilter(request,response):
    在每一次请求时,只要是拦截的目标资源,就会执行
    
* destroy():
    在服务器正常关闭、重启时执行。

生命周期

  • 什么时候创建
    • 服务器启动的时候创建
  • 什么时候销毁
    • 服务器正常关闭、重启时销毁

三、映射配置注意事项

  • 如何决定过滤器执行顺序

    • 过滤器先后执行的顺序取决于filter-mapping标签的顺序.
    • 如果使用的是注解,执行的顺序是类名的字母排列顺序
  • 配置过滤器进行过滤时,除了使用之外,也可以使用标签,但是对应的servlet也必须在web.xml中配置.

  • 指定过滤器拦截目标资源的调用方式

    • 四种调用方式
      • REQUEST : 客户端请求过滤(实际开发中一般使用该默认方式)
      • FORWARD : 转发过滤
      • INCLUDE : 包含过滤
      • ERROR : 错误页面过滤 web.xml 配置error-page
      • 如果没有指定拦截模式,默认拦截REQUEST

四、案例

案例一:解决全局乱码的问题

需求:

我们在用servlet执行服务器与客户端的请求响应时,为了避免产生中文乱码现象,会在servlet中解决乱码问题.
但是一旦我们的工程量比较大,servlet的数量太多的时候,解决编码的代码就会变得十分臃肿。
因为Filter过滤器可以拦截所有访问该站点的请求,因此在Filter中提前将乱码问题处理后,servlet中再获取请求提交的参数
时,就不用再考虑编码问题了。

案例分析:

1. 写一个表单提交页面,提交表单时访问servlet
2. 写一个Filter过滤器,拦截所有访问本站点的请求,提前处理编码问题
    1. 在filter中获取表单提交的方式
    2. 如果是POST方式提交的表单,直接用request.setCharacterEncoding("UTF-8")即可解决。
    3. 如果是GET方式提交的表单,就需要先获取到提交的包含中文的参数
    4. 但是我们在这里即使是将该参数重新编码,在放行的时候也不能把这个参数往下传递给servlet呀?
    因此在这里就需要对HttpServletRequest的getParameter(String name)进行包装,对这个getParameter()方法进行增强。

装饰者模式:

  1. 写一个类(包装类)与被包装类实现同一个接口
  2. 在包装类中创建一个特殊的构造方法,参数为被包装类的对象,从而对被包装类进行装饰。
  3. 为了在包装类中能够使用被包装类进行操作,将传递进来的被包装类对象传递给本类的成员变量。
  4. 对需要增强或修改的方法重写。

    在这里,Sun公司考虑到我们可能要对HttpServletRequest与HttpServletResponse的方法进行自定义,
    因此sun公司已经定义了两个实现了上述两个接口的实现类:HttpServletRequestWrapper与HttpServletResponseWrapper.
    
    因此我们直接继承HttpServletRequestWrapper,重写getParameter方法即可。

代码实现:

JSP:

用户名
密码
Servlet:(测试) //获取表单提交的用户名 String username = request.getParameter("username"); System.out.println(username); //将其写回客户端 response.getWriter().write(username); Filter代码: public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { // 处理全应用的编码问题 // 1.强制转换 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; response.setContentType("text/html;charset=UTF-8"); // 2.处理业务逻辑 // 因为在Servlet中都是通过request.getParameter()获取参数,因此用装饰者模式对该方法进行增强 MyRequest myRequest = new MyRequest(request);//对request进行装饰 //设置服务器写回数据时的编码格式 // 3.放行,将装时候的myrequest传递给servlet,这样servlet中使用getParameter方法时使用的就是增强后的方法 chain.doFilter(myRequest, response); } 装饰者模式包装类MyRequest: class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; public MyRequest(HttpServletRequest request) { super(request); // 将被包装对象传递给本类的成员变量,以便对其进行操作 this.request = request; } // 对getParameter方法增强 @Override public String getParameter(String name) { try { // 2.1 获取数据提交的方式 String method = request.getMethod(); System.out.println(method); // 2.2 根据提交的方式不同,处理编码乱码的问题 if ("POST".equalsIgnoreCase(method)) { // 如果是POST请求 request.setCharacterEncoding("UTF-8");// POST请求告知服务器使用UTF-8编码 // 如果是GET请求,就需要对request的getParameter()方法进行增强了 // 使用装饰者模式,写一个类继承sun公司提供的已经实现了HttpServletRequest的实现类 } else if ("GET".equalsIgnoreCase(method)) { //获取GET方式提交的参数 String parameter = request.getParameter(name); //对获取到的参数重新解码和编码 parameter = new String(parameter.getBytes("ISO-8859-1"),"UTF-8"); return parameter; } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return super.getParameter(name); }

案例二、登录页面自动登录功能的实现

分析:

自动登陆的功能
    

    1.完成简易的登陆功能。

        1.用户点击登陆后,将表单中的账密发送至服务器进行查询
        2.服务器查询完毕后返回一个User对象,servlet根据user的状态做出不同的转发定向
        3.  登录失败:服务器发送错误信息,客户端解析并显示在相应的位置
            登陆成功:跳转到购物网页的首页



    
    准备工作(搭建开发环境):

        1:准备数据库用户表,至少需要有id,name,username,password的字段。准备用户登录的页面
        2:导入jar包:
            数据库连接驱动,c3p0的jar包与配置文件,dbutils的jar包,因为在jsp需要对数据进行解析,还需要jstl.jar和与之配套的standard.jar
        3. 准备javabean类User和工具类JDBCUtils
        4. 创建包结构


    2.自动登陆功能,在第一步的登陆功能上进行完善。


    Servlet:
        1.servlet查询完成后,对User进行判断,如果User不为空,即用户登录成功。
            这时,需要获取到name为autoLogin的checkbox的复选框状态。
        2.如果复选框状态是未选中,则不进行任何任何额外的操作。
            如果复选框的状态是被选中的,那么就需要将用户的账密存储起来,下次用户再访问时,服务器就自动进行自动登陆.
        3. Cookie可以完成上述的要求,如果复选框被选中,将用户的账号密码存入Cookie中写回客户端存储。

    Filter : (需求)无论用户下一次访问应用的哪个页面,都会检查是否有账密这个Cookie存在。

        1. Filter可以拦截所有访问当前站点的请求,对其进行筛选,因此在Filiter中进行自动登录的操作。保证了用户无论访问哪个页面都能进行有效的检测.
        2. Filter中获取用户请求携带过来的所有Cookies,遍历获取代表用户账号和密码的Cookie,检查其是否为空,如果为空,直接放行,让其访问网页,不提供登陆。
        3.如果不为空,证明账密存在,调用业务层service处理登陆操作,操作返回一个User对象,这里也要将其存入Session。
            最后放行,让其既访问了本页面又完成了自动登录的操作。

自动登录思路图:

Filter过滤器_第1张图片
img01.png

1.搭建环境

1.1 新建数据库用户表User
Filter过滤器_第2张图片
img02.png
1.2 导入jar包
1.3 准备javabean类User和工具类JDBCUtils
1.4 创建包结构
Filter过滤器_第3张图片
img03.png

2.代码实现

2.1 登陆的基本功能实现

用户提交登陆数据到服务器查询,服务器查询完毕后根据返回的User对象是否存在,进行不同的操作。
如果存在,跳转到首页。如果不存在,提示用户错误信息。

由于有代码的重复,这里就不列举具体代码了,在后面一起写出来。

2.2 自动登录的功能实现
  1. 用户登录成功后,判断用户是否选择了自动登录复选框,如果选择了将其账密存入Cookie写回客户端。
    同时,将该User对象存入Session中,如果用户不退出当前浏览器而去访问其他页面,可以直接从session对象中获取代表该用户的该User对象

  2. 要实现:无论用户访问本站点的任何页面,都会事先监测其是否勾选了自动登录,(勾选了自动登录的用户请求会存在代表账密的Cookie)。

  3. 要完成上面的要求,Filter刚好可以对访问本应用的所有资源的请求进行拦截,因此使用Filter过滤器进行操作。


    1. 使用用户的请求获取Session,从而获取存在Session域对象的User对象.这一步主要是因为一个用户在没有
    退出当前客户端时,进行的其他页面访问时,可以直接从Session中获取到代表该用户的user对象而不需要重新登陆。

    2. 如果用户存在,直接放行,可以从Session中获取user对象。
    如果user对象不存在,就需要获取请求所携带的所有Cookies,获取它们当中代表用户账号密码的Cookie。

    3. 从代表用户账密的Cookie中获取用户的账号密码,判断账号密码的状态。
    如果账号密码为空,那么放行,其访问页面资源,但是是未登录的状态。
    如果账号密码不为空,那么就调用业务层去数据库中查询,根据该账密返回一个User对象。

    4. 判断返回的user对象的状态。
        如果为空,说明登录失败,放行。
        如果不为空,说明登陆成功,将该user对象存入session对象,放行。

web层

Servlet代码 :

// 1.获取登陆表单提交的账号密码
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username);
System.out.println(password);
try {
    // 2.调用service/dao层查询,将获取的账密作为参数传递
    LoginService service = new LoginService();
    User user = service.login(username, password);

    // 3.dao层查询返回一个User对象,根据User对象是否为null做出不同的处理
    if (user != null) {
        // 获得自动登陆的复选框状态,检查用户是否勾选了该复选框
        String autologin = request.getParameter("autologin");
        // 判断,如果勾选了该复选框,就将账号密码添加到Cookie中写回客户端
        if ("autologin".equals(autologin)) { // 用户勾选了该复选框
            Cookie cookie = new Cookie("username", username);// 存储账号的Cookie
            Cookie cookie2 = new Cookie("password", password);// 存储密码的Cookie
            cookie.setMaxAge(60*60*24*7);//设置cookie的最大生存时间为7天
            cookie2.setMaxAge(60*60*24*7);
            cookie.setPath("/");//设置该cookie的有效路径,斜线"/"代表当前应用都有效
            cookie2.setPath("/");
            //将Cookie写回客户端
            response.addCookie(cookie);
            response.addCookie(cookie2);
        }

        // 登陆成功,将该user对象存入session对象,以便在当前web站点所有页面都能获取到该user对象
        HttpSession session = request.getSession();
        session.setAttribute("user", user);
        // 重定向去到首页
        response.sendRedirect(request.getContextPath() + "/index.jsp");
    } else {
        // 登陆失败给用户提示错误信息
        request.setAttribute("msg", "账号或密码错误");
        request.getRequestDispatcher("login.jsp").forward(request, response);
    }

} catch (SQLException e) {
    e.printStackTrace();
}

service层

LoginDAO dao = new LoginDAO();
return dao.login(username,password);

dao层

QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from user where username=? and password =?";
return runner.query(sql, new BeanHandler(User.class), username,password);

Filter过滤器

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    // Filter的url-pattern设为/*拦截访问当前应用的所有请求

    // 1.强制转换request和response
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    // 2.实现业务逻辑
    // 2.1 首先获取存在session中的user对象.
    // 这一步是因为:如果用户在登陆后,继续点击其他页面而没有退出当前浏览器的时候,就可以直接从session中取出登陆成功时存在里面的user对象,不需要再次执行登陆的操作。
    HttpSession session = request.getSession();// 获取该客户端当前web应用存储的session域对象
    User user = (User) session.getAttribute("user");// 获取该客户端用户存储在当前web应用的user对象

    try {
        // 2.2接下来判断该user对象是否存在,如果存在,,放行,session中本就存在该user对象,无需在存储直接放行即可.如果不存在,说明session可能过期或其他原因找不到了.
        // 如果user对象不存在,就需要获取请求中带过来的cookies,如果用户勾选了自动登录并且cookie没有过期,那么其请求中会携带账密的cookie过来
        if (user == null) { // 用户user不存在 用户存在的话不需要进行操作,不会进入if语句,在最后执行放行代码即可
            // 获取请求携带的所有cookies
            Cookie[] cookies = request.getCookies();
            String username = null;// 用户的账号
            String password = null;// 用户的密码
            // 遍历所有cookie,获取代表用户账号密码的Cookie
            if (cookies != null) {
                for (Cookie cookie : cookies) { // 如果用户勾选了自动登录并且Cookie未失效,就会存在username和password的Cookie
                    String name = cookie.getName();// 获取每一个Cookie的key
                    String value = cookie.getValue();// 获取每一个Cookie的value
                    if ("username".equals(name)) {
                        username = value;
                    }
                    if ("password".equals(name)) {
                        password = value;
                    }
                }

                // 2.3获取到账密的Cookie进行判断,如果为空,就直接放行让其访问页面,如果不为空,就调用业务层执行登陆操作,返回一个User对象。
                if (username != null && password != null) { // 从Cookie中获得的用户名和账号都不为空,调用业务层执行登陆操作
                    LoginService service = new LoginService();
                    User user2 = service.login(username, password);// 登陆后返回的user对象

                    // 2.4判断User对象是否存在,如果不存在说明账密错误自动登陆失败,放行。如果存在,将该对象存入session中,自动登录成功,并放行。
                    if (user2 != null) { // 用户登陆成功,将代表该用户的对象存入session域
                        session.setAttribute("user", user2);// 这里的存入与Servlet中存储的name要保持一致
                    }
                }
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        // 3.放行
        chain.doFilter(request, response);
    }
}

测试结果:勾选自动登录后,无论访问本站点的哪个页面,重复登陆无需再输入用户密码,直接跳转到首页。

Filter过滤器_第4张图片
img04.png

你可能感兴趣的:(Filter过滤器)