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标签的顺序.
    • 如果使用的是注解,执行的顺序是类名的字母排列顺序
  • 配置过滤器进行过滤时,除了使用<url-pattern>之外,也可以使用<servlet-name>标签,但是对应的servlet也必须在web.xml中配置.

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

    • 四种调用方式
      • 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:

<form action="${pageContext.request.contextPath}/encodingServlet" method="GET">
用户名<input type="text" name="username" /><br/>
密码<input type="password" name="password" /><br/>
<input type="submit" value="提交" />
</form>

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。
            最后放行,让其既访问了本页面又完成了自动登录的操作。

自动登录思路图:

img01.png

1.搭建环境

1.1 新建数据库用户表User
img02.png
1.2 导入jar包
1.3 准备javabean类User和工具类JDBCUtils
1.4 创建包结构
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>(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);
    }
}

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

img04.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Filter可以视作是servlet的加强版,主要用作对用户的请求进行预处理,或者对返回给客户端的结果进行再次加工...
    Single_YAM阅读 2,675评论 1 7
  • Filter简介 Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter...
    java部落阅读 285评论 0 1
  • 创建 编写一个过滤器的类,实现Filter接口 实现接口尚未实现的方法,着重实现doFilter方法 在web.x...
    蕊er阅读 431评论 0 0
  • Filter介绍 Filter是定义在javax.servlet包下面的一个接口,开发web应用时,如果编写的Ja...
    耍流氓的汉子阅读 900评论 0 0
  • Filter基本使用 过滤器Filter作为Web的组件,能对所有Web资源(Jsp,Servlet,,静态图片或...
    云承寒阅读 328评论 0 1