个人博客地址:
http://xiaohe-blog.top
servlet说实在点就是个接口,浏览器发送请求给Tomcat(服务器),若是这个请求正好对应了servlet实现类的请求路径,Tomcat就会使用它来响应浏览器,这也就是request(请求)、response(响应)了。
servlet有5个方法,其中service是最重要的,也是我们接下来学习的重点。它的实现类也主要实现它来完成响应工作。
service方法
void service(ServletRequest var1, ServletResponse var2){}
service有两个参数,ServletRequest中带着请求的信息,例如一个学生管理系统你想删除学生,ServletRequest就会给你你想删除的学生的学号;ServletResponse中带着响应的信息,就像点击删除后你想告诉用户删除成功没。
其他四个方法中没有这两个参数,也就意味着它们无法与浏览器交互,只负责servlet中的一些初始化、get方法、销毁servlet等等。
所以在实现Servlet接口时其他方法不用管,只需要重写service方法来实现功能就好。
在配置文件web.xml中配置 :
<servlet>
<servlet-name>myServletservlet-name>
<servlet-class>com.MyServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>myServletservlet-name>
<url-pattern>/myServleturl-pattern>
servlet-mapping>
在前端中 :
在java类中 :
public class MyServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {}
@Override
public ServletConfig getServletConfig() {return null;}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
// 在控制台打印输出语句
System.out.println("MyServlet --> service");
}
@Override
public String getServletInfo() { return null;}
@Override
public void destroy() {}
}
再点击对应链接后控制台就会打印 “MyServlet --> service”。
什么是生命周期 ?
这些都是由服务器决定的。服务器决定它们什么时候活,什么时候死。
想要Tomcat创建servlet实现类,就必须让Tomcat知道他在哪,这也就是为什么要在web.xml配置servlet实现类的全限定类名 :
<servlet>
<servlet-name>myServletservlet-name>
<servlet-class>com.MyServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>myServletservlet-name>
<url-pattern>/myServleturl-pattern>
servlet-mapping>
一个程序中势必有很多servlet实现类,Tomcat将他们放在内部HashMap中统一管理,前端发送请求时去map中寻找相应的servlet,调用它完成响应。如果我们平时写好servlet后忘记配置了,Tomcat就不会将它放在map容器中,自然就不会调用它。
那么Tomcat什么时候创建servlet对象呢?
服务器启动并不会立即创建servlet对象,我们在servlet的无参构造方法中添加输出语句,启动服务器时并不会立即执行输出语句。
服务器启动只会读取web.xml文件,将里面你配置的所有servlet连同它的请求路径加载到上述所说的“map”集合中,以待日后使用。
在浏览器发送对应请求时,才会调用无参构造创建servlet对象。
注意是无参构造,如果只有有参构造,服务器无法调用哦。
这个设计特别合理,你打开一个小说网站可能只是想读其中一个小说,那也不至于把全网所有小说都加载完吧,肯定是你点击哪一个,服务器加载哪一个。
当然我们可以使用配置信息来设置它的创建时机 ,这里不做扩展。
Servlet中有一个方法 :init(ServletConfig var),由它来完成初始化功能。Tomcat服务器实例化对应servlet后立即初始化。
默认情况下执行流程,点击链接 -> 执行无参构造 **- > **执行init。
在发送多次请求后浏览器输出如下,即使发送了多次请求,构造器和初始化方法只执行一次,而不是每一次都执行构造方法,这就证明了servlet只创建一次,以后想用直接在容器中拿,这也就是servlet的单实例特点。
单实例与单例又不同,单实例是人家可以创建,但是只由Tomcat创建一次。单例是不能new,只能创建一次。
为什么服务器设计时要添加一个init方法呢?init方法在构造方法之后执行并且一定会执行,那么为什么不能把init中的内容写到构造方法中呢 ?
之前我们提过,服务器调用servlet的无参构造创建对象,如果没有无参构造,就会出bug。无参构造对于整个网站来说太重要了,服务器不想让你动它,于是提供了一个init方法,你想写什么就在init中写,千万别动构造方法。
甲骨文官方也是这样说的(bushi)
来到重头戏service。这个方法很重要,其他方法很少用甚至用不到,但是这个方法用的很勤(相对而言)。
我们在上述MyServlet的service方法中添加输出语句,发送多次请求 :
可以看到,第一次发送请求后执行构造器、初始化、调用service进行响应,以后每一次发送请求只调用service方法完成请求。我们在一个网站中,多次点击登录调用的是同一个Servlet实现类,只不过调用了很多次。
在destroy方法中设置输出语句,关闭浏览器后发现没有执行destroy方法,关闭服务器才算执行了相应输出语句。而我们并没有调用destroy,可以看出 一个Servlet对象的销毁是Tomcat调用其destroy来完成的。(关闭服务器肯定要销毁servlet,不能老让它们占用资源)
一个servlet的默认生命周期 :
发送对应请求后创建servlet对象,紧接着立即执行init方法,之后再发送对应请求就只执行service方法,在服务器关闭之前会调用容器中所有servlet的destroy方法销毁对象。
在servlet生命里只执行一次的方法 :构造器、初始化、销毁方法。
平时我们实现Servlet接口只为了使用它的service方法,但是还要实现另外四个方法,是不是觉得太过臃肿?这么一大坨init、destroy都是无用的东西,看着也烦。
java也为我们考虑到了这一点,推出了 GenericServlet 这个类。这个类实现了Servlet接口,并且对除了service方法之外的方法都进行了默认实现,我们继承GenericServlet ,只需要写service方法就行了。
改版后是不是悦目很多?
这是一个简易的适配器设计模式 :因为Servlet接口中只需要用到service,但是有时候要用到init等其他方法,没法直接把他们删掉,于是另外搞一个类实现它的其他方法,留一个抽象的service方法给我们,当我们想要使用Servlet中其他方法时也可以直接重写。
补充 :
刚才看到GenericServlet 类中有两个init方法 :
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
我们知道,Tomcat调用的是有参的init(ServletConfig var),在GenericServlet 的init(ServletConfig var)中初始化了一个成员变量config,接着调用了一个空的init()方法,为什么呢?
ServletConfig包含着 本servlet的基本信息,在其他方法中还要用到。
但是Tomcat不希望碰构造方法,所以它只有在init(ServletConfig var)中初始化本类中的那个ServletConfig变量,但是万一你重写了这个init(ServletConfig var),那内部的servletConfig就无法初始化也就无法让别的方法使用,就只好提供给你一个空参的init(),反正serlvetConfig已经变成成员变量了,就不用作为形参。
总结 :由于不想代码冗余,官方提供了抽象类GenericServlet 实现了Servlet接口中的方法,把service设置为抽象方法供我们使用,为了能够保证GenericServlet 类中的servletConfig能够顺利赋值,官方提供了两个init,一个供服务器调用,一个供我们重写。Tomcat调用有参init,有参init中赋值servletConfig并调用了无参init,保证这两个init都能够运行。
这个类就是我们在javaweb中日常使用的类了。
按理说 GenericServlet 已经够用了,但是 HttpServlet 是对它的扩展。HttpServlet, 类如其名,遵守Http协议,更加安全更加灵活。它提供了两个主要使用的方法 :doGet、doPost。
在html学习中我们学习了表单提交的两种方式 :get/post,是不是正好对标这两个方法?确实,前端表单如果是get方式提交数据,后端就要实现doGet方法,如果是post方法,就要实现doPost方法。
后端实现的方法要对应前端的数据提交方式
其实不管是doget还是doPost,最终还是调用了service,我们打开HttpServlet中的service看看 :
让我们还原一下具体的执行流程吧 :
我们写出以下代码 ,并将其访问路径设置为 :/myServlet:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 访问数据库,等等一系列操作
}
}
Tomcat服务器读取配置文件,将MyServlet这个类和它的路径加载到容器中。用户访问 http://localhost:8080/项目名/myServlet时,Tomcat利用反射调用MyServlet的构造器创建对象:Servlet servlet = Class.forName(“MyServlet”) ;
Tomcat调用init(ServletConfig var)方法,调用的是MyServlet中从HttpServlet中继承下来的init(ServletConfig var),在其中为servletConfig赋值,并调用我们自己写的init(),但我们并没有写。
Tomcat调用service方法,调用 MyServlet中从HttpServlet 继承的service方法,service方法判断前端请求是get方式,于是去调用我们重写的doGet方法,完成对请求的响应。
Tomcat即将关闭,调用 MyServlet 从HttpServlet继承的destroy方法将其销毁。
下来的init(ServletConfig var),在其中为servletConfig赋值,并调用我们自己写的init(),但我们并没有写。
Tomcat调用service方法,调用 MyServlet中从HttpServlet 继承的service方法,service方法判断前端请求是get方式,于是去调用我们重写的doGet方法,完成对请求的响应。
Tomcat即将关闭,调用 MyServlet 从HttpServlet继承的destroy方法将其销毁。
上述四步代表着一个servlet的生命周期。