过滤器
Java Web的三大组件
Servlet中组件一共有三种:Servlet、过滤器、监听器
组件 | 作用 | 实现接口 |
---|---|---|
Servlet | 是一个运行在服务器端的Java小程序,用来接收请求并做出响应 | javax.servlet.Servlet |
过滤器 | 用于拦截用户的请求和响应,并且修改请求中的数据,对数据进行处理。 | javax.servlet.Filter |
监听器 | 监听Web程序在运行过程中对作用域操作的事件,并且对事件进行处理 多个不同的接口 | javax.servlet.XxxListener |
过滤器的概念
过滤器是服务器与客户端请求与响应的中间层组件,在实际项目开发中过滤器主要用于对浏览器的请求进行过滤处理,将过滤后的请求再转给下一个资源。与其他的WEB应用程序组件不同的是,过滤器是采用了“链”的方式进行处理的。
过滤器的使用场景:
- 对用户登录权限进行拦截
- 实现一些日志记录的功能
- 集中处理处理一些公共的功能,如:汉字编码和解码
过滤器的执行特点:
与Servlet的执行不同,Servlet是有访问的地址。不是由用户主动调用,而是自动执行,是通过匹配用户的访问地址去进行过滤。
过滤器编写步骤:
开发过滤器的步骤:
- 编写一个类,实现javax.servlet.Filter接口
- 实现接口中所有的方法,其中doFilter()就是执行过滤任务
- 过滤器需要在web.xml中进行配置,配置与Servlet类似。
- 示例:创建一个过滤器HelloFilter,在运行HelloServlet前和后分别输出一句话,在HelloServlet中也输出一句话,观察控制台的运行效果。HelloServlet代表Web资源
/**
* a) 编写一个类,实现javax.servlet.Filter接口
* @author NewBoy
*
*/
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 执行过滤任务
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("请求经过过滤器");
//放行,让请求继续到达web资源(Servlet),调用chain中的方法
chain.doFilter(request, response);
System.out.println("响应回来经过过滤器");
}
@Override
public void destroy() {
}
}
Servlet
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("我是HelloServlet,Web资源");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
配置文件
<!-- 配置过滤器 -->
<filter>
<!-- 过滤器的名字 -->
<filter-name>hello</filter-name>
<!-- 实现了Filter接口的类全名 -->
<filter-class>com.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<!-- 过滤器的名字,与上面的一样 -->
<filter-name>hello</filter-name>
<!-- 要过滤的路径 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器的执行流程
- 用户发送请求,如果请求的地址匹配过滤器的url-pattern,则执行过滤器
- 执行过滤器中的doFilter方法,由chain.doFilter方法对请求进行放行
- 到达Web资源,响应回来的时候会再次经过过滤,执行响应的代码。
过滤器的生命周期:
过滤器加载的时机:
Servlet是用户第1次访问的时候实例化,并且初始化。
过滤器是在Web服务器启动加载当前项目完毕以后自动实例化
生命周期的方法:
Filter接口中的方法
- void init(FilterConfig filterConfig)
- 在初始化过滤器的时候**执行1次**
- void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- 参数:请求,响应,过滤链
- 请求和响应这两个接口是HttpServletRequest和HttpServletResponse的父接口
- 执行过滤的功能,**每次请求都会执行这个方法**
- public void destroy()
- 在服务器关闭的时候销毁,执行1次
过滤器的映射路径
Filter与Servlet中的区别:
在Servlet中url-pattern是访问的地址
在Filter中url-pattern是要过滤的地址
问:浏览器访问目标资源的路径,如果目标地址不存在,过滤器会不会运行?
如果Web资源不存在,只要匹配过滤的地址,同样会执行过滤器。
在Filter中URL的过滤方式
匹配方式 | 匹配哪些资源 | 示例 |
---|---|---|
完全匹配 | 必须与访问地址精确匹配 | /demo1 、/aaa/bbb |
目录匹配 | 匹配某一个目录下所有的Web资源 | /aaa/* 、/* 匹配所有的资源 |
扩展名匹配 | *.扩展名 匹配某一类扩展名 | *.do 、*.action |
有关匹配的要点:
- 以/开头的匹配模式和以扩展名结尾的配置方式,同时出现会怎样?
同时出现会在web项目加载的时候就失败,导致当前项目所有的web资源都无法访问。
过滤多个地址的写法:
一个filter-mapping中包含多个url-pattern
<filter>
<filter-name>life</filter-name>
<filter-class>com.filter.Demo1LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<url-pattern>/demo1</url-pattern>
<url-pattern>/demo2</url-pattern>
</filter-mapping>
一个filter对应多个filter-mapping
<filter>
<filter-name>life</filter-name>
<filter-class>com.filter.Demo1LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<url-pattern>/demo1</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>life</filter-name>
<url-pattern>/demo2</url-pattern>
</filter-mapping>
过滤Servlet的写法:
<filter>
<filter-name>life</filter-name>
<filter-class>com.filter.Demo1LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<!-- 指定Servlet的名字进行过滤 -->
<servlet-name>HelloServlet</servlet-name>
</filter-mapping>
过滤器的拦截方式:
默认的拦截方式
过滤器的拦截方式一共有4种,
REQUEST、FORWARD、INCLUDE、ERROR默认是请求的方式:只有直接来源于浏览器的请求,才经过过滤器。
来自转发的请求被拦截
- 在index.jsp转发到HelloServlet
<jsp:forward page="/demo1"></jsp:forward> 发现没有经过过滤器,但servlet还是访问了。
- 过滤器的配置
<filter>
<filter-name>life</filter-name>
<filter-class>com.filter.Demo1LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<url-pattern>/demo1</url-pattern>
<!-- 配置拦截方式: 转发的时候经过过滤器 -->
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
- 这样子转发也经过了过滤器
同时写多个拦截方式
<filter>
<filter-name>life</filter-name>
<filter-class>com.filter.Demo1LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<!-- 指定Servlet的名字进行过滤 -->
<!-- <servlet-name>HelloServlet</servlet-name> -->
<url-pattern>/demo1</url-pattern>
<!-- 配置拦截方式: 转发的时候经过过滤器 -->
<dispatcher>FORWARD</dispatcher>
<!-- 拦截方式:浏览器直接的请求经过过滤器,默认的方式 -->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
过滤器的拦截类型小结
过滤类型 | 作用 |
---|---|
REQUEST | 直接来源于浏览器的访问地址,(包含重定向) |
FORWARD | 转发的时候经过过滤器 |
INCLUDE | 页面包络另一个页面访问时也经过过滤器 |
ERROR | 页面错误时经过过滤器 |
FilterConfig对象
- 什么是FilterConfig对象:
用于得到过滤器中的配置参数
<filter>
<filter-name>Demo2ConfigFilter</filter-name>
<filter-class>com.filter.Demo2ConfigFilter</filter-class>
<!-- 过滤器的初始配置参数 -->
<init-param>
<param-name>charset</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>country</param-name>
<param-value>China</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Demo2ConfigFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
FilterConfig三个方法:
方法名 | 功能 |
---|---|
String getInitParameter(String name) | 通过初始参数名得到参数值 |
Enumeration getInitParameterNames() | 得到所有的初始参数名字,返回枚举类 |
ServletContext getServletContext() | 得到上下文对象 |
- 在过滤器初始化init方法中把上面的配置参数输出
//得到初始的配置参数
String charset = filterConfig.getInitParameter("charset");
String country = filterConfig.getInitParameter("country");
System.out.println(charset);
System.out.println(country);
//得到所有的配置参数名
Enumeration<String> names = filterConfig.getInitParameterNames();
//遍历
while (names.hasMoreElements()) {
//得到其中的一个名字
String name = names.nextElement();
System.out.println("名字:" + name + ", 值:" + filterConfig.getInitParameter(name));
}
案例:POST解码
- 需求:编写过滤器,过滤所有Servlet中使用POST方法提交的汉字的编码。
- 有2个Servlet,一个是LoginServlet登录,一个是RegisterServlet注册
- 有2个JSP页面,1个是login.jsp,有表单,登录名。1个register.jsp,有表单,有注册的名字。都使用POST提交用户名使用汉字提交。
- 使用过滤器,对所有的Servlet的POST方法进行过滤。
- 过滤的编码参数,通过filterConfig得到。
- 在没有使用过滤器之前,每个Servlet必须加上汉字编码:request.setCharacterEncoding(字符集); 字符集与网页的编码要一致
//过滤的编码参数,通过filterConfig得到
@Override
public void init(FilterConfig config) throws ServletException {
charset = config.getInitParameter("charset");
System.out.println(charset);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//设置汉字的编码, charset相当于utf-8
request.setCharacterEncoding(charset);
//放行
chain.doFilter(request, response);
}
过滤器配置
<!-- 编码的过滤器 -->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.filter.EncodingFilter</filter-class>
<!-- 编码的字符集 -->
<init-param>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<!-- 同时过滤2个Servlet -->
<url-pattern>/login</url-pattern>
<url-pattern>/register</url-pattern>
</filter-mapping>
过滤链
什么是过滤器链:
一次请求可以同时经过多个过滤器,每个过滤器会将请求传递给下一个过滤器,如果下一个没有过滤器了,则传递给Web资源,这多个过滤器就组成了一个过滤器链。请求的时候经过每一个过滤器,响应的时候以相反顺序再回到每一个过滤器。
过滤器的顺序是根据配置文件中配置的顺序
Filter接口:
Filter接口中的方法:过滤器接口
- 生命周期的方法
Filter接口中的方法 | 参数的作用 |
---|---|
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | **ServletRequest**:代表请求对象是HttpServletRequest接口的父接口,如果要使用HttpServletRequest对象,**需要对类型进行强转**。**ServletResponse**: 代表响应对象是HttpServletResponse的父接口,如果要使用HttpServletResponse对象,**需要对类型进行强转**。**FilterChain**:代表过滤链对象用于请求的放行。 |
FilterChain接口中的方法:过滤链接口
FilterChain接口中的方法 参数的作用
放行
doFilter(ServletRequest request, ServletResponse response) ServletRequest:代表请求对象,是HttpServletRequest接口的父接口,如果要使用HttpServletRequest对象,需要对类型进行强转。
ServletResponse: 代表响应对象,是HttpServletResponse的父接口,如果要使用HttpServletResponse对象,需要对类型进行强转。
将上一个请求传递给下一个过滤器或Web资源
示例:过滤器链
- 需求:创建两个过滤器OneFilter和TwoFilter,访问ResourceServlet,每个过滤器的请求和响应各输出一句话,观察过滤器的执行过程。
第一个过滤器
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//处理请求
System.out.println("请求经过了第1个过滤器");
//放行
chain.doFilter(request, response);
//处理响应
System.out.println("响应经过了第1个过滤器");
}
第二个过滤器
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//处理请求
System.out.println("请求经过了第2个过滤器");
//放行
chain.doFilter(request, response);
//处理响应
System.out.println("响应经过了第2个过滤器");
}
*配置参数的顺序!!*
<!-- 配置第1个过滤器 -->
<filter>
<filter-name>OneFilter</filter-name>
<filter-class>com.filter.OneFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OneFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置第2个过滤器 -->
<filter>
<filter-name>TwoFilter</filter-name>
<filter-class>com.filter.TwoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TwoFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
web.xml中出现的顺序,哪个配置在前面,哪个就是前面的过滤器。
案例
实现步骤:
1. 在WebRoot下创建页面 login.jsp上使用${msg},显示信息。
1. 创建LoginServlet, 判断用户名密码是否正确,如果正确,则在会话域中保存用户信息。登录成功跳转到list.jsp,登录失败则在域中写入登录失败的信息,并且跳转到login.jsp。
使用过滤器解决:创建LoginAuthorityFilter
创建成员变量HashSet,用于保存所有不经过过滤器的页面
在init方法中添加登录页面和登录的Servlet到HashSet中
- 在doFilter过滤的方法中获取当前访问的路径,判断当前路径是否在集合中,如果在集合中则放行并返回
- 否则判断会话域中是否有指定的用户信息,如果有则放行,并返回
- 最后重定向到登录页面
过滤器
public class LoginAuthorityFilter implements Filter {
//创建成员变量HashSet,用于保存所有不经过过滤器的页面
private HashSet<String> paths = new HashSet<String>();
@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;
//得到会话域
HttpSession session = request.getSession();
//在doFilter过滤的方法中获取当前访问的路径,判断当前路径是否在集合中
String path = request.getServletPath();
System.out.println(path);
if (paths.contains(path)) {
//如果在集合中则放行并返回
chain.doFilter(request, response);
return;
}
//否则判断会话域中是否有指定的用户信息
String user = (String) session.getAttribute("user");
if (user != null) {
//如果有则放行,并返回
chain.doFilter(request, response);
return;
}
System.out.println("拦截到非法的用户:" + request.getRemoteAddr());
//最后重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login.jsp");
}
@Override
public void init(FilterConfig config) throws ServletException {
// 在init方法中添加登录页面和登录的Servlet到HashSet中
paths.add("/login.jsp");
paths.add("/login");
}
}
servlet
HttpSession session = request.getSession();
//得到用户名和密码
String name = request.getParameter("user");
String pwd = request.getParameter("pwd");
//判断是否登录成功
if ("newboy".equals(name) && "123".equals(pwd)) {
//如果登录成功,将用户的信息保存到会话域中
session.setAttribute("user", name);
//跳转到list.jsp
response.sendRedirect(request.getContextPath() + "/list.jsp");
}
//如果登录失败,写错误信息到请求域,跳转到login.jsp显示出来
else {
request.setAttribute("msg", "用户名或密码不正确");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}