我们在上一篇里,创建了一个servlet程序,并且用浏览器进行了访问,发现根据form标签中method里面的get和post可以分别调到Servlet中的doGet方法和doPost方法,那么是怎么就调到了这两个方法呢。
Servlet的继承结构和执行原理
我们在上一篇里还提到了一下Serlect的继承结构
Servlet (顶层接口)---->GenericServlet (抽象类) ----->HttpServlet(抽象类)----->我们自定义的Servlet
我们来剖析一下里面所包含的内容:
我们来观察一下在上一篇创建的servlet程序:
package com.yht.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyFirstServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
System.out.println("doGet方法执行了。。。");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doPost方法执行了。。。");
}
}
我们可以发现 我们自定义的类需要继承HttpServlet,而且这些类导入的包前面是javax,而这个是在我们自带的jdk中找不到的。
而这些是在我们的tomcat的安装目录里存储的:
我们把上面的jar包复制一份出来,然后进行解压 可以看到里面的目录:
然后我们在Servlet文件夹下发现了 Servlet.class文件
又在子目录http里找到了相应的其他class文件。
我们对上面的class文件进行反编译:
Servlet :
package javax.servlet;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
可以看出里面一共定义了5个方法。而对于我们最重要的就是 service方法,这里先记住,等到下面再进行讲解:
GenericServlet :
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration 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();
}
}
这是一个抽象类,实现了Servlet的所有方法,然后又自己添加了属于自己的一些方法,其实这个类在这里只是一个中转的作用,以防万一以后哪一天http协议使用的不广泛了,又出现了更实用的协议,那么可以再创建一个类来继承这个类,而现在还有可能未来很长远的一段时间里还是http协议主导着网络。
HttpServlet: (请看注释:)
package javax.servlet.http;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.NoBodyResponse;
public abstract class HttpServlet extends GenericServlet {
/*
这下面的都是定义的成员变量,表示着所以html所发出的请求的种类
在html端,发回的请求不只有 get 和 post 两种
但是这两种是最最常用的,所以其他的现在只了解一下即可
*/
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
//当前端发来 get请求的时候,就会调用这个方法,我们自定义的servlet就是大部分情况下重写了这个方法
//如果没有进行重写,但是浏览器端发来get请求的时候,既然自定义的servlet类中没有重写,那么就会到父类
//中找到这个方法,而下面的这个方法以及下下面的post方法的作用就是抛出一个错误页面返回到浏览器端
//错误代码是 405,含义就是服务器端不能够响应 浏览器端的请求。
//当我们进行重写的时候,就可以进行数据的处理,而不会调用父类里的这个方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if(protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
//doPost的性质和doget对于服务器接受请求这个方面一模一样,也是不重写的时候就会报 405错误。 两者区别会再详细介绍
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if(protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
//...................这里有我删除的大部分对于其他请求的方法,因为暂时以及很长时间内都不用了解,所以先删除了,避免占据篇幅
//最最重要的一个service方法,他的作用就是进行相应分流,所有的请求进入服务端都会首先进入service方法,然后再根据请求的种类
//再去调用针对各种请求的方法,因为首先会进入service方法,所以如果我们自定义的servlet重写这个方法的话,那么就会
//首先调用重写的方法,其实在重写的service方法内就可以完成各种需求,他可以代替其他的所有方法来独自进行所有功能的实现,
//但是,因为请求的种类众多,为了让功能具体化,
//所以就采用service对请求进行分流,各做各的事情,互不打扰。也提高了代码的复用率,所以一般不进行重写。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long errMsg;
if(method.equals("GET")) {
errMsg = this.getLastModified(req);
if(errMsg == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
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 {
String errMsg1 = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg1 = MessageFormat.format(errMsg1, errArgs);
resp.sendError(501, errMsg1);
}
}
//这里是对serviece方法的一个重载
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((HttpServletRequest)request, (HttpServletResponse)response);
}
}
因为实在太多,所以进行了删减,详情请看上面注释。
所以现在我们有了一个框架:
所以基本的流程就是:
浏览器发来需求,然后进入HttpServlet中进行分流,针对不同的请求来执行相应的方法,自定义servlet如果重写doget方法和dopost方法,那么就执行重写的方法,如果找不到自定义servlet重写的方法,就会返回一个错误代码为405的页面,如果自定义servlet代码重写了service,那么就根据重写的service方法执行里面的语句。