Servlet接口下有一个GenericServlet抽象类。在GenericServlet抽象类下有一个子类HttpServlet,它是基于http协议。
javax.servlet.Servlet接口
javax.GenericServlet抽象类
javax.servlet.http.HttpServlet
文章目录
一、Servlet 接口
1)介绍
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();
}
2)接口及方法说明
Servlet 规范接口,要求所有的Servlet必须实现
public void init(ServletConfig config) throws ServletException;
- 初始化方法,容器在构造servlet对象后,自动调用的方法,容器负责实例化一个ServletConfig对象,并在调用该方法时传入
- ServletConfig对象可以为Servlet 提供初始化参数
public ServletConfig getServletConfig();
- 获取ServletConfig对象的方法,后续可以通过该对象获取Servlet初始化参数
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
- 处理请求并做出响应的服务方法,每次请求产生时由容器调用
- 容器创建一个ServletRequest对象和ServletResponse对象,容器在调用service方法时,传入这两个对象
public String getServletInfo();
- 获取ServletInfo信息的方法
public void destroy();
- Servlet实例在销毁之前调用的方法
二、GenericServlet 抽象类
这个类侧重除了service方法以外的其他方法的基础处理。
1)源码重点方法解释
这是一个抽象类,这个类实现了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;
}
3)源码中的全部方法解释
GenericServlet 抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法
private transient ServletConfig config;
- 初始化配置对象作为属性
public GenericServlet() { }
- 构造器,为了满足继承而准备
public void destroy() { }
- 销毁方法的平庸实现
public String getInitParameter(String name)
- 获取初始参数的快捷方法
public Enumeration<String> getInitParameterNames()
- 返回所有初始化参数名的方法
public ServletConfig getServletConfig()
- 获取初始Servlet初始配置对象ServletConfig的方法
public ServletContext getServletContext()
- 获取上下文对象ServletContext的方法
public String getServletInfo()
- 获取Servlet信息的平庸实现
public void init(ServletConfig config) throws ServletException()
- 初始化方法的实现,并在此调用了init的重载方法
public void init() throws ServletException
- 重载init方法,为了让我们自己定义初始化功能的方法
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()
- 获取ServletName的方法
三、HttpServlet 抽象类
它也是一个抽象类,目前这个类侧重service方法,因为其他方法都已经在GenericServlet抽象类进行了实现。
1)源码
首先发现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);
}
}
}
2)其他方法的解释
abstract class HttpServlet extends GenericServlet HttpServlet抽象类
,除了基本的实现以外,增加了更多的基础功能
- 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”;
- 上述属性用于定义常见请求方式名常量值
- public HttpServlet() {}
- 构造器,用于处理继承
- public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
- 对服务方法的实现
- 在该方法中,将请求和响应对象转换成对应HTTP协议的HttpServletRequest HttpServletResponse对象
- 调用重载的service方法
- public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
- 重载的service方法,被重写的service方法所调用
- 在该方法中,通过请求方式判断,调用具体的
do***
方法完成请求的处理
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- 对应不同请求方式的处理方法
- 除了doOptions和doTrace方法,其他的do*** 方法都在故意响应错误信息
四、自定义Servlet
自定义Servlet中,必须要对处理请求的方法 public void service(HttpServletRequest req, HttpServletResponse res)
进行重写,重写过后,Tomcat执行的就是实例化的Servlet对象中我们自己重写的service方法;如果我们没有重写这个service方法,就会找到父类 HttpServlet
,此时无论是GET还是POST,它都会调对应的doGet / doPost,此时它就会响应405。
- 要么重写service方法
- 要么重写doGet/doPost方法
- 如果两个方法都进行了重写,那么service生效
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请求处理的方法");
}