Filter(过滤器)简介
- filter(javax.servlet.Filter) 是JavaWeb的一个重要组件,可以对发送到Servlet 的请求进行拦截,并对响应也进行拦截。
- Servlet API中定义了三个接口类来供开发者编写Filter程序:Filter、FilterChain、FilterConfig
- Filter程序是 一个实现了Filter接口的Java类,与Servlet程序相似,它由Servlet容器进行调用和执行。
- Filter程序需要在 web.xml文件中进行注册和设置它所能拦截的资源:Filter可以拦截Jsp、Servlet、静态图片文件和静态html文件。
创建并配置一个Filter
1)创建一个Filter类:实现Filter接口:public class HelloFilter implements Filter
2)在web.xml 文件中配置并映射该Filter,其中url-pattern 指定该Filter 可以拦截哪些资源,即可以通过哪些url 访问到该Filter
<!-- 注册Filter -->
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>com.javaweb.helloFilter</filter-class>
</filter>
<!-- 映射Filter -->
<filter-name>helloFilter</filter-name>
<url-pattern>/test.jsp</url-pattern>
Filter 相关API
1)Filter接口:
》public void init(FilterConfig filterConfig):类似于Servlet的 init方法,在创建Filter对象时立即被调用,且只被调用一次,该方法用于对当前的Filter 进行初始化操作。Filter 实例是单例的。
* FilterConfig 类似于ServletConfig
* 可以在web.xml中配置当前Filter 的初始化参数,配置方式也和Servlet类似。
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>com.javaweb.helloFilter</filter-class>
<init-param>
<param-name>name</param-name>
<param-value>root</param-value>
</init-param>
</filter>
》public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain):真正Filter 的逻辑代码需要编写在该方法中。每次拦截都会调用此方法。
* FilterChain:Filter 链,多个Filter 可以构成一个Filter 链。它只有一个方法:
public void doFilter(ServletRequest request, ServletResponse response):把请求传给Filter链 的下一个Filter,若当前Filter 是Filter链的最后一个 Filter,将把请求给到目标Servlet(或Jsp)
*多个Filter 拦截的顺序和<filter-mapping>
配置的顺序有关,靠前的先被调用
》public void destroy():释放当前Filter 所占用的资源的方法。在Filter 被销毁之前被调用,且只被调用一次。
手写一个实现Filter接口的Filter类模板
由于没有提供一个Filter接口的实现类,我们每次写一个Filter程序时都需要重写 init() 和 destroy()方法,并且如果我们需要在doFilter()中使用HttpServletRequest 或 HttpServletResponse 都需要进行强转,这样很麻烦。
于是我们想自己写一个类实现 FIlter 接口,以该类为模板,以后每次写Filter程序时只需要继承该类,并只需重写doFilter()方法,而无需再重写init() 和 destroy()方法,也可以不需要强转直接使用HttpServletRequest 和 HttpServletResponse 中的方法。
public abstract class HttpFilter implements Filter {
private FilterConfig filterConfig;
//不建议子类直接覆盖,若直接覆盖将可能导致 filterConfig 成员变量初始化失败
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig();
init();
}
//供子类继承的初始化方法
//可以通过getFilterConfig()获取init(FilterConfig)中的filterConfig 对象
protect void init() {}
public FilterConfig getFilterConfig(){
return filterConfig;
}
//原生的doFilter()方法,不建议直接继承该方法
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException,ServletException {
HttpServletRequest request = (HttpServletRequest) res;
HttpServletResponse response = (HttpServletResponse) res ;
doFilter(request,response,chain);
}
//子类必须实现的方法,可以直接使用HttpServletRequest 和 HttpServletResponse的方法
public abstract void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException,ServletException;
@Override
public void destroy(){}
}
多个Filter代码的执行顺序
首先我们知道,多个Filter被调用的顺序和配置文件中<filter-mapping>
顺序相对应,假设Filter链中先后有两个Filter,Filter111和Filter222,filter拦截的资源为hello.jsp
Filter111:
-------------------------------------------------------
System.out.println("Filter111 before doFilter...");//1
//"放行"
chain.doFilter(request,response);
System.out.println("Filter111 after doFilter...");//2
Filter222:
--------------------------------------------------------
System.out.println("Filter222 before doFilter...");//3
//"放行"
chain.doFilter(request,response);
System.out.println("Filter222 after doFilter...");//4
hello.jsp:
----------------------------------------------------------
<%
System.out.print("hello.jsp...");//5
%>
我们思考一下,语句 1,2,3,4,5 的输出顺序是怎样的?
首先执行Filter111 “Filter111 before..”,然后执行chain.doFilter()把控制权给了第二个Filter222,执行”Filter222 before..”,然后Filter222执行chain.doFilter(),由于Filter222之后没有Filter了,所以给到了hello.jsp页面打印”hello jsp…”,接下来要执行完没有执行的代码,“Filter222 after..” —> “Filter111 after..”
所有打印顺序为:1-3-5-4-2
配置 Filter的dispatcher 节点
可以在web.xml中的<filter-mapping>
节点下配置<dispatcher>
元素:指定过滤器所拦截的资源被Servlet 容器调用的方式
可以是REQUEST, INCLUDE, FORWARD 和ERROR之一,也可以同时指定多个dispatcher节点,默认为REQUEST
1)REQUEST:当用户直接访问页面时,web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
2)INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将会被调用。除此之外不会被调用。
3)FORWARD:如果目标资源是通过RequestDispatcher的 forward()方法访问时,那么该过滤器将被调用,
或<jsp:forward page="/..." />
或通过page指令的errorPage 转发页面 <%@ page errorPage="test.jsp" %>
。
除此之外不会被调用。
4)ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。
即在web.xml中 通过error-page 节点进行声明:
<error-page>
<exception-type>java.lang.ArithmeticException</exception-type>
<location>/test.jsp</location>
</error-page>
除此之外不会被调用。
过滤器的典型应用
使用Filter 使浏览器不缓存页面:
有三个HTTP响应头字段都可以禁止浏览器缓存当前页面,它们的在Servlet中的示例代码如下:
》response.setDateHeader(“Expires”, -1);
》response.setHeader(“Cache-Control”, “no-cache”);
》response.setHeader(“Pragma”, “no-cache”);
并不是所有的浏览器都能完全支持上面的三个响应头,因此最好是同时使用上面的三个响应头。因此我们只需要在doFilter() 中将以上三行代码复制下来(皆为HttpServletResponse的方法),并filterChain.doFilter(request,response),就完成了一个禁用浏览器缓存的Filter程序。
字符编码的过滤器
在之前我们要想在处理每一个页面请求参数的字符编码问题,必须在每一个页面上获取请求信息之前写上<% request.setCharacterEncoding(“UTF-8”) ; %>
但是我们真正开发时不可能在每一个页面获取请求信息之前都加上这行代码,很麻烦,因此可以考虑做一个统一的过滤器,任何请求都需要经过该过滤器,我们在过滤器里边指定一个 request.setCharacterEncoding(encoding) ; 就可以了。
步骤: 1、在web.xml中配置一个当前web应用的初始化参数 encoding 2、读取encoding 3、指定请求的字符编码为 encoding 的值 4、调用chain.doFilter() “放行” 请求
//继承了我们之前写的Filter模板,无需强转为Http... public class EncodingFilter extends HttpFilter { private String encoding; protected void init(){ encoding = getFilterConfig().getServletContext() .getInitParamter("encoding"); } public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException,ServletException { request.setCharacterEncoding(encoding); filterChain.doFilter(request,response); } }
检查用户是否登录的过滤器
情景:系统中的某些页面只有在正常登录后才可以使用,用户请求这些页面时要检查session 中有无该用户信息,但在所有必要的页面加上 session的判断相当麻烦。因此,我们可以编写一个用于检测用户是否登录的过滤器,如果用户未登录,则重定向到指定的登录页面。
doLogin.jsp:
<% //1、获取用户的登录信息 String username = request.getParamer("username"); //2、若登录信息完整,则把登录信息放到HttpSession if(username != null && !username.trim().equals("")){ session.setAttribute(application.getInitParameter("userSessionKey"),username); //3、重定向到list.jsp response.redirect("list.jsp"); }else{ response.sendRedirect("login.jsp"); } %>
我们把用户信息写到配置文件中,而不是写死:
<!-- 用户信息放入到 session 中的键的名字 --> <context-param> <param-name>userSessionKey</param-name> <param-value>USERSESSIONKEY</param-value> </context-param> <!-- 若未登录,需重定向的页面 --> <context-param> <param-name>redirectPage</param-name> <param-value>/login.jsp</param-value> </context-param> <!-- 不需要拦截(或检查)的URL列表 --> <context-param> <param-name>uncheckedUrls</param-name> <param-value>/login.jsp,/list.jsp/,doLogin.jsp</param-value> </context-param>
//继承了我们之前写的Filter模板 public class LoginFilter extends HttpFilter { private String sessionKey; private String redirectUrl; private String uncheckedUrls; //初始化参数的获取放在init()方法中,则只需要获取一次即可,若放入doFilter()中每请求一次就需要获取一次 protected void init(){ ServletContext servletContext = getFilterConfig().getServletContext(); sessionKey = servletContext.getInitParameter("userSessionKey"); redirectUrl = servletContext.getInitParameter("redirectPage"); uncheckedUrls = servletContext.getInitParameter("uncheckedUrls"); } public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException,ServletException { //1、获取请求的 servletPath String servletPath = request.getServletPath(); //2、检查 1 获取的servletPath 是否为不需要检查的 URL中的一个,若是,则直接放行,方法结束 List<String> urls = Arrays.asList(uncheckedUrls.split(",") ); if(urls.contains(servletPath)) { filterChain.doFilter(request,response); return; } //3、从session中获取sessionKey 对应的值,若值不存在,则重定向到 redirectUrl Object user = request.getSession().getAttribute(sessionKey); if(user == null) { response.sendRedirect(request.getContextPath() + redirectUrl); return; } //若存在,则放行,允许访问 filterChain.doFilter(request,response); } }