Servlet笔记

这里记录了有关我重新整理和理解的有关Servlet的知识,这些基础知识极为的重要,因此给自己一个整理思路的机会。

第一章 Servlet简介及Servlet示例入门程序

对于每一个HTTP请求,Servlet容器都会创建一个ServletRequest实例,并将它转给Servlet的Service方法,ServletRequest封装了关于这个请求的信息。

ServletResponse接口表示一个Servlet响应,在调用Servlet的Service方法前会创建一个ServletResponse实例,并传递给Servlet的service方法。该类隐藏了向浏览器发送响应的复杂过程。

在ServletResponse中定义的一个方法为getWriter(),该方法返回了一个PrintWriter对象,默认情况下,该PrintWriter对象使用ISO-8859-1编码。

以及一个getOutputStream()方法,该方法用于发送二进制数据。

在发送任何HTML标签前,应该先调用setContentType()方法,设置响应的内容类型。并将text/html作为一个参数传入。

示例:

打开开发工具,创建一个Java Web项目。

添加任意package,并新建MyServlet类。

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "MyServlet", urlPatterns = {"/my"})
public class MyServlet implements Servlet{
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
System.out.println("servletConfig init");
}
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String servletName = servletConfig.getServletName();
System.out.println(servletRequest.getContentLength());
System.out.println(servletRequest.getContentType());
System.out.println(servletRequest.getProtocol());
servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
writer.print("<html><head><title>my servlet</title></head><body>"+ "Hello:" + servletName +"</body></html>");
}
@Override
public String getServletInfo() {
return "My Servlet";
}
@Override
public void destroy() {
System.out.println("close my servlet");
}
}

点击run,在浏览器中访问:http://localhost:8080/项目名/my

访问后,页面出现Hello:MyServlet,表示运行成功。

@WebServlet(name = "MyServlet", urlPatterns = {"/my"})

@WebServlet 将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据属性配置将类部署为 Servlet。

访问上方的http://localhost:8080/项目名/my时,会在控制台打印一次“servletConfig init”,仅在第一次访问时打印。

我们需要理解的一点是,任何的Servlet对象,会拥有以下几个方法。

void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();

其中我们可以看到void init(ServletConfig var1) 方法。

在我们上方给出的示例中,我们重写了该init方法,在其中初始化了ServletConfig并使用System.out.println方法打印了该字符。

Servlet有以下的生命周期:

通常在第一次访问该Servlet时,会执行init方法,初始化该Servlet方法,随后访问service方法提供对应的服务,在之后的多次访问该Servlet时,init方法不会再次执行,当关闭servlet容器时,例如关闭tomcat服务器时,会调用destroy方法执行清理。

以一个简单的操作数据库数据的应用场景为例,在init方法中编写获取JDBC对象,在Service方法中根据HTTP请求传递的参数来决定操作的数据行,可以在destory方法中关闭数据库连接。当服务器关闭时,会自动的调用destory方法,来执行关闭数据库连接的操作。

@WebServlet详解

该注解注解在Servlet类名上,通常有如下属性:

在第一章的示例中,我们已经使用到了其中的两个属性,即:

@WebServlet(name = "MyServlet", urlPatterns = {"/my"})

当我们访问http://localhost:8080/项目名/my时,tomcat会寻找到该servlet,并发现该servlet被@WebServlet注解,且其中urlPatterns的属性值为/my,与我们访问的地址一致,这时就会执行该servlet对象的service方法,并根据方法的内容来返回对应的内容。

现在,让我们来尝试使用@WebServlet的initParams属性来注入一些初始化的信息。

创建ServletConfigDemoServlet类,并复制以下代码到该类文件中。

import javax.servlet.*;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "ServletDemo",
urlPatterns = {"/servletDemo"},
initParams = {
@WebInitParam(name="admin",value = "Hallo"),
@WebInitParam(name = "email",value = "hahah@c.cn")
})
public class ServletConfigDemoServlet implements Servlet {
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
String admin = servletConfig.getInitParameter("admin");
String email = servletConfig.getInitParameter("email");
servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
writer.print(admin);
writer.print(email);
}
@Override
public String getServletInfo() {
return "servlet demo";
}
@Override
public void destroy() {
System.out.println("close servlet demo");
}
}

运行项目,访问http://localhost:8080/项目名/servletDemo,可以看到网页出现了如下内容:Hallohahah@c.cn

在代码的class类名上方,你会发现一个@WebServlet注解,在该注解中,拥有一个name属性,其属性值为ServletDemo,且有一个urlPatterns属性,该属性值指明了该Serlvet的web访问路径,并且还拥有了一个initParams属性,在该属性中拥有两个@WebInitParam,注入了变量名为admin,变量值为Hallo的属性以及注入了变量名为email,变量值为hahah@c.cn的属性。

这些注入的初始属性如何使用呢?在下方的service方法中,我们使用了getServletConfig();获取了一个ServletConfig对象,该对象封装了当前Servlet的一些信息,其中就包括我们在@WebServlet中注入的initParams属性中的两个使用@WebInitParam变量。

然后我们使用servletConfig对象的getInitParameter方法来获取这两个变量,getInitParameter方法传递的参数则是我们注入到ServletConfig中的变量名,返回的是该变量名所指向的值,在上方我们可以看到注入了变量名为admin,值为Hallo的变量,在service中我们使用servletConfig.getInitParameter("admin");来获得这个值,并在下方使用PrintWriter对象将该值写入到ServletResponse对象中。+

其他的属性,请自行测试。

第三章 ServletRequest详解

ServletRequest是serlvet为我们封装好的一个HTTP请求

  1. 在Web程序运行的过程中,用户访问一个Serlvet对象的url地址,当服务器获取到该用户的HTTP请求时,会寻找对应的Servlet实例

  2. 当寻找到该实例时,会初始化一个ServletRequest对象,并将用户的请求信息封装到该对象中

  3. 若该Servlet为第一次调用,则会执行init方法,然后调用该Servlet实例的Service方法

  4. 将封装了该用户访问信息的ServletRequest对象作为参数传递给该Servlet实例的service方法

在上面的流程中,我们可以看到ServletRequest实际上是封装了用户的HTTP请求,为了便捷程序员的开发,提供了一些get方法用于获取用户的请求信息,而不需要程序员亲自编写HTTP请求的解析过程,直接使用ServletRequest对象提供的方法获取需要属性即可。

在ServletRequest对象中,通常有以下几个常用的方法:

int getContentLength(); 返回请求主体的长度,若无法获取请求的主体长度,则返回-1
String getContentType(); 返回请求主体的MIME类型,若无法获取,返回NULL
String getParameter(String var1); 参数为HTML表单域中的name,返回HTML表单域中指定name变量的值
String getProtocol(); 返回该HTTP请求的协议名称与版本

第四章 ServletResponse详解

在第二章的代码中,我们可以看到servlet提供的service方法有如下的参数

public void service(ServletRequest servletRequest, ServletResponse servletResponse);

其中ServletRequest已经在第三章解释过了,那么后面这个ServletResponse又是用来干嘛的呢?

打开源码,我们可以发现他是一个接口,该接口提供了如下的方法。

public interface ServletResponse {
String getCharacterEncoding();
String getContentType();
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setBufferSize(int var1);
int getBufferSize();
void flushBuffer() throws IOException;
void resetBuffer();
boolean isCommitted();
void reset();
void setLocale(Locale var1);
Locale getLocale();
}

可以看到提供了一些get与set的方法,那么这些方法的具体作用是什么呢?

让我们重写回到第二章的代码,发现了如下的一些代码片段。

servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
writer.print(admin);
writer.print(email);

我们使用service传递进来的servletResponse对象的setContentType方法设置了一个"text/html"的类型。

然后使用该对象的getWriter()方法获取到了一个PrintWriter的对象,最后使用获取到的PrintWriter方法的print方法打印了admin与email这两个变量。

在我们运行项目时,访问第二章提供的servlet代码时,会在页面上显示:Hallohahah@c.cn,而在class上方,有一个@WebServlet的注解,注解中我们注入了这两个变量。很明显,我们打印的就是注入的这两个变量的值,那么我们在访问在Servlet的时候,是执行了service方法,那么执行之后为什么会在页面上显示出这两个变量的值呢?

因为在我们访问service时,servlet容器自动为我们创建了两个对象,其中一个对象就是之前提到过的ServletRequest,它封装了用户的请求信息,另外一个则是我们现在讲到的ServletResponse,它封装了我们对用户的响应,我们可以利用它提供的方法来对用户进行响应,例如我们利用ServletResponse对象提供的setContentType方法指定了这个响应的类型为“text/html”,然后使用getWriter()方法获取到响应的流,并且在该流中使用print的方法并将两个变量当作值传递进去,当方法执行完毕时,serlvet容器会将响应的内容,即ServletResponse对象进行处理,然后响应给用户,这时候我们就会在页面上看到admin与email的值。

有关ServletResponse接口提供的其他的方法的作用,可以查看官方提供的文档:http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletResponse.html

第五章 GenericServlet详解

在之前的代码中,我们都是实现了Servlet接口的方法,并编写该接口的方法以完成一个Servlet的编写。

可以看到,在之前的代码中,我们有很多的代码都是可以省略掉的,如果每次我们编写一个Servlet,都需要重写我们不需要的方法的话,或者是编写重复的初始化代码的话,会大大的增加我们的代码量以及对于后续的维护造成一定的麻烦。

幸运的是,早就有人想好了解决办法。

在通常情况下,我们编写servlet只需要编写其中的service方法,少数情况下可能会需要重写其他的方法。

那么GenericServlet这个类,就为我们提供了servlet接口中绝大多数方法的实现,而保留了service方法让我们自己实现。

先来看看GenericServlet类的源码。

package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}

可以看到GenericServlet是一个抽象类,并且实现了Servlet, ServletConfig, Serializable这三个接口,其中Servlet我们已经略为了解了,ServletConfig在之前的代码中出现过,而Serializable接口则是序列化接口。

可以看到的是,GenericServlet这个抽象类,已经为我们提供了默认的初始化方式,并且提供了一个私有的ServletConfig对象,在之前我们编写的代码中,都是需要我们自己手动来增加这个对象的,并且在init方法中对ServletConfig 对象进行了初始化。

下面让我们来继承GenericServlet,看看使用它与使用servlet接口有什么不同。

import javax.servlet.*;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "GenericServletDemo",
urlPatterns = {"/genericServletDemo"},
initParams = {
@WebInitParam(name = "admin",value = "li"),
@WebInitParam(name = "email",value = "sb@h.com")
} )
public class GenericServletDemo extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
ServletConfig config = getServletConfig();
String admin = config.getInitParameter("admin");
String email = config.getInitParameter("email");
servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
writer.print(admin);
writer.print("<br>");
writer.print(email);
}
}

还是第二章熟悉的内容,访问该servlet会在网页出现admin与email变量的值。

运行项目,访问http://localhost:8080/项目名/genericServletDemo

就会出现admin与email的值。

相比第二章的大段代码,继承GenericServlet显得精简了许多,我们只需要重写service方法即可完成同样的功能,为我们编写程序省下了不少的工作量,而且继承了同样的类可以让代码更加的清晰明了。

第六章 Http Servlets详解

在通常情况下,我们编写一个Java Web应用时,用户通常会发起一个HTTP请求,然后服务器返回一个页面或者一些数据,这个页面通常来说会是一个HTML格式的页面,而在我们1-5章的时候,当你访问某一个Servlet时,返回页面后,你可以右键查看网页源码,你会发现它并不是标准的HTML格式的页面,以第五章为例,访问http://localhost/genericServletDemo时,使用chrome浏览器右键选择查看网页源代码,你会看见这样的内容

li<br>sb@h.com

很明显,这不是标准的HTML代码,那么为什么chrome能够正确的解析呢?这又是另外一个话题,但是在通常情况下,用户的浏览器可能会有许多种,那么这时候,我们就需要返回的是一个标准的HTML格式的页面。就像下面这样:

<html>
<head>
<title>Hello</title>
</head>
<body>
<p>li</p><br>
<p>sb@h.com</p>
</body>
</html>

好了,回到正题,所谓的Http Servlets,是servlet封装好的一组有关HTTP请求的的Servlet组件,在通常的web开发中,最常用的协议莫过于HTTP协议了,因此封装HTTP请求的Servlet组件会让开发过程变得尤为方便。

Http Servlets通常情况下,以下的几个类需要熟练掌握:

  1. HttpServletRequest

  2. HttpServletResponse

  3. HttpServlet

由其名称即可知道,他们都是对于前面我们学习过的ServletRequest / ServletResponse / Servlet的封装,使其更好的支持HTTP协议的请求。

首先我们来看HttpServlet,看它对于我们开发做了哪些帮助。

public abstract class HttpServlet extends GenericServlet

以上是头部内容,显示了HttpServlet是一个抽象类,并且继承了之前说过的GenericServlet,对于GenericServlet类的特性想必你还没有忘记。

既然HttpServlet继承了GenericServlet,那么它也依旧是一个Servlet,所以它一定会有service方法,那么就让我们来看看它的service方法。

HttpServlet源码部分:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

看到这里你是不是会觉得很奇怪呢?为什么会有两个service方法,而不是和servlet接口一样只有一个

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

这样的接口方法实现呢?

在通常情况下,用户发起一个请求,servlet容器会根据请求寻找对应的servlet,然后创建servletRequest和servletResponse对象并作为参数传递进去。而当我们继承HttpServlet类时,其实也是一样的流程,但是在中间多了一个步骤,即将servletRequest转换为HttpServletRequest ,将servletResponse转换为HttpServletResponse。

那么这个转换是在哪一步执行的呢?

在上方我们提供了HttpServlet的两个service方法的源码,相信你已经理解转换发生在哪一步了。

我们继承的HttpServlet类,Servlet容器依旧将其当作一个正常的servlet类处理,当用户请求servlet时,servlet容器找到该servlet,然后创建servletRequest和servletResponse,并将其作为参数传递给上方第一个service方法,即

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request; //这是一个正常的service方法,servlet容器调用该方法并传递ServletRequest和ServletResponse对象
HttpServletResponse response;
try {
request = (HttpServletRequest)req; //然后会将传递的ServletRequest 对象转为HttpServletRequest对象
response = (HttpServletResponse)res; //以及将ServletResponse对象转为HttpServletResponse对象
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response"); //如果出现转换异常则报错
}
this.service(request, response); //然后调用当前类的service方法,即上方源码部分第二个service方法
}

接下来我们将目光转到源码的第二个service方法。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod(); //1.获取request对象中的方法类型
long lastModified;
if (method.equals("GET")) { //2.判断方法类型
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

可以看到,在第二个方法类型中,该service方法根据传递的HttpServletRequest对象中的参数,经过一系列的判断,来确定需要执行哪个方法。而这些方法我们可以在他的源码中看到。

以下为HttpServlet类的方法列表,包括构造方法:

可以看到HttpServlet支持多种HTTP请求的方法,例如Post,Get,Delete方法。

有关HttpServletRequest和HttpServletResponse接口的使用,请参阅API文档。

下面给出一个简单的示例,看看如何使用HttpServlet。

public class SimpleServlet extends HttpServlet{
private static final long serialVersionUID = 121L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("Simple servlet");
}
}

然后你需要编写web.xml,示例如下:

<?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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>com.noesblog.servlet.servlet02.SimpleServlet</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SimpleServlet</servlet-name>
<url-pattern>/simple</url-pattern>
</servlet-mapping>
</web-app>

在之前的开发中,我们使用@WebServlet注解来对servlet类直接进行注解,但是我们仍需要熟练编写部署描述符,即在web.xml中配置servlet。

在上方web.xml示例中

<servlet> 该标签表示定义一个servlet
<servlet-name> 表示定义该servlet名称
<servlet-class> 该servlet类的所在路径
<load-on-startup> 加载的优先级,可以不写,
load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
1.它的值必须是一个整数,表示servlet应该被载入的顺序
2.当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
3.当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
4.正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
5.当值相同时,容器就会自己选择顺序来加载。
<servlet-mapping> 代表servlet映射配置
<servlet-name> 需要配置的servlet名称,与在<servlet>标签内定义的<servlet-name>的属性一致
<url-pattern> 映射的url路径

有关web.xml的更多配置,可以百度查询。

下面你可以运行项目,然后访问http://localhost:8080/项目名称/simple

接下来你会看到页面显示如下字符:

Simple servlet

第七章 Cookie详解

在web开发中,如何跟踪用户的会话则是我们需要解决的一个问题。

因为HTTP是一个无状态的协议,所谓的无状态则是指HTTP请求不会携带用户的身份标识,我们的服务器无法知道一个HTTP请求是由哪个用户发起的。

好在我们可以利用一些技巧,来让我们知道这个HTTP请求来自于哪个用户。

我们通常可以使用以下的方式来管理用户的会话:

  1. url重写

  2. 表单隐藏域

  3. Cookie

  4. HttpSession

1.其中url重写是指在发送请求时,在请求的url后附上一些例如用户的id,或者经过加密的一串编码字符,当服务器获取到这个请求时,可以根据后面附上的字符进行解析,然后来确认这个请求来自于哪个用户。

2.表单隐藏域则是在用户提交的表单中增加一个隐藏的提交字段,示例如下

<input type='hidden' name='id' value='useridis01'/>

当用户填写表单时,该内容不会被显示,也无法被填写,当用户提交表单时,该内容随表单内容一起发送,服务器获取到该请求时可根据该字段内容来判断该请求来自于哪一个用户。

3.Cookie则是利用浏览器的Cookie技术,我们可以在用户第一次发送请求时候在服务器端创建一个Cookie并附加到响应中,当用户下次发起请求时,Cookie会附加在HTTP头部一起发送过来,我们可以利用HttpServletRequest对象提供的getCookies()方法获取到该请求的所有Cookie对象,然后我们可以遍历该数组来获取需要的Cookie。

4.HttpSession,该内容会单独列出一个章节。

其中第一种技术url重写与表单隐藏域,由于难以维护和加重工作量,现在已经很少使用,你们可以尝试了解相关的知识即可。本章节所要讲述的Cookie则是需要重点掌握的知识。

现在,我们以一个简单的用户登陆的需求来编写代码,通常情况下我们登陆某一网站,例如微博,登陆一次过后,你执行发送微博,查看用户相册,上传照片等,都不需要你再次进行登陆操作,而可以直接进行操作。因为在你第一次登陆后,微博服务器已经发送了一个Cookie给你,你的下次请求将会附带上这个Cookie,微博服务器会判断你的这个Cookie,然后准许你进行操作。

下面则是一个简单的登陆示例,当你第一次访问http://localhost:8080/项目名/cookieDemo

时,会出现一个登陆的按钮,当你点击该按钮时,你会发现页面的内容发生了变化,显示你已经登陆成功,你可以在十秒内一直访问这个地址来刷新你的Cookie时间,当你在十秒内没有再次访问该地址之后,你的再次访问会要求你重新登陆。

package com.noesblog.servlet.servlet02;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "cookieDemo",urlPatterns = {"/cookieDemo"})
public class CookieDemo extends HttpServlet{
@Override0
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
Cookie[] cookies = req.getCookies();
if (cookies == null){
Cookie cookie = new Cookie("isLogin","true");
cookie.setMaxAge(10);
resp.addCookie(cookie);
writer.print("<html><head></head><body>");
writer.print("<h1>正在进行登陆操作!</h1>");
writer.print("<form action='cookieDemo' method='post'><input type='submit' value='submit'/></form>");
writer.print("</body>");
}else {
if (cookies != null){
for (Cookie cookie:cookies) {
if (cookie.getName().equals("isLogin") && cookie.getValue().equals("true")){
doPost(req,resp);
break;
}
}
}else {
writer.print("你还没有登陆");
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Content-type", "text/html;charset=UTF-8");
Cookie[] cookies = req.getCookies();
PrintWriter writer = resp.getWriter();
if (cookies != null){
for (Cookie cookie:cookies) {
if (cookie.getName().equals("isLogin") && cookie.getValue().equals("true")){
System.out.println(cookie.getMaxAge());
writer.print("<h1>你已经登陆成功。</h1>");
writer.print("<p>CookieName:" + cookie.getName() + "CookieValue:" + cookie.getValue() + "</p>");
cookie.setMaxAge(10);
resp.addCookie(cookie);
break;
}
}
}else {
writer.print("你还没有登陆");
}
}
}

首先我们分析这个类的头部

@WebServlet(name = "cookieDemo",urlPatterns = {"/cookieDemo"})
public class CookieDemo extends HttpServlet{

声明了CookieDemo这个类为一个Servlet,Servlet名为cookieDemo,映射的url地址为/cookieDemo,并且CookieDemo类继承了HttpServlet这个类。

然后我们看看CookieDemo实现了HttpServlet的哪些方法,可以明确的看到,它实现了doGet及doPost方法。

我们先来分析doGet方法。

resp.setCharacterEncoding("UTF-8");
resp.setHeader("Content-type", "text/html;charset=UTF-8");

doGet的第一行与第二行设置了响应的编码类型及响应的类型。缺少此步骤时,返回的网页内容会出现中文乱码的情况。

然后我们获取了一个PrintWriter对象用于输出网页内容

PrintWriter writer = resp.getWriter();

接着我们使用了HttpServletRequest对象的getCookies()方法获取了该次请求的Cookie数组。

Cookie[] cookies = req.getCookies();

然后我们开始判断这个数组是否为空,如果为空的话,则说明发起该请求的用户在之前没有发送过请求

if (cookies == null){
Cookie cookie = new Cookie("isLogin","true"); //创建了一个Cookie,名称为isLogin,属性为true
cookie.setMaxAge(10); //设置了这个Cookie的超时时间为10秒
resp.addCookie(cookie); //将这个Cookie附加到HttpServletResponse对象中,即响应内容中
writer.print("<html><head></head><body>"); //输出网页内容
writer.print("<h1>正在进行登陆操作!</h1>");
writer.print("<form action='cookieDemo' method='post'><input type='submit' value='submit'/></form>");
writer.print("</body>");
}

当然,如果用户请求的Cookie数组内容不为空的话,则

else {
if (cookies != null){ //如果Cookie数组不为空的话
for (Cookie cookie:cookies) { //使用foreach操作遍历这个数组
if (cookie.getName().equals("isLogin") && cookie.getValue().equals("true")){//如果当前遍历的对象符合要求的话
doPost(req,resp); //将该请求转交给doPost方法处理,并传入HttpServletRequest和HttpServletResponse对象
break; //停止遍历
}
}
}else {
writer.print("你还没有登陆");
}
}

接下来我们来看看doPost请求,看看它做了什么事情。

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8"); //设置响应编码
resp.setHeader("Content-type", "text/html;charset=UTF-8"); //设置响应格式
Cookie[] cookies = req.getCookies(); //获取请求的Cookie数组
PrintWriter writer = resp.getWriter();
if (cookies != null){ //判断Cookie数组是否为空
for (Cookie cookie:cookies) { //遍历数组对象
if (cookie.getName().equals("isLogin") && cookie.getValue().equals("true")){ //符合要求则写入网页内容
System.out.println(cookie.getMaxAge());
writer.print("<h1>你已经登陆成功。</h1>"); //提示用户登陆成功
writer.print("<p>CookieName:" + cookie.getName() + "CookieValue:" + cookie.getValue() + "</p>");
cookie.setMaxAge(10); //重新设置超时时间
resp.addCookie(cookie); //添加当前Cookie对象到响应内容
break;
}
}
}else {
writer.print("你还没有登陆");
}
}

根据上面的注释,你可以完整的理解了这个简单的登陆示例,当然这个示例并不完美,你可以根据自己的想法来改进这个登陆Servlet,例如添加对数据库的读写操作来判断用户名称和密码等。

Cookie对象还提供了其他的一些方法:

Modifier and Type

Object

clone()Overrides the standardjava.lang.Object.clonemethod to return a copy of this Cookie.

String

getComment()Returns the comment describing the purpose of this cookie, ornullif the cookie has no comment.

String

getDomain()Gets the domain name of this Cookie.

int

getMaxAge()Gets the maximum age in seconds of this Cookie.

String

getName()Returns the name of the cookie.

String

getPath()Returns the path on the server to which the browser returns this cookie.

boolean

getSecure()Returnstrueif the browser is sending cookies only over a secure protocol, orfalseif the browser can send cookies using any protocol.

666

getValue()Gets the current value of this Cookie.

int

getVersion()Returns the version of the protocol this cookie complies with.

boolean

isHttpOnly()Checks whether this Cookie has been marked asHttpOnly.

void

setComment(Stringpurpose)Specifies a comment that describes a cookie's purpose.

void

setDomain(Stringdomain)Specifies the domain within which this cookie should be presented.

void

setHttpOnly(boolean isHttpOnly)Marks or unmarks this Cookie asHttpOnly.

void

setMaxAge(int expiry)Sets the maximum age in seconds for this Cookie.

void

setPath(Stringuri)Specifies a path for the cookie to which the client should return the cookie.

void

setSecure(boolean flag)Indicates to the browser whether the cookie should only be sent using a secure protocol, such as HTTPS or SSL.

void

setValue(StringnewValue)Assigns a new value to this Cookie.

void

setVersion(int v)Sets the version of the cookie protocol that this Cookie complies with.

你可以尝试执行上面的方法来熟悉这些方法的具体作用。

第八章 HttpSession详解

对于HttpSession,其实可以简单的理解为增强的Cookie技术。

在前面我们所讲述的Cookie技术中,cookie会附加在httpservletresponse上,当用户下次请求时,httpservletrequest会在http请求头部附加这个cookie,这时候我们使用.getcookies()方法就能获取到所有的cookie。

但是很显然,这种方法仍然有不完美的地方,有的浏览器支持的cookie数量有限,一不小心就会超过限制的数量。

以及安全性的问题,他人可以截获cookie,然后转发cookie即可,并且cookie会有长度的限制。

现在,我们来尝试使用HttpSession来管理用户的会话,看看它与其他的会话管理方式有什么不同。

以下为一个简单的登陆示例,现在我们有一个用户名为noesblog的用户需要执行登陆和注销等一系列的操作。

package com.noesblog.servlet.servlet02;
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.io.PrintWriter;
@WebServlet(urlPatterns = {"/loginTest","/outTest"})
public class LoginTest extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type","text/html;charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();
HttpSession httpSession = req.getSession(false);
if (req.getRequestURI().equals("/loginTest")){
if (httpSession == null){
httpSession = req.getSession();
httpSession.setAttribute("user","noesblog");
httpSession.setMaxInactiveInterval(10);
writer.print("你已经登陆成功!");
}else{
String user = (String) httpSession.getAttribute("user");
if (user.equals("noesblog")){
writer.print("你已经登陆过了!<br>");
writer.print("<a href='/outTest'>注销</a>");
httpSession.setMaxInactiveInterval(10);
}else {
writer.print("你的身份验证已过期!");
}
}
}else if (req.getRequestURI().equals("/outTest")){
if (httpSession == null){
writer.print("你还没有登陆!");
writer.print("<a href='/loginTest'>去登陆</a>");
}else {
String user = (String) httpSession.getAttribute("user");
if (user.equals("noesblog")){
httpSession.invalidate();
writer.write("你已经注销登陆!");
writer.print("<a href='/loginTest'>去登陆</a>");
writer.print("<a href='/outTest'>测试注销</a>");
}else {
writer.print("你的身份验证已过期!");
}
}
}else {
writer.write("错误访问!");
}
}
}

首先我们来看@WebServlet这个注解的内容

@WebServlet(urlPatterns = {"/loginTest","/outTest"})

这个servlet监听了"/loginTest"和"/outTest"这两个路径,当我们访问这两个地址时,会访问到该servlet,比如:

http://localhost:8080/项目名/loginTest

http://localhost:8080/项目名/outTest

当你复制最上方代码部分并运行项目后,访问上面两个地址即可测试该servlet。

我们先来简单看看这个servlet的功能部分。

他只重写了Httpservlet的doGet方法,这里主要是便于测试,你也可以尝试改写为其他的方法。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type","text/html;charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();

首先是我们十分熟悉的设置响应内容的编码格式,然后我们获取了一个PrintWriter对象用于在页面上打印我们想要的内容。

HttpSession httpSession = req.getSession(false);

然后我们使用了HttpServletRequest对象的getSession方法,注意,这里我传递了一个参数进去,这个参数可以为boolean类型。

当你传递的参数为true时,HttpServletRequest会从请求中查找HttpSession对象,如果有就会返回这个对象。如果没有就会创建一个并返回。

但是我这里传递的参数是false,当你传递false参数时,HttpServletRequest会从请求中查找HttpSession对象,如果有就返回这个对象,如果没有就会返回null。

上方传递参数的区别已经使用粗体标识起来了。

这里我们使用false传递时候,我们可以根据返回的内容来判断用户是不是第一次访问我们的服务。

if (req.getRequestURI().equals("/loginTest")){

但是我们先进行了这个判断,根据HttpServletRequest对象中的url地址来判断它到底想要访问什么内容。

理论上来说,这一部分其实可以不用写

else {
writer.write("错误访问!");
}

接着我们继续向下看。

if (httpSession == null){ //当返回的httpSession对象为空时,我们确认这个用户没有访问过我们的服务
httpSession = req.getSession(); //于是我们使用无参数的getSession方法创建一个HttpSession对象
httpSession.setAttribute("user","noesblog"); //并且设置这个HttpSession对象的内容
httpSession.setMaxInactiveInterval(10); //以及超时时间
writer.print("你已经登陆成功!"); //最后打印登陆成功的提示
}
else{
String user = (String) httpSession.getAttribute("user");//当HttpSession对象不为空时,我们来获取指定名称的HttpSession的值
if (user.equals("noesblog")){ //判断这个值是不是noesblog
writer.print("你已经登陆过了!<br>");
writer.print("<a href='/outTest'>注销</a>"); //如果是,打印已登陆过的提示
httpSession.setMaxInactiveInterval(10); //刷新超时时间。
}else {
writer.print("你的身份验证已过期!"); //如果不是noesblog,其实此处应该提示为类似账户密码错误的提示。
}
}
else if (req.getRequestURI().equals("/outTest")){ //进入注销登陆的操作
if (httpSession == null){ //判断httpsession是否为空
writer.print("你还没有登陆!"); //当为空时提示你还没有登陆
writer.print("<a href='/loginTest'>去登陆</a>"); //添加一个去登陆的超链接
else {
String user = (String) httpSession.getAttribute("user"); //获取名称为user的变量值
if (user.equals("noesblog")){ //判断是不是为noesblog
httpSession.invalidate(); //清空有关该用户的所有会话关系
writer.write("你已经注销登陆!"); //提示注销成功
writer.print("<a href='/loginTest'>去登陆</a>");
writer.print("<a href='/outTest'>测试注销</a>"); //增加一些测试超链接
}else {
writer.print("你的身份验证已过期!"); //当获取的值不是noesblog时,提示身份验证已经过期了。
}
}

总的来说这个简单的登陆测试用例可以让你了解HttpSession的一些操作,如果需要更加深入的熟悉HttpSession,可以查阅api文档。

当你理解了以上示例时,可以运行项目并访问该servlet的url地址来测试登陆和注销的操作。

第九章 监听器详解

1.ServletContextListener

ServletContextListener可以对ServletContext的注册与销毁做出响应。

现在我们有一个简单的需求,使用监听器,在每个Servlet的ServletContext中增加一个map,这个map保存了一些有关国家的信息,我们需要使用printwriter将其打印到网页上。

首先我们创建一个实现了ServletContextListener的类。

package com.noesblog.servlet.servlet03;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.HashMap;
import java.util.Map;
@WebListener
public class AppListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
Map<String,String> map = new HashMap<>();
map.put("ca","Canada");
map.put("us","USA");
context.setAttribute("map",map);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}

然后我们编写一个Servlet,来打印出map中的内容。

package com.noesblog.servlet.servlet03;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
@WebServlet(name = "testAppListener",urlPatterns = {"/testAppListener"})
public class TestAppListener extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = getServletContext();
PrintWriter writer = resp.getWriter();
Map<String,String> map = (Map<String, String>) context.getAttribute("map");
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
}

现在让我们来解析这些代码,看看他们都做了些什么。

package com.noesblog.servlet.servlet03;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.HashMap;
import java.util.Map;
@WebListener //使用注解申明与注册该类是一个监听器类型
public class AppListener implements ServletContextListener{ //实现了ServletContextListener接口
@Override
public void contextInitialized(ServletContextEvent sce) { //这里我们重写了注册Servlet时监听器的动作
ServletContext context = sce.getServletContext(); //使用ServletContextEvent对象获取了一个ServletContext对象
Map<String,String> map = new HashMap<>(); //然后我们创建了一个map对象
map.put("ca","Canada");
map.put("us","USA"); //添加一些数据
context.setAttribute("map",map); //将这个map作为一个参数添加到ServletContext对象中
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}

然后我们再来看看Servlet是如何打印出我们在AppListener类中添加的数据的。

package com.noesblog.servlet.servlet03;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
@WebServlet(name = "testAppListener",urlPatterns = {"/testAppListener"}) //使用注解声明为一个Servlet
public class TestAppListener extends HttpServlet{
@Override //重写了doGet方法。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = getServletContext(); //使用HttpServlet封装好的getServletContext()方法获取一个ServletContext对象
PrintWriter writer = resp.getWriter(); //然后获取一个printwriter对象用于打印内容
Map<String,String> map = (Map<String, String>) context.getAttribute("map"); //从之前获取的servletcontext对象中获取到之前添加的对象
for (Map.Entry<String, String> entry : map.entrySet()) { //使用foreach循环并打印map中的内容。
writer.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
}

运行项目,然后访问http://localhost:8080/项目名/testAppListener

你会看见页面上显示出了如下的内容:

Key = ca, Value = Canada
Key = us, Value = USA

现在,让我们来看看我们编写的TestAppListener是如何打印这些内容的。

  1. Servler容器会对所有注册的ServletContextListener进行管理。

  2. 当某个servlet第一次被访问时,servlet容器会调用所有注册的ServletContextListener并执行contextInitialized方法。

  3. 我们创建了一个ServletContext对象,并在其中注入了一些内容。

  4. 然后开始执行servlet对象的service方法,现在我们继承的是HttpServlet,所以我们可以简单的重写doGet方法即可。

  5. 我们使用HttpServlet封装好的getServletContext方法获取到之前ServletContextListener类型对象中contextInitialized方法内创建的ServletContext对象。

  6. 然后我们使用PrintWriter对象打印这些内容到response对象中。

  7. 浏览器接收到响应内容,将内容打印到页面。

这就是一个完整的ServletContextListener的流程。

例如,我们可以在一个ServletContextListener实现类中添加对数据库的访问对象,然后将这个访问对象注册到ServletContext中,当我们在任意的Servlet中想要访问数据库时,可以使用getServletContext方法然后使用context.getAttribute方法获取这个数据库访问对象。当我们需要在服务器关闭时,关闭对数据库的连接,我们可以重写ServletContextListener实现类的contextDestroyed方法,并在其中关闭对数据库的连接。

2.HttpSessionListener

HttpSessionListener可用于监听HttpSession的创建与销毁,其内有两个方法。

public interface HttpSessionListener extends EventListener {
default void sessionCreated(HttpSessionEvent se) {
}
default void sessionDestroyed(HttpSessionEvent se) {
}
}

传递的参数HttpSessionEvent中有两个方法

public class HttpSessionEvent extends EventObject {
private static final long serialVersionUID = 1L;
public HttpSessionEvent(HttpSession source) { //传递一个HttpSession对象进行构造
super(source);
}
public HttpSession getSession() { //获取一个HttpSession对象
return (HttpSession)super.getSource();
}
}

现在,我们有一个简单的需求,针对用户访问后创建的HttpSession对象进行计数。

示例代码如下:

package com.noesblog.servlet.servlet03;
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;
@WebServlet(name = "sessionListenerTest",urlPatterns = {"/SessionListenerTest"})
public class SessionListenerTest extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(); //获取一个HttpSession对象
session.setMaxInactiveInterval(10); //设置该HttpSession对象超时时间为10秒
System.out.println("SessionListenerTest Create Session!!"); //控制台打印创建成功的消息
}
}

然后我们编写一个HttpSessionListener接口实现类

package com.noesblog.servlet.servlet03;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.concurrent.atomic.AtomicInteger;
@WebListener //声明为一个监听器
public class SessionListener implements HttpSessionListener,ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) { //重写ServletContextListener的contextInitialized用于创建计数器
System.out.println("创建httpsession计数器");
ServletContext context = sce.getServletContext();
context.setAttribute("sessionCount",new AtomicInteger());
}
@Override
public void contextDestroyed(ServletContextEvent sce) { //重写ServletContextListener的contextDestroyed用于在关闭服务器时
ServletContext context = sce.getServletContext(); //清空计数器,当然也可以不写。
System.out.println("删除httpsession计数器");
context.removeAttribute("sessionCount");
}
@Override
public void sessionCreated(HttpSessionEvent se) { //重写HttpSessionListener的sessionCreated方法,用于实际计数操作
HttpSession session = se.getSession(); //使用传入的HttpSessionEvent对象的getSession方法获取一个session对象
ServletContext context = session.getServletContext(); //获取ServletContext对象
AtomicInteger sessionCount = (AtomicInteger) context.getAttribute("sessionCount");//拿到计数器
sessionCount.getAndIncrement(); //计数器+1操作
System.out.println("HttpSession add count:" + sessionCount); //打印计数
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
ServletContext context = session.getServletContext();
AtomicInteger sessionCount = (AtomicInteger) context.getAttribute("sessionCount");
sessionCount.getAndDecrement(); //计数器-1操作
System.out.println("HttpSession remove count:" + sessionCount); //打印计数
}
}

运行项目,访问http://localhost:8080/项目名/SessionListenerTest

会在控制台看到相应的输出内容。

注释:AtomicInteger为原子操作的Integer类型对象,这里使用是为了保证计数正确,关于该类的详细内容请查看api文档

有关HttpSession的监听器还有以下几种:

  1. HttpSessionActivationListener //有关HttpSessi on激活或失效的监听器

  2. HttpSessionAttributeListener //有关HttpSession参数变更的监听器

  3. HttpSessionBindingListener //有关HttpSession绑定的监听器

  4. HttpSessionIdListener //有关HttpSession的Id变更的监听器

以上几种请读者自行尝试,这里不再概述。

其他还有几种类型的监听器,他们都处于javax.servlet包中,以Listener结尾,可以去尝试使用。

结束语

花了大概三天的时间,重新熟悉了一下servlet,感觉基础依旧是学习Java的重中之重,很多之前的疑惑也都豁然开朗了,对于此次编写的这本电子书,其实没有太多的想法,只是觉得把学习的笔记记录在word中不是很好,特别是代码的部分,经常需要修改样式。

然后就发现了gitbook,尝试了之后发现还不错,而且可以在每次理解完之后来写这个教程,这样的话可以重新熟悉了梳理整个逻辑,并补充自己对于不熟悉的地方的知识点,尤其是在编写第八章的时候,一直没能理解HttpSession的实际运行概念,卡了大概一个多小时,查了许多文档,才终于理解了HttpSession的具体用法和它的实际运行的流程,并且在编写教程的时候,也重新熟悉了这些概念。

其他的也没什么好说的,希望大家都能学好Java,走向人生巅峰。