目录
Web项目目录结构
举例,在Intellij IDEA中如下:
其中这个Web项目被命名为:servlet,即Web工程的名称,在MyEclipse中,配置好我们自己的Tomcat服务器后,该工程部署时,在webapps目录下就会有一个servlet的Web应用。在Intellij IDEA中,部署工程时,会创建一个artifacts文件夹放置生成后可运行的Web应用,而不是在Tomcat服务器下的webapps目录直接生成。(当然我们可以配置部署时Web应用的输出路径,如下)
servlet项目中src目录为Java程序的开发目录,当然我们对Servlet的编写也是在该目录下,该目录下编写的所有程序在部署时,会自动部署到servlet/WEB-INF/classes目录下。
servlet项目中的web目录(在MyEclipse中为WebRoot目录)对应于Web应用的根目录,该目录下的所有子目录和子文件在部署时,会原封不动的发布到Web应用目录下。在Intellij IDEA中,部署到Tomcat生成的文件如下:
在Web项目根目录下的文件可以被外界直接访问,如这里的 1.html,index.jsp。可以直接访问的原因,下面还需要重点说明。
WEB-INF目录:Java类、jar包、web应用的配置文件存在该目录下,该目录下的文件外界无法直接访问,由Web服务器负责调用。
在WEB-INF目录下的web.xml文件是整个Web应用中最重要的配置文件,它必须放在WEB-INF目录中。在开发Web应用时,但凡涉及到对Web应用中的Web资源进行配置的操作,均在web.xml文件中进行设置。如:
- 将某个Web资源配置为网站首页;
- 将servlet程序映射到某个url地址上;
- 将Web应用配置监听器;
- 将Web应用配置过滤器;
- ……
Servlet的调用过程
- 浏览器请求服务器,连接服务器;
- 浏览器发送HTTP请求给服务器;
- 服务器接收HTTP请求后,解析浏览器想访问的主机名;
- 解析浏览器想访问的Web应用;
- 解析浏览器想访问的Web资源(这里指的是想访问哪个Servlet);
- 根据访问的Servlet,创建Servlet实例对象(内部根据web.xml文件使用反射技术进行创建;当然这里并不是每次请求都会创建Servlet对象,而是首次创建后,后续请求则使用之前创建过的Servlet对象);
- 调用servlet实例对象的
init
方法完成对象初始化操作; - (服务器)创建代表请求的request和代表响应的response对象,然后调用servlet这个实例对象的
service
方法; - 执行servlet实例对象的
service
方法过程中,向代表客户端响应的response
对象写入向浏览器输出的数据; service
方法执行完毕后,服务器从response
对象中取出数据,并构建出一个HTTP响应,响应客户机;- 浏览器获取到服务器返回的HTTP响应,解析HTTP响应,读取数据进行显示。
Servlet的运行过程
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
Servlet程序是由Web服务器调用的,Web服务器接收到客户端的Servlet访问请求后:
(当然,客户端请求Servlet后,Web服务器需要根据web.xml配置查找客户端请求的Servlet,即判断客户端是在请求哪个Servlet。)
1. Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第 4 步,否则执行第 2 步。
2. 装载并创建该Servlet的一个实例对象。
3. 调用Servlet实例对象中的init()
方法。
4. 对HTTP请求消息进行封装,创建并封装为HttpServletRequest
对象和创建一个代表HTTP响应消息的HttpServletResponse
对象,然后调用Servlet的service()
方法并将请求和响应对象作为参数传递进去。(在调用service()
方法的过程中,该方法会根据请求的方式调用对应的doXxx方法)
5. Web应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destory()
方法。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦被创建,它就会驻留在内存中,为后续的其它请求服务,直至Web容器退出或Web应用被删除,Servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次(即在Servlet创建时)。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次Servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service方法,service方法再根据请求方式调用对应的doXxx方法。
Servlet接口实现类
- 对于Servlet接口,SUN公司为其定义了两个默认的实现类,分别为:GenericServlet、HttpServlet。HttpServlet类是指能够处理HTTP请求的servlet,在实际开发中,我们一般创建Servlet继承于HttpServlet类。
- HttpServlet在实现Servlet接口时(实际上HttpServlet继承于GenericServlet),覆盖了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为POST请求,则调用doPost方法。
Servlet的URL映射
- 由于客户端是通过URL地址访问Web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上。如:
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>com.wm103.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>
- 其中
<servlet>
元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>
和<servlet-class>
,分别用于设置Servlet的注册名称和Servlet的完整类名。 - 一个
<servlet-mapping>
元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>
和<url-pattern>
,分别用于指定Servlet的注册名称和Servlet的对外访问路径。 - 除了通过配置文件方式配置Servlet的URL映射外,还可以通过注解的形式进行配置,这样做后,无需在web.xml中再进行配置,如下:
package com.wm103;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by DreamBoy on 2017/4/27.
*/
@WebServlet(name="ServletDemo3", urlPatterns={"/ServletDemo3"})
/*@WebServlet(name="ServletDemo3", urlPatterns={"/ServletDemo3"},loadOnStartup=1)*/
public class ServletDemo3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getOutputStream().write("This is ServletDemo3.".getBytes());
}
}
- 同一个Servlet可以被映射到多个URL上,即多个
<servlet-mapping>
元素的<servlet-name>
子元素的设置值可以是同一个Servlet的注册名。 - 在Servlet映射到的URL中可以使用*通配符,但是只有两种固定格式:1、
*.扩展名
;2、以正斜杠(/)开头并以“/*”结尾
。如下:
<servlet-mapping>
<servlet-name>ServletDemo2</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletDemo2</servlet-name>
<url-pattern>/action/*</url-pattern>
</servlet-mapping>
即任何以.html
结尾的URL都有可能匹配访问到ServletDemo2;任何以/action
开头的URL都有可能匹配访问到ServletDemo2。
- 那么根据Servlet配置的URL映射关系,对于某一URL而言,可能符合多个Servlet配置的URL映射关系,这时将选择匹配度最高的Servlet。此外,这里需要强调下,以 *
开头的url-pattern
设置其优先级最低。
- 要求在Web应用程序启动时,就装载并创建Servlet的实例对象,并调用Servlet实例对象中的init方法,可以在web.xml中的<servlet>
元素中配置一个<load-on-start-up>
元素,元素的值表示装载并创建Servlet的优先级,其值越小则优先级越高,即越先被创建。
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>com.wm103.ServletDemo1</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
以注解方式的配置则如下:
@WebServlet(name="ServletDemo3", urlPatterns={"/ServletDemo3"},loadOnStartup=1)
重点
- 如果某个Servlet的映射路径设置为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。(注:经测试设置
/*
,同样也配置了缺省Servlet)即当URL匹配不到任何一个Servlet时,Web服务器就会调用缺省Servlet。 - 凡是在web.xml文件中找不到匹配的
<servlet-mapping>
元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其它Servlet都不处理的访问请求。 - 在
<tomcat的安装目录>\conf\web.xml
文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet
的Servlet,并将这个Servlet设置为缺省Servlet。如下:
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- 当访问Tomcat服务器中某个静态HTML文件和图片时,实际上是访问Tomcat服务器默认设置的缺省Servlet,即
org.apache.catalina.servlets.DefaultServlet
。
Servlet线程安全
- 当多个客户端并发访问同一Servlet时,Web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
- 如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
- SingleThreadModel接口中没有定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。(没有定义任何东西的接口,也称为标记接口,如:Serializable、Cloneable接口)
- 对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
- 实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。
- 事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。建议开发人员应当采取其他手段来解决这些问题,而不是实现该接口,比如 避免实例变量的使用或者在访问资源时同步代码块(即synchronized中)。