过滤器:
顾名思义,过滤器不能处理用户请求,仅能过滤用户请求。通过某些过滤规则,从过滤源中过滤出结果集合。
比如用户访问某些登录可见的内容,会出现登录的页面,这就是过滤器要求http请求经过登录。
过滤器在服务器端,截取用户端的请求与响应信息,并对之进行过滤。
工作原理:
在web容器启动时就加载了过滤器,它位于用户和web资源之间,过滤请求与响应。
流程:实例化(发生于web容器加载) - > 初始化(调用init()方法) - > 过滤(调用doFilter()方法) - > 销毁(调用destroy()方法,发生于web容器关闭)。
函数解析:
init(filterConfig arg0):arg0用来获取web.xml中关于初始化的配置(init-param)。
doFilter(request, response, filterChain):过滤器的主函数,与该过滤器有关的url(定义于web.xml的filter-mapping中)都将进入此过滤器。在完成过滤后,可以调用filterChain的doFilter(request, response)方法,将请求传往下一个filter或目标资源,故filterChain.doFilter方法称为放行方法。也可以将请求转发,重定向到其他资源(调用response.sendRedirect(request.getContextPath() + "url"))。(也可以导往403)
如果在重定向中出现循环(重定向到自己,或者两个链接互相重定向),会导致死循环。在转发中出现循环也会死循环。(详见过滤器的分类)
destroy():在过滤器销毁时被调用,释放过滤器占用的资源。(一般不用)
过滤器配置:
在web.xml中,<filter>域用于配置过滤器类的类名(自己定义的过滤器将类名写于此处),过滤器的初始化参数init-param也提供于此处,<filter-mapping>用于配置应用该过滤器的url(可以有多个)。
RD自己实现过滤器时,也可以调用Spring MVC自动加载filter,则filter-class写org.springframework.web.filter.DelegatingFilterProxy,filter-name写自己实现的过滤器类名(必然实现了Filter接口),并在类定义上方写@Component("类名"),用于自动装配。【常用】
过滤器使用方法:
过滤器可以用于过滤请求,也可以用于改变用户请求的资源(对于符合过滤器请求的url)。
但是过滤器不能用于返回用户请求,因为它不是一个标准的servlet。它可以将请求转到目的资源,也可以将请求重定向到其他资源,但不可以返回数据给用户。
过滤器链:
当web.xml中为同一个url定义了多个过滤器时,服务器会按照定义先后顺序组成过滤器链,先后执行filter的doFilter方法(filterChain作为doFilter的第三个参数,会在doFilter的方法体中被调用:filterChain.doFilter(),将该request传给下一个过滤器。
注意:定义在filterChain.doFilter()之后的方法,在servlet将response返回后,仍然会执行。且执行顺序是:定义在最前面的过滤器,request将第一个经过该过滤器,但是其filterChain.doFilter()之后的代码将在servlet返回response之后,作为response经过的最后的过滤器最后执行,之后将response返回给用户。
过滤器的分类:
一共分为四种过滤器:REQUEST, FORWARD, INCLUDE, ERROR,在web.xml的<filter-mapping>域中<dispatcher>进行配置。默认是REQUEST,可复选。
REQUEST是默认的请求过滤器,即普通的请求都会进入。重定向的页面也属于普通的request。
事实上,重定向是服务器向客户端发送一个302的页面,客户端获取后,自动向重定向目的域名发送一个request,相当于两次访问。
FORWARD即转发过滤器,当请求是通过转发的方式获得,进入该过滤器。转发的定义如下:
重定向与转发的区别:
重定向为response.sendRedirect(request.getContextPath() + "uri"),转发为request.getRequestDispatcher("uri").forward(req,response)。(此为java,jsp也有自己的重定向、转发方法。)前者主体是response,后者主体是request。
重定向告知客户端浏览器重新请求页面,其浏览器域名显示将变化为新url,即重新发送REQUEST请求。
转发是在服务器端,容器内部调用新界面,浏览器不知情,故浏览器域名不会变化,即在服务器内部生成FORWARD请求。
通常转发比重定向性能更好一些。
INCLUDE为包含过滤器,当请求是通过包含的方式获得,进入该过滤器。包含定义如下:
转发与包含:
转发为request.getRequestDispatcher("uri").forward(req,response),包含只需将forward换位include。转发是将发往本页面的请求转到uri对应页面,包含是在本页面中同时包含了下一个uri的内容(意味着本页面也会继续执行)。
ERROR为错误过滤器,当请求是通过声明式异常处理获得,进入该过滤器。
常用的404导向页面,可以通过配置web.xml实现。在web.xml中配置<error-page>,<error-code>填写404,<location>填写目标页面(error.jsp)。
此时定义一个过滤器filter,配置dispatcher为ERROR,url为error.jsp,就可以在用户输错网址时进入该过滤器。
如果不配置dispatcher,则只有用户自己敲入error.jsp时才能捕捉,输错网址时不会被捕捉。(当然,如果想让用户端看到error.jsp,需要在末尾调用放行方法filterChain.doFilter())。
异步处理的新特性:
在servlet 3.0中,过滤器的dispatcher添加了ASYNC选项,作为异步处理特性。实际开发中多使用框架支持异步。
filter实践中init-param的使用:
init-param在web.xml中配置,是一个<String,String>的键值对,可以在过滤器初始化时导入。
以登录过滤功能为例,filter过滤所有页面,进入session寻找“username”,没有就重定向到login.jsp。从login.jsp读取用户名和密码,正确就将信息存入session之后导向用户本来访问的页面,错误就导向fail.jsp。那么login.jsp;fail.jsp都需要避免被filter循环拦截。
此时需要配置init-param,将noFilterURL(随便起)作为key,不需拦截的域名统一存成一个String(可以用分号等间隔)作为value存入init-param。之后在filter中将init方法中的filterConfig参数保存下来(比如保存为private FilterConfig myConfig)。就可以调用myConfig.getInitParameter("noFilterURL")得到不需拦截的域名组成的String,将它split成单独的字符串,当uri中可以匹配这些域名就放行加return,不再重定向。
此外,filter中可以添加request.setCharacterEncoding("UTF-8")用于将请求转码为Unicode,这样就不需要再每个controller中去做处理,而是在filter进行统一处理。
也可以把"Encoding":"UTF-8"存入init-param中,用同样的方法在filter中获得这个配置项,用于设置转码。(这样代码的侵入度更低,低耦合)