2.1 Session & Cookie
HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。
为了跟踪用户状态,服务器可以向浏览器分配一个唯一ID,并以Cookie的形式发送到浏览器,浏览器在后续访问时总是附带此Cookie,这样,服务器就可以识别用户身份。
Session
我们把这种基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。
Servlet 提供了 HttpSession 接口,该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。
Servlet 容器使用这个接口来创建一个 HTTP 客户端和 HTTP 服务器之间的 session 会话。会话持续一个指定的时间段,跨多个连接或页面请求。
您会通过调用 HttpServletRequest 的公共方法 getSession() 来获取 HttpSession 对象,如下所示:
HttpSession session = request.getSession();
下面我们来看一个模拟用户登录的案例:
先来一段html的登录代码:
<form action="/first-session" method="post">
<span>username:</span><input name="username" /><br />
<span>password:</span><input name="password" /><br />
<button type="submit">submit</button>
</form>
接收登录信息的FirstSessionServlet
@WebServlet(urlPatterns = "/first-session")
public class FirstSessionServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
if(name != null && password != null){
if(name.equals("admin") && password.equals("admin")){
req.getSession().setAttribute("user", "admin");
resp.sendRedirect("/second-session");
return;
}
}
resp.getWriter().println("error");
}
}
登录成功跳转的SecondSessionServlet
@WebServlet(urlPatterns = "/second-session")
public class SecondSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = (String)req.getSession().getAttribute("user");
resp.getWriter().println("hello:"+user == null ? "" : user);
}
}
服务器识别Session的关键就是依靠一个名为JSESSIONID的Cookie。在Servlet中第一次调用req.getSession()时,Servlet容器自动创建一个Session ID,然后通过一个名为JSESSIONID的Cookie发送给浏览器
Cookie
实际上,Servlet提供的HttpSession本质上就是通过一个名为JSESSIONID的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用
Cookie 通常设置在 HTTP 头信息中(虽然 JavaScript 也可以直接在浏览器上设置一个 Cookie)。设置 Cookie 的 Servlet 会发送如下的头信息:
Set-Cookie: name=xyz; expires=Friday, 04-Feb-07 22:03:38 GMT; path=/; domain=localhost
正如您所看到的,Set-Cookie 头包含了一个名称值对、一个 GMT 日期、一个路径和一个域。名称和值会被 URL 编码。expires 字段是一个指令,告诉浏览器在给定的时间和日期之后"忘记"该 Cookie。
如果浏览器被配置为存储 Cookie,它将会保留此信息直到到期日期。如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送 Cookie 到服务器。浏览器的头信息可能如下所示:
Accept-Charset: utf-8
Cookie: name=xyz
案例,在原来代码的基础上,往浏览器上增一个user的cookie.
@WebServlet(urlPatterns = "/first-session")
public class FirstSessionServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
if(name != null && password != null){
if(name.equals("admin") && password.equals("admin")){
req.getSession().setAttribute("user", "admin");
Cookie cookie = new Cookie("user", "admin");
cookie.setPath("/");
cookie.setMaxAge(100);
resp.addCookie(cookie);
resp.sendRedirect("/second-session");
return;
}
}
resp.getWriter().println("error");
}
}
读取cookie
@WebServlet(urlPatterns = "/second-session")
public class SecondSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = (String)req.getSession().getAttribute("user");
resp.getWriter().println("hello:"+user == null ? "" : user);
Cookie[] cookies = req.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
resp.getWriter().println("from cookie:" + cookie.getName() + "=" + cookie.getValue());
}
}
}
}
输出:
2.2 Event & EventListener
Servlet监听器(Listener)是Java Servlet规范中定义的一种特殊类,它可以监听Web应用程序中的特定事件,并在这些事件发生时执行相应的处理,其实它就是一种观察者模式的实现。通过监听器,开发者可以在无需修改已有代码的情况下,对Servlet容器的生命周期事件、Http会话的创建和销毁、Http session属性的增加和删除、Http请求的初始化和结束等进行监控和操作。
Servlet中的事件类如下:
ServletRequestEvent
ServletContextEvent
ServletRequestAttributeEvent
ServletContextAttributeEvent
HttpSessionEvent
HttpSessionBindingEvent
Servlet中的事件接口如下:
ServletRequestListener
ServletRequestAttributeListener
ServletContextListener
ServletContextAttributeListener
HttpSessionListener
HttpSessionAttributeListener
HttpSessionBindingListener
HttpSessionActivationListener
事例:系统启动时,可以初始化一些事情, 可以定义一个类继承ServletContextListener, Spring当中org.springframework.web.context.ContextLoaderListener就实现了这个接口,当容器加载时启动并初始化Spring容器
在Web.xmll定义一个Listener
<listener>
<listener-class>com.example.listener.MyServletContextListener</listener-class>
</listener>
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 做一些初始化的动作
System.out.println("contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed");
}
}
事例2 : 如果要统计在线人数,可以定义一个类实现HttpSessionListener
public class OnlineUserCounterListener implements HttpSessionListener {
// 维护一个全局计数器或集合存储在线用户信息
public void sessionCreated(HttpSessionEvent event) {
// 增加在线人数
}
public void sessionDestroyed(HttpSessionEvent event) {
// 减少在线人数
}
}
2.3 Servlet Filter
过滤器属于Servlet规范,主要用于对到资源的请求或来自资源的响应执行过滤、筛选操作。
当存在过滤器的时候,对于来自客户端的请求来说,请求必须先经过滤器,放行之后,才能到达Web资源;对于返回的响应来说,响应同样会经过滤器,才能到达Web服务器,进而响应给客户端。
过滤器可以有如下的应用:
身份验证过滤器(Authentication Filters)。
数据压缩过滤器(Data compression Filters)。
加密过滤器(Encryption Filters)。
触发资源访问事件过滤器。
日志记录和审核过滤器(Logging and Auditing Filters)。
Filter接口的定义如下:
public interface Filter {
/*web 应用程序启动时,web 服务器将创建Filter 的实例对象,
并调用其init方法,读取web.xml配置,完成对象的初始化功能,
从而为后续的用户请求作好拦截的准备工作
(filter对象只会创建一次,init方法也只会执行一次)。
开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。*/
default public void init(FilterConfig filterConfig) throws ServletException {}
/*该方法完成实际的过滤操作,当客户端请求方法与过滤器设置匹配的URL时
,Servlet容器将先调用过滤器的doFilter方法。FilterChain用户访问后续过滤器。*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
/*Servlet容器在销毁过滤器实例前调用该方法,在该方法中释放Servlet过滤器占用的资源。*/
default public void destroy() {}
}
Filter的事例, 指定一个路径的访问权限,在session中有user并且值为admin的时候,才会放行访问,否则打印error
Web.xml定义Filter
<filter>
<filter-name>authFilter</filter-name>
<filter-class>com.example.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/auth</url-pattern>
</filter-mapping>
AuthFilter的实现:
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
Object user = httpServletRequest.getSession().getAttribute("user");
if(user != null && user.equals("admin")){
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}else{
servletResponse.getWriter().println("error");
}
}
@Override
public void destroy() {
}
}
路径/auth对应的Servlet
@WebServlet(urlPatterns = "/auth")
public class AuthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("enter auth");
}
}
刚开始访问,返回error
执行上面的登录之后
再次访问/auth
2.4 Servlet相关的注解
上面的事例可能你观察到了,为了方便,我们并没有在Web.xml定义Servlet, 而是使用@WebServlet注解,来简化了配置,除了@WebServlet, 还有Filter对应@WebFilter , Listener对应@WebListener, 大家可以自己试试。