Servlet本质
Servlet接口定义一套处理网络请求的规范,所有实现Servlet的类都需要实现它的五个方法,其中最主要的是两个生命周期方法init() destory(),以及一个处理请求的service(),也就是说所有实现Servlet接口的类都需要有以下三个部分:
- 初始化时所作的工作
- 销毁时所作的工作
- 接收到请求做出的响应
// jdk中的Servlet接口
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
Servlet与Tomcat
Tomcat Servlet的交互
Tomcat主要是一个Java servlet容器。
利用其Java Servlet和JSP api的实现,Tomcat能够接收来自客户机的请求,动态编译容器管理的Java类来处理相关应用程序上下文中指定的请求,并将结果返回给客户机。这种生成动态内容的方法支持非常快的、线程化的、独立于平台的请求处理。
而且,由于Java Servlet规范是为与所有其他主要Java web技术的互操作性而设计的,所以托管在Tomcat服务器上的Servlet能够利用Tomcat提供给它的任何资源。Tomcat的嵌套层次化XML配置文件允许非常细粒度的资源访问控制,同时保持松散耦合、易于部署和逻辑清晰、易于阅读的体系结构描述。
Tomcat如何与Servlet协同
Servlet规范的一个关键需求是,它们只需要处理整个数据事务处理过程的某些部分。例如,servlet代码本身不会监听某个端口上的请求,也不会直接与客户机通信,也不负责管理其对资源的访问。相反,这些东西是由Tomcat (servlet容器)管理的。
这允许在各种环境中重用servlet,或者在组件之间进行异步开发——只要不做重大更改,就可以重构连接器以提高效率,而无需对servlet代码本身进行任何更改。
Servlet生命周期
-
Tomcat通过它的一个连接器接收来自客户机的请求。
-
Tomcat将此请求映射到适当的引擎进行处理。这些引擎包含在其他元素中,比如主机和服务器,这限制了Tomcat搜索正确引擎的范围。
-
一旦请求被映射到适当的servlet, Tomcat将检查该servlet类是否已加载。如果没有,Tomcat将servlet编译成Java字节码(JVM可执行),并创建servlet的实例。
-
Tomcat通过调用它的init方法来初始化servlet。servlet包含的代码可以读取Tomcat配置文件并相应地采取行动,还可以声明它可能需要的任何资源,这样Tomcat就可以以一种有序的、受管理的方式创建它们。
-
一旦servlet被初始化,Tomcat就可以调用servlet的服务方法来处理请求,该请求将作为响应返回。
-
在servlet的生命周期中,Tomcat和servlet可以通过使用侦听器类进行通信,侦听器类监视servlet的各种状态变化。Tomcat可以以各种方式检索和存储这些状态更改,并允许其他servlet访问它们,允许给定上下文的各种组件跨单个或多个用户会话维护和访问状态。此功能的一个实际示例是电子商务应用程序,它记住用户添加到购物车的内容,并能够将此数据传递给付款流程。
-
Tomcat调用servlet的destroy方法来平稳地删除servlet。该操作由正在侦听的状态更改触发,或者由传递给Tomcat的外部命令触发,该命令用于取消部署servlet的上下文或关闭服务器。
编写Servlet
Servlet源码实现
Interface Servlet
// jdk中的Servlet接口
public interface Servlet {
// Tomcat反射创建Servlet后Tomcat调用init传入对象
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
// 解析、响应HTTP请求Tomcat负责完成,封装成网络请求对象供Servlet使用;
// Servlet处理后得到的结果使用response.write()方法写入response内部缓冲区,Tomcat在Servlet除了完成后拿到response,组装成HTTO响应发给客户端
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接,网络请求等没有丝毫关系。该部分由Tomcat负责实现并以ServletConfig对象的形参形式传递给Servlet
GenericServlet
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 message) {
this.getServletContext().log(this.getServletName() + ": " + message);
}
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接口做了如下优化:
- 提升init方法中原本是形参的ServletConfig对象的作用域方便其他方法使用
- init方法中调用了一个init空参方法,可以继承后对其进行定制
- 添加了getServletConfig方法,其他方法中也可获取到ServletConfig
HttpServlet
浏览器的请求方式有Get/Post等,如果直接重写Servlet接口处理service的方法过于复杂,HttpServlet中实现了service方法,区分请求类型来降低程序员的处理成本
// 判断请求类型分别调用不同处理函数
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
Q :HttpServlet中没有一个抽象方法却是抽象类?
A :不希望被实例化,要求程序员继承后重写不同请求类型的处理函数doGet、doPost…
设计模式:
上面的HttpServlet中使用了模板方法模式
总结
实现Servlet的关键在于:
- 继承HttpServlet抽象类并重写doGet() doPost()等方法
参考资料:
【Servlet与Tomcat交互与生命周期】https://www.mulesoft.com/tcat/tomcat-servlet
【Servlet源码分析】servlet的本质是什么,它是如何工作的? - bravo1988的回答 - 知乎
https://www.zhihu.com/question/21416727/answer/690289895