在上一节的内容中,我们已经系统的学习了HTTP的相关概念、知道了GET和POST请求在服务器上的运行原理、请求响应在服务器中究竟是怎样运行的,从这一节开始我们将系统的学习实现Servlet的完整过程
我们已经在板块零的第二节中创建了Java Web项目;又在板块零的第三节中成功把Tomcat集成到IDEA中,现在我们就在这个Web项目中实现Servlet规范
在www.caijiyuan包中创建一个普通的Java类
helloServlet.java
此类继承HttpServlet类,还记得继承怎么写吗:extends
在上一节中我们详细学习了请求响应在服务器中究竟是怎样运行的,而在Servlet中请求响应就是通过HttpServlet类中的service方法实现的,service方法有两个形参(Request,Rrsponse)
分别对应请求、响应
在HttpServlet类中,IDEA重写方法的快捷键是Ctrl+O
但我们发现有两个service,它们的区别是什么?
先来看第一个,HttpServlet的service()方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取http request的method参数,其实就是html的form标签
//中method属性对应的字符串
String method = req.getMethod();
long errMsg;
//判断请求方式
if(method.equals("GET")) {
//获取最后被修改时间
errMsg = this.getLastModified(req);
if(errMsg == -1L) {
/**如果servlet不支持http request header的if-modified-since属性
* 则继续处理
**/
this.doGet(req, resp);
} else {
//如果支持这个属性
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
/**
* 如果客户端的文件最后修改时间和服务器端的文件最后修改时间一致则返回304不需要修改状态
* 这样服务器就不返回html,浏览器读取本地缓存文件,否则重新获取服务器端的对应html文件
**/
if(ifModifiedSince < errMsg / 1000L * 1000L) {
this.maybeSetLastModified(resp, errMsg);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if(method.equals("HEAD")) {
errMsg = this.getLastModified(req);
this.maybeSetLastModified(resp, errMsg);
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 {
//如果请求不是以上的所有请求方式,该方法就会响应501错误,也就是不支持这种请求
String errMsg1 = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg1 = MessageFormat.format(errMsg1, errArgs);
resp.sendError(501, errMsg1);
}
}
这是第二个ServletRequest
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);
}
原来只有第二个 ServletRequest service
方法是由Tomcat自动调用,它将接收的客户端请求转交给HttpServlet
中的第一个HttpServletRequest protected service
方法,此保护类型的service方法再把将请求分发给doPost()
、doGet()
方法进行下一步处理
因此这里就Request/Response
俩个形参而言,重写调用第一个或第二个service方法的效果应该是一样的,此处我们直接重写第一个,也就是 HttpServletRequest protected service
我们还应将项目对外访问路径(就是在服务器中此项目的站点名)更改为自己想要的样子
此处我更改为与包名一致/www.caijiyuan
注意:我们还应在类前设置注解@WebServlet("/helloServlet")
,告诉服务器当前资源在站点下的真实路径
helloServlet.java中写入测试内容
package www.caijiyuan;
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;
@WebServlet("/helloServlet")
public class helloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.service(req, resp);
//打印内容在控制台
System.out.println("Hello Servlet with terminal");
//通过流输出数据到浏览器
resp.getWriter().write( "Hello Servlet with brower");
}
}
启动服务器在浏览器中访问得
那么HttpServletRequest request
请求是在服务器中是怎样接受到的呢?
即是上一节中详解过的请求头的功劳
Host
中的localhost
找到主机是本机8080
端口确定本机中占用端口的程序是Tomcat"/www.caijiyuan"
确定是Tomcat下的哪个站点"/helloServlet"
确定是当前站点下的那个资源路径找到资源路径后,如果服务器是第一次被访问就会创建一个Servlet,否则就会调用service方法生成request对象来处理会话中的请求;接受到请求后经过设定的代码生成response对象保存响应内容返回给客户端(浏览器),这就是Servlet工作的流程
对于一个 Servlet 类,我们日常最常用的方法是继承自 HttpServlet 类,实际上,HttpServlet是扩展了GenericServlet 类。GenericServlet 类实现了Servlet,ServletConfig和Serializable接口。它主要完成了这些任务:
abstract class GenericServlet implements Servlet,ServletConfig{
//GenericServlet通过将ServletConfig赋给类级变量
private trServletConfig servletConfig;
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig=servletConfig;
/*自定义init()的原因是:如果子类要初始化必须覆盖父类的init() 而使它无效 这样
this.servletConfig=servletConfig不起作用 这样就会导致空指针异常 这样如果子类要初始化,
可以直接覆盖不带参数的init()方法 */
this.init();
}
//自定义的init()方法,可以由子类覆盖
//init()不是生命周期方法
public void init(){
}
//实现service()空方法,并且声明为抽象方法,强制子类必须实现service()方法
public abstract void service(ServletRequest request,ServletResponse response)
throws ServletException,java.io.IOException{
}
//实现空的destroy方法
public void destroy(){ }
}
可以看到如果继承这个类的话,我们必须重写 service() 方法来对处理请求
继承自GenericServlet 类实现Servlet
package www.caijiyuan;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/Servlet_Gener")
public class Servlet_Gener extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//打印内容在控制台
System.out.println("Hello Servlet with terminal");
//通过流输出数据到浏览器
servletResponse.getWriter().write( "Hello Servlet with brower");
}
}
启动服务器后在浏览器中访问得
除了两个继承抽象类实现Servlet的接口,还有一个通过实现interface Servlet
来实现Servlet规范的方法
实例
创建类Servlet_imp.java ,使其实现Servlet接口
IDEA中使用快捷键Alt+Shift+Enter
实现方法,在service()方法中添加测试代码,并且在类前添上@WebServlet()
注释
package www.caijiyuan;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/Servlet_imp")
public class Servlet_imp implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//通过流输出数据到浏览器
servletResponse.getWriter().write( "Hello Servlet with brower");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
我们在调用HttpServlet中service方法时,底层实际上是判断GET请求还是POST请求从而分别调用doGet()和doPost()方法
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);
}
}
实例
创建类Servlet_do.java使其继承自HttpServlet,然后重写doGet()和doPost()方法
通过上一节我们已经知道了浏览器访问地址是使用GET方法,所以我们在doGet()中添加测试代码,并且在类前添上@WebServlet()
注释
package www.caijiyuan;
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;
@WebServlet("/Servlet_do")
public class Servlet_do extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
//通过流输出数据到浏览器
resp.getWriter().write( "Hello Servlet with brower");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
}
}
以上三种方式都可以实现Servlet规范,使得普通Java类升级成Servlet类,但最简便的方式还是第一种,也就是继承自HttpServlet类
有了以上的知识储备,我们就可以梳理一下Servlet的整个生命周期了:由于Servlet没有main()方法,不能独立运行,它的运行完全由Servlet引擎来控制和调度,所谓生命周期,指的就是Servlet容器何时创建 Servlet实例、何时调用其方法进行请求的处理、何时并销毁其实例的整个过程。
观察这三个生命周期方法即可观察到Servlet的生命周期
init
方法,在Servlet 实例创建之后执行(证明该Servlet有实例创建了)
public void init() throws ServletException {
System.out.println("创建实例调用");
}
service
方法,每次有请求到达某个Servlet 方法时执行,用来处理请求(证明该Servlet 进行服务了)
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service方法调用了");
}
destroy
方法,Servlet实例销毁时执行(证明该Servlet的实例被销毁了)
public void destroy() {
System.out.println("实例被销毁了");
}
实例
启动服务器在浏览器中访问项目得
在控制台输出,即是在浏览器访问项目时调用了init()
方法(只会被调用一次init()
),并且紧接着接受请求调用了service
方法
接着关闭Tomcat服务器,在控制台输出
Servlet的生命周期,可以更详细的分为四步:Servlet类加载 – >实例化 – >服务 – >销毁
下面我们描述一下Tomcat与Servlet是如何工作的,看看下面的时序图:
以上就是此小节的所有内容,我们系统的学习了Servlet的概念、实现Servlet规范的多个方法、Servlet的工作流程、生命周期等,为之后Servlet具体对象学习打下了坚实的基础。从下一节开始我们将学习service方法两个形参之一:HttpServletRequest实例的具体操作