Servlet类的继承体系以及源码分析
写在前面
Servlet是我开始接触javaweb后的第一个难点,当时课程设置在大二,在Java基础匆忙结课后就开始学习Javaweb,Servlet所涉及的一些方法以及原理完全没有仔细了解过。写这篇笔记的目的就是用现有的知识对servlet加深理解,也是对知识对复习笔记
Servlet的继承体系
首先我在IDEA中新建了一个web项目导入了Servlet的jar包,通过 Ctrl+H 可以得到下图的目录结构:
可以看到,最外层的目录项为Interface,即Servlet接口。然后二级目录中,GenericServlet实现了Interfa Servlet,这就是我们常在编写类写的implement Servlet。 在日常使用中,我们常用到getMethod()方法,而在继承类中调用该方法是会报错的,因为servlet实现的方法的父类是图中的三级目录:HttpServlet实现的,你无法通过调用抽象类的父类来实现抽象类中的方法。所以就出现了我们日常使用的:
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
不止如此,GenericService在实现interface时定义了很多空实现,我们可以通过httpServlet继承来实现其中service(),即请求分发。
而通过上述方式来继承HttpServlet就可以来实现我们自己的方法。
所以概括来说,Servlet的继承体系大概如下图所示:
源码分析
Interface Servlet
我们首先来看Servlet接口:
package javax.servlet;
import java.io.IOException;
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所能实现的操作。
GenericServlet
然后再来看GenericServlet,可以看到都是Servlet接口的实现,返回值都是一些空值或者参数,并且引入了ServletConfig来实现一些方法:
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 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();
}
}
其中,可以看到service()方法被定义为抽象方法,作为唯一,且最重要的抽象方法,其存在的意义就是:谁来继承该类,谁就要来实现父类的方法,即实现接口的功能。那么谁来继承GenericServlet?就是刚才结构中所提到的HttpServlet:
HttpServlet
我们来看继承类后对service()方法的实现:
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 {
它所实现的,就是对请求方式的获取和判断,即根据请求方法来分发。在上面的代码中可以看到在获取到请求后通过字符串匹配来判断,并且通过if语句执行对应的方法,这就是请求分发。
这里我们就以上面的GET和POST请求为例,当判断到请求方式为GET或POST后,执行doGet(),doPost()方法:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
<!------------------------------->
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
他们都执行了sendMethodNotAllowed()
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
String protocol = req.getProtocol();
if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
而这个方法,只是抛出了异常来指明是否支持该类型请求。
在我们实际生产中,我们接收请求后需要发给对应的方法来执行特定的业务逻辑,那么我们在自定义类中继承HttpServlet后,只需要通过重写doGet和doPost方法即可。
总述
通过对继承体系以及代码实现的分析,我们可以概括为如下的逻辑图:
Q.E.D.