灵魂三问:(what,why,how)

1.什么是过滤器

过滤器介绍

  • Filter是sun公司中servlet2.3后增加的一个新功能
  • Servlet规范中三个技术 Servlet Listener Filter
  • 在JavaEE中定义了一个接口 javax.servlet.Filter来描述过滤器
  • 通过Filter可以拦截访问web资源的请求与响应操作
  • web开发人员通过Filter技术,对web服务器管理资源:例如Jsp,Servlet,静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如**实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息、避免中文乱码【规定web资源都使用UTF-8编码】、权限验证【规定只有带有Session或Cookie的浏览器,才能访问web资源】**等等一些高级功能

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

过滤器在web容器中的流程:

java修约 java修改器方法_服务器


从图上可以发现,当浏览器的请求发送给服务器时,会遇到过滤器,将访问请求进行过滤,之后服务器才响应,web资源返回时,也会经过过滤器。

Filter API介绍

(由于自身的接触较浅,现在明白Java基础的API为jdk_API,现在使用的javaweb技术属于javaee,为java_ee_API)

Filter是javax.servlet包下的一个接口,主要有以下三个方法:

java修约 java修改器方法_数据_02

Filter的快速入门

  • 创建一个类实现javax.servlet.Filter接口并实现方法
public class MyFilter implements Filter{

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
			//chain.doFilter(request.response);
	}

	@Override
	public void destroy() {
	}
}
  • 在web.xml文件中配置Filter拦截路径(拦截规则在下文中)
<!-- 配置 一个过滤器,用于拦截请求-->
 <filter>
 	<filter-name>myfilter</filter-name>
 	<filter-class>com.gyf.web.filter.MyFilter</filter-class>
 </filter>
 <!-- 过滤器的拦截规则 -->
 <filter-mapping>
 	<filter-name>myfilter</filter-name>
 	<!-- 拦截任何一个请求 -->
 	<url-pattern>/*</url-pattern>
 </filter-mapping>
  • 图解过滤的拦截原理

    注意:chain.doFilter(request,repsonse);是放行资源,如果不写,则被拦截的规则没有办法访问到资源

FilterChain功能介绍

FilterChain是servlet容器为开发人员提供的对象,它提供了对某一资源的已过滤请求调用链的视图。过滤器使用FilterChain调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。

java修约 java修改器方法_java修约_03

Filter链

Filter链介绍

多个Filter对同一个资源进行了拦截,那么当我们在开始的Filter中执行chain.doFilter(request,response)时,是访问下一个Filter,直到最后一个Filter执行时,它后面没有了Filter,才会访问web资源。

多个Filter的访问顺序问题

它们的执行顺序取决于<filter-mapping>在web.xml文件中的顺序

过滤器的生命周期

  • 当服务器启动,会创建Filter对象,并调用init方法,只调用一次。
  • 当访问资源时,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法
  • 当服务器关闭时,会调用Filter的destroy方法来进行销毁操作。

FilterConfig介绍

FilterConfig功能介绍

在Filter中的init方法上有一个参数叫FilterConfig,FilterConfig是Filter的配置对象。 其作用如下:

  1. 获取Filter的名称
  2. 获取初始化参数
  3. 获取ServletContext对象

FilterConfig常用API

java修约 java修改器方法_java修约_04

获取web.xml中配置的filter初始化参数

<filter>
 	<filter-name>myfilter</filter-name>
 	<filter-class>com.gyf.web.filter.MyFilter</filter-class>
 	<!-- 初始化参数 -->
 	<init-param>
 		<param-name>username</param-name>
 		<param-value>zhangsan</param-value>
 	</init-param>
 	<init-param>
 		<param-name>password</param-name>
 		<param-value>123456abcdefg</param-value>
 	</init-param>
 </filter>
<!--  过滤器的拦截规则 -->
 <filter-mapping>
 	<filter-name>myfilter</filter-name>
 <!-- 	拦截任何一个请求 -->
 	<url-pattern>/*</url-pattern>
 </filter-mapping>

可以通过FilterConfig的方法直接获取初始化参数

Filter映射配置详情

Filter基本配置格式介绍

<filter>
	<filter-name>filter 名称</filter-name>
	<filter-value>filter 类全名</filter-value>
</filter>

<filter-mapping>
	<filter-name>filter 名称</filter-name>
	<url-pattern>映射路径</url-pattern>
</filter-mapping>

关于url-pattern配置

  1. 完全匹配
    要求必须以“/”开始。【不能写成/*.jsp,否则就会报错,映射报错】
    例:<url-pattern>/PersonServlet</url-pattern> 会拦截PersonServlet请求。
  2. 目录匹配
    要求必须以“/“开始,以*结束。
    例:<url-pattern>/*</url-pattern> 拦截任一请求,如Servlet请求和.jsp请求。
  3. 扩展名匹配
    不能以“/“开始,以*.xxx结束
    例:<url-pattern>*.jsp</url-pattern> 拦截以jsp后缀的请求。

关于servlet-name配置

针对于servlet拦截的配置 配置,在Filter中它的配置项上有一个标签,它用于设置当前Filter拦截哪一个servlet。是通过servlet的name来确定的。

关于dispatcher配置

可以取的值有** REQUEST FORWARD ERROR INCLUDE**
它的作用是:当以什么方式取访问web资源时,进行拦截操作。

  1. REQUEST 当时从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的 它也是默认值
  2. FORWARDD 它描述的是请求转发的拦截方式配置
  3. ERROR 如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
  4. INCLUDE 如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。

2.为什么需要用到过滤器

举个栗子:

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

  • 如果没有用到过滤器:浏览器通过http请求放数据给Servlet,如果存在中文呢,就必须指定编码,否则就会乱码!
  • jsp页面提交中文数据给Servlet处理
<form action="${pageContext.request.contextPath}/Demo1" method="post">

    <input type="text" name="username">

    <input type="submit" value="提交">

</form>
  • Servlet没有指定编码的情况下,获取得到的是乱码

    POST和GET方式产生中文乱码的原因:
Servlet中解决中文乱码的问题:
- post和get请求在接收参数时都会产生乱码问题。两者存在本质的区别。
POST乱码的原因:
- Tomcat7版本,接收客户端的请求数据时以ISO-8859-1来接收的,到了Tomcat8后,接收的请求数据是UTF-8.
- 浏览器使用的是UTF-8的编码,浏览器的中文数据提交给服务器时,Tomcat8以UTF-8的编码接收数据,在Servlet中读取数据时,拿到的UTF-8的数据,此时只需将request的编码转为UTF-8,(request.setCharacterEncoding("utf-8"))乱码问题即可解决。
GET请求乱码的问题:
-利用post传参数时,点击提交按钮之后,数据会封装到Form Data中(http中多了一个上传请求头,专门用来封装post的删除),request对象可以解析到发送过来的数据,所以只用把编码设置成UTF-8就可以解决乱码问题。
- Get方式的数据时通过消息行带过去的(Servlet?后面的时get方式带过去的参数),没有封装到reqeust对象,所以使用request设置编码是无效的。
- GET方式由消息题带过去给服务器的时候则根据Tomcat的版本可能会发生区别,则先将数据反向转换得到原始的数据,然后进行UTF-8的转码。
利用new String进行转码
username = new String(usernaem.getBytes("ISO-8859-1","utf-8")
- 总结	
	- post方式直接改request对象的编码
	- get方式需要手工转换编码,原因在于get的参数是从消息体传过去的。

tomcat7.0中get请求中文乱码解决方案:

  • 在config/server配置uri解码方式
  • 如果是项目开发完成了,打包成war包,放在tomcat7.0/webapps的时候,修改tomcat/conf/server.xml,在connector中添加URIEncoding=”utf-8”
  • 如果是开发阶段,在Eclipse开发工具的server/tomcat7.0/server.xml配置。添加<Connector URIEncoding=”utf-8” connectionTimeout=”20000”
  • 在Servlet中配置uri解码方式
    把字符串转回字节码,然后在进行utf-8编码
String username = request.getParameter(“username”);
//先还原iso编码,再转成utf-8
username = new String (username.getBytes(“ISO-8859-1”),“utf-8”);

post请求中中文乱码解决:

  • 设置request.setCharacterEncoding(“utf-8”);

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


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

使用过滤器:当在过滤器中指定了编码,可以使全站的web资源都是使用该编码,并且重用性是非常理想的!
利用过滤器解决post请求的中文乱码问题:

  • 创建一个MyEncodingFilter类实现Filter接口
  • 配置web.xml文件
    其中存在三个对象之间的关系:RequestFacade、HttpServletRequest以及ServletRequest

    利用过滤器解决get请求的中文乱码问题:
    利用装饰设计模式,创造了一个新的myRequest对象。
@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		//1,设置POST请求中文乱码的问题
		request.setCharacterEncoding("utf-8");
		System.out.println("拦截请求:"+request);
			
		//2.解决get请求中文乱码问题
		//request  :  RequestFacade;
		HttpServletRequest hsr =(HttpServletRequest)request;
		if(hsr.getMethod().equalsIgnoreCase("get")) {
			MyRequest myRequest = new MyRequest(hsr);
			//放行请求
			chain.doFilter(myRequest, response);
		}else {
			chain.doFilter(request, response);
		}
	}
}

/**
 * Wrapper包装类,装饰设计模式,内部有个真实对象的引用
 * @author Yyyz
 *
 */
class MyRequest extends HttpServletRequestWrapper{
	
	private HttpServletRequest request;
	private boolean isEncoding = false;//是否已经是utf-8编码
	public MyRequest(HttpServletRequest request) {
		super(request);
		this.request = request;
	}
	
	@Override
	public String getParameter(String name) {
		return getParameterMap().get(name)[0];
	}
	
	@Override
	public Map<String, String[]> getParameterMap() {
		
		Map<String, String[]> map = request.getParameterMap();		
		if(isEncoding == true) {
			return map;
		}		
		//遍历value,改成utf-8编码
		for(Entry<String,String[]> entry : map.entrySet()) {
			//取数组值
			String[] values = entry.getValue();
			for(int i=0;i<values.length;i++) {
				try {
					values[i] = new String(values[i].getBytes("ISO-8859-1"),"UTF-8");
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}
		}
		isEncoding = true;
		
		return map;
	}
}

过滤器API

只要java类实现了Filter接口就可以称为过滤器。
接口中的三个方法:init()、destory()、doFilter()。init和destory方法只有在web服务器加载和销毁时才执行,且只执行一次。
doFilter(ServletRequest,ServletResponse,FilterChain),从前面两个参数可以发现:过滤器可以完成任何协议的过滤操作
FilterChain是一个接口,里面有定义了doFilter()方法。可以理解:过滤器不单单只有一个,在Java中使用了链式结构。把所有的过滤器都放在FilterChain里面,如果符合条件,就执行下一个过滤器(如果没有过滤器,就执行目标资源)

Filter部署

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

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

filter

<filter>用于注册过滤器

  • 用于为过滤器指定一个名字,该元素的内容不能为空。
  • 元素用于指定过滤器的完整的限定类名
  • 元素用于为过滤器指定初始化参数,它的子元素指定参数的名字,指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数

filter-mapping
<filter-mapping>元素用于设置一个Filter所负责拦截的资源
一个Filter拦截的资源可通过两种方式来指定:Servlet名称和资源访问的请求路径

  • 子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
  • 设置 filter 所拦截的请求路径(过滤器关联的URL样式)
  • 指定过滤器所拦截的Servlet名称
  • 指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个 子元素用来指定 Filter 对资源的多种调用方式进行拦截

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

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

上面的配置是"/*",所有的web资源都会被过滤器拦截。

过滤器的执行顺序

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

Filter简单应用

  • filter的三种典型应用:
  1. 可以在filter中根据条件决定是否调用chain.doFilter(request,response)方法,即是否让目标资源执行
  2. 在让目标资源执行之前,可以对request和response作预处理,再让目标资源执行
  3. 在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能

禁止浏览器缓存所有动态页面


@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		//让web资源不缓存,只需要设置http中的response的请求头即可
		
		//我们使用的是http协议,ServletResponse并没有能够设置请求头的方法,所以要强转成HttpServlet
		
		//一般我们写Filter都会把他俩强转成Http类型的
		HttpServletRequest myrequest =(HttpServletRequest)request;
		HttpServletResponse myresponse = (HttpServletResponse)response;
		
		myresponse.setDateHeader("Expires", -1);
		myresponse.setHeader("Cache-control", "no-cache");
		myresponse.setHeader("pragma", "no-cache");
				
		//放行目标资源的response以及设置成不缓存的
		chain.doFilter(request, response);
	}
  • 没有过滤之前,响应头是这样的:
  • 过滤之后,响应头是这样的:

例子2:实现自动登录(一种视频教学,一种博客)

本文为笔记整理,是视频学习和参考Java3y博主的知识整理,如有侵权,请联系删除。

本文参考:过滤器第一篇【介绍、入门、简单引用】