(一)于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把Servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成
(二)<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
(三)一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
★这里需要注意的是,一个servlet可以被多次映射,也即一个Servlet可以有多个<servlet-mapping>,对外提供多个访问路径。举例如下:
- <servlet>
- <servlet-name>MyServlet1</servlet-name>
- <servlet-class>com.gavin.servlet.MyServlet1</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>MyServlet1</servlet-name>
- <url-pattern>/MyServlet1</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>MyServlet1</servlet-name>
- <url-pattern>/Gavin.html</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>MyServlet1</servlet-name>
- <url-pattern>/servlet/Gavin.html</url-pattern>
- </servlet-mapping>
这里可以看到,我们将MyServlet1映射成了一个名字为MyServlet1,另一个名字为Gavin.html,还有一个名字为/servlet/Gavin.html。这里也得出结论:
★后缀名为html的资源不一定真的就是html。
★映射的名字可以有多级,有多个斜杠。
★在Servlet映射到的URL中也可以用*通配符,但只能有两种固定的格式,一种格式是“*.扩展名”,另一种是以正斜杠(/)开头并以”/*”结尾。
- <servlet-mapping>
- <servlet-name>AnyName</servlet-name>
- <url-pattern>/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>AnyName</servlet-name>
- <url-pattern>/news/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>AnyName</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
则第一种可以匹配任何名字的servlet,第二种可以匹配前面有一层为news的servlet,这种方法可以应用在:比如一个网站要对新闻版块进行整改,则可以用这种方法将新闻版块的请求指向其他地方,将其暂时关闭。第三种匹配后缀为do的servlet。
匹配原则:在匹配的时候,(1)看谁的匹配度高,谁就被选中;(2)【*.后缀名】的优先级最低,实在没有匹配项的时候才匹配这个。
看一个面试题:
对于如下的一些映射关系:
Servlet1映射到/abc/*
Servlet2映射到/*
Servlet3映射到/abc
Servlet映射到*.do
当请求URL为“/abc/a.html”,“/abc/*”和”/*”都匹配,哪个Servlet响应?
答案:Servlet1
当请求URL为“/abc”时,”/abc/*”、”/abc”和”/*”都匹配,哪个Servlet响应?
答案:Servlet3
当请求URL为“/abc/a.do”,“/abc/*”和” *.do”都匹配,哪个Servlet响应?
答案:Servlet1
当请求URL为“/a.do”,“/*”和”*.do”都匹配,哪个Servlet响应?
答案:Servlet2à【*.后缀名】的优先级最低
5、当请求URL为“/xxx/yyy/a.do”,“/*”和” *.do”都匹配,哪个Servlet响应?
答案:Servlet2à还是【*.后缀名】的优先级最低
通配符的价值在于可以在网站某版块或者整个网站进行维护时,进行暂时的关闭版块或网站。
(四)Servlet是一个供其他java程序(Servlet引擎)调用的java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度
(五)针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,(单例模式)也就是说Servlet实例一旦创建,它就会驻留在内存中,为后续的其他请求服务,直至web容器退出/或者reload该web运用,servlet实例对象才会销毁。
★Servlet单例模式存在线程安全问题,要注意并发处理,比如买票系统,票是所有客户端共享的,那么售票的代码应该使用同步机制。
synchronized(this){
}
如果一个变量不是共享的,就不需要设置为成员变量,只需要在doGet或者doPost方法中定义为局部变量即可。
(六)在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,service方法再根据请求方式分别调用doXXX方法。
(七)★如果在<servlet>元素中配置了一个<load-on-startup>元素,那么web应用程序启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
用途:为web应用写一个InitServlet,这个Servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。或者是启动一个后台线程,定时去完成某些工作(比如每隔10秒发一封电子邮件)
举例:
新建Servlet为InitServlet,其代码如下:
- package com.gavin.servlet;
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- public class InitServlet extends HttpServlet {
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- this.doGet(request, response);
- }
- //初始化函数
- public void init() throws ServletException {
- System.out.println("InitServlet的init函数被调用...");
- System.out.println("创建库、表...或者定时任务...");
- }
- }
其中只写了init函数的代码。在web.xml部署如下,其不用<servlet-mapping>映射,因为它只要在启动web应用时调用,而不需要外部访问。
- <servlet>
- <servlet-name>InitServlet</servlet-name>
- <servlet-class>com.gavin.servlet.InitServlet</servlet-class>
- <!-- 这里的1表示所有自启动的servlet的启动顺序,可以用1、2、3... -->
- <load-on-startup>1</load-on-startup>
- </servlet>
这里的1表示所有自启动的servlet的启动顺序,可以用1、2、3...
启动Tomcat服务器,可以看到:
启动Tomcat服务器的同时,这个Servlet已经被装载到内存了。
这里我们来模拟一个定时发送电子邮件的功能,实现思路:
这里当用户设定了一个定时发送邮件的任务后,数据库里肯定存在一张表,大概如下:
Id | Content | sendTime |
1 | Hello | 2014-5-18 00:00 |
2 | Happy Birthday | 2014-5-20 13:14 |
这里只是做一个简单的模拟,新建一个sendMainThread线程类
- package com.gavin.model;
- public class SendMailThread extends Thread {
- public void run() {
- int i = 0;
- while (true) {
- try {
- // 每休眠一分钟,就去扫表sendmail,看看哪些信件应当发送
- Thread.sleep(1000 * 10);
- System.out.println("发出 第" + (++i) + "封信件");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
在InitServlet中创建该线程,并启动即可
- public void init() throws ServletException {
- System.out.println("InitServlet的init函数被调用...");
- System.out.println("创建库、表...或者定时任务...");
- //创建一个线程
- SendMailThread sendMailThread = new SendMailThread();
- sendMailThread.start();
- }
效果:
当然真实的情况应该是要比对数据库中的时间的。
大型的网站会有多个自启动的Servlet,这里要用1、2、3….表示启动的顺序。前面也提到了!
●ServletConfig对象
◆在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
◆当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
如下:
- <servlet>
- <servlet-name>MyServlet1</servlet-name>
- <servlet-class>com.gavin.servlet.MyServlet1</servlet-class>
- <!-- 这里可以给servlet配置信息,这里的配置信息,只能被该servlet读取 -->
- <init-param>
- <param-name>encoding</param-name>
- <param-value>utf-8</param-value>
- </init-param>
- </servlet>
说明:这里的配置参数方法,只能被该servlet读取,而不能被其他servlet使用
在Servlet程序中,可以读取出设置参数,如下:
- response.setCharacterEncoding(this.getInitParameter("encoding"));
如果有多个参数,可以使用getInitParameterNames()方法,该方法返回枚举类型Enumeration,可以通过for循环拿到对应的参数名称,然后通过参数名称再通过上述方法拿到参数,当然也可以一个个拿到。
◆当然,还有一种全局的参数设置,<context-param>节点也可以配置初始化参数,并且可以被所有servlet读取。