Java Web之过滤器

1.过滤器的概念

过滤器是一个服务器端的组件,它可以拦截客户端的请求和响应信息,并对这些信息进行过滤。

注意:1. javaWeb三大组件:Filter、Servlet、Listener

   2. Filter 程序可以拦截 Jsp, Servlet, 静态图片文件和静态 html 文件。

Servlet API中提供了一个Filter接口,如果编写额类实现了这个接口,则称这个类为过滤器。Filter接口源码如下:

package javax.servlet;
import java.io.IOException;
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();
}

接口里有三个抽象方法,分别是init() 初始化  ,   doFilter()  执行过滤   和destory()销毁。

2. 过滤器的特点     请记住:一般处理方式是放行,转发

    1. 以常规方式调用资源(,调用servletJSP页面)

    2. 利用修改过的请求信息调用资源

    3. 调用资源,但在发送响应到客户机前对其进行修改,修改响应

    4. 阻止该资源调用,代之以转到其他的资源,返回一个特定状态代码或生成替换输出

    5. 阻止资源调用,不转到其它资源(错误的情况)

3. 过滤器的生命周期

Filter的创建和销毁由WEB服务器负责在启动tomcat的时候就行创建过滤器,并且执行init方法,完成对象的初始化。filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。 每次拦截到都会去执行doFilter方法。

  1.  实例化———————————————–web.xml
  2.  初始化———————————————–init()
  3.  执行过滤——————————————–doFilter()
  4.  销毁————————————————–destory()

1.实例化,执行构造方法。 :在web.xml中对过滤器进行配置和映射,也可以使用注解@WebFilter。

2.初始化:web容器创建过滤器实例后将调用init方法,这个方法的参数FilterConfig对象可以获取web.xml文件中过滤器的初始化参数。在Filter的生命周期中,只会初始化一次。

3.执行过滤:当用户访问的URL与web.xml中url-pattern配置的值一样时,就触发这个过滤器,web容器会先调用过滤器,过滤器会调用doFilter方法,这个方法的参数FilterChain对象可以调用doFilter方法,将请求传给下一个过滤器(过滤器链的情况下)或目标资源,也可以利用重定向或转发的方式将请求转发到其他资源。在Filter的生命周期中,可以执行0到n次过滤。

4.销毁:web容器在销毁过滤器实例前调用这个方法(比如stop tomcat),在这个方法中可以释放过滤器占用的资源。在Filter的生命周期中,只会进行一次销毁。

    在web.xml中配置过滤器。这里要谨记一条原则:在web.xml中,监听器>过滤器>servlet。也就是说web.xml中监听器配置在过滤器之前,过滤器配置在servlet之前,否则会出错。

<!--配置过滤器--> 
<filter> 
    <filter-name>FilterTest</filter-name> 
    <filter-class>com.codeliu.FilterTest</filter-class> //类的全限定名(通过反射去创建这个过滤器对象)
    <init—param> //可选 
            <param—name>参数名</param-name>//过滤器初始化参数
            <param-value>参数值</param-value>  
    </init—pamm>  
</filter> 

<!--映射过滤器--> 
<filter-mapping> 
    <filter-name>FilterTest</filter-name>  
    <url-pattern>/*</url-pattern>
    <dispatcher>请求的类型</dispatcher> 
</filter-mapping>

在配置中需要注意的有三处:<filter-class>(类的全限定名(通过反射去创建这个过滤器对象)

             <url-pattren>要拦截的资源路径  

              <dispatcher>请求的类型

<url-pattren>一般有以下规则:

    1:作用与所有web资源:<url—pattern>/*</url-pattern>。则客户端请求访问任意资源文件时都要经过过滤器过滤,通过则访问文件,否则拦截。

    2:作用于某一文件夹下所有文件:<url—pattern>/dir/*</url-pattern>

    3:作用于某一种类型的文件:<url—pattern>*.扩展名</url-pattern>。比如<url—pattern>*.jsp</url-pattern>过滤所有对jsp文件的访问请求。

    4:作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名</url-pattern>

   如果一个过滤器需要过滤多种文件,则可以配置多个<filter-mapping>,一个mapping定义一个url-pattern来定义过滤规则。

<filter>
      <filter-name>loginFilter</filter-name>
      <filter-class>com.ygj.control.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>

 

<dispatcher>请求的类型,四个取值,分别为 REQUEST | INCLUDE | ORWARD | ERROR,不填时默认为 REQUEST。
  
  1. REQUEST:当用户直接访问页面时,web容器将会调用过滤器,如果目标资源是通过请求转发(request.getRequestDisPatcher)的include方法或forward方法进行访问,那么该过滤器就不会被调用。
  2. INCLUDE:如果目标资源是通过request.getRequestDisPatcher的include方法进行访问,那么该过滤器将会被调用,其他情况下,不会被调用。
  3. FORWAED:如果目标资源是通过request.getRequestDisPatcher的forward方法进行访问,那么该过滤器将会被调用,其他情况下,不会被调用。
  4. ERROR:如果目标资源是通过声明或异常处理机制调用,那么该过滤器将会被调用,除此之外,不会被调用。
使用注解的方式 (属性urlPatterns指定要过滤的URL模式,也可以用属性value来指定。)
@WebFilter(urlPatterns = {"/*"},  
           initParams = {@WebInitParam(name = "noFilterPath", value = "login.jsp;LoginServlet;fail.jsp", description = "不触发该过滤器的页面"),
       @WebInitParam(name = "charset", value = "UTF-8")})

 

4. 过滤器初始化配置(FilterConfig)

FilterConfig对象提供对servlet环境及web.xml文件中指派的过滤器名的访问

常用方法:

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

  

5.过滤器的执行过程

滤器链(FilterChain)    

  过滤器对象使用FilterChain对象调用过滤器链中的下一个过滤器,如果该过滤器是链中最后一个过滤器,那么将调用目标资源。

 chain.doFilter(request, response);//放行,通过了当前过滤器,递交给下一个filter进行过滤,写在doFilter ()方法体内最后一行。

上图是一个滤器链(FilterChain)的执行过程,所谓过滤器链,就是当多个过滤器的URL相同时,就形成了过滤器链。那么在过滤器链里每个过滤器的执行顺序是怎么样的呢?有两种情况:

    1. 如果你是在web.xml中配置的过滤器,那么执行时就按照你配置的<filter-mapping> 顺序进行执行。

               2. 如果你是通过注解的方式配置的过滤器,那么执行时就按照首字母的大小进行执行,首字母相同看第二个字母。比如我在过滤器链里一个过滤器为TestFilter,一个过滤器为MyFilter,那显然先执行MyFilter再执行TestFilter。

 6.过滤器解决字符编码

方案一:直接写一个过滤器,在过滤器中的request对象上设置字符编码即可(硬编码方式,通用性不好,后期修改麻烦)

方案二:编码写到web.xml,过滤器读取编码,当请求中不带编码,咱们才进行自己的编码(设置到request对象即可),并且提供一个force参数确定是否强制使用自己设置的编码

   分析:force如果为true,则全部使用只剩配置好的编码

              如果为false, 则对自带的编码不进行修改(默认)

<filter>
	<filter-name>encoding</filter-name>
	<filter-class>cn.itsource._02_encoding.EncodingFilter</filter-class>
	<!-- 设置我们自己的编码 -->
<init-param>
	<param-name>encoding</param-name>
	<param-value>UTF-8</param-value>
</init-param>
<!-- 是否强制使用该编码 -->
	<init-param>
		<param-name>force</param-name>
		<param-value>true</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>encoding</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>	
//编码,会从xml中读取
private String encoding;

//是否强制使用我们自己的编码
private boolean force = false;

@Override
public void init(FilterConfig config) throws ServletException {
	this.encoding = config.getInitParameter("encoding");
	this.force = Boolean.valueOf(config.getInitParameter("force")); 
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
		FilterChain chain) throws IOException, ServletException {
	//请求中没有编码,但是我们有自己的编码要设置
	//请求中有编码,但是我们要强制设置编码
	if((req.getCharacterEncoding()==null || force) && hasLength(encoding)){
		req.setCharacterEncoding(encoding);
	}
	chain.doFilter(req, resp);
}

private boolean hasLength(String str){
	return str!=null && !"".equals(str.trim());
}

@Override
public void destroy() {
}

  

 7. 权限判断

情景:系统中的某些页面只有在正常登录后才可以使用,用户请求这些页面时要检查 session 中有无该用户信息,但在所有必要的页面加上session的判断相当麻烦的事情

解决方案:编写一个用于检测用户是否登录的过滤器,如果用户未登录,则重定向到指定的登录页面.(一般为回到登录页面)

public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
        
        HttpServletRequest request=(HttpServletRequest) arg0;
        HttpServletResponse response=(HttpServletResponse) arg1;
        HttpSession session=request.getSession();
        
        String path=request.getRequestURI();
        
        Integer uid=(Integer)session.getAttribute("userid");
        
        if(path.indexOf("/login.jsp")>-1){//登录页面不过滤
            arg2.doFilter(arg0, arg1);//递交给下一个过滤器
            return;
        }
        if(path.indexOf("/register.jsp")>-1){//注册页面不过滤
            arg2.doFilter(request, response);
            return;
        }
        
        if(uid!=null){//已经登录
            arg2.doFilter(request, response);//放行,递交给下一个过滤器
            
        }else{
            response.sendRedirect("login.jsp");
        }

    }

由于上面的 路径, 用户的id 都是写死的,故引出下面的解决方案:

解决方法:将写死的这三个东西改成配置文件中去。(虽然这个方案不错,但是约定大于配置

web.xml

<filter>
	<filter-name>checkLoginFilter</filter-name>
	<filter-class>cn.itsource._03_check.CheckLoginFilter</filter-class>
	<!-- 不检查的url路径 -->
	<init-param>
		<param-name>unCheckUrls</param-name>
		<param-value>/login.jsp,/login</param-value>
	</init-param>
	<!-- 用户存在session中的名称 -->
	<init-param>
		<param-name>loginSessionName</param-name>
		<param-value>USER_IN_SESSION</param-value>
	</init-param>
	<!-- 权限检查失败后要返回的路径 -->
	<init-param>
		<param-name>backUrl</param-name>
		<param-value>/login.jsp</param-value>
	</init-param>
</filter>
 	<filter-mapping>
 		<filter-name>checkLoginFilter</filter-name>
 		<!-- 对所有的请求都进行拦截 -->
	<url-pattern>/*</url-pattern>
</filter-mapping>

  

//不受检查的资源
private List<String> unCheckUrls;
//用户存放在session中的名称 -
private String loginSessionName;
//登录失败后返回的路径
private String backUrl;

@Override
public void init(FilterConfig config) throws ServletException {
	//从配置中拿到相应的数据
	String[] urls = config.getInitParameter("unCheckUrls").split(",");
	this.unCheckUrls = Arrays.asList(urls);
	this.loginSessionName = config.getInitParameter("loginSessionName");
	this.backUrl = config.getInitParameter("backUrl");
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
		FilterChain chain) throws IOException, ServletException {
	HttpServletRequest request =(HttpServletRequest)req;
	HttpServletResponse response = (HttpServletResponse)resp;
	//从Session中拿到用户
	Object user = request.getSession().getAttribute(loginSessionName);
	//这里可以拿到请求的uri 例:/login.jsp /login
	String uri = request.getRequestURI();
	//如果路径列表中不包含相应的路径,则进行权限检查
	if(!unCheckUrls.contains(uri)){
		//如果用户不存在,返回登录界面
		if(user==null){
			response.sendRedirect(backUrl);
			return;
		}
	}
	//代码放行
	chain.doFilter(request, response);
}

  

8. 顺便介绍下:Arrays.asList()

使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportOperationException异常
说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

asList的代码:
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

  

Java代码 
public static void main(String[] args) {

    int[] data = {1,2,3,4,5};

    List list = Arrays.asList(data);

    System.out.println("列表中的元素数量是:" + list.size());

}    
asList接受的是一个泛型类型的长度可变的参数,再构造了一个ArrayList。
然而基本数据类型是不支持泛型化的,但是数组支持,所以采用基本数据类型的数组转化后是将数组放入了构造的ArrayList中,长度是1。
 
Java代码 复制代码收藏代码Arrays类鈥斺擜rrays.asList()方法使用
Integer[] data = {1,2,3,4,5};

List list = Arrays.asList(data);

System.out.println("列表中的元素数量是:" + list.size());

输出结果:
列表中的元素数量是:5  

说明编译器对Integer[] 处理不一样。传入过程中asList()方法实际是将Integer数组里的元素进行存储。

9. 文字过滤

需要的获取参数的时候可以完成敏感字过滤但是HttpServletRequest的实现类的getParameter方法本身不支持敏感字过滤.

解决办法:在不改变HttpServletRequest默认实现类的基础之上,使用装饰模式增强getParameter方法,使之支持敏感字过滤.

  

开始使用装饰模式来完成咱们的功能增强吧:

public class MyHttpServletRequestWapper implements HttpServletRequest

当我们创建这个类实现HttpServletRequest接口后会发现,它里面有太多的功能(方法)需要我们去实现。这个确实是很麻烦的一件事。

不过幸好,sun公司早已经想到了这个问题,为我们准备了一个适配器 HttpServletRequestWrapper,让咱们只需要去关注咱们自己的getParameter方法即可:

<form action="MessageServlet" method="post">
	标题:<input type="text" name="title"/>
	内容:<textarea rows="20" cols="20" name="content"></textarea>
	<input type="submit" value="提交"/>
</form>


@WebServlet("/MessageServlet")
public class MessageServlet extends HttpServlet {
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String title = request.getParameter("title");
		String content = request.getParameter("content");
		System.out.println(title);
		System.out.println(content);
	}
}

  

 FilterUtil.java 代码

public class FilterUtil {
private static String[] banChars= {"操","日","傻逼","sb","cnm","c","n","m"};//敏感字库
	
	public static String filter(String name) {
		for (String  str: banChars) {
			if(name.contains(str)){
				String replaceContent = "";
				for (int i = 0; i < str.length(); i++) {
					replaceContent+="*";
				}
				name = name.replaceAll(str,replaceContent);
			}
		}
		return name;
	}
}

  

MyHttpServletRequestWapper完整代码

public class MyHttpServletRequestWapper extends HttpServletRequestWrapper{

	public MyHttpServletRequestWapper(HttpServletRequest request) {
		super(request);
	}
	
	//对getParameter功能进行增强
	@Override
	public String getParameter(String name) {
		if("title".equals(name) || "content".equals(name)){
			return  FilterUtil.filter(super.getParameter(name));//过滤字符方法 注意传的是name键对应的value
		}
		return super.getParameter(name);
	}
	
}

MessageFilter 完整代码

@WebFilter("/*")
public class MessageFilter implements Filter {
    @Override
    public void destroy() {

    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)resp;
        
        //将request进行替换
        HttpServletRequest wapper = new MyHttpServletRequestWapper(request);
        chain.doFilter(wapper, response);
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }
}
原文地址:https://www.cnblogs.com/gshao/p/10356047.html