您的位置:首页 > 其它

[Servlet]过滤器

2015-11-24 21:41 330 查看
1. 为什么需要过滤器:

1) 应用程序的很多需求往往都集中在服务之前或者是,服务完成后回送之前;

2) 列举一下这些常见的需求:

i. 字符转换:应用程序内部要求使用统一的编码方案,但是外界请求的浏览器可能使用的是各种不同的编码方案,这就要求在接受请求后/服务之前将外界编码转换成应用内部的编码,处理完成之后但在响应给用户之前又需要将应用程序内部的编码转换成浏览器可以接受的编码再响应;

ii. 用户验证:有时候要求不同的用户享受不同的服务,我们不希望写一个大大的Servlet,在里面用各种if-else判断请求的用户是谁,然后根据用户的不同编写不同的代码块进行处理,这使得代码非常臃肿,而且模块性不强,不方便管理;

!相反,我们希望为不同类型用户分别别写不同的Servlet,然后在服务之前先判断是那种用户,然后再为这种用户分配不同的Servlet进行服务;

iii. 解压/压缩:有时用户发送的请求是经过压缩的数据,而服务器响应的数据有时也需要经过压缩才能响应(设想数据量非常大的话压缩是非常必要的),这就要求在接受请求后/服务前先将数据解压,服务后/响应前则需要将数据压缩;

iv. 性能测评:有时候需要统计一次服务的时间,这就需要在请求后/服务前设一个时间戳,然后在服务后/响应前再设一个时间戳,两个时间戳一相减就能得到服务的时长,诸如此类性能测评只有在开发阶段才需要,上线后需要去掉性能测评的功能;

3) 以上这些需求都不应该出现在业务逻辑代码中(即服务代码中,即Servlet中),因为它们都和应用程序的业务需求没有任何直接的关系,这些功能都会随时改变或者随时去掉,如果放在业务逻辑代码中那么每次这些需求的改变都会导致重新改写业务逻辑代码,这种代价是很大的,因为生怕这些功能已改会影响到原有的业务逻辑,所以还要测试干什么的,非常麻烦;

4) 所以以上这些需求都应该作为一个额外的独立的原件来设计,而这些原件就叫做过滤器,过滤器介于请求/服务、服务/响应之间,起到过滤作用(转换编码、字符替换、用户验证、解压/压缩、性能测评等);

5) Servlet/JSP提供的过滤机制:

i. 可以视需求来调换和调整过滤器的顺序(在服务之前或之后可以连续调用多个过滤器);

ii. 可以针对不同的URL或不同的Servlet应用不同的过滤器,也可以针对某个特定的Servlet应用某个特定的过滤器;

iii. 允许在请求调配之间应用过滤器;

6) 过滤器大大增强的程序的模块化,更加方便大型程序的管理;

2. 过滤器的基本实现和操作流程:

1) 所有实现了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();
}
2) 过滤器是有配置信息的:

i. 在实现Filter接口的类定义之前要使用@WebFilter标注(有属性的)设置过滤器的配置信息(或者在web.xml中注明该过滤器的配置信息);

ii. 因此,通常需要在实现类中自己定义一个private FilterConfig config或者一些自定义数据成员来接受这些配置信息,以方便在过滤时可以用到;

iii. 配置信息中需要注明该过滤器的URL模式或者Servlet目标,这些用来指定该过滤器专门用来过滤那些Servlet的请求与响应的,URL模式指定了某一系列URL的请求和相应的响应需要用该过滤器来过滤,而Servlet目标就指定了某个特定的Servlet的请求与响应要用该过滤器来过滤;

3) 过滤器运行的流程基本流程:

i. 容器收到请求后根据过滤器URL模式或者Servlet目标选定某个过滤器;

ii. 创建并加载选定的过滤器;

iii. 调用过滤器的init函数,将过滤器的配置信息读入到filterConfig中作为参数传入init,在init中用config中的信息初始化自己的数据成员;

iv. 调用doFilter开始过滤:接下来的事按照顺序发生在doFilter中

a. 进行过滤操作,如果过滤不合格则不能进行接下来的服务,因此可以抛出异常或者返回错误页面等,视具体需求自己实现;

b. 调用符合URL模式的Servlet的service方法进行服务,然后服务完毕;

c. 进行响应回送前的过滤工作;

v. 调用过滤器的destroy方法销毁过滤器;

vi. 容器进行最终的响应;

4) doFilter是需要自己实现的,但是之前讲过用户是无法自己调用Servlet的service方法的,那我们要怎样才能在doFilter中开启服务呢?那就要用到FilterChain类型的参数chain了:

i. FilterChain的doFilter方法里面会调用目标Servlet的service方法,doFilter方法不需要自己编写,它是一个API:void FilterChain.doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;

ii. FilterChain是过滤器链的意思,顾名思义就是指一串过滤器,也就是很多过滤器的意思,其实在一个Servlet服务之前有可能需要用多个过滤器连续过滤,它的doFilter方法里面会连续调用多个符合要求的过滤器进行过滤,最后才调用目标Servlet的service方法;

iii. 可以看一下FilterChain.doFilter的大致实现:伪代码

if (filterIterator.hasNext()) {
Filter filter = filterIterator.next();  // filterIterator是FilterChain的迭代器,可以遍历链中的各个过滤器
filter.doFilter(request, response, this);
}
else {
targetServlet.service(request, response);  // 如果链中没有更多的过滤器了就调用目标Servlet的service方法
}
!!可以看到这样迭代的调用最后会以栈的顺序返回,因此大致总结为:

// 下一次请求过滤前或者service前的过滤操作
chain.doFilter
// 下一次响应过滤后或者service后的过滤操作


!!这就是chain.doFilter的作用以及调用时机;

iv. 如何产生FilterChain,其实很简单,一个请求的URL符合多个过滤器的URL模式或者Servlet目标,则会将这些所有符合要求的过滤器按照排列顺序(在web.xml中声明的顺序,如果没有使用web.xml则按照在代码中的编写顺序)插入到FilterChain当中,然后传给第一个执行的过滤器的doFilter,过滤完后再传给第二个过滤器doFilter,然后一个个传下去;

3. 过滤器的配置:

1) 过滤器的配置信息主要有这5个:注册名(过滤器注册给Web容器识别的名称)、URL模式/Servlet目标、初始参数、触发类型

2) 过滤器的配置信息可以在@WebFilter中配置也可以在web.xml中配置,各个配置信息在两者中都有对应的属性和标签;

3) 绑定注册名:

i. 注册名是过滤器给Web容器识别的别名,默认情况下就是过滤器完整的类名(包括包路径),注册名必须和具体的过滤器类名绑定在一起,这和Servlet一样;

ii. @WebFilter中注册名是属性filterName,格式是:filterName="XXX",这样该注册名就和@WebFilter之后的具体类绑定在了一起;

iii. web.xml中绑定注册名需要用到<filter>标签,在该标签下用<filter-name>注册名和<filter-class>完整的包路径类名进行绑定,例如:

<filter>
<filter-name>test</filter-name>
<filter-class>com.lirx.test</filter-class>
</filter>
!!除了注册名的绑定外,其余的配置信息都要写在<filter-mapping>标签中,但是该在标签中需要首先注明这是哪个filter的配置,这时就要使用<filter-name>子标签来注明(这就是绑定时的注册名),比如:

<filter-mapping>
<filter-name>test</filter-name>
<url-pattern>/*</url-pattern>
<init-param>
...
<init-param>
...
</filter-mapping>


4) URL模式/Servlet目标:

i. 用于指定那些URL或者那些Servlet该运用该过滤器;

ii. @WebFilter中的属性是urlPatterns和servletNames,格式分别是:urlPatterns={"XXX"}和servletNames={"XXX"},如果有多个具体的Servlet可以运用那么可以这样写,servletNames={"XXX", "XXX"...}

iii. web.xml中是<url-pattern>标签和<servlet-name>标签,如果可以运用多个指定的Servlet则写多个<servlet-name>标签即可;

iv. 例如:

<filter-mapping>
<filter-name>test</filter-name>
<url-pattern>*.do</url-pattern>
<servlet-name>test.serv</servlet-name>
<servlet-name>_test.serv</servlet-name>
</filter-mapping>

!这里就表示所有以.do为结尾的URL请求都可以用test来过滤,还有指定请求test.serv和_test.serv的Servlet也可以用test来过滤;

v. 如果url-pattern和serlvet-name同时存在,而且有多个,则Web容器会先匹配url-pattern再匹配servlet-name,如果匹配完之后发现还有多个过滤器符合要求,则按照web.xml中声明的顺序作为过滤器调用的顺序;

5) 初始参数:

i. 和ServletContext等的初始参数一个概念,用来初始化过滤器类自定义的数据成员;

ii. 过滤器创建后会将所有配置信息(包括初始参数)读入FilterConfig中传给init,用户可以从config中拿出这些数据对自定义的数据成员初始化;

iii. @WebFilter中的属性是initParams,格式是和Servlet的initParam一模一样,例如:

initParams={
@WebInitParam(name="XXX", value="XXX"),
@WebInitParam(name="XXX", value="XXX"),
...
}
iv. web.xml中的标签也和Servlet的一模一样,但必须写在<filter>标签下!!:

<init-param>
<param-name>XXX</param-name>
<param-value>XXX</param-value>
</init-param>
<init-param>
<param-name>XXX</param-name>
<param-value>XXX</param-value>
</init-param>
...

!!可以通过FilterConfig的String FilterConfig.getInitParameter(String name);来获取参数值,该步骤可以在init中进行;

4) 触发时机:

i. 是指触发过滤器过滤的时机,默认情况下是只有当浏览器发出请求时才触发,但有些时候需要在请求调配间要进行过滤,比如forward和inlcude之间;

ii. @WebFilter中的属性是dispatcherTypes,格式是:dispatcherTypes={type1, type2, type3...},对于这里的type,Tomcat定义了静态常量,都属于DispatcherType类的属性,分别有:

DispatcherType.FORWARD

DispatcherType.INCLUDE

DispatcherType.REQUEST

DispatcherType.ERROR

DispatcherType.ASYNC

!!默认情况下是DispatcherType.REQUEST,前4个见名知意,最后一个是指在发生异步处理的时候套用该过滤器,异步处理在之后的章节中会具体讲到;

5) web.xml中的标签是<dispatcher>,如果满足多种调配类型则写多个<dispatcher>标签即可,而该标签的值也是预定义好的常量,分别是:REQUEST、FORWARD、INCLUDE、ERROR、ASYNC;

6) 一个完整的web.xml配置范例:

<filter>
<filter-name>test</filter-name>
<filter-class>com.lirx.test</filter-class>
<init-param>
<param-name>XXX</param-name>
<param-value>123</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>test</filter-name>
<url-pattern>/*</url-pattern>
<servlet-name>SomeServlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
...


4. 一个过滤器的简单应用——性能测评:统计服务时长

package com.lirx;

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;

public class TestFilter implements Filter {
private FilterConfig config;
private String PARAM1;
private String PARAM2;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
config = filterConfig;
PARAM1 = config.getInitParameter("PARAM1");
PARAM2 = config.getInitParameter("PARAM2");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub
long begin = System.currentTimeMillis();
chain.doFilter(request, response);
config.getServletContext().log("Request process in " +
(System.currentTimeMillis() - begin) + " milliseconds");
}

@Override
public void destroy() {
// TODO Auto-generated method stub

}
}


5. FilterConfig的方法:

1) String getFilterName(); // 返回注册名

2) String getInitParameter(String name); // 返回指定参数名的初始参数值

3) Enumeration<String> getInitParameterNames(); // 返回所有初始参数的参数名的集合

4) ServletContext getServletContext(); // 返回目标Servlet的环境句柄,需要在服务之前对Servlet环境进行操作就使用该方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: