Servlet接口下有一个GenericServlet抽象类。在GenericServlet抽象类下有一个子类HttpServlet,它是基于http协议。
javax.servlet.Servlet接口
javax.GenericServlet抽象类
javax.servlet.http.HttpServlet
Servlet是我们Servlet的顶级接口,我们自己定义Servlet时重写Servlet接口也是可以的,只不过我们要是实现这个Servlet接口的话,里面所有的抽象方法我们都需要重写,这样就会给我们造成除处理业务以外的其他代码的压力,所以在整个Tomcat的环境里,它给我们提供了一个HttpServlet和GenericServlet帮助我们处理一些基础的接口要求。
public interface Servlet {
// 初始化方法,构造完毕后,由tomcat自动调用完成初始化功能的方法
void init(ServletConfig var1) throws ServletException;
// 获取ServletConfig对象的方法
ServletConfig getServletConfig();
// 接收用户请求,向用户响应信息的方法,说白了就是处理请求的方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
// 返回Servlet字符串申明信息描述信息的方法
String getServletInfo();
// Servlet在回收前,由tomcat调用的方法,往往用于做资源的释放工作
void destroy();
}
Servlet 规范接口,要求所有的Servlet必须实现
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy();
这个类侧重除了service方法以外的其他方法的基础处理。
这是一个抽象类,这个类实现了Servlet接口,因此需要重写Servlet中所有的抽象方法
public abstract class GenericServlet implements Servlet {
private transient ServletConfig config;
// 如果一个类被其他类继承,并且子类没有显式调用父类的构造器,那么子类会默认调用父类的无参构造器。如果父类没有无参构造器,编译时会报错。因此,为了避免这种情况,父类通常会提供一个无参构造器。
public GenericServlet() {
}
public void destroy() {
// 将抽象方法,重写为普通方法,在方法内没有任何的实现代码
// 这种实现叫平庸实现
// 这样的好处是,destroy已经是一个非抽象方法了,你之后想重写就重写,不想重写就不重写
}
// 这个init方法是对Servlet接口中init方法的实现
// tomcat在调用init方法时,会读取配置信息注入一个ServletConfig对象并将该对象传入init方法
public void init(ServletConfig config) throws ServletException {
// 将config对象存储为当前的属性,这样谁继承GenericServlet,谁就可以通过这个属性拿到config初始配置信息
this.config = config;
// 调用子重载的无参的init
// 为什么要这么设计呢?上节我们在讲Servlet生命周期的时候,重写的init方法一定要是无参的init方法,如果你重写的是这个带参的init方法,那你就需要自己为config对象赋值了,并且如果有一个API跟这里获取config对象有关呢,那么跟它有关的API也需要我们自己去处理,例如下面的getServletConfig(),这样的话就意味着如果你重写这个带参的init方法,那么你要处理的代码就比较多、比较麻烦。但我们既不想处理ServletConfig参数,又想让自己的代码参与到它的初始化生命周期里,那么我们重写无参的init方法就行了。Tomcat调用的是有参的init方法,但是有参的init方法中又调用了无参的init方法,这样我们重写的init方法特别干净,而且又不干扰重写它的时候会造成这个config属性无法赋值的情况。
this.init();
}
// 重载的初始化方法,我们重写初始化方法时的方法
public void init() throws ServletException {
}
// 返回ServletConfig配置对象的方法
public ServletConfig getServletConfig() {
// Tomcat在初始化这个Servlet的时候,由Tomcat给我们传进来了一个ServletConfig对象
return this.config;
}
// 再次抽象声明service方法,并没有实现
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
GenericServlet 抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() { }
public String getInitParameter(String name)
public Enumeration getInitParameterNames()
public ServletConfig getServletConfig()
public ServletContext getServletContext()
public String getServletInfo()
public void init(ServletConfig config) throws ServletException()
public void init() throws ServletException
public void log(String msg)
public void log(String message, Throwable t)
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletName()
它也是一个抽象类,目前这个类侧重service方法,因为其他方法都已经在GenericServlet抽象类进行了实现。
首先发现GenericServlet抽象类和Servlet抽象类中参数都是 ServletRequest、ServletResponse
,而这里却是 HttpServletRequest、 HttpServletResponse
。
通过查看源码我们能发现,ServletRequest
是 HttpServletRequest
的父接口,ServletResponse
是 HttpServletResponse
的父接口。
如果我们重写的时候,如果是将父接口作为参数传入,所调用的API不多,子接口所声明的那些API我们就用不到了
public abstract class HttpServlet extends GenericServlet {
// 参数的父类字、调用重载service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 参数的父转子,转换的原因就是因为子类中的API更多更丰富。而ServletRequest、ServletResponse接口在设计的时候为什么不直接设计为子接口的参数呢?目的就是因为需要考虑到其他情况。我们在HTTP协议下用的就是HttpServletRequest、HttpServletResponse,我们写的Java代码也一定是遵循HTTP协议的,因此就算我们写的是父接口ServletRequest、ServletResponse,但实际上Tomcat给我们传入的也一定是HttpServletRequest、HttpServletResponse
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 调用重载的service
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的方式
String method = req.getMethod(); // GET POST PUT DELETE OPTIONS ... ...
long lastModified;
// 各种if判断,根据请求方式不同,决定去调用不同的do方法
// 在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应的方法,否则默认会报405错误
// 因此,我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写哪个do方法
if (method.equals("GET")) { // 如果发过来的是GET请求
this.doGet(req, resp);
} 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);
}
}
// 故意响应405
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 它会根据http.method_post_not_supported字符串去找另一个字符串,所对应的value值就是消息
String msg = lStrings.getString("http.method_get_not_supported");
// 故意响应 405 请求方式不 允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
// 故意响应405
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 它会根据http.method_post_not_supported字符串去找另一个字符串,所对应的value值就是消息
String msg = lStrings.getString("http.method_post_not_supported");
// 故意响应 405 请求方式 不允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) {
String protocol = req.getProtocol(); // 获取http协议
// 如果协议版本不是 HTTP/0.9 或 HTTP/1.0,并且协议字符串非空,则执行 if 块中的代码
if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
// 报405错,然后把msg显示出来
resp.sendError(405, msg); // 这个方法里面就这一行代码在起作用,也就是说这个方法就是在故意响应405
} else {
resp.sendError(400, msg);
}
}
}
abstract class HttpServlet extends GenericServlet HttpServlet抽象类
,除了基本的实现以外,增加了更多的基础功能
do***
方法完成请求的处理自定义Servlet中,必须要对处理请求的方法 public void service(HttpServletRequest req, HttpServletResponse res)
进行重写,重写过后,Tomcat执行的就是实例化的Servlet对象中我们自己重写的service方法;如果我们没有重写这个service方法,就会找到父类 HttpServlet
,此时无论是GET还是POST,它都会调对应的doGet / doPost,此时它就会响应405。
1.部分程序员推荐在servlet中重写do***方法处理请求 理由:service方法中可能做了一些处理,如果我们直接重写service的话,父类中service方法中的处理的功能则失效,他们就会觉得这样可能会产生一些问题
2.目前直接重写service也没有什么问题
3.后续使用了SpringMVC框架后,我们则无需继承HttpServlet,处理请求的方式也无须是do***或者service方法,既然我们以后不会这么写了,所以现在我们重写哪种方法其实也都无所谓了
4.如果doGet和doPost方法中,我们定义的代码都一样,可以让一个方法去调用另一个方法
例如:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("post请求处理的方法");
}