Java之Filter --Servlet技术中最实用的技术

一、背景:

项目需要一个做二级域的功能,大概是通过二级域跳转访问服务器对应的目录文件,类似于访问当 abc.baidu.com/index.html,实际上访问的是根目录下面abc/index.html。当时的实现思路有两个:

①利用nginx 服务器 做URL重写(rewrite)

②利用Filter 仿一个urlrewrite 来支持二级域名跳转(之前有写过urlrewrite 做301 跳转,但是它不支持二级域名跳转)

当时考虑到搭建nginx 服务器比较麻烦,所有还是选择思路二。那么什么是Filter ?

二、Filter 简介

Filter 也称之为过滤器,是Servlet 技术中最激动人心也是最实用的技术,它处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。web 开发人员可以通过Filter 技术,对web 服务器管理的所有web 资源:例如Jsp、Servlet、静态图片文件或静态 html 文件、字符编码转换等进行过滤,从而实现一些特殊的功能,还可以实现URL 级别的权限访问控制、过滤敏感词汇、防止脚本攻击的过滤器、压缩响应信息等一些高级功能。

下面是客户端与服务器一个filter 请求过程,浏览器发送访问请求,经过第一个filter 过滤器,符合条件的则放行到下一个filter,以此类推,每个过滤器的执行顺序和它在web.xml 中的注册顺序一致,直到访问请求到达服务器,同样在服务器返回响应时也需要经过filter ,这样一个过程就形成了filter 链。

在servlet-api 中提供了一个Filter 接口,如果编写的java 类实现了这个接口,则把这个java 类称之为过滤器Filter。通过Filter 技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行过滤,Filter 接口源代码如下:

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
    public void destroy();
}

三、Filter 的功能实现

Filter 如何实现过滤(工作原理)?

Filter 接口中有一个doFilter 方法,当我们编写好Filter,并配置对哪个web 资源进行过滤后,web 服务器每次在调用web 资源的service 方法之前,都会先调用一下filter 的doFilter 方法,因此,在该方法内编写代码可达到如下目的:

  1. 调用目标资源之前,让一段代码执行。
  2. 是否调用目标资源(即是否让用户访问web 资源)。
  3. 调用目标资源之后,让一段代码执行。

web 服务器在调用doFilter 方法时,会传递一个filterChain 对象进来,filterChain 对象是filter 接口中最重要的一个对 象,它也提供了一个doFilter 方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web 资源的service 方 法,即web 资源就会被访问,否则web 资源不会被访问。

四、Filter 的简单应用

1. 编写Filter 的实现类

package com.github.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 过滤器实现用户登录校验功能,如果用户已经登录则跳转到下一个过滤器,否则则跳转到登录页面
 * 
 * @date 2018年6月4日 下午2:14:31
 */
public class LoginFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("初始化过滤器");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		HttpSession session = httpServletRequest.getSession();
		String path = httpServletRequest.getRequestURI();
		Integer uid = (Integer) session.getAttribute("userid");
		// 登录页面不过滤
		if (path.indexOf("/login.jsp") > -1) {
			// 递交给下一个过滤器
			chain.doFilter(request, response);
			return;
		}
		// 注册页面不过滤
		if (path.indexOf("/register.jsp") > -1) {
			chain.doFilter(httpServletRequest, httpServletResponse);
			return;
		}

		// 已经登录
		if (uid != null) {
			// 放行,递交给下一个过滤器
			chain.doFilter(httpServletRequest, httpServletResponse);

		} else {
			httpServletResponse.sendRedirect("login.jsp");
		}
	}

	@Override
	public void destroy() {
		System.out.println("销毁过滤器");
	}

}

2. 在web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,并设置它所能拦截的资源。

<!-- spring 登录校验过滤器 -->
	<filter>
		<filter-name>loginFilter</filter-name>
		<filter-class>com.github.filter.LoginFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>loginFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>loginFilter</filter-name>
		<url-pattern>*.do</url-pattern>
	</filter-mapping>

  

再看一个防止脚本攻击的过滤器,直接贴上实现类和fliter 配置

package com.github.filter;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.filter.CharacterEncodingFilter;
/**
 * InvalidCharacterFilter:过滤request请求中的非法字符,防止脚本攻击
 * InvalidCharacterFilter继承了Spring框架的CharacterEncodingFilter过滤器,当然,
 * 我们也可以自己实现这样一个过滤器
 * 
 * @date 2018年6月4日 下午2:41:08
 */
public class InvilidCharacterFilter extends CharacterEncodingFilter {
	// 需要过滤的非法字符
	private static String[] invalidCharacter = new String[] { "script", "select", "insert", "document", "window",
			"function", "delete", "update", "prompt", "alert", "create", "alter", "drop", "iframe", "link", "where",
			"replace", "function", "onabort", "onactivate", "onafterprint", "onafterupdate", "onbeforeactivate",
			"onbeforecopy", "onbeforecut", "onbeforedeactivateonfocus", "onkeydown", "onkeypress", "onkeyup", "onload",
			"expression", "applet", "layer", "ilayeditfocus", "onbeforepaste", "onbeforeprint", "onbeforeunload",
			"onbeforeupdate", "onblur", "onbounce", "oncellchange", "oncontextmenu", "oncontrolselect", "oncopy",
			"oncut", "ondataavailable", "ondatasetchanged", "ondatasetcomplete", "ondeactivate", "ondrag", "ondrop",
			"onerror", "onfilterchange", "onfinish", "onhelp", "onlayoutcomplete", "onlosecapture", "onmouse", "ote",
			"onpropertychange", "onreadystatechange", "onreset", "onresize", "onresizeend", "onresizestart", "onrow",
			"onscroll", "onselect", "onstaronsubmit", "onunload", "IMgsrc", "infarction" };

	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String parameterName = null;
		String parameterValue = null;
		Enumeration<String> parameterNames = request.getParameterNames();
		while (parameterNames.hasMoreElements()) {
			parameterName = parameterNames.nextElement();
			parameterValue = request.getParameter(parameterName);
			if (null != parameterValue || !"".equals(parameterValue)) {
				for (String str : invalidCharacter) {
					if (parameterValue.contains(str)) {
						request.setAttribute("errorMessage", "非法字符:" + str);
						request.getRequestDispatcher("/error.jsp").forward(request, response);
						return;
					}
				}
			}
		}
		super.doFilter(request, response, filterChain);
	}
}
<!-- spring 防止脚本攻击的过滤器 -->
	<filter>
		<filter-name>InvalidCharacterFilter</filter-name>
		<filter-class>com.yangcq.filter.InvalidCharacterFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>InvalidCharacterFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

当然我们也可以不用继承上面这个CharacterEncodingFilter,直接实现Filter 类也是一样的,这里就不写了。

Spring 中自带的过滤器

1. 字符编码过滤器,防止出现中文乱码

<!--  注册过滤器 -->
	<filter>
		<!-- 对过滤器的描述,可以为空 -->
		<description>字符集过滤器</description>
		<!-- 用于为过滤器指定一个名字,该元素内容不能为空 -->
		<filter-name>encodingFilter</filter-name>
		<!-- 用于指定过滤器的完整的限定类名 -->
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<!-- 为元素指定初始化参数,param-name 指定参数名,param-value 指定参数值 -->
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<!-- 注册完之后还需要映射过滤器 -->
	<filter-mapping>
		<!-- 注册的过滤器名 -->
		<filter-name>encodingFilter</filter-name>
		<!--“/*”表示拦截所有的请求 -->
		<url-pattern>/*</url-pattern>
	</filter-mapping>

2.浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持,spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求,该过滤器为HiddenHttpMethodFilter。

<!-- 将DELETE、PUT等method 转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求 -->
	<filter>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>  

五、Filter 的生命周期

1. Filter 的创建

  filter 的创建和销毁都是有web 服务器负责。web 应用程序启动时,web 服务器将创建Filte 实例对象,并调用其init() 方法,完成对象的初始化。功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init()方法也只会执行一次。通过init()方法的参数,可获得代表当前filter 配置信息的FilterConfig 对象。注意,web.xml 的加载顺序是: context-param -> listener -> filter -> servlet,所以filter 需要放在listener 后面,不然启动会报错。
2. Filter 的销毁

  web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

3.FilterConfig接口
  用户在配置filter 时,可以使用<init-param>为filter 配置一些初始化参数,当web 容器实例化Filter 对象,调用其init()方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:

  String getFilterName():得到filter的名称。
  String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
  Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
  public ServletContext getServletContext():返回Servlet上下文对象的引用

例如:

package com.github.filter;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 初始化过滤器,获取web.xml filter 的配置参数
 * @date 2018年6月4日 下午3:17:48
 */
public class LoginFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("初始化过滤器");
		/** 
         *  <filter> 
                  <filter-name>FilterTest</filter-name> 
                  <filter-class>com.yangcq.filter.FilterTest</filter-class> 
                  <!--配置FilterTest过滤器的初始化参数--> 
                  <init-param> 
                      <description>FilterTest</description> 
                      <param-name>name</param-name> 
                      <param-value>gacl</param-value> 
                  </init-param> 
                  <init-param> 
                      <description>配置FilterTest过滤器的初始化参数</description> 
                      <param-name>like</param-name> 
                      <param-value>java</param-value> 
                  </init-param> 
            </filter> 
             
             <filter-mapping> 
                  <filter-name>FilterDemo02</filter-name> 
                  <!--“/*”表示拦截所有的请求 --> 
                  <url-pattern>/*</url-pattern> 
             </filter-mapping> 
         */
		//得到过滤器的名字  
        String filterName = filterConfig.getFilterName();  
        //得到在web.xml文件中配置的初始化参数  
        String initParam1 = filterConfig.getInitParameter("name");  
        String initParam2 = filterConfig.getInitParameter("like");  
        //返回过滤器的所有初始化参数的名字的枚举集合。  
        Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();  
          
        System.out.println(filterName);  
        System.out.println(initParam1);  
        System.out.println(initParam2);  
        while (initParameterNames.hasMoreElements()) {  
            String paramName = (String) initParameterNames.nextElement();  
            System.out.println(paramName);  
        }  
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("FilterDemo02执行前!!!");  
        chain.doFilter(request, response);  //让目标资源执行,放行  
        System.out.println("FilterDemo02执行后!!!"); 
	}

	@Override
	public void destroy() {
		System.out.println("销毁过滤器");
	}

}

六、映射Filter

  在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter

<!--映射过滤器-->
  <filter-mapping>
      <filter-name>FilterTest</filter-name>
      <!--“/*”表示拦截所有的请求 -->
      <url-pattern>/*</url-pattern>
  </filter-mapping>

<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
  <filter-name>子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字
  <url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
  <servlet-name>指定过滤器所拦截的Servlet名称。
  <dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:

<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/index.jsp</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

 <dispatcher> 子元素可以设置的值及其意义:
    REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
    INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
    FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
    ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。  

 七、总结

Filter 是Servlet2.3 新增加的功能,它不是Servlet,不能处理用户请求,也不能处理用户响应。主要对HttpServletRequest 进行预处理(相当于对Servlet 做一次过滤),也对HttpServletResponse进行后处理,是典型的处理链。

一个filter 包括:
1. 在servlet被调用之前截获;
2. 在servlet被调用之前检查servlet request;
3. 根据需要修改request头和request数据;
4. 根据需要修改response头和response数据;
5. 在servlet被调用之后截获;

传统filter和基于Aop思想的interceptor对比:

1.filter 基于回调函数doFilter(),而interceptor 则基于java 本身的反射机制,这是两者最本质的区别。

2.filter 依赖Servlet 容器,而interceptor 与该容器无关

3.filter 过滤范围比interceptor 大,filter 可以过滤请求,通过通配符可以保护页面、图片、文件等等,而interceptor 只能过滤请求

4.filter 的过滤例外在init 方法声明,而Interceptor 可以通过xml声明是guest请求还是user请求来辨别是否过滤。

AOP思想:Aop即为Aspect Oriented Programming的缩写,面向切面的编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。Aop 可以使公共服务和业务逻辑的分离,从而使两者间的解耦度降低,提高代码的复用性。无论是filter还是interceptor,都是对aop 思想很好的体现。

参考:https://blog.csdn.net/reggergdsg/article/details/52821502 和 https://www.cnblogs.com/ygj0930/p/6374212.html

版权声明:本文为博主原创文章,未经博主允许不得转载。 
原文地址:https://www.cnblogs.com/hellovoyager1/p/9132717.html