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. 以常规方式调用资源(即,调用servlet或JSP页面)
2. 利用修改过的请求信息调用资源
3. 调用资源,但在发送响应到客户机前对其进行修改,修改响应
4. 阻止该资源调用,代之以转到其他的资源,返回一个特定状态代码或生成替换输出
5. 阻止资源调用,不转到其它资源(错误的情况)
3. 过滤器的生命周期
Filter的创建和销毁由WEB服务器负责。在启动tomcat的时候就行创建过滤器,并且执行init方法,完成对象的初始化。filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。 每次拦截到都会去执行doFilter方法。
- 实例化———————————————–web.xml
- 初始化———————————————–init()
- 执行过滤——————————————–doFilter()
- 销毁————————————————–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。
- REQUEST:当用户直接访问页面时,web容器将会调用过滤器,如果目标资源是通过请求转发(request.getRequestDisPatcher)的include方法或forward方法进行访问,那么该过滤器就不会被调用。
- INCLUDE:如果目标资源是通过request.getRequestDisPatcher的include方法进行访问,那么该过滤器将会被调用,其他情况下,不会被调用。
- FORWAED:如果目标资源是通过request.getRequestDisPatcher的forward方法进行访问,那么该过滤器将会被调用,其他情况下,不会被调用。
- 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); }
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。
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 { } }