1.过滤器的概述和入门案例
(1)概述
什么是过滤器?
Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。客户端发送请求服务器端,过滤这个请求。
过滤器的工作方式
*用户在访问web资源的时候,发送的请求会先经过过滤器
*这时可以对请求对象进行控制和操作,然后过滤器将用户的请求发送至web资源
*访问资源后,响应时也是会经过过滤器
*然后可以对响应对象进行控制和操作,最后过滤器将web资源的响应发送给用户
(2)Filter是如何实现拦截的?
Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源(拦截url)进行拦截后,WEB服务器每次在调用web资源之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
*调用目标资源之前,让一段代码执行
*是否调用目标资源(即是否让用户访问web资源)。
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
*调用目标资源之后,让一段代码执行
(3)过滤器使用场景
![](https://i-blog.csdnimg.cn/blog_migrate/f4e0db7badcf4130a4af53e396e2e325.png)
(4)入门案例
如何实现过滤器?
![](https://i-blog.csdnimg.cn/blog_migrate/493867a83fe1f1df285a180505950398.png)
具体步骤:
*步骤1:编写java类实现Filter接口(javax.servlet.Filter;),并实现(三个方法)其doFilter方法
public class TestFilter implements Filter { public void destroy() { System. out.println( "destroy in Filter 1!....."); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { System. out.println( "doFilter called BEGIN! in Filter1!"); filterChain.doFilter(request, response); System. out.println( "doFilter called END! in Filter1!"); } public void init(FilterConfig filterConfig) throws ServletException { System. out.println( "init in Filter 1!......"); } }
*步骤二:在 web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,,让服务器知道这是一个过滤器,并设置它所能拦截的资源。
<filter> <filter-name >TestFilter </filter-name > <filter-class >cn.itcast.filter.TestFilter </filter-class > </ filter> < filter-mapping> <filter-name >TestFilter </filter-name > <url-pattern >/* </url-pattern > </ filter-mapping>
问题:创建jsp,也配置了过滤器,但是访问jsp页面的时页面却是一片空白,这是为什么?
解答:因为执行到了过滤器,而过滤器没有做放行操作,要想页面执行,需要在过滤器中执行一个放行操作。
public
void
doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain)
throws
IOException, ServletException {
System.
out
.println(
"doFilter called BEGIN! in Filter1!"
);
filterChain.doFilter(request, response);//放行操作
System.
out
.println(
"doFilter called END! in Filter1!"
);
}
2.FilterChain对象简介
过滤器链:在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。在一个项目中可以配置多个过滤器,只要过滤到匹配的url-pattern,过滤器就会执行,
根据web.xml配置文件中<filter-mapping>配置的顺序决定谁先执行谁后执行。
<filter> <filter-name>simple</filter-name> <filter-class>org.lxh.filterdemo.SimpleFilter</filter-class> <init-param> <param-name>ref</param-name> <param-value>HELLO WORLD</param-value> </init-param> </filter> <filter-mapping> <filter-name>simple</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>simple2</filter-name> <filter-class>org.lxh.filterdemo.SimpleFilter2</filter-class> <init-param> <param-name>ref</param-name> <param-value>HELLO WORLD</param-value> </init-param> </filter> <filter-mapping> <filter-name>simple2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3.过滤器的生命周期:
Servlet的生命周期:
*创建:第一次访问Servlet时创建,执行init方法,创建一次
*服务:每次请求时执行service方法,执行多次
*销毁:服务器关闭的时候销毁,执行destory方法
过滤器的生命周期:
*创建:服务器启动的时候创建过滤器对象,创建一次,调用init方法
*服务:过滤到匹配的url-pattern请求时,执行doFilter方法,执行多次
*销毁:服务器关闭的时候销毁过滤器对象,调用destroy方法
4.FilterConfig对象
Method Summary | |
---|---|
String | getFilterName() Returns the filter-name of this filter as defined in the deployment descriptor. |
String | getInitParameter(String name) Returns a String containing the value of the named initialization parameter, or null if the parameter does not exist. |
Enumeration | getInitParameterNames() Returns the names of the filter's initialization parameters as an Enumeration of String objects, or an empty Enumeration if the filter has no initialization parameters. |
ServletContext | getServletContext() Returns a reference to the ServletContext in which the caller is executing. |
*配置初始化参数
<filter> <filter-name >TestFilter </filter-name > <filter-class >cn.itcast.filter.TestFilter </filter-class > <init-param > <param-name >user </param-name > <param-value >Michael</param-value> </init-param > <init-param > <param-name >psw</param-name> <param-value >123 </param-value > </init-param > </ filter>
*使用FilterConfig对象的API获取参数信息:
public void init(FilterConfig filterConfig) throws ServletException { System. out.println(filterConfig.getFilterName()); System. out.println(filterConfig.getInitParameter( "user")); Enumeration<String> namesEnum=filterConfig.getInitParameterNames(); while(namesEnum.hasMoreElements()){ String name=namesEnum.nextElement(); String val=filterConfig.getInitParameter(name); System. out.println(name+ ":"+val); } }
5.过滤器中url-pattern的配置
完全路径匹配 :以/开头 ,如/a、 /b
目录匹配:以/开头,如/aa/* 、/*
扩展匹配:不以/开头 *.jsp 、*.action
在Java web开发中常会使用到功能强大的过滤器,他毕竟能给我们带来很大的方便,但是针对过滤的资源我们需要详细的了解他们在web.xml中的配置信息。这个根据几种常用的不同情况进行了总结:
(1)如果要映射过滤应用程序中所有资源:
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
(2)过滤指定的类型文件资源
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping>
其中<url-pattern>*.html</url-pattern>要过滤jsp那么就改*.html为*.jsp,但是注意没有“/”斜杠
如果要同时过滤多种类型资源:
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
(3)过滤指定的目录
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>/folder_name/*</url-pattern> </filter-mapping>
(4)过滤指定的servlet
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <servlet-name>loggerservlet</servlet-name></filter-mapping> <servlet> <servlet-name>loggerservlet</servlet-name> <servlet-class>myfilter.LoggerServlet</servlet-class> </servlet>
(5).过滤指定文件
<filter> <filter-name>loggerfilter</filter-name> <filter-class>myfilter.LoggerFilter</filter-class> </filter> <filter-mapping> <filter-name>loggerfilter</filter-name> <url-pattern>/simplefilter.html</url-pattern> </filter-mapping>
以上都要注意是否有斜杠“/”
6.<filter-maping>中其他的一些过滤配置
(1)<servlet-name >可以匹配就过滤某一个Servlet
<
filter-mapping
>
<
filter-name
>
CharEncoderFilter
</
filter-name
>
<
url-pattern
>
/char
</
url-pattern
>
</
filter-mapping
>
调用url-pattern值为/char的Servlet时,会执行此过滤器,其他均不会执行
(2)<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,FORWARD,INCLUDE和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。
*REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
*FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
*INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
*ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
7.过滤器的一些经典案例:
7.1案例1:统一网站字符集编码过滤器
案例简介:有jsp页面,通过post方式提交到servlet里面, 如果提交的数据有中文,在servlet获取数据处理中文, 想要把中文数据输出到页面上,处理中文乱码。
实现方式:在过滤器的doFilter方法中设置编码方法,并配置让过滤器过滤所有请求,这样就可以处理所有页面的中文乱码问题,不需要在每次出现中文的时候在Servlet都写乱码处理方式,达到一劳永逸的效果
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { request.setCharacterEncoding( "utf-8"); //以post方式提交数据中含有中文乱码的问题 response.setContentType( "text/html;charset=utf-8");//字符流 乱码解决方式 filterChain.doFilter(request, response); }
存在的问题:此种方式只能解决post方式提交数据中含有乱码的问题,以及以字符流向页面输出数据出现乱码的问题,不能解决get方式以及以字节流向页面输出数据出现乱码的问题,因为后两种均需要先获取到数据,才能处理乱码。
7.2案例2:分IP来统计网站的访问次数
比如通过192.168.23.112访问之后,可以统计这个IP的访问次数
实现过程:创建一个IPVisitFilter实现Filter接口
(1)在Filter 的init方法中:
*在ServletContext里面创建一个map,用来存放IP与当前IP访问的次数
(2)在Filter的doFilter方法中:
*使用request.getRemoteAddr();取得当前访问的IP,然后从map集合中取此IP的访问次数,访问次数+1
问题:在doFilter方法中如何得到ServletContext对象?使用成员变量的方法,从init方法中传过来。
有没有其他方式呢?注意:在Session对象中有一个方法可以直接获取ServletContext对象。故而可以通过request对象获取Session对象,在获取ServletContext对象
具体代码:
public class IPVisitFilter implements Filter{ //监听器创建的时候,创建一个map集合用于存放键值对(IP,访问次数),将map集合放到ServletContext域中 public void init(FilterConfig filterConfig) throws ServletException { ServletContext context=filterConfig.getServletContext(); Map<String,Integer> ipMap= new HashMap<String,Integer>(); context.setAttribute( "ipMap", ipMap); } public void doFilter(ServletRequest sRequest, ServletResponse sResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest hsRequest=(HttpServletRequest) sRequest; HttpSession session=hsRequest.getSession(); ServletContext context=session.getServletContext(); @SuppressWarnings( "unchecked") Map<String,Integer> ipMap=(Map<String, Integer>) context.getAttribute("ipMap" ); String currentIP=hsRequest.getRemoteAddr(); int times=0; if(ipMap.containsKey(currentIP)){ times=ipMap.get(currentIP); times++; } ipMap.put(currentIP, times); //放行 filterChain.doFilter(hsRequest, sResponse); } public void destroy() { } }
(3)在IP.jsp页面显示统计结果
<body> <h4 ><font face ="楷体" color= "green">分IP统计访问次数 </font ></h4 > <table border ="2"> <tr ><td >IP地址 </td ><td >访问次数 </td ></tr > <c:forEach var ="IP" items="${ipMap } "> <tr > <td >${IP.key } </td > <td >${IP.value } </td > </tr > </c:forEach > </table > </ body>
*在页面上显示访问结果:JSTL的foEach标签与EL表达式
7.3案例3:自动登录的过滤器
问题:如果UserBean里面字段的名称与数据库中对应字段的名称不一致会出现什么情况?可以做个小实验测试一下,
实现过程:
(1)创建登录的页面,输入用户名和密码输入框,一般都是复选框(提示是否自动登录)
* 登录提交到一个servlet里面(LoginServlet),登录成功之后到一个页面welcom.jsp.
* 如果登录页面选中自动登录,登录成功之后,关闭浏览器,再直接访问welcom.jsp,直接登录
(2) 实现登录的servlet
*** 判断用户名和密码是否正确,如果用户名或者密码不正确,转发到登录页面
**** 如果用户名和密码都正确,登录成功
**** 判断是否需要自动登录(复选框是否被选中)
*****如果选择了复选框,表示要自动登录
****** 需要向cookie里面存用户名和密码(cookie使用持久性的cookie)
====过滤器
***** 如何实现自动登录?
*** 可以使用cookie技术来实现,使用持久性cookie
*** 关闭浏览器器,打开浏览器直接访问success.jsp
**** 需要得到存到cookie里面的用户名和密码,登录
** 核心的步骤:
*** 过滤器里面,从回写的cookie里面把用户名和密码取出来,在放到seesion里面
**** 1、首先到session里面判断user是否存在,如果存在,直接放行
**** 2、如果session里面不存在user对象,从cookie里面获取用户名和密码
**** 3、获取到了用户名和密码,严谨判断,查询数据库判断cookie用户名和密码是否正确
**** 4、如果正确,把user对象放到session里面,最终在success页面显示用户信息
* 登录提交到一个servlet里面(LoginServlet),登录成功之后到一个页面welcom.jsp.
* 如果登录页面选中自动登录,登录成功之后,关闭浏览器,再直接访问welcom.jsp,直接登录
(2) 实现登录的servlet
*** 判断用户名和密码是否正确,如果用户名或者密码不正确,转发到登录页面
**** 如果用户名和密码都正确,登录成功
**** 判断是否需要自动登录(复选框是否被选中)
*****如果选择了复选框,表示要自动登录
****** 需要向cookie里面存用户名和密码(cookie使用持久性的cookie)
====过滤器
***** 如何实现自动登录?
*** 可以使用cookie技术来实现,使用持久性cookie
*** 关闭浏览器器,打开浏览器直接访问success.jsp
**** 需要得到存到cookie里面的用户名和密码,登录
** 核心的步骤:
*** 过滤器里面,从回写的cookie里面把用户名和密码取出来,在放到seesion里面
**** 1、首先到session里面判断user是否存在,如果存在,直接放行
**** 2、如果session里面不存在user对象,从cookie里面获取用户名和密码
**** 3、获取到了用户名和密码,严谨判断,查询数据库判断cookie用户名和密码是否正确
**** 4、如果正确,把user对象放到session里面,最终在success页面显示用户信息
**** 5、如果不正确,直接放行
7.4 案例4:Post与Get方式通用的乱码解决方案
实现步骤:
1.装饰者模式:增强HttpServletRequest中与乱码有关的方法
注意:Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper , (HttpServletRequestWrapper 类实现了request 接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法)以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
这样就避免了在增强request对象时候,实现HttpServletRequest接口,实现接口中所有的方法,这种解决方式实际上是增强方式的继承方式与装饰者模式的一个结合。
public class MyHttpServletRequest extends HttpServletRequestWrapper { private HttpServletRequest request; public MyHttpServletRequest(HttpServletRequest request) { super(request); this. request = request; // 获得待增强类的引用 } // 处理get与post不同方式提交数据的中文乱码 @Override public String getParameter(String name) { String method = request.getMethod(); if (method.equalsIgnoreCase( "GET")) { String data = request.getParameter(name); try { data = new String(data.getBytes("iso8859-1" ), "utf-8" ); return data; } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } // 此处不能使用else,因为表单提交的方式很多 else if (method.equalsIgnoreCase( "POST")) { try { request.setCharacterEncoding( "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return super.getParameter(name); } //解决以数组方式获取数据的乱码 @Override public String[] getParameterValues(String name) { String method = request.getMethod(); if (method.equalsIgnoreCase( "GET")) { String[] datas = request.getParameterValues(name); try { for ( int i = 0; i < datas. length; i++) { datas[i] = new String(datas[i].getBytes("iso8859-1" ), "utf-8"); } return datas; } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } // 此处不能使用else,因为表单提交的方式很多 else if (method.equalsIgnoreCase( "POST")) { try { request.setCharacterEncoding( "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return super.getParameterValues(name); } // 解决以Map方式获取数据时候的乱码 @Override public Map getParameterMap() { String method = request.getMethod(); if (method.equalsIgnoreCase( "GET")) { Map<String, String[]> dataMap = request.getParameterMap(); Set<Entry<String, String[]>> entryset = dataMap.entrySet(); Iterator<Entry<String, String[]>> it = entryset.iterator(); Map<String, String[]> newDataMap = new HashMap<String, String[]>(); while (it.hasNext()) { try { Entry<String, String[]> entry = it.next(); String key = entry.getKey(); key = new String(key.getBytes("iso8859-1" ), "utf-8" ); String[] values = entry.getValue(); for ( int i = 0; i < values. length; i++) { values[i] = new String(values[i].getBytes("iso8859-1" ),"utf-8" ); } newDataMap.put(key, values); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } return newDataMap; } // 此处不能使用else,因为表单提交的方式很多 else if (method.equalsIgnoreCase( "POST")) { try { request.setCharacterEncoding( "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return super.getParameterMap(); } }
2.实现过滤器:用增强的request对象替代原有的request对象,这样在Servlet中调用提取参数信息的方法时,实际上就是调用了增强的方法,以此达到解决中文乱码的目的。
public class EncoderFilter implements Filter { public void init(FilterConfig arg0) throws ServletException { } //使用自定义的Request类来解决post与get乱码问题 public void doFilter(ServletRequest sRequest, ServletResponse sResponse, FilterChain chain) throws IOException, ServletException { //解决Post与Get方式请求提交数据的乱码 HttpServletRequest hsRequest=(HttpServletRequest) sRequest; MyHttpServletRequest myRequest= new MyHttpServletRequest(hsRequest); //解决服务器向JSP以字符流输出中文的乱码问题 sResponse.setCharacterEncoding( "utf-8"); chain.doFilter(myRequest, sResponse); } public void destroy() { } }
存在的问题:如果在服务器端的Servlet同时使用数组方式与Map方式获取参数信息,Map方式仍然会出现中文乱码问题,问题出在什么地方?
有兴趣的同学,还可以尝试使用动态代理的方式来增强Request类,来实现乱码问题。
结束。欢迎拍砖