1. Servlet、ServletConfig、GenericServlet、HttpServlet之间的关系:
1) Servlet接口:提供最基本的管理Servlet生命周期的功能,主要有init(构造)、service(执行服务)、destroy(析构)三大功能,因此Servlet接口是任何Servlet类的生命和希望;
2) ServletConfig接口:提供最基本的查询Servlet配置信息的功能,主要有getServletName(获取当前Servlet注册名)、getInitParameter(获取Servlet初始参数)、getServletContext(获取Servlet环境句柄)等,但是它只是一个接口,因此里面没有ServletConfig对象,只是规定了该接口必须实现的功能;
3) GnericServlet类:它是个真正的类,它实现了Servlet和ServletConfig接口,并且它是所有具体Servlet的框架类,所有Servlet类都是该类的子类(都继承自它),包括HttpServlet类,并且它包含真正的数据对象ServletConfig,可以通过ServletConfig接口中的各种方法获取该对象中的信息(即Servlet配置信息);
3. 启动一个Servlet程序的步骤:整个过程都是由Web容器控制的,实际中不实现Servlet类的构造器(构造器是空的),生命周期完全用init、service和destroy控制
1) 首先Web容器会读取web.xml配置信息并创建一个ServletConfig对象,将配置信息存在该对象中;
2) 接着Web容器调用Servlet类的空的构造函数构造一个空的Servlet类对象(里面什么都没有初始化)将其实例化;
3) 然后才是初始化,调用Servlet接口的init(config: ServletConfig)方法,将前面保存Servlet配置的ServletConfig对象作为参数传入init进行初始化,init中只有两句话:
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }i. 第一句当然是初始化config对象;
ii. 第二句不是调用基类的init函数,因为当前方法本身就是从Servlet接口继承来的方法,第二个无参的init方法是GenericServlet自己定义的方法,专门用来初始化自定义的数据成员(比如自己继承HttpServlet类实现了一个MyServlet类,里面自定义了一个数据成员String myName,而这样的数据成员的初始化就可以放在GenericServlet定义的无参init方法中;
4) 接受请求创建request和response对象并传入service进行服务;
5) 最后在一定条件下(关闭服务器、或主动关闭Servlet)调用destroy方法关闭并回收Servlet的空间;
4. GenericServlet的具体实现:
1) 首先是Servlet接口:
i. init(config: ServletConfig): void // 这之前讲过了
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }ii. service(req: ServletRequest, res: ServletResponse): void
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;!!该函数仍然保留为抽象函数,留给继承它的具体Servlet类来实现,比如HttpServlet的实现(我们之前讲过),就包括了对请求方法的判断,调用相应的doXXX方法;
iii. destroy(): void
public void destroy() { // NOOP by default }!!该函数保留为空,因为Servlet类中的数据成员都可由Java虚拟机的垃圾回收器自动管理回收,像String、ServletConfig等Java的类,都可以自动回收无需程序员关心;
!!但是另一些资源,比如数据库连接资源、调用C++本地代码等,这些资源需要程序员自己手动回收不能由Java虚拟机来管理,特别像C++,析构函数就可以在destroy中调用;
/////////除了上面管理生命周期的三个基本方法外还提供了两个获取基本信息的方法,一个用来获取当前Servlet配置对象ServletConfig,另一个获取Servlet描述信息
iv. getServletConfig(): ServletConfig // 获取配置对象
public ServletConfig getServletConfig() { return config; }v. getServletInfo(): String // 获取描述信息
public String getServletInfo() { return ""; }!!这个描述信息是用户自定义的,比如描述一下该Servlet的版本、功能等,所以这里留了一个空的信息;
2) ServletConfig接口:
i. getServletName(): String // 获取注册名
ii. getServletContext(): ServletContext // 获取环境资源句柄
iii. getInitParameter(name: String): String // 获取初始化信息(参数)
iv. getInitParameterNames(): Enumeration<String> // 获取初始化参数名称列表
!!!所有的这些方法的实现都是调用了ServletConfig对象的相应方法,例如getServletName:
public String getServletName() { return getServletConfig().getServletName(); }!都是通过getServletConfig方法的返回结果调用同名的方法
!!这是一种适配器设计模式
3) 有了这些包装程序员就不用直接意识到ServletConfig数据成员的存在了!!相当方便!
4) GenericServlet自己定义的方法:就3个
i. 构造器:GernericServlet(); // 空的,初始化用init(config: ServletConfig): void
ii. 自己的初始化方法:init(): void // 用于初始化用户自定的数据,被init(config: ServletConfig)调用,因此用户不会手动调用init()函数
iii. 日志输出函数:log(msg: String): void
public void log(String msg) { getServletContext().log(getServletName() + ": " + msg); }!!里面调用了ServletContext的log输出,ServletContext的log输出默认保存到Tomcat目录的logs目录中;
!!该log提供的功能太少,一般都是用JDK功能更强大的日志包或Log4j
5. 设置初始参数:
1) 可以在web.xml配置Servlet的初始参数,然后在init中提取Servlet配置的这些初始参数来初始化Servlet类的一些自定义数据成员:
i. 初始参数定义在<serlvet>的<init-param>标签下,其两个属性分别是<param-name>(表示参数名)、<param-value>(表示参数值);
ii. 举例:
<servlet> <servlet-name>... <servlet-class>... <init-param> <param-name>PARAM1</param-name> <param-value>VALUE1</param-value> </init-param> <init-param> <param-name>PARAM2</param-name> <param-value>VALUE2</param-value> </init-param> </servlet>2) 在web容器初始化Servlet时就会从web.xml中读取Servlet的配置并填入ServletConfig的相关字段,最后可以在init中使用getInitParameter来获取配置中的初始参数并用来初始化自己定义的数据成员:
public class ServletConfigDemo extends HttpServlet { private String PARAM1; private String PARAM2; public void init() throws ServletException { PARAM1 = getInitParameter("PARAM1"); PARAM2 = getInitParameter("PARAM2"); } ... }!!当然也可以通过getServletConfig().getInitParameter来获取,但通常都要可以将ServletConfig这个对象给隐藏起来;
3) 也可以使用Servlet标注来设置初始参数:
i. 定义格式是:
initParams={ @WebInitParam(name = "...", value = "..."), @WebInitParam(...), ... }ii. 例如:
@WebServlet( name="ServletConfigDemo", urlPatterns={"/conf"}, initParams={ @WebInitParam(name="PARAM1", value="VALUE1"), @WebInitParam(name="PARAM2", value="VALUE2") } )!!当然web.xml可以覆盖@WebServlet标注,但前提必须是web.xml中的<servlet-name>必须与@WebServlet中的name相同,否则会报错!!
!!通常用标注来设置默认配置值,而web.xml则为发布版的配置值,但以web.xml为准;
!!web.xml协助@WebServlet进行应用部署:
a. 如果只提供@WebServlet标注的话,一旦日后要修改就还必须得打开源代码修改@WebServlet标注中的内容,然后再重新编译,最后才能发布;
b. 但是如果你直接提供一个web.xml就无需打开源代码修改再编译了,所以标注中提供的都是默认值,日后想要修改就直接到web.xml中修改;
6. ServletContext:
1) 即Servlet环境信息,是Config配置信息的一部分,Web容器初始化Servlet时会将该对象设置给ServletConfig;
2) ServletContext中包含请求URL、对象(属性)、环境(应用程序)初始化参数等,由于ServletContext是和整个Servlet共存亡的,因此可以在Servlet的整个生命周期中使用ServletContext,因此可以将一些需要在整个生命周期中需要共享的数据放在里面;
3) 环境初始参数:
i. 之前讲过的那种初始化参数<init-param>叫做配置初始参数,即专门在init中对自定义数据成员进行初始化的;
ii. 现在要讲的是环境初始参数,用来初始化在整个生命周期中需要共享的一些环境数据(环境变量,即一些自定义的Servlet环境变量);
iii. 设置环境初始化参数:由于@WebServlet中没有可以设置环境初始参数的属性,因此必须在web.xml中部署,在<servlet>下使用<context-param>标签定义,其下仍然使用<param-name>和<param-value>定义参数名和参数值;
iv. 获取环境初始参数:调用ServletContext的getInitParameter方法,比如getServletContext().getInitParameter("PARAM_NAME"); 首先必须获得ServletContext对象!
4) 设置环境参数:
i. 使用ServletContext的setAttribute和getAttribute来设置和获取对象属性,这些对象属性专门保存在应用程序环境中(即应用的环境变量),在整个生命周期中共享;
ii. 一个完整的初始化环境变量的过程:通常将getInitParameter获取的环境初始参数进行面向对象包装后使用setAttribute转载成环境变量,例如:
ServletContext context = getServletContext(); String value = context.getInitParameter("PARAM"); MyClass obj = new MyClass(value); // 面向对象包装 context.setAttribute("PARAM", obj); // 作为环境变量加载进context
5) 请求转发:ServletContext也有getRequestDispatcher方法,用法也和HttpServletRequest的一模一样,也可以接着调用forward和include,只不过它们在转发路径上有一点区别,ServletContext转发的路径要求是环境相对路径,而HttpServletRequest转发的路径默认是请求相对路径
i. 环境相对路径:ServletContext的转发路径强制要求为环境相对路径,即以"/"开头的路径,在Servlet里/表示应用程序的根目录(不是Unix中整个文件系统的根目录),接下来的路径都是相对于"/"这个应用程序的根目录的,比如"/ServletFolder/xxx.jsp";
ii. 请求相对路径:HttpServletRequest请求转发的路径默认为该种类型的路径,即不以"/"打头的路径,表示当前Servlet目录中的某个Servlet;
iii. ServletContext、HttpServletRequest的请求转发的联系:如果在Request中给出的是环境相对路径则直接委托给Context转发,如果Request中给出的是请求相对路径,则将被转化为环境相对路径再委托给Context的处理,因此背后都是调用Context的getRequestDispatcher;
6) 遍历资源:
i. 使用ServletContext的getResourcePaths方法:Set<String> getResourcePaths(String path);
ii. 该方法会递归的遍历以path为起点的目录下的所有目录和文件,将每一条路径都按照递归顺序保存在返回的Set中,其中不包括path这个起点本身;
iii. 举例:
for (String path: getServletContext().getResourcePaths("/")) { System.out.println(path); }!path必须是环境相对路径;
!返回的也都是环境环境相对路径;
!返回的如果是子目录则以/作为结尾;
!比如:
/welcome.html /catalog/ /catalog/index.html /catalog/products.html /catalog/offers/books.html /catalog/offers/music.html /customer/ /customer/login.jsp /WEB-INF/ /WEB-INF/web.xml /WEB-INF/classes/com.acme.OrderServlet.class!其中/catalog/、/customer/、/WEB-INF/都是子目录
7) 以二进制方式获取应用程序中的其它资源:使用ServletContext的getResourceAsStream方法,这在前面展示过,用该方法来传送pdf等资源;