JavaWeb开发学习笔记之Servlet(二) Servlet生命周期与配置信息

创建Servlet对象的过程

  • 当Servlet容器启动时,会首先读取web.xml配置文件的信息,构造指定的Servlet对象,创建ServletConfig对象,同时将ServletConfig对象作为参数来调用Servlet对象的init方法。
  • 在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet 对象的service方法。
  • Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的<load-on-startup>属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在。web.xml配置如下:
1
2
3
4
5
<servlet>
<servlet-name>Init</servlet-name>
<servlet-class>org.xl.servlet.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
  • 关于<load-on-startup>的含义是, 标记容器是否在启动的时候就加载这个servlet; 当是一个负数或者未指定时,则指示容器在该servlet被选择时才加载。 正数的值越小,启动该servlet的优先级越高

如何创建配置Servlet

关于Servlet的创建于使用我们需要两个实现要步

  1. 创建一个java类,继承HttpServlet类。 当然,根据JAVA的类继承顺序,我们也可以对HttpServlet的父类进行操作,但是继承HttpServlet类是最常见的,因为HttpServlet类帮我们内部实现了一部分方法,无需我们手动再进行实现
  2. 配置web.xml文件

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;

public class HelloServlet extends HttpServlet {

@Override
public void init() throws ServletException {
System.out.println("init....");
// Servlet初始化
}

@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入Service方法");
// 实际进行业务逻辑处理的地方
}

@Override
public void destroy() {
System.out.println("destroy....");
// Servlet销毁
}

}

在web.xml配置

1
2
3
4
5
6
7
8
9
10
11
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.hel.util.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

// <servlet>用于注册Servlet,包含了<servlet-name>和<servlet-class>两个子元素,分别用于设置servlet的名称以及servlet的类名。
// <servlet-mapping>用于映射上面<servlet>中的对外访问路径,同样包含<servlet-name>和<url-pattern>两个元素,分别用于设置servlet的名称以及servlet的对外访问路径。

测试启动与发送请求,输出:

Servlet的生命周期

关于Servlet的生命周期,在上面的例子中其实也可以看到。servlet的声明周期主要围绕三个方法进行,init(), service(), destroy(), 关于这三个方法的说明如下。

  • init(),Servlet初始化时调用,只执行一次
  • service(), 直接处理业务逻辑请求的方法
  • destroy(), Servlet生命周期结束时调用,进行销毁。只执行一次

ServletConfig

ServletConfig主要用于加载Servlet的初始化参数,在一个web应用可以存在多个ServletConfig对象。

ServletConfig主要包含四个方法

  • String getServletName() – 获取当前Servlet在web.xml中配置的名字
  • String getInitParameter(String name) – 获取当前Servlet指定名称的初始化参数的值
  • Enumeration getInitParameterNames() – 获取当前Servlet所有初始化参数的名字组成的枚举
  • ServletContext getServletContext() – 获取代表当前web应用的ServletContext对象

ServletConfig获取基本配置信息

在Servlet的配置文件中,可以使用一个或多个标签为servlet配置一些初始化参数。(配置在某个 servlet标签)当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数 封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet,因此通过ServletConfig对象就可以得到当前servlet的初始化参数信息。

ServletConfig代表的是Servlet在web.xml中的配置信息,对应web.xml中的Servlet元素

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.hel.util.HelloServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!--配置HelloServlet的初始化参数 -->
<init-param>
<param-name>name</param-name>
<param-value>gacl</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

在HttpServlet中获取.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HelloServlet extends HttpServlet {

@Override
public void init() throws ServletException {

}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("======进入Service方法========");

ServletConfig config = this.getServletConfig();

System.out.println("ServletName: "+config.getServletName());
//获取在web.xml中配置的初始化参数
String paramVal = config.getInitParameter("name");//获取指定的初始化参数
System.out.println(paramVal);

//获取所有的初始化参数
Enumeration<String> e = config.getInitParameterNames();
while(e.hasMoreElements()){
String name = e.nextElement();
String value = config.getInitParameter(name);
System.out.println(name + "=" + value);
}
}

@Override
public void destroy() {
System.out.println("destroy....");
}

}

输出:

我们如果是用IDE工具进行调试的话(此处我用的是Eclipse+Tomcat),其实可以看到我们获取的ServletConfig对象,本身都是一个org.apache.catalina.core.StandardWrapperFacade类对象,来源于Tomcat容器。针对这一点,在以后tomcat的笔记中再进行详细研究。

关于Servlet的配置, 在servlet3.0以上的版本中增加了注解配置,无需继续在web.xml中进行配置,直接在类上使用@WebServlet注解即可,例:

1
2
3
4
5
6
@WebServlet(name = "hello", urlPatterns = { "/hello", "/hello/*" }, loadOnStartup = 1, initParams = {
@WebInitParam(name = "name", value = "gacl"), @WebInitParam(name = "password", value = "123"),
@WebInitParam(name = "charset", value = "UTF-8") })
public class HelloServlet extends HttpServlet {

}

对于@WebServlet注解与web.xml中配置servlet的属性对照,@WebServlet注解的属性都为可选属性,但是value或者urlPatterns通常是必须存在其中之一,它配置了servlet请求的限定范围,且两个属性不能共存,一般我们是用urlPattern,忽略value属性

属性名 类型 描述
name String 指定Servlet的name属性,等价于<servlet-name>。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。
value String[] 该属性等价于 urlPatterns 属性。两个属性不能同时使用。
urlPatterns String[] 指定一组Servlet的URL匹配模式。等价于<url-pattern>标签。
loadOnStartup int 指定Servlet的加载顺序,等价于<load-on-startup>标签。
initParams WebInitParam[] 指定一组Servlet初始化参数,等价于<init-param>标签。
asyncSupported boolean 声明Servlet是否支持异步操作模式,等价于<async-supported>标签。
description String 该Servlet的描述信息,等价于<description>标签。
displayName String 该Servlet的显示名,通常配合工具使用,等价于<display-name>标签。

关于initParams的值,它是一个WebInitParam数组,数组中的每一个值由一个@WebInitParam注解进行配置,关于@WebInitParam注解,不光配合@WebServlet中可以使用,在配置过滤器使用@Filter时也可以使用,以下是@WebInitParam的参数说明

属性名 类型 是否可选 描述
name String 指定参数的名字,等价于<param-name>。
value String 指定参数的值,等价于<param-value>。
description String 关于参数的描述,等价于<description>。

ServletContext对象

ServletContext对象的定义

ServletContext代表的是当前的web应用,是Servlet容器上下文环境对象。定义一组方法,servlet使用这些方法与其servlet容器进行通信。例如,获取文件的 MIME 类型、分发请求或写入日志文件。每个web应用都有且仅有一个ServletContext对象,这个对象在所有的Servlet都可以使用。

以下为ServletContext源码中对其的定义说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Defines a set of methods that a servlet uses to communicate with its
* servlet container, for example, to get the MIME type of a file, dispatch
* requests, or write to a log file.
*
* <p>There is one context per "web application" per Java Virtual Machine. (A
* "web application" is a collection of servlets and content installed under a
* specific subset of the server's URL namespace such as <code>/catalog</code>
* and possibly installed via a <code>.war</code> file.)
*
* <p>In the case of a web
* application marked "distributed" in its deployment descriptor, there will
* be one context instance for each virtual machine. In this situation, the
* context cannot be used as a location to share global information (because
* the information won't be truly global). Use an external resource like
* a database instead.
*
* <p>The <code>ServletContext</code> object is contained within
* the {@link ServletConfig} object, which the Web server provides the
* servlet when the servlet is initialized.
*/

ServletContext对象的获取方式

在Servlet中获取ServletContext对象可通过以下三种方式获得

  • this.getServletContext();
  • this.getServletConfig().getServletContext();
  • request.getSession().getServletContext();

三种方式获取的ServletContext对象都为同一个。

ServletContext的作用

web容器启动时,它为每一个web程序都创建一个对应的ServletContext对象,它代表着当前的web应用,事实上SpringMVC封装的ApplicationContext及Struts2封装的ApplicationContext里面都保存着原本的ServletContext.

ServletConfig获取的是配置在web.xml中的信息,ServletContext可以获取外部资源信息,如常用的properties文件信息。

ServletContext配置的参数,可以被同一web应用下的所有servlet共享, 也正因此,Servlet对象之间可以通过ServletContext对象进行通讯,ServletContext通常也被称为域对象。

总结ServletContext的作用: Web应用范围内存共享数据;访问web应用的静态资源; Servlet对应之间通过ServletContext对象实现通讯。

例,获取web.xml中配置的上下文初始化参数:

在web.xml配置

1
2
3
4
5
<!-- 配置context -->
<context-param>
<param-name>username</param-name>
<param-value>zhangsan</param-value>
</context-param>

在servlet中获取

1
2
3
ServletContext context = this.getServletContext();
context.getInitParameter("username")
// ServletContext中的getInitParameter()和getInitParameterNames()方法与ServletConfig中的用法一致

例,获取ServletContext的上下文属性

1
2
3
4
5
ServletContext context =this.getServletContext();
context.setAttribute("name","value");
context.getAttribute("name");

Enumberation p=application.getAttributeNames();

ServletContext的主要方法

  1. 属性相关,web应用范围内存共享数据
1
2
3
4
setAttribute(String name,Object obj): 添加属性
getAttribute(String name): 获取属性,返回Object
removeAttribute(name): 删除属性:
getAttributeNames(), 获取所有属性
  1. 加载资源文件,访问静态资源

getResource(String parh): 获取代表某个资源的url对象,其中,path必须是/开头,代表当前web应用程序的根目录

1
2
3
4
URL url = context.getResource("/WEB-INF/classes/jdbc.properties");//得到URL              
InputStream in_url = url.openStream();//直接得到流
Properties props1 = new Properties();
props1.load(in_url);//获取资源文件

getResoutceAsStream(String parh),返回文件流。这个好处是可以使用相对于根目录的路径访问到web目录下的所有文件,而不必知道绝对路径。

1
2
3
InputStream in = context.getResourceAsStream("/WEB-INF/classes/jdbc.properties");  
Properties props = new Properties();
props.load(in);

请求转发与重定向

请求转发能实现的主要原因是由于ServletContext的数据共享。

关于请求转发与重定向的模式说明,举例,一个人去银行办事,如果柜台的员工无法处理,她叫你等在这里,去叫经理过来处理,那么这就是请求转发, 重定向是,一个人去银行办事,如果柜台的员工无法处理,她说这个业务我们这个网点办理不了,建议去其他网点办理,然后你就去了另外一个网点办理业务,这就是重定向。

请求转发与重定向的区别

类别 请求转发 重定向
浏览器URL 原来的url 新的url
请求次数 1次 2次
跳转范围 只能请求项目中的资源 可以跳转到项目外
转发 只能请求项目中的资源 可以跳转到项目外

请求转发的实现方式

1
2
RequestDispatcher rd = this.getServletContext().getRequestDispatcher(urlPath);
rd.forward(request, response);

重定向

1
response.sendRedirect(urlPath);

使用ServletContext动态配置

在servlet3.0以后的版本中对于ServletContext的性能进行了增强,该对象支持在运行时动态部署Servlet,Filter,Listener,以及为Servlet和Filter增加url映射。

此处我们以Servlet为例,Filter与Listener的与其大同小异。

之前我们说到配置一个servlet有两种方式

  1. 在web.xml中配置映射
  2. 为servlet对象增加@WebServlet注解配置映射

在3.0以后的servlet版本中,ServeltContext类中对于Servlet增加了以下几个方法。

  • ServletRegistration.Dynamic addServlet(String servletName, String className);
  • ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
  • ServletRegistration.Dynamic addServlet(String servletName, String className)
  • T createServlet(Class clazz)
  • ServletRegistration getServletRegistration(String servletName)
  • Map<String,? extends ServletRegistration> getServletRegistrations()、

关于以上几个方法,前三个重载方法为添加serlvet到注册中心,createServlet动态创建一个servlet, 后两个为获取注册中心ServletRegistration。

ServletRegistration,这个类提供动态注册,像SpringBoot就是通过动态注册servlet到容器。

使用context动态注册新的servlet,是在context初始化时调用。即,在ServletContextListener的contexInitialized()方法中, 或者是在ServletContainerInitializer类中的onStartup()方法中被调用。ServletContainerInitializer是servlet3.0后新增的一个接口

例:

新建一个Servlet类

1
2
3
4
5
6
7
8
public class DynamicCreateTestServlet extends HttpServlet {

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

System.out.println("DynamicCreateTestServlet service ......");
}
}

在context初始化时动态注册一个新的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@WebListener
public class MyServletContextListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("listener init... ");

// 在Listener监听器中获取ServletContext
ServletContext context = sce.getServletContext();
HttpServlet servlet;
try {
// 创建一个Servlet
servlet = context.createServlet(DynamicCreateTestServlet.class);
// 将创建的Servlet添加到注册中心,键为servlet-name, 值为新建的servlet类
context.addServlet("testServlet", servlet);
// 根据新建的servlet,通过其servlet-name获取对应的注册中心
ServletRegistration registration = context.getServletRegistration("testServlet");
// 为注册中心增加映射,相当于为新建的servlet增加urlPattern, 值为一个动态String数组
registration.addMapping("/dynimcServlet");
} catch (ServletException e) {
System.out.println("dynamic add servlet fail");
e.printStackTrace();
}
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("listener destroy...");
}
}

这里还有一点,获取注册中心ServletRegistration时除了使用getServletRegistration()方法
精确获取某一个ServletRegistration之外

1
ServletRegistration getServletRegistration(String servletName)

也可以使用getServletRegistrations()获取所有注册注册中心

1
Map<String,? extends ServletRegistration> getServletRegistrations()

而我们获取的这个Map打印时,除了我们自己配置的Servlet,还有两个不是我们配置的Servlet, 它们的servlet-name一个叫default,一个叫jsp,这两者来源于我使用的tomcat容器,在后面tomcat的内容中会进行讲述。

参考与引用:

IBM developerWorks: Servlet3.0新特性详解——张建平