Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),须要完成如下2个步骤:
一、编写一个Java类,实现servlet接口。
二、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,一般咱们也把实现了servlet接口的java程序,称之为Servletphp
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并建立了该Servlet的实例对象。若是是,则直接执行第④步,不然,执行第②步。
②装载并建立该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④建立一个用于封装HTTP请求消息的HttpServletRequest对象和一个表明HTTP响应消息的HttpServletResponse对象,而后调用Servlet的service()方法并将请求和响应对象做为参数传递进去。
⑤WEB应用程序被中止或从新启动以前,Servlet引擎将卸载Servlet,并在卸载以前调用Servlet的destroy()方法。 html
在eclipse中新建一个web project工程,eclipse会自动建立下图所示目录结构:java
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。web
HttpServlet指可以处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。所以开发人员在编写Servlet时,一般应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。所以,开发人员在编写Servlet时,一般只须要覆写doGet或doPost方法,而不要去覆写service方法。数据库
选中gacl.servlet.study包,右键→New→Servlet,以下图所示:apache
这样,咱们就经过Eclipse帮咱们建立好一个名字为ServletDemo1的Servlet,建立好的ServletDemo01里面会有以下代码:浏览器
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 public class ServletDemo1 extends HttpServlet { 12 13 /** 14 * The doGet method of the servlet. <br> 15 * 16 * This method is called when a form has its tag value method equals to get. 17 * 18 * @param request the request send by the client to the server 19 * @param response the response send by the server to the client 20 * @throws ServletException if an error occurred 21 * @throws IOException if an error occurred 22 */ 23 public void doGet(HttpServletRequest request, HttpServletResponse response) 24 throws ServletException, IOException { 25 26 response.setContentType("text/html"); 27 PrintWriter out = response.getWriter(); 28 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); 29 out.println("<HTML>"); 30 out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); 31 out.println(" <BODY>"); 32 out.print(" This is "); 33 out.print(this.getClass()); 34 out.println(", using the GET method"); 35 out.println(" </BODY>"); 36 out.println("</HTML>"); 37 out.flush(); 38 out.close(); 39 } 40 41 /** 42 * The doPost method of the servlet. <br> 43 * 44 * This method is called when a form has its tag value method equals to post. 45 * 46 * @param request the request send by the client to the server 47 * @param response the response send by the server to the client 48 * @throws ServletException if an error occurred 49 * @throws IOException if an error occurred 50 */ 51 public void doPost(HttpServletRequest request, HttpServletResponse response) 52 throws ServletException, IOException { 53 54 response.setContentType("text/html"); 55 PrintWriter out = response.getWriter(); 56 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); 57 out.println("<HTML>"); 58 out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); 59 out.println(" <BODY>"); 60 out.print(" This is "); 61 out.print(this.getClass()); 62 out.println(", using the POST method"); 63 out.println(" </BODY>"); 64 out.println("</HTML>"); 65 out.flush(); 66 out.close(); 67 } 68 69 }
这些代码都是Eclipse自动生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>两对标签,这两对标签是配置ServletDemo1的,以下图所示:tomcat
而后咱们就能够经过浏览器访问ServletDemo1这个Servlet,以下图所示:安全
因为客户端是经过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的对外访问路径。例如:服务器
1 <servlet> 2 <servlet-name>ServletDemo1</servlet-name> 3 <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> 4 </servlet> 5 6 <servlet-mapping> 7 <servlet-name>ServletDemo1</servlet-name> 8 <url-pattern>/servlet/ServletDemo1</url-pattern> 9 </servlet-mapping>
同一个Servlet能够被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值能够是同一个Servlet的注册名。 例如:
1 <servlet> 2 <servlet-name>ServletDemo1</servlet-name> 3 <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> 4 </servlet> 5 6 <servlet-mapping> 7 <servlet-name>ServletDemo1</servlet-name> 8 <url-pattern>/servlet/ServletDemo1</url-pattern> 9 </servlet-mapping> 10 <servlet-mapping> 11 <servlet-name>ServletDemo1</servlet-name> 12 <url-pattern>/1.htm</url-pattern> 13 </servlet-mapping> 14 <servlet-mapping> 15 <servlet-name>ServletDemo1</servlet-name> 16 <url-pattern>/2.jsp</url-pattern> 17 </servlet-mapping> 18 <servlet-mapping> 19 <servlet-name>ServletDemo1</servlet-name> 20 <url-pattern>/3.php</url-pattern> 21 </servlet-mapping> 22 <servlet-mapping> 23 <servlet-name>ServletDemo1</servlet-name> 24 <url-pattern>/4.ASPX</url-pattern> 25 </servlet-mapping>
经过上面的配置,当咱们想访问名称是ServletDemo1的Servlet,可使用以下的几个地址去访问:
http://localhost:8080/JavaWeb_Servlet_Study_20140531/servlet/ServletDemo1
http://localhost:8080/JavaWeb_Servlet_Study_20140531/1.htm
http://localhost:8080/JavaWeb_Servlet_Study_20140531/2.jsp
http://localhost:8080/JavaWeb_Servlet_Study_20140531/3.php
http://localhost:8080/JavaWeb_Servlet_Study_20140531/4.ASPX
ServletDemo1被映射到了多个URL上。
在Servlet映射到的URL中也可使用*通配符,可是只能有两种固定的格式:一种格式是"*.扩展名",另外一种格式是以正斜杠(/)开头并以"/*"结尾。例如:
1 <servlet> 2 <servlet-name>ServletDemo1</servlet-name> 3 <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class> 4 </servlet> 5 6 <servlet-mapping> 7 <servlet-name>ServletDemo1</servlet-name> 8 <url-pattern>/*</url-pattern>
*能够匹配任意的字符,因此此时能够用任意的URL去访问ServletDemo1这个Servlet,以下图所示:
对于以下的一些映射关系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题:
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪一个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪一个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪一个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪一个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪一个servlet响应
Servlet引擎将调用Servlet2。
匹配的原则就是"谁长得更像就找谁"
Servlet是一个供其余Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行彻底由Servlet引擎来控制和调度。
针对客户端的屡次Servlet请求,一般状况下,服务器只会建立一个Servlet实例对象,也就是说Servlet实例对象一旦建立,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都致使Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会建立一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,而后将这两个对象做为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
若是在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并建立Servlet的实例对象、以及调用Servlet实例对象的init()方法。
举例:
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用建立必要的数据库表和数据。
web.xml中load-on-startup的做用:
以下一段配置,熟悉DWR的再熟悉不过了:
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
咱们注意到它里面包含了这段配置:<load-on-startup>1</load-on-startup>,那么这个配置有什么做用呢?
贴一段英文原汁原味的解释以下:
Servlet specification:
The load-on-startup element indicates that this servlet should be loaded (instantiated and have its init() called) on the startup of the web application. The optional contents of these element must be an integer indicating the order in which the servlet should be loaded. If the value is a negative integer, or the element is not present, the container is free to load the servlet whenever it chooses. If the value is a positive integer or 0, the container must load and initialize the servlet as the application is deployed. The container must guarantee that servlets marked with lower integers are loaded before servlets marked with higher integers. The container may choose the order of loading of servlets with the same load-on-start-up value.
翻译过来的意思大体以下:
1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
2)它的值必须是一个整数,表示servlet应该被载入的顺序
2)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
3)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
4)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
5)当值相同时,容器就会本身选择顺序来加载。
因此,<load-on-startup>x</load-on-startup>,中x的取值1,2,3,4,5表明的是优先级,而非启动延迟时间。
以下题目:
2.web.xml中不包括哪些定义(多选)
a.默认起始页
b.servlet启动延迟时间定义
c.error处理页面
d.jsp文件改动后从新载入时间
答案:b,d
一般大多数Servlet是在用户第一次请求的时候由应用服务器建立并初始化,但<load-on-startup>n</load-on-startup> 能够用来改变这种情况,根据本身须要改变加载的优先级!
若是某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理全部其余Servlet都不处理的访问请求。 例如:
1 <servlet> 2 <servlet-name>ServletDemo2</servlet-name> 3 <servlet-class>gacl.servlet.study.ServletDemo2</servlet-class> 4 <load-on-startup>1</load-on-startup> 5 </servlet> 6 7 <!-- 将ServletDemo2配置成缺省Servlet --> 8 <servlet-mapping> 9 <servlet-name>ServletDemo2</servlet-name> 10 <url-pattern>/</url-pattern> 11 </servlet-mapping>
当访问不存在的Servlet时,就使用配置的默认Servlet进行处理,以下图所示:
在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
1 <servlet> 2 <servlet-name>default</servlet-name> 3 <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> 4 <init-param> 5 <param-name>debug</param-name> 6 <param-value>0</param-value> 7 </init-param> 8 <init-param> 9 <param-name>listings</param-name> 10 <param-value>false</param-value> 11 </init-param> 12 <load-on-startup>1</load-on-startup> 13 </servlet> 14 15 <!-- The mapping for the default servlet --> 16 <servlet-mapping> 17 <servlet-name>default</servlet-name> 18 <url-pattern>/</url-pattern> 19 </servlet-mapping>
当访问Tomcat服务器中的某个静态HTML文件和图片时,其实是在访问这个缺省Servlet。
当多个客户端并发访问同一个Servlet时,web服务器会为每个客户端的访问请求建立一个线程,并在这个线程上调用Servlet的service方法,所以service方法内若是访问了同一个资源的话,就有可能引起线程安全问题。例以下面的代码:
不存在线程安全问题的代码:
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 public class ServletDemo3 extends HttpServlet { 11 12 13 public void doGet(HttpServletRequest request, HttpServletResponse response) 14 throws ServletException, IOException { 15 16 /** 17 * 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗 18 * i变量被多个线程并发访问,可是没有线程安全问题,由于i是doGet方法里面的局部变量, 19 * 当有多个线程并发访问doGet方法时,每个线程里面都有本身的i变量, 20 * 各个线程操做的都是本身的i变量,因此不存在线程安全问题 21 * 多线程并发访问某一个方法的时候,若是在方法内部定义了一些资源(变量,集合等) 22 * 那么每个线程都有这些东西,因此就不存在线程安全问题了 23 */ 24 int i=1; 25 i++; 26 response.getWriter().write(i); 27 } 28 29 public void doPost(HttpServletRequest request, HttpServletResponse response) 30 throws ServletException, IOException { 31 doGet(request, response); 32 } 33 34 }
存在线程安全问题的代码:
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 public class ServletDemo3 extends HttpServlet { 11 12 int i=1; 13 public void doGet(HttpServletRequest request, HttpServletResponse response) 14 throws ServletException, IOException { 15 16 i++; 17 try { 18 Thread.sleep(1000*4); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 response.getWriter().write(i+""); 23 } 24 25 public void doPost(HttpServletRequest request, HttpServletResponse response) 26 throws ServletException, IOException { 27 doGet(request, response); 28 } 29 30 }
把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了,以下图所示:同时开启两个浏览器模拟并发访问同一个Servlet,原本正常来讲,第一个浏览器应该看到2,而第二个浏览器应该看到3的,结果两个浏览器都看到了3,这就不正常。
线程安全问题只存在多个线程并发操做同一个资源的状况下,因此在编写Servlet的时候,若是并发访问某一个资源(变量,集合等),就会存在线程安全问题,那么该如何解决这个问题呢?
先看看下面的代码:
1 package gacl.servlet.study; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 11 public class ServletDemo3 extends HttpServlet { 12 13 int i=1; 14 public void doGet(HttpServletRequest request, HttpServletResponse response) 15 throws ServletException, IOException { 16 /** 17 * 加了synchronized后,并发访问i时就不存在线程安全问题了, 18 * 为何加了synchronized后就没有线程安全问题了呢? 19 * 假如如今有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁 20 * 等到它执行完以后才会把锁还给Servlet对象,因为是它先拿到了Servlet对象的那把锁, 21 * 因此当有别的线程来访问这个Servlet对象时,因为锁已经被以前的线程拿走了,后面的线程只能排队等候了 22 * 23 */ 24 synchronized (this) {//在java中,每个对象都有一把锁,这里的this指的就是Servlet对象 25 i++; 26 try { 27 Thread.sleep(1000*4); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 response.getWriter().write(i+""); 32 } 33 34 } 35 36 public void doPost(HttpServletRequest request, HttpServletResponse response) 37 throws ServletException, IOException { 38 doGet(request, response); 39 } 40 41 }
如今这种作法是给Servlet对象加了一把锁,保证任什么时候候都只有一个线程在访问该Servlet对象里面的资源,这样就不存在线程安全问题了,以下图所示:
这种作法虽然解决了线程安全问题,可是编写Servlet却万万不能用这种方式处理线程安全问题,假若有9999我的同时访问这个Servlet,那么这9999我的必须按前后顺序排队轮流访问。
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,若是某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
查看Sevlet的API能够看到,SingleThreadModel接口中没有定义任何方法和常量,在Java中,把没有定义任何方法和常量的接口称之为标记接口,常常看到的一个最典型的标记接口就是"Serializable",这个接口也是没有定义任何方法和常量的,标记接口在Java中有什么用呢?主要做用就是给某个对象打上一个标志,告诉JVM,这个对象能够作什么,好比实现了"Serializable"接口的类的对象就能够被序列化,还有一个"Cloneable"接口,这个也是一个标记接口,在默认状况下,Java中的对象是不容许被克隆的,就像现实生活中的人同样,不容许克隆,可是只要实现了"Cloneable"接口,那么对象就能够被克隆了。
让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增长实现SingleThreadModel接口的声明便可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每一个线程分别调用一个独立的Servlet实例对象。 实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,由于Servlet引擎会建立多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过期的)。