实现Servlet接口
jakarta.servlet.Servlet是Servlet规范中的核心接口
Servlet对象的生命周期
Servlet对象的创建,对象上方法的调用,对象最终的销毁都是由Tomcat服务器全权负责的,JavaWeb程序员是无权干预的
- 第一步: Tomcat服务器本质是一个WEB容器, 服务器在启动的时候会解析XML文件根据Servlet类的全类名利用反射机制创建Servlet对象
- 第二步: 这些创建的Servlet对象都会被放到一个HashMap集合当中,请求路径作为map集合的key,Servlet对象作为map集合的value
只有放到Tomcat服务器创建的HashMap集合中的Servlet对象才能够被WEB容器管理, 我们自己new的Servlet对象不在容器当中不受WEB容器的管理
Servlet对象的创建时机: 默认情况下服务器在启动的时候Servlet对象并不会被实例化(启动服务器的时发现Servlet中的无参构造方法没有执行)
- 用户没有发送请求之前,如果提前创建出来所有的Servlet对象必然耗费内存,另外如果这些Servlet一直没有用户访问那就没必要先创建
服务器启动的时候创建Servlet对象: 在servlet父标签中添加 子标签(整数值越小优先级越高)
<servlet>
<servlet-name>aservlet</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class>
<!--在servlet标签中添加<load-on-startup>子标签,在该子标签中填写整数,越小的整数优先级越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>aservlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
Servlet类中的一些方法
Servlet类中如果没有提供无参数的构造方法Tomcat就无法实例化Servlet对象,此时会出现500状态表示服务器端的Java程序发生了错误
- 在Servlet实际开发当中不建议程序员来手动定义构造方法,因为定义不当很容易让无参数构造方法消失使服务器无法实例化Servlet对象
是否可以把Servlet中的init方法中的代码放到无参数构造方法中执行(无参数构造方法和init方法都是在对象第一次创建的时候执行并且只执行一次)
- 不能,因为在编写Servlet类的时候手动编写构造方法很容易让无参数构造方法消失导致代码无法执行, 所以需要将初始化的的操作单独放在一个init方法中
方法名 | 执行时机 | 方法作用 | 使用次数 |
---|---|---|---|
无参构造方法 | 当服务器接收到用户的请求路径时执行 | 创建Servlet对象的方法 | 只执行一次 |
init | 在Servlet对象第一次被创建之后执行 | 完成初始化操作的方法(如初始化数据库连接池,线程池等) | 只需要执行一次 |
service | 只要用户发送一次请求,service方法必然会执行一次 | 处理用户请求的核心方法,使用最多的方法 | 发送N次请求则执行N次 |
destroy | 在销毁Servlet对象之前会调用一次destroy方法 | 销毁Servlet对象之前的最后准备工作(如关闭程序中开启的流和数据库连接资源) | 只执行一次 |
getServletInfo | 获取servlet的相关信息(作者,版权号) | ||
getServletConfig | 获取ServletConfig 对象(封装Servelt对象的初始化参数信息) |
对Servlet中方法的测试
public class AServlet implements Servlet {
// 无参数构造方法
public AServlet() {
System.out.println("AServlet无参数构造方法执行了");
}
// init方法通常是完成初始化操作的,init方法在执行的时候AServlet对象已经被创建出来了
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("AServlet's init method execute!");
}
// 只要用户发送一次请求,service方法必然会执行一次
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("AServlet's service method execute!");
}
// destroy方法在执行的时候,AServlet对象的内存还没有被销毁
@Override
public void destroy() {
System.out.println("AServlet's destroy method execute!");
}
//获取Servlet的相关信息(作者,版权号)
@Override
public ServletConfig getServletConfig() {
return null;
}
//获取ServletConfig对象(封装Servelt对象的初始化参数信息)
@Override
public String getServletInfo() {
return null;
}
}
Servlet处理请求的流程
用户发送第一次请求的时候
- 第一步: Tomcat服务器根据请求路径解析web.xml文件匹配对应的全类名然后通过反射机制调用无参数构造方法创建Servlet对象
- 第二步: Tomcat服务器调用Servlet对象的init方法完成初始化(init方法在执行的时候Servlet对象已经被创建出来了)
- 第三步: Tomcat服务器调用Servlet对象的service方法处理请求
用户在发送第二次,第三次请求的时候,此时不会再创建新的Servlet对象,直接调用之前创建好的Servlet对象的service方法处理请求
- 虽然Servlet对象Tomcat只创建了一次只有一个实例但是Servlet类并不符合单例模式的特点(构造方法私有化), 只能说是假单例
由于关闭服务器时Tomcat服务器会释放Servlet对象的内存, 所以此时会调用Servlet对象的destroy方法做销毁之前的最后准备工作
- destroy方法执行的时候AServlet对象还没有被销毁, destroy方法执行结束之后AServlet对象的内存才会被Tomcat释放
继承GenericServlet抽象类
jakarta.servlet.GenericServlet 是官方提供的标准通用的Servlet抽象类不需要我们手写 , 但是我们可以仿造手写一个
适配器设计模式
手机不能直接插到220V的电压上,可以找一个充电器/适配器(Adapter)连接220V的电压解决问题
对于UserService类来说core方法是最主要的方法;对于CustomerService类来说m2是最主要的方法;其他方法大部分情况下是不用使用的
public interface MyInterface {
void m1();
void m2();
void m3();
void core();
}
UserService类的适配器是个抽象类没有实现主要的方法core()
public abstract class UserAdapter implements MyInterface {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
//UserService类的主要方法适配器没有去实现
public abstract void core();
}
// UserService类只需要实现主要的core()方法即可,其他方法适配器已经帮我们实现了
public class UserService extends UserAdapter {
@Override
public void core() {
}
}
CustomerService类专用的适配器是个抽象类没有实现主要的方法m2()
public abstract class CustomerAdapter implements MyInterface {
@Override
public void m1() {
}
//CustomerService类的主要方法适配器没有去实现
public abstract void m2() ;
@Override
public void m3() {
}
@Override
public void core() {
}
}
// CustomerService类只需要实现主要的m2()方法即可,其他方法适配器已经帮我们实现了
public class CustomerService extends CustomerAdapter{
@Override
public void m2() {
}
}
手写GenericServlet
对于编写的Servlet类经常使用service方法,其他方法大部分情况下是不需要使用的, 如果直接实现Servlet接口就需要实现接口中的所有方法代码会很冗余
编写一个标准通用的抽象类GenericServlet实现Servlet接口(这个类就是一个适配器), 其中有一个抽象方法service
- 以后编写的所有Servlet类都不要直接实现Servlet接口了 , 直接继承GenericServlet重写service方法即可
Servlet类中的init方法中的ServletConfig对象是Tomcat服务器创建的,当调用init方法的时候会将ServletConfig对象传给了init方法(方法参数上属于局部变量)
public class Tomcat {
public static void main(String[] args){
// .....
// 创建LoginServlet对象(通过反射机制调用无参数构造方法来实例化LoginServlet对象)
Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
Object obj = clazz.newInstance();
// 向下转型
Servlet servlet = (Servlet)obj;
// 创建ServletConfig对象
// 多态(Tomcat服务器完全实现了Servlet规范)
ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
// 调用Servlet的init方法
servlet.init(servletConfig);
// 调用Servlet的service方法
//....
}
}
// init方法通常是完成初始化操作的,init方法在执行的时候AServlet对象已经被创建出来了
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("AServlet's init method execute!");
}
如何保证ServletConfig对象在service方法中能够使用
- 当XxxServlet继承GenericServlet类之后,当调用XxxServlet对象init方法时最终会调用父类GenericServlet中的init方法接收ServletConfig对象(局部变量)
- 给GenericServlet类声明一个成员变量把init方法中的局部变量赋值给成员变量 , 这样子类就可以通过父类提供的getServletConfig方法访问
public abstract class GenericServlet implements Servlet {
// 成员变量
private ServletConfig config;
@Override
//使用final修饰的init方法子类不能重写
public final void init(ServletConfig config) throws ServletException {
//System.out.println("servletConfig对象,小猫咪创建好的:" + config);
this.config = config;
// 调用init()方法,这个this就是Servlet对象,如果子类重写了init方法那么一定是调用子类的init方法
this.init();
}
//这个init方法是供子类重写的
public void init(){
}
//抽象方法,这个方法最常用。所以要求子类必须实现service方法。
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
@Override
public void destroy() {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public ServletConfig getServletConfig() {
return config;
}
}
GenericServlet类提供一个无参的init方法供子类重写 , 当服务器调用有参的init方法的时候再调用无参的init方法,如果子类重写了无参的init方法就调用子类的
- 子类重写有参的init方法会导致父类有参的init方法不执行 , 那么GenericServlet类的ServletConfig对象就为null , 将来造成空指针异常
public class LoginServlet extends GenericServlet{
@Override
public void init(){
System.out.println("LoginServlet's init() method execute!");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("正在处理用户登录请求,请稍后。。。。。");
// 想在LoginServlet子类中使用ServletConfig对象怎么办?
ServletConfig config = this.getServletConfig();
System.out.println("service方法中是否可以获取到ServletConfig对象?" + config);
}
}
GenericServlet中的方法
GenericServlet实现Servlet类的基本方法
方法名 | 功能 |
---|---|
getServletConfig() | 获取 ServletConfig对象(封装Servelt对象的初始化参数信息即init-param标签中的信息) |
getServletContext() | 获取ServletContext对象(封装了上下文初始化参数信息的对象即context-param标签中的信息) |
getServletInfo() | 获取servlet的相关信息(作者,版权号) |
log() | 记录日志信息 |
GenericServlet继承ServletConfig类的扩展方法
- 只要自己写的Servlet继承了GenericServlet就可以不用先通过获取ServletConfig对象调用方法, 使用this调用父类ServletConfig的这四个方法
方法名 | 功能 |
---|---|
public String getInitParameter(String name) | 通过init-param标签的name获取value |
public Enumeration< String > getInitParameterNames() | 获取所有init-param标签的name |
public ServletContext getServletContext() | 获取context-param标签中的信息 |
public String getServletName() | 获取Servlet对象的name |
继承HttpServlet抽象类
以后我们编写Servlet类的时候,实际上也不会直接继承 GenericServlet类,而是要继承HttpServlet(处理HTTP协议更便捷)
- B/S结构的系统是基于HTTP超文本传输协议的 , 所以在Servlet规范当中专门提供了一个为HTTP协议准备的Servlet类叫做HttpServlet
jakarta.servlet.http包下的类和接口
- HttpServletRequest: Tomcat服务器将“请求协议”中的数据全部解析出来,然后将这些数据全部封装到request对象当中
- HttpServletResponse: Tomcat服务器将“响应协议”中的数据全部解析出来,然后将这些数据全部封装到response对象当中
类或接口 | 作用 |
---|---|
jakarta.servlet.http.HttpServlet(抽象类) | HTTP协议专用的Servlet类,处理HTTP协议更便捷 |
jakarta.servlet.http.HttpServletRequest(接口) | HTTP协议专用的请求对象,面向HttpServletRequest获取请求协议中的数据 |
jakarta.servlet.http.HttpServletResponse | HTTP协议专用的响应对象 |
HttpServlet类的继承结构及源码分析: 我们以后编写的Servlet要继承HttpServlet类
- jakarta.servlet.Servlet(接口)【爷爷】
- jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
- jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
HttpServlet处理请求流程
第一步用户发起请求: Tomcat服务器匹配请求路径,通过反射机制调用无参数构造方法创建对应的Servlet对象,
public class HelloServlet extends HttpServlet {
// 用户第一次请求,创建HelloServlet对象的时候会执行这个无参数构造方法
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
第二步Tomcat服务器调用Servlet对象的有参init方法完成初始化: 若本类没有提供该方法,父类HttpServlet也没有,最终执行GenericServlet类中有参的init方法
public abstract class GenericServlet implements Servlet, ServletConfig,java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后这个init方法会执行
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
第三步Tomcat服务器调用Servlet对象的参数不含httpXxx的service方法处理请求: 若本类没有提供service方法,执行父类HttpServlet类service方法
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行, 用户发送第N次请求的时候还是会执行
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service模板方法
service(request, response);
}
}
第四步调用HttpServlet类提供的重载的参数含httpXxx的service模板方法(该方法负责定义核心算法骨架,具体的实现步骤延迟到子类中去完成)
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式,可能是GET POST PUT DELETE HEAD OPTIONS TRACE七种之一
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
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)) {
// 如果请求方式是POST请求,则执行doPost方法。
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);
}
}
第五步Tomcat服务器根据请求方式调用具体的doXxx方法处理请求: 若本类没有提供则调用父类的doXxx方法(此时就会报405错误)
public class HelloServlet extends HttpServlet {x
// 通过无参数构造方法创建对象
public HelloServlet() { }
// 本类没有提供有参的init方法,执行父类HttpServlet的该方法。HttpServlet类中没有该方法,会继续执行GenericServlet类中的该方法
// 本类没有提供参数不含httpXxx的service方法, 执行父类HttpServlet的该方法
// 当前端发送的请求是get请求的时候,我这里重写doGet方法
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doGet</h1>");
}
// 当前端发送的请求是post请求的时候,我这里重写doPost方法
/*protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doPost</h1>");
}*/
}
HttpServlet细节分析
如果Tomcat根据前端发起的请求方式调用具体的doXxx方法处理请求时而XxxServlet没有提供对应的方法时就会执行HttpServelt的doXxx方法(必然报405错误)
//HttpServlet中的doXxx方法的作用就是报一个405错误,提醒开发人员编写对应的方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
//如果子类没有重写doPost方法 , 则会执行 HttpServlet 的doPost方法
//该方法的作用是报一个 405 错误
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
重写HttpServlet类中重载的参数含httpXxx的service()模板方法后就享受不到HTTP协议专属的东西如405错误的提醒
public class HelloServlet extends HttpServlet {x
// 通过无参数构造方法创建对象
public HelloServlet() {
}
// 重写HttpServlet类中重载的service()方法后HttpServlet类中的service()模板方法就不会在执行,里面的doXxx方法也就无法执行 , 就不会报405错误
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("<h1>hello servlet</h1>");
}
}
在Servlet类当中将doGet和doPost方法都进行重写确实可以避免405错误的发生,但是不建议因为405错误还是有用的该报错的时候就应该让他报错
- 如果你要是同时重写doGet和doPost,还不如直接重写service模板方法代码还能少写一点
- 后端决定前端需要发什么样的请求,后端重写了doGet方法前端一定要发get请求,端重写了doPost方法,前端一定要发post请求
Servlet类的最终开发步骤
第一步:编写一个Servlet类,直接继承HttpServlet
第二步:重写doGet方法或者重写doPost方法,到底重写谁javaweb程序员说了算( 重写父类某个方法有快捷键 ctrl + o )
第三步:将编写的Servlet类配置到web.xml文件当中
第四步:准备前端页面指定发起的请求路径
public class HelloServlet extends HttpServlet {x
// 通过无参数构造方法创建对象
public HelloServlet() { }
// 本类没有提供有参的init方法,执行父类HttpServlet的该方法。HttpServlet类中没有该方法,会继续执行GenericServlet类中的该方法
// 本类没有提供参数不含httpXxx的service方法, 执行父类HttpServlet的该方法
// 当前端发送的请求是get请求的时候,我这里重写doGet方法
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doGet</h1>");
}
// 当前端发送的请求是post请求的时候,我这里重写doPost方法
/*protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doPost</h1>");
}*/
}