Filter的使用和经典案例
一、什么是Filter
Filter也称之为过滤器。
通过Filter可以拦截所有对Web服务器管理的任意Web资源的请求,从而实现特殊的控制功能。例如实现url权限的控制,过滤敏感信息以及压缩响应格式等一些高级的功能。
-
FilterChain
- 在一个Web工程中,对同一个Web资源访问时,可以有多个Filter过滤器对其进行拦截,多个过滤器在tomcat服务器启动时,组成过滤器链FilterChain。
每一个Filter必须执行chain.doFilter(request,response)方法,才能将控制权交到下一个过滤器或者目标资源。
二、Filter的生命周期
生命周期方法
* init(FilterConfig):
在服务器启动时,Filter对象会被创建,init方法被执行,Filter对象只有一个,该方法只执行一次。
这里与Servlet不同,servlet在第一次访问时,执行servlet对象的初始化。
* doFilter(request,response):
在每一次请求时,只要是拦截的目标资源,就会执行
* destroy():
在服务器正常关闭、重启时执行。
生命周期
- 什么时候创建
- 服务器启动的时候创建
- 什么时候销毁
- 服务器正常关闭、重启时销毁
三、映射配置注意事项
-
如何决定过滤器执行顺序
- 过滤器先后执行的顺序取决于filter-mapping标签的顺序.
- 如果使用的是注解,执行的顺序是类名的字母排列顺序
配置过滤器进行过滤时,除了使用<url-pattern>之外,也可以使用<servlet-name>标签,但是对应的servlet也必须在web.xml中配置.
-
<dispatcher> 指定过滤器拦截目标资源的调用方式
- 四种调用方式
- REQUEST : 客户端请求过滤(实际开发中一般使用该默认方式)
- FORWARD : 转发过滤
- INCLUDE : 包含过滤
- ERROR : 错误页面过滤 web.xml 配置error-page
- 如果没有指定拦截模式,默认拦截REQUEST
- 四种调用方式
四、案例
案例一:解决全局乱码的问题
需求:
我们在用servlet执行服务器与客户端的请求响应时,为了避免产生中文乱码现象,会在servlet中解决乱码问题.
但是一旦我们的工程量比较大,servlet的数量太多的时候,解决编码的代码就会变得十分臃肿。
因为Filter过滤器可以拦截所有访问该站点的请求,因此在Filter中提前将乱码问题处理后,servlet中再获取请求提交的参数
时,就不用再考虑编码问题了。
案例分析:
1. 写一个表单提交页面,提交表单时访问servlet
2. 写一个Filter过滤器,拦截所有访问本站点的请求,提前处理编码问题
1. 在filter中获取表单提交的方式
2. 如果是POST方式提交的表单,直接用request.setCharacterEncoding("UTF-8")即可解决。
3. 如果是GET方式提交的表单,就需要先获取到提交的包含中文的参数
4. 但是我们在这里即使是将该参数重新编码,在放行的时候也不能把这个参数往下传递给servlet呀?
因此在这里就需要对HttpServletRequest的getParameter(String name)进行包装,对这个getParameter()方法进行增强。
装饰者模式:
- 写一个类(包装类)与被包装类实现同一个接口
- 在包装类中创建一个特殊的构造方法,参数为被包装类的对象,从而对被包装类进行装饰。
- 为了在包装类中能够使用被包装类进行操作,将传递进来的被包装类对象传递给本类的成员变量。
- 对需要增强或修改的方法重写。
在这里,Sun公司考虑到我们可能要对HttpServletRequest与HttpServletResponse的方法进行自定义,
因此sun公司已经定义了两个实现了上述两个接口的实现类:HttpServletRequestWrapper与HttpServletResponseWrapper.
因此我们直接继承HttpServletRequestWrapper,重写getParameter方法即可。
代码实现:
JSP:
<form action="${pageContext.request.contextPath}/encodingServlet" method="GET">
用户名<input type="text" name="username" /><br/>
密码<input type="password" name="password" /><br/>
<input type="submit" value="提交" />
</form>
Servlet:(测试)
//获取表单提交的用户名
String username = request.getParameter("username");
System.out.println(username);
//将其写回客户端
response.getWriter().write(username);
Filter代码:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
// 处理全应用的编码问题
// 1.强制转换
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setContentType("text/html;charset=UTF-8");
// 2.处理业务逻辑
// 因为在Servlet中都是通过request.getParameter()获取参数,因此用装饰者模式对该方法进行增强
MyRequest myRequest = new MyRequest(request);//对request进行装饰
//设置服务器写回数据时的编码格式
// 3.放行,将装时候的myrequest传递给servlet,这样servlet中使用getParameter方法时使用的就是增强后的方法
chain.doFilter(myRequest, response);
}
装饰者模式包装类MyRequest:
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public MyRequest(HttpServletRequest request) {
super(request);
// 将被包装对象传递给本类的成员变量,以便对其进行操作
this.request = request;
}
// 对getParameter方法增强
@Override
public String getParameter(String name) {
try {
// 2.1 获取数据提交的方式
String method = request.getMethod();
System.out.println(method);
// 2.2 根据提交的方式不同,处理编码乱码的问题
if ("POST".equalsIgnoreCase(method)) { // 如果是POST请求
request.setCharacterEncoding("UTF-8");// POST请求告知服务器使用UTF-8编码
// 如果是GET请求,就需要对request的getParameter()方法进行增强了
// 使用装饰者模式,写一个类继承sun公司提供的已经实现了HttpServletRequest的实现类
} else if ("GET".equalsIgnoreCase(method)) {
//获取GET方式提交的参数
String parameter = request.getParameter(name);
//对获取到的参数重新解码和编码
parameter = new String(parameter.getBytes("ISO-8859-1"),"UTF-8");
return parameter;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return super.getParameter(name);
}
案例二、登录页面自动登录功能的实现
分析:
自动登陆的功能
1.完成简易的登陆功能。
1.用户点击登陆后,将表单中的账密发送至服务器进行查询
2.服务器查询完毕后返回一个User对象,servlet根据user的状态做出不同的转发定向
3. 登录失败:服务器发送错误信息,客户端解析并显示在相应的位置
登陆成功:跳转到购物网页的首页
准备工作(搭建开发环境):
1:准备数据库用户表,至少需要有id,name,username,password的字段。准备用户登录的页面
2:导入jar包:
数据库连接驱动,c3p0的jar包与配置文件,dbutils的jar包,因为在jsp需要对数据进行解析,还需要jstl.jar和与之配套的standard.jar
3. 准备javabean类User和工具类JDBCUtils
4. 创建包结构
2.自动登陆功能,在第一步的登陆功能上进行完善。
Servlet:
1.servlet查询完成后,对User进行判断,如果User不为空,即用户登录成功。
这时,需要获取到name为autoLogin的checkbox的复选框状态。
2.如果复选框状态是未选中,则不进行任何任何额外的操作。
如果复选框的状态是被选中的,那么就需要将用户的账密存储起来,下次用户再访问时,服务器就自动进行自动登陆.
3. Cookie可以完成上述的要求,如果复选框被选中,将用户的账号密码存入Cookie中写回客户端存储。
Filter : (需求)无论用户下一次访问应用的哪个页面,都会检查是否有账密这个Cookie存在。
1. Filter可以拦截所有访问当前站点的请求,对其进行筛选,因此在Filiter中进行自动登录的操作。保证了用户无论访问哪个页面都能进行有效的检测.
2. Filter中获取用户请求携带过来的所有Cookies,遍历获取代表用户账号和密码的Cookie,检查其是否为空,如果为空,直接放行,让其访问网页,不提供登陆。
3.如果不为空,证明账密存在,调用业务层service处理登陆操作,操作返回一个User对象,这里也要将其存入Session。
最后放行,让其既访问了本页面又完成了自动登录的操作。
自动登录思路图:
1.搭建环境
1.1 新建数据库用户表User
1.2 导入jar包
1.3 准备javabean类User和工具类JDBCUtils
1.4 创建包结构
2.代码实现
2.1 登陆的基本功能实现
用户提交登陆数据到服务器查询,服务器查询完毕后根据返回的User对象是否存在,进行不同的操作。
如果存在,跳转到首页。如果不存在,提示用户错误信息。
由于有代码的重复,这里就不列举具体代码了,在后面一起写出来。
2.2 自动登录的功能实现
用户登录成功后,判断用户是否选择了自动登录复选框,如果选择了将其账密存入Cookie写回客户端。
同时,将该User对象存入Session中,如果用户不退出当前浏览器而去访问其他页面,可以直接从session对象中获取代表该用户的该User对象要实现:无论用户访问本站点的任何页面,都会事先监测其是否勾选了自动登录,(勾选了自动登录的用户请求会存在代表账密的Cookie)。
要完成上面的要求,Filter刚好可以对访问本应用的所有资源的请求进行拦截,因此使用Filter过滤器进行操作。
1. 使用用户的请求获取Session,从而获取存在Session域对象的User对象.这一步主要是因为一个用户在没有
退出当前客户端时,进行的其他页面访问时,可以直接从Session中获取到代表该用户的user对象而不需要重新登陆。
2. 如果用户存在,直接放行,可以从Session中获取user对象。
如果user对象不存在,就需要获取请求所携带的所有Cookies,获取它们当中代表用户账号密码的Cookie。
3. 从代表用户账密的Cookie中获取用户的账号密码,判断账号密码的状态。
如果账号密码为空,那么放行,其访问页面资源,但是是未登录的状态。
如果账号密码不为空,那么就调用业务层去数据库中查询,根据该账密返回一个User对象。
4. 判断返回的user对象的状态。
如果为空,说明登录失败,放行。
如果不为空,说明登陆成功,将该user对象存入session对象,放行。
web层
Servlet代码 :
// 1.获取登陆表单提交的账号密码
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username);
System.out.println(password);
try {
// 2.调用service/dao层查询,将获取的账密作为参数传递
LoginService service = new LoginService();
User user = service.login(username, password);
// 3.dao层查询返回一个User对象,根据User对象是否为null做出不同的处理
if (user != null) {
// 获得自动登陆的复选框状态,检查用户是否勾选了该复选框
String autologin = request.getParameter("autologin");
// 判断,如果勾选了该复选框,就将账号密码添加到Cookie中写回客户端
if ("autologin".equals(autologin)) { // 用户勾选了该复选框
Cookie cookie = new Cookie("username", username);// 存储账号的Cookie
Cookie cookie2 = new Cookie("password", password);// 存储密码的Cookie
cookie.setMaxAge(60*60*24*7);//设置cookie的最大生存时间为7天
cookie2.setMaxAge(60*60*24*7);
cookie.setPath("/");//设置该cookie的有效路径,斜线"/"代表当前应用都有效
cookie2.setPath("/");
//将Cookie写回客户端
response.addCookie(cookie);
response.addCookie(cookie2);
}
// 登陆成功,将该user对象存入session对象,以便在当前web站点所有页面都能获取到该user对象
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 重定向去到首页
response.sendRedirect(request.getContextPath() + "/index.jsp");
} else {
// 登陆失败给用户提示错误信息
request.setAttribute("msg", "账号或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
}
service层
LoginDAO dao = new LoginDAO();
return dao.login(username,password);
dao层
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from user where username=? and password =?";
return runner.query(sql, new BeanHandler<User>(User.class), username,password);
Filter过滤器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// Filter的url-pattern设为/*拦截访问当前应用的所有请求
// 1.强制转换request和response
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 2.实现业务逻辑
// 2.1 首先获取存在session中的user对象.
// 这一步是因为:如果用户在登陆后,继续点击其他页面而没有退出当前浏览器的时候,就可以直接从session中取出登陆成功时存在里面的user对象,不需要再次执行登陆的操作。
HttpSession session = request.getSession();// 获取该客户端当前web应用存储的session域对象
User user = (User) session.getAttribute("user");// 获取该客户端用户存储在当前web应用的user对象
try {
// 2.2接下来判断该user对象是否存在,如果存在,,放行,session中本就存在该user对象,无需在存储直接放行即可.如果不存在,说明session可能过期或其他原因找不到了.
// 如果user对象不存在,就需要获取请求中带过来的cookies,如果用户勾选了自动登录并且cookie没有过期,那么其请求中会携带账密的cookie过来
if (user == null) { // 用户user不存在 用户存在的话不需要进行操作,不会进入if语句,在最后执行放行代码即可
// 获取请求携带的所有cookies
Cookie[] cookies = request.getCookies();
String username = null;// 用户的账号
String password = null;// 用户的密码
// 遍历所有cookie,获取代表用户账号密码的Cookie
if (cookies != null) {
for (Cookie cookie : cookies) { // 如果用户勾选了自动登录并且Cookie未失效,就会存在username和password的Cookie
String name = cookie.getName();// 获取每一个Cookie的key
String value = cookie.getValue();// 获取每一个Cookie的value
if ("username".equals(name)) {
username = value;
}
if ("password".equals(name)) {
password = value;
}
}
// 2.3获取到账密的Cookie进行判断,如果为空,就直接放行让其访问页面,如果不为空,就调用业务层执行登陆操作,返回一个User对象。
if (username != null && password != null) { // 从Cookie中获得的用户名和账号都不为空,调用业务层执行登陆操作
LoginService service = new LoginService();
User user2 = service.login(username, password);// 登陆后返回的user对象
// 2.4判断User对象是否存在,如果不存在说明账密错误自动登陆失败,放行。如果存在,将该对象存入session中,自动登录成功,并放行。
if (user2 != null) { // 用户登陆成功,将代表该用户的对象存入session域
session.setAttribute("user", user2);// 这里的存入与Servlet中存储的name要保持一致
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 3.放行
chain.doFilter(request, response);
}
}
测试结果:勾选自动登录后,无论访问本站点的哪个页面,重复登陆无需再输入用户密码,直接跳转到首页。