解读 Servlet 源码:GenericServlet,ServletConfig,ServletContext
每博一文案
人活着,就得随时准备经受磨难。他已经看过一些书,知道不论是普通人还是了不起的人,都要在自己的一生中经历许多磨难。磨难使人坚强。
人和社会、一切斗争的总结局也许都是中庸而已。与其认真、不如随便、采菊东篱下、悠然见南山。有钱就寻一醉、无钱就寻一睡、与过无争、随遇而安。
hellip;…人哪,活着是这么的苦!一旦你从幸福的彼岸被抛到苦难的此岸,你真是处处走头无路;而现在你才知道,在天堂与地狱之间原来也只有一步之遥!
倘若流落在他乡异地,生活中的一切都将失去保障,得靠自己一个人去对付冷酷而严峻的现实了
青年,青年,无论受怎样的挫折和打击,都要咬着牙关挺住,因为你们完全有机会重建生活;只要不灰心丧气,每一次挫折就只不过是通往新境界的一块普通的绊脚石,而绝不会置人于死命。
你永远要宽恕众生,不论他有多坏,甚至他伤害过你,你一定要放下,才能得到真正的快乐。
也许人生仅有那么一两个辉煌的瞬间————甚至一生都可能在平淡无奇中度过......不过,每个人的生活同样也是一个世界,即是最平凡的人,也得要为他那个世界的存在而战斗。这个意义上来说,在这些平凡的世界里,也没有一天是平静的。因此,大多数
普通人不会像飘飘欲仙的老庄,时常把自己看作是一粒尘埃————尽管地球在浩渺的宇宙中也只不过是一粒尘埃罢了。
——————《平凡的世界》
@
1. Servlet对象的生命周期
什么是Servlet 对象的生命周期 ?
Servlet 对象的生命周期表示:一个Servlet对象从出生在最后的死亡,整个过程是怎样的 。
Servlet对象是由谁来维护的?
- Servlet 对象的创建,Servlet对象上方法的调用,以及Servlet 对象的销毁,JavaWeb程序员都无权干预的。
- Servlet 对象的生命周期是由
Tomcat
服务器(web Server)全权负责的
- Tomcat
服务器通常又被我们称之为
WEB容器 (WEB Container)
。WEB 容器来管理 Servlet对象的死活
我们自己new的Servlet对象受WEB容器的管理吗?
我们自己 new 的 Servlet 对象是不受
WEB容器
管理的。
因为:
WEB容器
创建的 Servelt 对象,这些Servlet 对象都会被放到一个集合当中
(HashMap)
,只有放到这个 HashMap 集合中的 Servlet 才能够被 WEB容器管理,自己 new 的 Servlet 对象不会被 WEB容器管理,因为我们自己 new 的Servlet对象并不没有存放到 WEB 容器当中。
WEB容器底层应该有一个 HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系。
1.1 Servlet 五 个方法的调用周期
Servlet 必须重写的五方法分别为:
init(),service(ServletRequest request, ServletResponse response),getServletConfig(),getServletInfo(),destroy(),还有一个无参构造器
什么时候创建的,什么时候调用的,什么时候销毁的。
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
执行如下代码,启动 Tomcat 服务器:并访问该AServlet ,通过在浏览器其当中输入:
http://127.0.0.1:8080/blog01/A
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
public AServlet(){
System.out.println("AServlet 的无参构造器的调用");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("AServlet 中的 init 的调用执行");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet 中的 service 的方法的调用执行");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("AServlet 中的 destroy 方法的执行调用");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- 两个 name 值要保持一致-->
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<!-- 注意: / 开始-->
<url-pattern>/A</url-pattern>
</servlet-mapping>
</web-app>
执行结果如下:
重点:
- 当我们在默认普通配置的情况下,启动Tomcat 服务器的时候,我们编写的
AServlet
对象并没有被实例化(因为并没有调用我们 AServlet 构造器创建对象)。
- 当用户访问该资源,第一次向我们的 AServlet 发送请求的时候。我们的 Servlet对象就被实例化了(这里是 AServlet 的构造器被执行了,并且执行的是该 AServlet 无参构造器。 )
- 之后,当 Servlet 对象被创建出来了,也就是这里的 AServlet 对象,Tomcat 服务器马上就调用了 AServlet 对象当中的
init()对象方法
,(注意:init()方法在执行的时候,我们的 Servlet(这里的 AServlet)对象就已经存在了,已经被创建出来了,因为 init() 是对象方法,需要通过创建对象才能被调用(特殊的 :反射除外))。
- 用户发送第一次请求的时候,init()方法执行完,Tomcat 服务器马上调用了 Servlet (这里的AServlet 对象)当中的
servelt()
方法。
- 注意:当用户继续发送第二次请求(刷新该,页面的资源,重新发送新的请求)。或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的Servlet对象,直接调用该Servlet对象的service方法
结果如下:
- 根据上述结果:我们可以知道如下信息:
- Servlet
对象使单例的 (单实例的。但是要注意:Servlet 对象使单实例的,但是 Servlet 类并不符合
单例模式
的设计。因为
真正的单例模式的构造器private私有的
,所以我们称之为
假单例
,之所以单例是因为 Servelt 对象的创建,我们 javaWeb程序员是管不着的,这个对象的创建只能是
Tomcat
服务器或其他服务器来说的算的。Tomcat 只会创建一个,所以导致了单例,但是属于假单例。)
- 无参构造器,只会执行一次,该无参构造器是通过反射机制调用的(通过从 web.xml 中的配置信息获取到其中对应类的:
全限定类名
创建对象),
- init()
方法只在第一次用户发送请求的时候执行,init对象方法也只被 Tomcat 服务器调用一次。
- 只要用户发送一次请求:
service()
方法必然会被
Tomcat
服务器调用一次,发送 100 次请求,servlect() 方法就会被调用 100次。
- 我们关闭 Tomcat 服务器,控制台上显示如下结果
:
从上述控制台上显示的结果,我们可以知道:
当我们关闭服务器的时候 destroy()方法被调用了
问题:destroy() 方法调用的时候,Servlet 对象销毁了还是没有销毁 ???
答:
destroy()
方法执行的时候,AServlet 对象还在,没有被销毁,因为
destroy()
方法是对象方法。调用该方法的话需要通过对象才能调用的(反射除外),destroy()方法执行结束之后,AServlet 对象的内存才会被 Tomcat 释放。
注意点:
对应 Servlet 类我们不要编写构造器。
当我们Servlet 类中编写了一个有参数的构造器,那么如果我们没有手动再编写一个无参构造器的话,无参构造器就会消失。
如果一个 Servlet 无参构造器消失的会,如何:结果如下:
报错:500 错误。500 是一个 HTTP 协议的错误状态码。 500 一般情况下是因为服务器端的 java 程序出现了异常 。(服务器端的错误基本上都是 500 错误,服务器内部错误)
- 如果一个 Servlet 类没有无参构造器的话,会导致 500 错误,反射机制无法通过
无参构造器创建对象
。导致 无法实例化 Servlet 对象。
- 所以,一定要注意:在 Servlet 开发当中,不建议 程序员来定义构造器,因为定义不当,一不小心就会导致无法实例化 Servlet对象了。
思考:Servlet 的无参数构造器是在对象第一次创建的时候执行的,并且只会执行一次。而
init()
方法也是在对象第一次拆创建的时候执行的,并且也只会执行一次。那么这个无参构造器可以代替 init()方法吗 ?
答:不能。
Servlet 规范中有要求,作为 javaWeb 程序员,编写 Servlet 类的时候,不建议手动编写构造器,因为编写构造器,很容易让无参数构造器消失。这个操作可能会导致 Servlet 对象无法实例化,所以
init()
方法是有存在的必要的。
1.2 Servlet 常用的三个方法使用总结
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
// init 被翻译为初始化
// init 方法只会被执行一次,基本上和 Servlet构造器的调用同时执行,在Servlet 对象第一次被创建只会执行
// init 方法通常是完成初始化操作的。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("AServlet is init method execute!");
}
// service 方法:是处理用户请求的核心方法
// 只要用户发送一次请求,service 方法必然会执行一次
// 发送100次请求,service方法执行100次
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet is service method execute");
}
// destroy ()方法也是只执行一次
// Tomcat 服务器在销毁AServlet 对象之前会调用一次destroy 方法
// destroy()方法在执行的时候,AServlet 对象的内存还没有被销毁,即将被销毁,因为destroy 是对象方法,需要通过对象调用
// destroy 方法中可以编写销毁前的准备
// 比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接
// 那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()
@Override
public void destroy() {
System.out.println("AServlet is destroy method execute");
}
}
关于Servlet类中方法的说明:
- 无参构造器
:只会执行调用一次。
- init()
方法:init 被翻译为初始化,init ()方法只会被执行一次;基本上和 Servlet构造器的调用同时执行;在Servlet 对象第一次被创建只会执行。init 方法通常是完成初始化操作的。
- service()
方法:
service() 方法是处理用户请求的核心方法。
只要用户发送一次请求,service 方法必然会执行一次;发送100次请求,service方法执行100次。
- destroy()
方法:destroy 方法中可以编写销毁前的准备;比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接;那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()。
init、service、destroy方法中使用最多的是哪个方法?
使用最多就是service()方法,service方法是一定要实现的,因为service方法是处理用户请求的核心方法。
什么时候使用init方法呢?
init方法很少用;通常在init方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池....
什么时候使用destroy方法呢?
destroy方法也很少用;通常在destroy方法当中,进行资源的关闭。马上对象要被销毁了,还有什么没有关闭的,抓紧时间关闭资源。还有什么资源没保存的,抓紧时间保存一下。
Servlet对象更像一个人的一生:
- Servlet的无参数构造方法执行:标志着你出生了。
- Servlet对象的init方法的执行:标志着你正在接受教育。
- Servlet对象的service方法的执行:标志着你已经开始工作了,已经开始为人类提供服务了。
- Servlet对象的destroy方法的执行:标志着临终。有什么遗言,抓紧的。要不然,来不及了。
1.3 补充:让启动Tomcat 服务器的时候会调用构造器
从上述内容我们知道了,当 Tomcat 服务器启动的时候,我们的构造器实际上还没有调用,也就是对象还没有被实例化创建出来。但是我们想在启动 Tomcat 服务器的时候就调用构造器。可以使用如下方式:
在我们想让Tomcat 服务器启动的时候调用哪个类的构造器,就在该类当中的
web.xml
加上如下:标签
<load-on-startup>(填写数值)</load-on-startup>
: 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建
<!--<load-on-startup> 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
<load-on-startup>2</load-on-startup>
举例:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- 两个 name 值要保持一致-->
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<!-- 注意: / 开始-->
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<!-- 两个 name 保持一致-->
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
<!-- 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<!-- 注意 / 开始-->
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
2. GenericServlet
2.1 适配器
我们编写一个Servlet 类直接实现了 这个
Servlet 接口
存在什么缺点没有:?
有的:我们编写的 Servlet 类实现了该 Servlet 接口中所有的方法。但是我们其实只需要其中的
service()方法
,其他方法大部分情况下是不需要使用的。简单粗暴的实现了我们所有的方法,但是其中大部分的方法我们是不需要的,这样就显的我们的代码很丑陋了:
所以:我们不想要重写所以的方法,而是重写我们需要用的方法。这样该怎么做呢?
我们可以使用 23种设计模式中的一种:
适配器模式(Adapter)
。
适配器模式的理解:举例
比如:如果我们的手机充电时直接插入到 220V 的电压上,手机会直接报废的,因为手机无法接受如此高的电压,怎么办呢?
我们可以该手机配备一个电源适配器——充电器,手机连接充电器(适配器),适配器连接 220V的电压,这样就问题解决了。同理的还有很多:电脑的充当器。充电热水壶等等。
编写一个 GenericServlet (abstract )抽象类适配器。
这个适配器是一个
abstract
抽象类。:我们该类实现了 Servlet 接口中所有的方法,但是其中我们常用的方法
service()
定义为是抽象方法,因为如果不想实现的方法,可以定义将该方法定义为抽象方法就不用重写了,而是交给
继承
的子类重写就好了。因为重写方法只能存在于抽象类,接口当中,所以我们这个
适配器就为了一个抽象类
。这样我们以后编写的所以 Servlet 类就只需要继承 GenericServlet 抽象类,并且只要重写其中的
service()
抽象方法即可。但是我们又可以使用 Servlet 接口中的方法,因为我们的父类GenericServlet 抽象类 实现了 Servlet 接口中的所有方法。如有必要,我们编写的Servlet 类也可以重写GenericServlet 抽象类中的方法,该重写的方法也是重写Servlet 接口中的方法。
具体编写如下:
abstract class GenericServlet 抽象类适配器
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 编写一个标准通用的Servlet
* 以后所有的Servlet 类都不要直接实现Servlet 接口了。
* 以后所有的Servlet 类都继承 GenericServlet 类。
* GenericServlet 就是一个适配器
*/
public abstract class GenericServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 抽象方法:这个方法最常用,所以要求子类必须实现Service 方法。
*/
@Override
public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
以后编写的Servlet 都继承该 GenericServlet 抽象类适配器
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet 处理请求中....");
}
}
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("BServlet 处理请求中....");
}
}
问题:提供了 GenericServlet 类,init()方法还好执行吗?
答:还会执行,执行 GenericServlet 中被重写的 init()方法。
问题:init()方法是谁调用的 ?
Tomcat 服务器调用的。
问题:init()方法中的 ServletConfig 对象是谁创建的 ? 有是谁传过来的?
是由 Tomcat服务器 创建的,也是由 Tomcat 服务器传过来的。都是Tomcat 服务器干的。
Tomcat 服务器先创建了 ServleConfig 对象,然后调用 init()方法,将ServleConfig 对象传给了 init()方法
。
Tomcat 执行init()方法的伪代码
:
@Override
public void init(ServletConfig config) throws ServletException {
}
public class Tomcat {
public static void main(String[] args){
// .....
// Tomcat服务器伪代码
// 创建LoginServlet对象(通过反射机制,调用无参数构造方法来实例化LoginServlet对象)
Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
Object obj = clazz.newInstance();
// 向下转型
Servlet servlet = (Servlet)obj;
// 创建ServletConfig对象
// Tomcat服务器负责将ServletConfig对象实例化出来。
// 多态(Tomcat服务器完全实现了Servlet规范)
ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
// 调用Servlet的init方法
servlet.init(servletConfig);
// 调用Servlet的service方法
// ....
}
}
2.2 模板方法设计模式
思考:
init 方法中的ServileConfig 对象是小猫咪创建好的,这个ServletConfig对象目前在init 方法的参数上,属于局部变量。那么ServletConfig 对象肯定以后要在Service 方法中使用,怎么才能保证ServletConfig 对象在Service方法中能够使用呢?
答: 可以定义一个类的属性,再通过 init 方法的,将该ServleConfig 赋值上去
具体代码如下:
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 编写一个标准通用的Servlet
* 以后所有的Servlet 类都不要直接实现Servlet 接口了。
* 以后所有的Servlet 类都继承 GenericServlet 类。
* GenericServlet 就是一个适配器
*/
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
// ServletConfig 对象了
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
// 获取该 config 对象值
public ServletConfig getConfig() {
return config;
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 抽象方法:这个方法最常用,所以要求子类必须实现Service 方法。
*/
@Override
public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
举例:Servale 类在 service ()方法当中获取到该 ServletConfig 对象值,并使用
package com.RainbowSea.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置 response 在浏览器页面当中显示的格式类型
// 注意需要在显示之前,设置
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("AServlet 处理请求中...." + "<br>");
ServletConfig config = super.getConfig();
out.print("AServlet 当中 service 获取到 Tomcat 服务器创建的ServletConfig对象: " + config);
}
}
重点:
我们通过上述定义了一个
private ServletConfig config
成员属性,可以获取到Tomcat 创建的 ServletConfig 对象了,并在
service()
方法当中使用。但是存在一个问题:如果我们的子类要是重写了,我们父类适配器
GenericServlet
类当中的
init()
方法的话。因为多态性的缘故:在实际的运行过程中,调用的会是我们子类重写的的
init()
方法,这样导致的结果就,我们父类中的
private ServletConfig config
成员属性的值无法赋值上为
null
了。因为我们父类当中的 config 成员属性是通过父类当中的 init()方法赋值的,现在我们的子类重写了父类的 init()方法。不会调用我们父类的 init()进行赋值操作了,从而导致为了 null 值。
解决方式:将我们父类当中的 init()方法被
final
修饰,这样我们的子类就无法重写 init()方法了。
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
// ServletConfig 对象了
@Override
public final void init(ServletConfig config) throws ServletException {
this.config = config;
}
}
上述的解决方式仍然存在一个问题:就是如果我们的子类一定要重写 init()方法该怎么办,现在你父类当中的 init()方法被 final 修饰了无法,被 子类重写。
解决方式:
我们使用 23中设计模式当中的 模板方法设计模式:
因为子类想要重写我们父类当中的 init()方法,那么我们就在父类当中,再创建一个 无参数的init()方法(方法的重载),这个方法让子类去重写,但是我们父类当中还有一个 带参数的 init(ServletConfig config) 方法,在该带有参数的init()方法当中调用我们这个提供该子类重写的 init()方法。具体代码如下:
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 编写一个标准通用的Servlet
* 以后所有的Servlet 类都不要直接实现Servlet 接口了。
* 以后所有的Servlet 类都继承 GenericServlet 类。
* GenericServlet 就是一个适配器
*/
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
// ServletConfig 对象了,final 修饰的方法无法重写
@Override
public final void init(ServletConfig config) throws ServletException {
// 赋值依旧存在,config 不会为 null
this.config = config;
// 调用子类重写后的 init()方法
this.init();
}
// 用于提供给子类重写
public void init() {
}
public ServletConfig getConfig() {
return config;
}
}
举例:在AServlet 类当中重写 init()方法,并且执行该重写的方法:
package com.RainbowSea.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置 response 在浏览器页面当中显示的格式类型
// 注意需要在显示之前,设置
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("AServlet 处理请求中...." + "<br>");
ServletConfig config = super.getConfig();
out.print("AServlet 当中 service 获取到 Tomcat 服务器创建的ServletConfig对象: " + config);
}
// 重写的父类当中的 init()无参方法
@Override
public void init() {
System.out.println("AServlet 重写的 init()方法执行了");
}
}
2.3 补充:GenericServlet 不用我们自己编写
注意:
GenericServlet
抽象类适配器是不需要我们自己编写的,Servlet 已经编写好了,我们只需要使用就可以了。
上述是为了理解 GenericServlet 源码,从而自己编写的 GenericServlet 适配器的。如下是
Servlet 为我们编写好的 GenericServlet 源码
,和我们上面自己编写的设计结构是一样的。
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
// NOOP
}
@Override
public void destroy() {
// NOOP by default
}
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
// NOOP by default
}
public void log(String message) {
getServletContext().log(getServletName() + ": " + message);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
}
3. ServletConfig
从上面的知识我们知道了 : Tomcat 服务器先创建了 ServletConfig 对象,然后调用init()方法,将ServletConfig对象传给了init()方法。
先来一波,自问自答:
ServletConfig 是什么?
package javax.servlet; 显然 ServletConfig 是一个接口。
谁去实现了这个接口呢 ?
org.apache.catalina.core.StandardWrapperFacade
这个类实现了这个 ServletConfig 接口
StandardWrapperFacade
源码如下:
package org.apache.catalina.core;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
/**
* Facade for the <b>StandardWrapper</b> object.
*
* @author Remy Maucherat
*/
public final class StandardWrapperFacade
implements ServletConfig {
// ----------------------------------------------------------- Constructors
/**
* Create a new facade around a StandardWrapper.
* @param config the associated wrapper
*/
public StandardWrapperFacade(StandardWrapper config) {
super();
this.config = config;
}
// ----------------------------------------------------- Instance Variables
/**
* Wrapped config.
*/
private final ServletConfig config;
/**
* Wrapped context (facade).
*/
private ServletContext context = null;
// -------------------------------------------------- ServletConfig Methods
@Override
public String getServletName() {
return config.getServletName();
}
@Override
public ServletContext getServletContext() {
if (context == null) {
context = config.getServletContext();
if (context instanceof ApplicationContext) {
context = ((ApplicationContext) context).getFacade();
}
}
return context;
}
@Override
public String getInitParameter(String name) {
return config.getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
Tomcat 服务器实现了ServletConfig 接口。
思考:
如果 Tomcat 服务器换成 了其他的服务器,比如换成是 Jetty 服务器,输出 ServletConfig 对象的时候,还是这个结果吗?
不一定是一样的结果了,包含的类名可能和Tomcat 不一样了,但是他们都实现了 ServletConfig 这个规范。
问题:ServletConfig对象是谁创建的,在什么时候创建的?
Tomcat 服务器(WEB服务器) 创建了ServletConfig 对象,在创建Servlet 对象的时候,同时创建ServletConfig 对象.
问题:ServletConfig 接口到底是干什么的? 有什么用?
Config 是 Configuration单词的缩写:n. 布局,构造;配置
ServletConfig对象中创建包装了
web.xml <servlet></servlet> 标签配置信息
:
Tomcat 小喵咪解析web.xml 文件,将web.xml文件中
<servlet><init-param></init-param></servlet>
标签中的配置信息自动包装到ServletConfig 对象当中
。
如下:
注意:一个Servlet 对象就会有一个
<servlet></servlet>
配置信息标签
。如下是
AServlet对象的
xml 配置信息。
<--以上是 <servlet-name>ConfigTestServlet</servlet-name> <init-param></init-param>
<--是初始化参数,这个初始化参数信息被小喵咪封装到 ServletConfig 对象当中 -->
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 这里是可以配置一个Servlet对象的初始化信息的 注意:这里的配置对应的是上述<servlet-name>
为AServlet-->
<init-param>
<param-name>Driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
3.1 ServletConfig 当中常用的四个方法
public String getServletName(); // 获取该Servlet对象的名称
public String getInitParameter(String name); // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应 <param-value>root</param-value> 标签当中的 value 值
public Enumeration<String> getInitParameterNames(); // 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
public ServletContext getServletContext(); // 获取到ServletContext对象
// 以上的4个方法: 在自己的编写的Servlet表当中也可以使用this,/super去调用(这个Servlet继承了GenericServet)
3.1.2 通过ServletConfig 对象获取到 web.xml 配置文件
标签当中的配置信息
举例:获取到 web.xml 配置文件当中 :
<servlet><init-param></init-param></servlet>
标签中的配置信息。因为该配置信息是会被动包装到ServletConfig 对象当中
。这里我们分别获取到 AServlet 对象和 BServlet 对象当中的配置信息。
对应web.xml 信息如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 这里是可以配置一个Servlet对象的初始化信息的 注意:这里的配置对应的是上述<servlet-name>
为AServlet-->
<init-param>
<param-name>Driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
<init-param>
<param-name>password</param-name>
<param-value>root123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
获取BServlet 对象当中的 web.xml 标签
的配置信息
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class BServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到 GenericServlet 父类适配器当中的 ServletConfig config成员属性
ServletConfig servletConfig = super.getServletConfig();
// 获取该Servlet对象的名称
String ServletName = servletConfig.getServletName();
writer.print(ServletName + "<br>"); // 输出 Servlet 的对象的名称
String name = "password"; // 这里如果我们直接知道name 为 pawword
// 通过 <param-name>user</param-name> 标签当中的name 值获取到对应 <param-value>root</param-value> 标签当中的 value 值
String value = servletConfig.getInitParameter(name);
writer.print(name + "--->" + value); // 页面输出
}
}
获取AServlet 对象当中的 web.xml 标签
的配置信息
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到 GenericServlet 父类适配器当中的 ServletConfig config成员属性
ServletConfig servletConfig = super.getServletConfig();
// 获取该Servlet对象的名称
String ServletName = servletConfig.getServletName();
writer.print(ServletName + "<br>"); // 输出 Servlet 的对象的名称
// 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
Enumeration<String> names = servletConfig.getInitParameterNames();
while(names.hasMoreElements()) { // 判断是否还有 元素,有返回 true,没有返回false。和集合当中的迭代器类似
String name = names.nextElement(); // 取出name 值
// 通过 <param-name>user</param-name> 标签当中的name 值获取到对应 <param-value>root</param-value> 标签当中的 value 值
String value = servletConfig.getInitParameter(name);
writer.print(name + "--->" + value); // 页面输出
writer.print("<br>"); // 页面换行
}
}
}
3.1.3 获取到 ServletContext对象
方式一:
通过先获取到 ServletConfig 对象,再通过 ServletConfig 对象获取到 ServletContext 对象
使用 ServletConfig 当中的 public ServletContext getServletContext(); // 获取到ServletContext对象
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 获取到 GenericServlet父类适配器当中的 private transient ServletConfig config; 成员属性
ServletConfig servletConfig = super.getServletConfig();
// 获取到 servletContext 对象
ServletContext servletContext = servletConfig.getServletContext();
System.out.println("servletContext的值: " + servletContext);
}
}
方式二:
直接通过 父类 GenericServlet 适配器当中的。this,super.getServletContext() 方法 也可以获取到ServletContext对象
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重写我们常用的 service 父类中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
System.out.println("servletContext的值: " + servletContext);
}
}
4. ServletContext
- ServletContext
是接口,是Servlet规范中的一员。Tomcat服务器(WEB服务器) 实现了ServletContext 接口。
- ServletContext
是一个接口,Tomcat 服务器对 ServletContext 接口进行了实现。ServletContext 对象的创建也是 Tomcat 服务器来完成的。启动
webapp
的时候创建的。
- ServletContext
对象在服务器启动阶段创建的,在服务器关闭的时候销毁。这就是 ServletContext 对象的生命周期。
- ServletContext 对象的环境对象,(Servlet对象的上下文对象)。
重点:
一个ServletContext 表示的就是一个 webapp 当中的
web.xml
文件当中下配置信息。而一个 webapp 一般只有一个 web.xml 文件。所以只要在同一个 webapp 当中,只要在同一个应用当中,所以的Servlet 对象都是共享同一个
ServletContext对象的
。举例:Tomcat 服务器中有一个 webapps ,这个 webapps 下存放了多个 webapp 。假设有 100 个 webapp ,那么就会有 100 个 ServeltContext 对象,因为一个 webapp 对应一个 web.xml 文件。总之,一个应用,一个 webapp 只有一个 web.xml文件,则只有一个 ServletContext 对象,所有该应用下/webapp下的 Servlet 对象共享。
如下:我们一个 webapp 下有两个 Servlet 对象,分别为 BServlet 和 AServlet 对象,但是他们两个的同时在同一个 webapp下的,只有一个 web.xml 文件。所以这个两个 Servlet 对象共享 一个 ServletContext 对象:
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
writer.print("BServlet 下的 servletContext的值: " + servletContext);
}
}
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
writer.print("AServlet 下的 servletContext的值: " + servletContext);
}
}
ServletContext对应显示生活中的什么例子呢?
一个教室里有多个学生,那么每一个学生就是一个Servlet,这些学生都在同一个教室当中,那么我们可以把这个教室叫做ServletContext对象。那么也就是说放在这个ServletContext对象(环境)当中的数据,在同一个教室当中,物品都是共享的。比如:教室中有一个空调,所有的学生都可以操作。可见,空调是共享的。因为空调放在教室当中。教室就是ServletContext对象。
4.1 ServletContext 获取web.xml 配置文件当中的 <context-param>标签当中的配置信息
我们想要获取到web.xml 配置文件大当中的<context-param>标签当中编写的的配置信息,需要使用到如下两个方法:
public String getInitParameter(String name); // 通过初始化参数的name获取value
public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name
<!--以上两个方法是ServletContext对象的方法,这个方法获取的是什么信息?是以下的配置信息-->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。-->
<!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。-->
举例:
如下是 Servle 对象通过,ServletContext对象的上述两个方法,获取到
标签当中编写的配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--如下是: context-param 的编写的配置信息,该配置信息,被所有的Servlet共享
可以使用ServletContext对象获取到 -->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
writer.print("AServlet 下的 servletContext的值: " + servletContext + "<br>");
// 一次性获取到 <context-param> </context-param>标签当中所有的 name 值
Enumeration<String> names = servletContext.getInitParameterNames();
while(names.hasMoreElements()) { // 判断该集合是否还有元素,有返回true,没有返回false
// 获取到元素当中的 name值
String name = names.nextElement();
// 通过对象 <context-param> </context-param>标签下的name获取到对应的value值
String value = servletContext.getInitParameter(name);
writer.print(name + "--->" + value);
writer.print("<br>");
}
}
}
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
writer.print("BServlet 下的 servletContext的值: " + servletContext + "<br>");
// 一次性获取到 <context-param> </context-param>标签当中所有的 name 值
Enumeration<String> names = servletContext.getInitParameterNames();
while(names.hasMoreElements()) { // 判断该集合是否还有元素,有返回true,没有返回false
// 获取到元素当中的 name值
String name = names.nextElement();
// 通过对象 <context-param> </context-param>标签下的name获取到对应的value值
String value = servletContext.getInitParameter(name);
writer.print(name + "--->" + value);
writer.print("<br>");
}
}
}
4.2 ServletContext 获取文件的绝对路径
获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径。 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,会给你的另外取起一个什么名字。所以我们需要动态获取。
public String getContextPath(); // 获取到该项目在 url 上输入的项目名
public String getRealPath(String path);// 获取文件的绝对路径(真实路径)
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
String realPath = servletContext.getRealPath("/index.html");
举例:
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
// 获取到这个项目名。
String contextPath = servletContext.getContextPath();
writer.print(contextPath + "<br>");
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
String realPath = servletContext.getRealPath("/index.html");
writer.print(realPath);
}
}
注意:如果想要获取到web目录下面的子目录下的,文件的绝对路径的话,需要写明该文件是在 web目录下的哪个子目录。不然无法找到的
。因为
getRealPath()
方法是从 web目录下开始寻找的。
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
// 获取到这个项目名。
String contextPath = servletContext.getContextPath();
writer.print(contextPath + "<br>");
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
// 如果是web目录下的子目录的文件,需要写明对应的子目录
String realPath = servletContext.getRealPath("/test/index.html");
writer.print(realPath);
}
}
4.3 ServletContext对象"应用域" 存放数据量小,不易修改的数据信息
ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)
如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中存储起来。存储起来后,可以被其他的 Servlet 对象获取到。
为什么是所有用户共享的数据? 不是共享的没有意义。
因为ServletContext这个对象只有一个。只有共享的数据放进去才有意义。
为什么数据量要小?
因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。
为什么这些共享数据很少的修改,或者说几乎不修改?
所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的。
数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率。
对于ServletContext对象应用域,的存,取,删的方法如下:
// 存(怎么向ServletContext应用域中存数据)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取(怎么从ServletContext应用域中取数据)
public Object getAttribute(String name); // Object v = map.get(k)
// 删(怎么删除ServletContext应用域中的数据)
public void removeAttribute(String name); // map.remove(k)
举例:如下我们定义了一个 User 类,将该 User类的实例化对象存储到 ServletContext 对象当中去,并在不同的Servlet 对象当中取出来。
package com.RainbowSea.servlet;
public class User {
private String name;
private String password;
public User() {
}
public User(String name,String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
AServlet 对象,存,取
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
// 创建一个 User 实例对象
User user = new User("Tom","123456");
// 存
servletContext.setAttribute("userObj",user); // map<K,V>
// 取 注意参数是我们 ,setAttribute设置的 K 值
//Object userObj = servletContext.getAttribute("userObj");
// 因为我们这里知道我们存储的是什么类型的数据所以可以直接强制类型转换
User userObj = (User) servletContext.getAttribute("userObj");
writer.print(userObj); // 浏览器页面输出
}
}
BServlet 对象的取
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 设置在浏览器页面显示的格式类型,必须在输出前设置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 获取到GenericServlet父类适配器当中的 servletContext 对象
ServletContext servletContext = super.getServletContext();
// 取 注意参数是我们 ,setAttribute设置的 K 值
//Object userObj = servletContext.getAttribute("userObj");
// 因为我们这里知道我们存储的是什么类型的数据所以可以直接强制类型转换
User userObj = (User) servletContext.getAttribute("userObj");
writer.print(userObj); // 浏览器页面输出
}
}
两者浏览器显示的结果:注意要先访问 AServlet (先将数据存储到 ServletContext对象当去),不然BServlet 访问的话就是 null了
4.4 ServletContext 日志信息
我们可以通过 ServletContext 生成日志信息:
// 通过ServletContext对象也是可以记录日志的
public void log(String message);
public void log(String message, Throwable t);
// 这些日志信息记录到哪里了?
// localhost.2021-11-05.log
// Tomcat服务器的logs目录下都有哪些日志文件?
//catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
//localhost.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
//localhost_access_log.2021-11-05.txt 访问日志
举例:
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
ServletContext servletContext = super.getServletContext();
servletContext.log("你好世界"); // 添加日志信息
// 达到某个条件,计入日志信息:异常
int age = 17;
if( age < 17) {
servletContext.log("对不起,您未成年,请绕行",new RuntimeException("小屁孩,快走开,不适合你"));
}
}
}
5. 总结:
- Servlet 对象的生命周期:
- 无参构造器
:只会执行调用一次。
- init()
方法:init 被翻译为初始化,init ()方法只会被执行一次;基本上和 Servlet构造器的调用同时执行;在Servlet 对象第一次被创建只会执行。init 方法通常是完成初始化操作的。
- service()
方法:
service() 方法是处理用户请求的核心方法。
只要用户发送一次请求,service 方法必然会执行一次;发送100次请求,service方法执行100次。
- destroy()
方法:destroy 方法中可以编写销毁前的准备;比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接;那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()。
- GenericServlet 抽象类适配器思想以及模板方法设计思想
- ServletConfig对象中创建包装了
web.xml <servlet></servlet> 标签配置信息
:
Tomcat 小喵咪解析web.xml 文件,将web.xml文件中
<servlet><init-param></init-param></servlet>
标签中的配置信息自动包装到ServletConfig 对象当中
。这个是 :一个 Servlet 对象就一 个 ServletConfig 对象,有 100 个 Servlet 对象就有 100 个 ServletConfig 对象。
public String getServletName(); // 获取该Servlet对象的名称
public String getInitParameter(String name); // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应 <param-value>root</param-value> 标签当中的 value 值
public Enumeration<String> getInitParameterNames(); // 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
public ServletContext getServletContext(); // 获取到ServletContext对象
// 以上的4个方法: 在自己的编写的Servlet表当中也可以使用this,/super去调用(这个Servlet继承了GenericServet)
- ServletContext 获取web.xml 配置文件当中的 <context-param>标签当中的配置信息。一个 webapp 就有一个ServleContext 对象,因为一个 webapp 就只有一个 web.xml 配置文件,所有的 Servlet 对象共享 一个 ServletContest 对象。有 100 个 webapp 就有 100 个 web.xml配置文件,就有 100 个 ServletContext 对象。简单的来说 一个 ServletContext 对象就对应着一个 web.xml 配置文件。
- ServletContext 获取文件的绝对路径。
public String getContextPath(); // 获取到该项目在 url 上输入的项目名
public String getRealPath(String path);// 获取文件的绝对路径(真实路径)
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
String realPath = servletContext.getRealPath("/index.html");
- ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中存储起来。存储起来后,可以被其他的 Servlet 对象获取到。
// 存(怎么向ServletContext应用域中存数据)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取(怎么从ServletContext应用域中取数据)
public Object getAttribute(String name); // Object v = map.get(k)
// 删(怎么删除ServletContext应用域中的数据)
public void removeAttribute(String name); // map.remove(k)
- Servlet 接口的结构图
jakarta.servlet.Servlet(接口)【爷爷】
jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
我们以后编写的Servlet要继承HttpServlet类。
6. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后会有期!!!