JavaWeb之会话跟踪-过滤器和监听器

会话跟踪

在面前的章节中可以知道,Java Web正常工作的基础就是Http协议,但是Http协议是无状态的,也就是说该协议对于是哪个用户访问了服务器并不知晓,如果只是简单的访问页面不需要服务器知道用户是谁这种情况,HTTP协议完全可以满足需要,但是对于服务器需要知道访问者是谁的情况显然HTTP协议无法满足需要,例如,在购物网站购买商品,通常情况下服务器需要知道是谁购买了什么商品等信息。

会话跟踪的技术

鉴于HTTP协议无状态的属性,服务器并不知道用户有没有登录,登录的用户是谁,这就需要会话跟踪,换句话说就是要想办法让服务器知道是谁在访问服务器,或者在服务端保存一份访问者的信息。在本小节内容中,将详细了解会话跟踪的技术。

会话从浏览器访问某个页面开始,到浏览器关闭这段时间叫做会话。在会话当中可以发出任意次数的请求,会话指的是一段时间或者一个过程,例如A和B打电话的过程就是一次会话,当电话接通时此时会话开始,A、B双方交流完毕以后挂断电话,则会话结束,在会话开始到结束的过程中A和B可以说无数句话。

会话跟踪的技术通常由如下4种,其中第3种需要在页面内放置一个隐藏表单,表单内放置着能鉴别用户的信息。这么做可能会带来安全隐患,因此第3种方式并不常用。

  1. Cookie
  2. Session
  3. 隐藏表单域
  4. URL重写

Cookie及常用方法

Cookie是一小段文本,保存在客户端,随着请求与响应在客户端和浏览器之间来回传递。当向一台服务器发出请求并得到响应后,用浏览器打开开发者工具在Network选项卡中查看请求报文和响应报文,就会发现在请求头和响应头中都存在Cookie,如下图所示:

方法 描述
public void setMaxAge(int expiry) 设置Cookie的最大存活时间,单位为秒,如果expiry是正值,则代表expiry秒后cookie过期,如果是0,则响应到客户端浏览器后立刻删除,如果是负值,则表示浏览器关闭时即cookie过期。
public void setPath(String uri) 设置Cookie的路径,只有在相同路径下才能访问到对应的Cookie
public void setDomain(String domain) 设置Cookie所属的域,以“.”开头
public void setHttpOnly(boolean isHttpOnly) 设置脚本是否可以访问cookie,值为false时脚本可访问,值为true时,脚步不可访问

Session及常用方法

JavaEE中使用Session对象表示会话,通过Session中保存用户名可以让服务器记住用户的访问和判断用户是否登录,Session常用方法如下:

方法 描述
public long getCreationTime(); 获取创建Session的时间
public String getId(); 获取Session的唯一标识
public boolean isNew(); 判断当前会话是否是新的会话
public long getLastAccessedTime(); 获取最后一次请求的时间
public void setMaxInactiveInterval(int interval); 指定在servlet容器使会话失效之前客户端请求之间的时间单位为秒
public Object getAttribute(String name); 获取session中封装的数据
public void setAttribute(String name, Object value); session中封装数据
public void removeAttribute(String name); 移除session中封装的数据
public void invalidate(); 强制session过期

设置Session过期也可以在Tomcat中conf目录下web.xml中修改配置

1
2
3
<session-config>
<session-timeout>30</session-timeout>
</session-config>

会话跟踪实战

在了解了Session的常用方法以后就可以使用Session进行会话跟踪,也就是将用户的登录信息保存进Session中,在整个会话过程中服务器都可以获取到是哪个用户在访问服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package cn.bytecollege.controller;

import cn.bytecollege.entity.Student;
import cn.bytecollege.service.StudentService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.SQLException;

@WebServlet(name = "LoginController",value = "/login")
public class LoginController extends HttpServlet {
private StudentService studentService;
public LoginController(){
studentService = new StudentService();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
//模拟从数据库查询
if(username.equals("admin")&&password.equals("admin")){
//登录成功后将用户名保存进session
HttpSession session = request.getSession();
session.setAttribute("username",username);
response.sendRedirect(request.getContextPath()+"/success.jsp");
return;
}
request.setAttribute("msg","用户名或者密码错误");
request.getRequestDispatcher("login.jsp").forward(request,response);
}
}

上面的代码中模拟了登录的过程,当用户名和密码正确时,将用户名保存进session,当用户再次访问时就可以从会话中获取该用户名,一方面可以判断用户是否登录,另一方面服务器即可知道是哪个用户访问了服务器。

Session和Cookie的区别

  1. Session保存在服务端,而Cookie保存在客户端,因此Cookie安全性低于session的
  2. Session可以保存对象,而Cookie只能保存字符串
  3. Session是JSP内置对象,Cookie不是内置对象

Session的活化和钝化

  • 当客户端访问服务器,服务器创建好会话,此时如果关闭服务器,服务器会将当前Session持久化到work目录下,扩展名为.ser的文件。这个过程就叫Session钝化,需要注意的是保存在Session中的对象一定要实现序列化接口。
  • 当服务器再次启动时,web容器会读取.ser文件。并恢复该Session对象。这个过程就叫Session的活化

过滤器和监听器

过滤器概述

过滤器就是在访问目标资源前起过滤作用的中间组件。例如,污水净化设备可以看做现实中的一个过滤器,它负责将污水中的杂质过滤,从而使进入的污水变成净水。而对于Web应用程序来说,过滤器是一个和Servlet同等地位的Web组件,它可以截取客户端和资源之间的请求与响应信息,并对这些信息进行过滤,并且在此过程中目标资源并不会感知到过滤器的存在。

当Web容器接收到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么容器将把请求交给过滤器进行处理。在过滤器中,你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源。当目标资源对请求作出响应时,容器同样会将响应先转发给过滤器,在过滤器中,你可以对响应的内容进行转换,然后再将响应发送到客户端。从上述过程可以看出,客户端和目标资源并不需要知道过滤器的存在,在一个Web应用程序中,可以部署多个过滤器,这些过滤器组成了一个过滤器链。过滤器链中的每个过滤器负责特定的操作和任务,客户端的请求在这些过滤器之间传递,直到目标资源。

在请求资源时,过滤器链中的过滤器将依次对请求进行处理,并将请求传递给下一个过滤器,直到目标资源;在发送响应时,则按照相反的顺序对响应进行处理,直到客户端。过滤器并不是必须要将请求传送到下一个过滤器(或目标资源),它也可以自行对请求进行处理,然后发送响应给客户端,或者将请求转发给另一个目标资源。

下面是过滤器在Web开发中的一些主要应用:

  • 对用户请求进行统一认证。
  • 对用户的访问请求进行记录和审核。
  • 对用户发送的数据进行过滤或替换。
  • 对响应内容进行压缩,减少传输量。
  • 对请求和响应进行加解密处理。

Filter API

Filter接口

与开发Servlet要实现javax.servlet.Servlet接口类似,开发过滤器要实现javax.servlet.Filter接口,并提供一个公开的不带参数的构造方法。在Filter接口中,定义了下面的3个方法。

1
public void init(FilterConfig filterConfig) throws ServletExceptionWeb

容器调用该方法来初始化过滤器。容器在调用该方法时,向过滤器传递FilterConfig对象,FilterConfig的用法和ServletConfig类似。利用FilterConfig对象可以得到ServletContext对象,以及在部署描述符中配置的过滤器的初始化参数。在这个方法中,可以抛出ServletException异常,通知容器该过滤器不能正常工作。

1
public void doFilter(ServletRequest request, ServletResponseresponse, FilterChain chain) throws java.io.IOException,ServletExceptiondoFilter()

方法类似于Servlet接口的Service()方法。当客户端请求目标资源的时候,容器就会调用与这个目标资源相关联的过滤器的doFilter()方法。在这个方法中,可以对请求和响应进行处理,实现过滤器的特定功能。在特定的操作完成后,可以调用chain.doFilter(request, response)将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用RequestDispatcher的forward()方法,以及HttpServletResponse的sendRedirect()方法将请求转向到其他资源。需要注意的是,这个方法的请求和响应参数的类型是ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。

1
public void destroy()

Web容器调用该方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。

FilterConfig接口

javax.servlet.FilterConfig接口类似于javax.servlet.ServletConfig接口,用于在过滤器初始化时向其传递信息。FilterConfig接口由容器实现,容器将其实例作为参数传入过滤器对象的init()方法中。在FilterConfig接口中,定义了以下4个方法。

1
public java.lang.String getFilterName()

得到在部署描述符中指定的过滤器的名字。

1
public java.lang.String getInitParameter(java.lang.String name)

返回在部署描述中指定的名字为name的初始化参数的值。如果这个参数不存在,该方法将返回null。

1
public java.util.Enumeration getInitParameterNames()

返回过滤器的所有初始化参数的名字的枚举集合。如果过滤器没有初始化参数,这个方法将返回一个空的枚举集合。

1
public ServletContext getServletContext()

返回Servlet上下文对象的引用。

FilterChain接口

javax.servlet.FilterChain接口由容器实现,容器将其实例作为参数传入过滤器对象的doFilter()方法中。过滤器对象使用FilterChain对象调用过滤器链中的下一个过滤器,如果该过滤器是链中最后一个过滤器,那么将调用目标资源。FilterChain接口只有一个方法,如下:

1
public void doFilter(ServletRequest request, ServletResponseresponse) throws java.io.IOException, ServletException

调用该方法将使过滤器链中的下一个过滤器被调用。如果调用该方法的过滤器是链中最后一个过滤器,那么目标资源被调用。

过滤器部署

web.xml注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<!--过滤指定Servlet的请求-->
<servlet-name>Hello</servlet-name>
<servlet-name>TestController</servlet-name>
</filter-mapping>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>cn.bytecollege.filter.CharacterEncodingFilter</filter-class>
</filter>
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>cn.bytecollege.controller.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

注解方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.bytecollege.filter;

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;
import javax.servlet.annotation.WebFilter;
@WebFilter(value ="/CharacterEncodingFilter",urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}

过滤器实战

在第2章的学习中可以了解到在web开发中会出现请求乱码的问题,通常解决办法是在接收数据前,使用request的方法,手动设置字符编码request.setCharachterEncoding("utf-8");,这种方式虽然简洁方便,但是在所有的Servlet中都需要设置,因此急需要一个一劳永逸的方法。过滤器就可以帮助开发者实现这种效果。当客户端发送请求后,如果有与之对应的过滤器存在,则会先经过过滤器。那么在请求到达Servlet之前先对字符编码进行转换即可。下面将示范一个完整的字符编码过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.bytecollege.filter;

import java.io.IOException;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import cn.bytecollege.request.MyRequest;
@WebFilter(urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取请求方式
HttpServletRequest req = (HttpServletRequest) request;
String method = req.getMethod();
//如果是GET请求
if(method.equalsIgnoreCase("get")) {
request = new MyRequest(req);
}else if(method.equalsIgnoreCase("post")) {
request.setCharacterEncoding("utf-8");
}
chain.doFilter(request, response);
}
}

在上述代码中首先获取了请求方式,如果是post请求则直接设置请求编码,如果是get请求,则先自定义HttpServletRequestWrapper的子类(该类就是HttpServletRequest的实现类),并重写getParamter()方法,在该方法内首先获取请求参数,然后将请求参数进行转码,并返回转码后的内容,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package cn.bytecollege.request;

import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MyRequest extends HttpServletRequestWrapper{

public MyRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
//调用父类方法获取参数
String parameter = super.getParameter(name);
String s = null;
try {
s =new String(parameter.getBytes("ISO-8859-1"),"UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return s;
}

}

监听器

监听器的作用是监听Web容器的有效期事件,由容器管理。利用Listener接口监听在容器中的某个执行程序,并且根据其应用程序的需求做出适当响应。Java web提供了8个监听器,分别用于监听application、Session、request对象的创建及事件,这8个监听器如下:

接口 描述
ServletContextListener 用于监听ServletContext对象(也可以理解为application对象)的创建和销毁
ServletContextAttributeListener 用于监听ServletContext对象属性的增加、删除和修改
HttpSessionListener 用于监听Session对象的创建和销毁
HttpSessionActivationListener 监听Session的活化与钝化
HttpSessionBindingListener 用于监听Session中对象的绑定信息
HttpSessionAttributeListener 用于监听Session中属性的绑定
ServletRequestListener 用于监听ServletRequest对象的创建于销毁
ServletRequestAttributeListener 用于监听request对象属性的增加、删除和修改

ServletContextListener接口

主要监听ServletContext的创建和删除。它提供了如下两个方法:

  • contextInitialized(ServletContextEvent event)方法:通知正在收听的对象应用程序已经被加载及初始化。
  • contextDestroyed(ServletContextEvent event)方法:通知正在收听的对象应用程序已经被载出,即关闭。

ServletContextAttributeListener接口

主要监听ServletContext属性的增加、删除及修改,它提供了如下3个方法。

  • attributeAdded(ServletContextAttributeEvent event)方法:若有对象加入Application的范围,通知正在收听的对象。
  • attributeReplaced(ServletContextAttributeEvent event)方法:若在Application范围内的一个对象取代另一个对象,通知正在收听的对象。
  • attributeRemoved(ServletContextAttributeEvent event)方法:若有对象从Application的范围移除,通知正在收听的对象。

HttpSessionListener接口

该接口监听Session的创建及销毁,它提供了如下两个方法。

  • sessionCreated(HttpSessionEvent event)方法:通知正在收听的对象,session已经被加载及初始化。
  • sessionDestroyed(HttpSessionEvent event)方法:通知正在收听的对象,session已经被载出(HttpSessionEvent类的主要方法是getSession(),可以使用该方法回传一个session对象)。

HttpSessionActivationListener接口

该接口实现监听Session的活化或者钝化情况,它提供了如下3个方法。

  • sessionDidActivate(HttpSessionEvent event)方法:通知正在收听的对象,其session已经变为有效状态。
  • sessionWillPassivate(HttpSessionEvent event)方法:通知正在收听的对象,其session已经变为无效状态。

HttpSessionBindingListener接口

该接口实现监听Session对象的绑定信息,它是唯一不需要在web.xml中设置Listener的,并提供了以下两个方法。

  • valueBound(HttpSessionBindingEvent event)方法:当有对象加入session的范围时,会被自动调用。
  • valueUnBound(HttpSessionBindingEvent event)方法:当有对象从session的范围内移除时,会被自动取消调用。

HttpSessionAttributeListener接口

该接口实现监听Session中属性的设置请求,它提供了如下两个方法。

  • attributeAdded(HttpSessionBindingEvent event)方法:若有对象加入session的范围,通知正在收听的对象。
  • attributeReplaced(HttpSessionBindingEvent event)方法:若在session范围内的一个对象取代另一个对象,通知正在收听的对象。
  • attributeRemoved(HttpSessionBindingEvent event)方法:若有对象从session的范围移除,通知正在收听的对象(HttpSessionBindingEvent类主要有3个方法,即getName()、getSession()和getValues())。

ServletRequestListener接口

该接口提供了如下两个方法。

  • requestInitalized(ServletRequestEvent event)方法:通知正在收听的对象,ServletRequest已经被加载及初始化。
  • requestDestroyed(ServletRequestEvent event)方法:通知正在收听的对象,ServletRequest已经被载出,即关闭。

ServletRequestAttributeListener接口

该接口提供了如下3个方法。

  • attributeAdded (ServletRequestAttributeEvent event)方法:若有对象加入request的范围,通知正在收听的对象。
  • attributeReplaced(ServletRequestAttributeEvent event)方法:若在request范围内的一个对象取代另一个对象,通知正在收听的对象。
  • attributeRemoved(S