资讯专栏INFORMATION COLUMN

过滤器入门看这一篇就够了

yy13818512006 / 3171人阅读

摘要:我们很容易发现,过滤器可以比喻成一张滤网。这究竟是怎么回事啊我们可以这样理解过滤器不单单只有一个,那么我们怎么管理这些过滤器呢在中就使用了链式结构。第一种方式在文件中配置用于注册过滤器用于为过滤器指定一个名字,该元素的内容不能为空。

什么是过滤器

过滤器是Servlet的高级特性之一,也别把它想得那么高深,只不过是实现Filter接口的Java类罢了!

首先,我们来看看过滤器究竟Web容器的哪处:

从上面的图我们可以发现,当浏览器发送请求给服务器的时候,先执行过滤器,然后才访问Web的资源。服务器响应Response,从Web资源抵达浏览器之前,也会途径过滤器。。

我们很容易发现,过滤器可以比喻成一张滤网。我们想想现实中的滤网可以做什么:在泡茶的时候,过滤掉茶叶。那滤网是怎么过滤茶叶的呢?规定大小的网孔,只要网孔比茶叶小,就可以实现过滤了!

引申在Web容器中,过滤器可以做:过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证【规定只有带Session或Cookie的浏览器,才能访问web资源】等等等,过滤器的作用非常大,只要发挥想象就可以有意想不到的效果

也就是说:当需要限制用户访问某些资源时、在处理请求时提前处理某些资源、服务器响应的内容对其进行处理再返回、我们就是用过滤器来完成的!

为什么需要用到过滤器

直接举例子来说明吧:

没有过滤器解决中文乱码问题

如果我没有用到过滤器:浏览器通过http请求发送数据给Servlet,如果存在中文,就必须指定编码,否则就会乱码!

jsp页面提交中文数据给Servlet处理

Servlet没有指定编码的情况下,获取得到的是乱码

Servlet中如何解决中文乱码问题,我的其他博文中有:http://blog.csdn.net/hon_3y/article/details/54632004

也就是说:如果我每次接受客户端带过来的中文数据,在Serlvet中都要设定编码。这样代码的重复率太高了!!!!

有过滤器解决中文乱码问题

有过滤器的情况就不一样了:只要我在过滤器中指定了编码,可以使全站的Web资源都是使用该编码,并且重用性是非常理想的!

过滤器 API

只要Java类实现了Filter接口就可以称为过滤器!Filter接口的方法也十分简单:

其中init()和destory()方法就不用多说了,他俩跟Servlet是一样的。只有在Web服务器加载和销毁的时候被执行,只会被执行一次!

值得注意的是doFilter()方法,它有三个参数(ServletRequest,ServletResponse,FilterChain),从前两个参数我们可以发现:过滤器可以完成任何协议的过滤操作

那FilterChain是什么东西呢?我们看看:

FilterChain是一个接口,里面又定义了doFilter()方法。这究竟是怎么回事啊??????

我们可以这样理解:过滤器不单单只有一个,那么我们怎么管理这些过滤器呢?在Java中就使用了链式结构把所有的过滤器都放在FilterChain里边,如果符合条件,就执行下一个过滤器(如果没有过滤器了,就执行目标资源)

上面的话好像有点拗口,我们可以想象生活的例子:现在我想在茶杯上能过滤出石头和茶叶出来。石头在一层,茶叶在一层。所以茶杯的过滤装置应该有两层滤网。这个过滤装置就是FilterChain,过滤石头的滤网和过滤茶叶的滤网就是Filter。在石头滤网中,茶叶是属于下一层的,就把茶叶放行,让茶叶的滤网过滤茶叶。过滤完茶叶了,剩下的就是茶(茶就可以比喻成我们的目标资源)

快速入门 写一个简单的过滤器

实现Filter接口的Java类就被称作为过滤器

    public class FilterDemo1 implements Filter {
        public void destroy() {
        }
    
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    
            //执行这一句,说明放行(让下一个过滤器执行,如果没有过滤器了,就执行执行目标资源)
            chain.doFilter(req, resp);
        }
    
        public void init(FilterConfig config) throws ServletException {
            
        }
    }
filter部署

过滤器和Servlet是一样的,需要部署到Web服务器上的。

第一种方式:在web.xml文件中配置 filter

用于注册过滤器

    
              FilterDemo1
             FilterDemo1
             
             word_file    
             /WEB-INF/word.txt
             
    

用于为过滤器指定一个名字,该元素的内容不能为空。

元素用于指定过滤器的完整的限定类名

元素用于为过滤器指定初始化参数,它的子元素指定参数的名字,指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数

filter-mapping

元素用于设置一个Filter 所负责拦截的资源

一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径


    
        FilterDemo1
        /*
    

子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字

设置 filter 所拦截的请求路径(过滤器关联的URL样式)

指定过滤器所拦截的Servlet名称

指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个 子元素用来指定 Filter 对资源的多种调用方式进行拦截。

dispatcher

子元素可以设置的值及其意义:

REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。

INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。

FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。

ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

第二种方式:通过注解配置

    @WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")

上面的配置是“/*”,所有的Web资源都需要途径过滤器

如果想要部分的Web资源进行过滤器过滤则需要指定Web资源的名称即可!

过滤器的执行顺序

上面已经说过了,过滤器的doFilter()方法是极其重要的,FilterChain接口是代表着所有的Filter,FilterChain中的doFilter()方法决定着是否放行下一个过滤器执行(如果没有过滤器了,就执行目标资源)

测试一

首先在过滤器的doFilter()中输出一句话,并且调用chain对象的doFilter()方法

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        System.out.println("我是过滤器1");

        //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
        chain.doFilter(req, resp);
    }

我们来访问一下test.jsp页面:

我们发现test.jsp(我们的目标资源)成功访问到了,并且在服务器上也打印了字符串!

测试二

我们来试试把chain.doFilter(req, resp);这段代码注释了看看!

test.jsp页面并没有任何的输出(也就是说,并没有访问到jsp页面)。

测试三

直接看下面的代码。我们已经知道了”准备放行“会被打印在控制台上和test.jsp页面也能被访问得到,但“放行完成“会不会打印在控制台上呢?

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        System.out.println("准备放行");

        //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
        chain.doFilter(req, resp);

        System.out.println("放行完成");
    }

答案也非常简单,肯定会打印在控制台上的。我们来看看:

注意,它的完整流程顺序是这样的:客户端发送http请求到Web服务器上,Web服务器执行过滤器,执行到”准备放行“时,就把字符串输出到控制台上,接着执行doFilter()方法,Web服务器发现没有过滤器了,就执行目标资源(也就是test.jsp)。目标资源执行完后,回到过滤器上,继续执行代码,然后输出”放行完成“

测试四

我们再多加一个过滤器,看看执行顺序。

过滤器1

        System.out.println("过滤器1开始执行");

        //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
        chain.doFilter(req, resp);

        System.out.println("过滤器1开始完毕");

过滤器2

        System.out.println("过滤器2开始执行");
        chain.doFilter(req, resp);
        System.out.println("过滤器2开始完毕");

Servlet


        System.out.println("我是Servlet1");

当我们访问Servlet1的时候,看看控制台会出现什么:

执行顺序是这样的:先执行FilterDemo1,放行,执行FilterDemo2,放行,执行Servlet1,Servlet1执行完回到FilterDemo2上,FilterDemo2执行完毕后,回到FilterDemo1上

注意:过滤器之间的执行顺序看在web.xml文件中mapping的先后顺序的,如果放在前面就先执行,放在后面就后执行!如果是通过注解的方式配置,就比较urlPatterns的字符串优先级

Filter简单应用

filter的三种典型应用:

1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行

2、在让目标资源执行之前,可以对requestresponse作预处理,再让目标资源执行

3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能

禁止浏览器缓存所有动态页面
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        //让Web资源不缓存,很简单,设置http中response的请求头即可了!

        //我们使用的是http协议,ServletResponse并没有能够设置请求头的方法,所以要强转成HttpServletRequest

        //一般我们写Filter都会把他俩强转成Http类型的
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        response.setDateHeader("Expires", -1);
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");

        //放行目标资源的response已经设置成不缓存的了
        chain.doFilter(request, response);
    }

没有过滤之前,响应头是这样的:

过滤之后,响应头是这样的:

实现自动登陆 开发实体、集合模拟数据库、Dao

实体:


    private String username ;
    private String password;


    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    //各种setter和getter

集合模拟数据库

    public class UserDB {
    
        private static List users = new ArrayList<>();
    
    
    
        static {
            users.add(new User("aaa", "123"));
            users.add(new User("bbb", "123"));
            users.add(new User("ccc", "123"));
        }
    
        public static List getUsers() {
            return users;
        }
    
        public static void setUsers(List users) {
            UserDB.users = users;
        }
    }

开发dao

    public User find(String username, String password) {

        List userList = UserDB.getUsers();

        //遍历List集合,看看有没有对应的username和password
        for (User user : userList) {
            if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
                return user;
            }
        }
        return null;
    }

登陆界面

用户名
密码
10分钟 30分钟 1小时
处理登陆的Servlet
        //得到客户端发送过来的数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        UserDao userDao = new UserDao();
        User user = userDao.find(username, password);

        if (user == null) {
            request.setAttribute("message", "用户名或密码是错的!");
            request.getRequestDispatcher("/message.jsp").forward(request, response);
        }

        //如果不是为空,那么在session中保存一个属性
        request.getSession().setAttribute("user", user);
        request.setAttribute("message", "恭喜你,已经登陆了!");
        
        //如果想要用户关闭了浏览器,还能登陆,就必须要用到Cookie技术了
        Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + user.getPassword());

        //设置Cookie的最大声明周期为用户指定的
        cookie.setMaxAge(Integer.parseInt(request.getParameter("time")) * 60);
        
        //把Cookie返回给浏览器
        response.addCookie(cookie);
        
        //跳转到提示页面
        request.getRequestDispatcher("/message.jsp").forward(request, response);
过滤器
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request = (HttpServletRequest) req;

        //如果用户没有关闭浏览器,就不需要Cookie做拼接登陆了
        if (request.getSession().getAttribute("user") != null) {
            chain.doFilter(request, response);
            return;
        }

        //用户关闭了浏览器,session的值就获取不到了。所以要通过Cookie来自动登陆
        Cookie[] cookies = request.getCookies();
        String value = null;
        for (int i = 0; cookies != null && i < cookies.length; i++) {
            if (cookies[i].getName().equals("autoLogin")) {
                value = cookies[i].getValue();
            }
        }

        //得到Cookie的用户名和密码
        if (value != null) {

            String username = value.split(".")[0];
            String password = value.split(".")[1];

            UserDao userDao = new UserDao();
            User user = userDao.find(username, password);

            if (user != null) {
                request.getSession().setAttribute("user", user);
            }
        }
        
        chain.doFilter(request, response);

效果:

改良

我们直接把用户名和密码都放在了Cookie中,这是明文的。懂点编程的人就会知道你的账号了。

于是乎,我们要对密码进行加密!

        Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + md5.md5(user.getPassword()));

在过滤器中,加密后的密码就不是数据库中的密码的。所以,我们得在Dao添加一个功能【根据用户名,找到用户】

    public User find(String username) {
        List userList = UserDB.getUsers();

        //遍历List集合,看看有没有对应的username和password
        for (User user : userList) {
            if (user.getUsername().equals(username)) {
                return user;
            }
        }

        return null;
    }

在过滤器中,比较Cookie带过来的md5密码和在数据库中获得的密码(也经过md5)是否相同

        //得到Cookie的用户名和密码
        if (value != null) {

            String username = value.split(".")[0];
            String password = value.split(".")[1];

            //在Cookie拿到的密码是md5加密过的,不能直接与数据库中的密码比较
            UserDao userDao = new UserDao();
            User user = userDao.find(username);

            //通过用户名获得用户信息,得到用户的密码,用户的密码也md5一把

            String dbPassword = md5.md5(user.getPassword());
            //如果两个密码匹配了,就是正确的密码了
            if (password.equals(dbPassword)) {
                request.getSession().setAttribute("user", user);
            }

        }
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70947.html

相关文章

  • JSON入门看这一篇够了

    摘要:采用完全独立于任何程序语言的文本格式,使成为理想的数据交换语言为什么需要提到,我们就应该和来进行对比。也是一种存储和交换文本信息的手段。那么好在哪里呢比更小更快,更易解析。使用的时候,也支持将转成但是,我们不一定使用框架来做开发呀。 什么是JSON JSON:JavaScript Object Notation 【JavaScript 对象表示法】 JSON 是存储和交换文本信息的语法...

    gplane 评论0 收藏0
  • Scrapy详解 爬虫框架入门看这一篇够了

    摘要:目录前言架构安装第一个爬虫爬取有道翻译创建项目创建创建解析运行爬虫爬取单词释义下载单词语音文件前言学习有一段时间了,当时想要获取一下百度汉字的解析,又不想一个个汉字去搜,复制粘贴太费劲,考虑到爬虫的便利性,这篇文章是介绍一个爬虫框架, 目录 前言 架构 安装 第一个爬虫:爬取有道翻译 创建项目 创建Item 创建Spider 解析 运行爬虫-爬取单词释义 下载单词语音文件 ...

    lordharrd 评论0 收藏0
  • Python字符串的格式化,看这一篇够了

    摘要:相信很多人在格式化字符串的时候都用的语法,提出一种更先进的格式化方法并成为的标准用来替换旧的格式化语法,从开始已经实现了这一方法其它解释器未考证。 showImg(https://segmentfault.com/img/remote/1460000018650325); 相信很多人在格式化字符串的时候都用%s % v的语法,PEP 3101 提出一种更先进的格式化方法 str.for...

    BDEEFE 评论0 收藏0
  • Java3y文章目录导航

    摘要:前言由于写的文章已经是有点多了,为了自己和大家的检索方便,于是我就做了这么一个博客导航。 前言 由于写的文章已经是有点多了,为了自己和大家的检索方便,于是我就做了这么一个博客导航。 由于更新比较频繁,因此隔一段时间才会更新目录导航哦~想要获取最新原创的技术文章欢迎关注我的公众号:Java3y Java3y文章目录导航 Java基础 泛型就这么简单 注解就这么简单 Druid数据库连接池...

    KevinYan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<