容器对路径的处理 、 Servlet特性

来源:互联网 发布:淘宝客服售后聊天技巧 编辑:程序博客网 时间:2024/06/05 20:16

1. Servlet开发要点

1.1. 重定向

1.1.1. 什么是重定向

在服务器为浏览器提供响应时,回传的数据包中的状态行里面是302状态码,同时在消息头内会增加一个键值对,名称为Location,值是一个新的URL地址。当这个响应到达浏览器的时候,这一次的请求响应过程并未结束,浏览器遇见302状态码之后,会立即按照Location头信息中指定的URL地址发送新的一个请求,这样一个在接到响应后又立即发出请求的过程叫做重定向。对于客户端用户来讲,中间的变化过程不会被察觉,因为这个过程是由浏览器自动完成的。

1.1.2. 重定向原理

在重定向的过程中,影响浏览器做出动作的关键点即响应中的状态码及Location这个消息头。302状态就像一道命令一样,使得浏览器做出新的一次请求,而请求的地址会从头信息中查找。由于这个新的请求动作是由浏览器发出的,所以浏览器的地址栏上的地址会变成Location消息头中的地址。

1.1.3. 如何重定向

由于发回的响应信息由response对象控制,所以使用如下代码即可实现重定向的过程:

copytextpop-up
  1. response.sendRedirect(String url);
response.sendRedirect(String url);

该方法的参数值url即Location消息头中的重定向地址。注意,该段代码后面如果还有其他代码的话也会被继续执行的。

1.1.4. 重定向特点

由于重定向动作的执行者为浏览器,所以请求的地址可以是任意地址,哪怕是当前应用以外的应用;浏览器发出请求时一定会保持地址栏与目标地址的一致,所以发生重定向时可以从地址栏中看到地址的改变;由于整个跳转过程是在浏览器收到响应后重新发起请求,所以涉及到的Web组件并不会共享同一个request和response。

图- 1

在图 – 1中,1和4是两个完全不同的请求,如果在1号请求中曾经携带了某些表单数据,但4号这个全新请求中则不会获取到这些表单数据,也就是两次请求涉及到的Web组件不会共享request和response。

1.2. Servlet容器如何处理请求资源路径

1.2.1. 什么是请求资源路径

在地址栏中输入的请求地址中,端口号之后的部分都是请求资源路径。紧跟端口号的是部署到Web服务器上的应用名(appName),紧跟应用名的则是具体的应用内的组件路径。

1.2.2. Web服务器对请求地址的处理过程

浏览器依据地址中的IP和端口号与Web服务器建立连接,服务器会获取到请求资源路径信息。根据端口号后面的应用名找到服务器上对应的应用。默认情况下容器会认为应用名后面的是一个Servlet,所以回到web.xml文件中所有是否有与该值匹配的<url-pattern>,找到匹配的值之后再按照<servlet-name>完成对应关系的查找,进而找到要执行的Servlet。如果没有找到匹配的资源服务器就会返回404错误。

1.2.3. 匹配Servlet的规则

容器在进行url-pattern比对的时候是遵循一定的匹配原则的。这些原则主要有:

精确匹配

即具体资源名称与web.xml文件中的url-pattern严格匹配相等才执行。如,配置的内容如下:

copytextpop-up
  1. <servlet>
  2.     <servlet-name>someServlet</servlet-name>
  3.     <servlet-class>test.MyServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6.     <servlet-name>someServlet</servlet-name>
  7.     <url-pattern>/abc.html</url-pattern>
  8. </servlet-mapping>
<servlet><servlet-name>someServlet</servlet-name><servlet-class>test.MyServlet</servlet-class></servlet><servlet-mapping><servlet-name>someServlet</servlet-name><url-pattern>/abc.html</url-pattern></servlet-mapping>

则在地址栏中输入 http://ip:port/appName/abc.html 时,服务器就会去执行test.MyServlet这个组件,就算是在应用的根目录下的确有abc.html这个文件,也不会执行。

通配符匹配

使用“*”这个符号来匹配0个或多个字符,已达到路径的批量匹配的效果。

如配置文件中的节点为如下代码所示:

copytextpop-up
  1. <servlet>
  2.     <servlet-name>someServlet</servlet-name>
  3.     <servlet-class>test.MyServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6.     <servlet-name>someServlet</servlet-name>
  7.     <url-pattern>/*</url-pattern>
  8. </servlet-mapping>
<servlet><servlet-name>someServlet</servlet-name><servlet-class>test.MyServlet</servlet-class></servlet><servlet-mapping><servlet-name>someServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>

则,在地址栏中输入以下任何地址时都是匹配成功的。

copytextpop-up
  1. http://ip:port/appName/abc.html
  2. http://ip:port/appName/abc/def/ghi.html
http://ip:port/appName/abc.htmlhttp://ip:port/appName/abc/def/ghi.html 

后缀匹配

在配置url-pattern节点时,不使用斜杠开头,用“*.”开头来匹配任意多个字符的模式叫做后缀匹配。

如配置文件中的节点为如下代码所示:

copytextpop-up
  1. <servlet>
  2.     <servlet-name>someServlet</servlet-name>
  3.     <servlet-class>test.MyServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6.     <servlet-name>someServlet</servlet-name>
  7.     <url-pattern>*.do</url-pattern>
  8. </servlet-mapping>
<servlet><servlet-name>someServlet</servlet-name><servlet-class>test.MyServlet</servlet-class></servlet><servlet-mapping><servlet-name>someServlet</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping>

则,在地址栏中输入以下任何地址时都是匹配成功的。

copytextpop-up
  1. http://ip:port/appName/abc.do
  2. http://ip:port/appName/abc/def/ghi.do
http://ip:port/appName/abc.dohttp://ip:port/appName/abc/def/ghi.do

在这三种匹配方式中,优先级最高的是精确匹配。如果容器在使用以上原则都不能找到相匹配的资源来执行时,就按照地址到应用中查找对应的文件。此时如果找到文件则返回,找不到资源来执行就返回404错误。

1.3. 一个Servlet实现多请求

1.3.1. 为什么要将多Servlet合并

Servlet作为Web应用中最核心的环节是因为这个组件不仅能接受请求,还能够为该请求提供响应,所以Servlet一般都会充当整个应用的控制器来进行请求的分发,为不同的请求找到对应的资源。于是程序中大多只需要一个Servlet完成这个分发工作即可,合并多个Servlet为一个Servlet会让程序的处理逻辑更加明确。

要想完成多个Servlet合并为一个Servlet,需要完成以下两个步骤:

  • 使用后缀模式完成请求资源路径的匹配
  • 分析请求资源路径中的请求目标,完成分发的动作 

1.3.2. 使用后缀匹配模式完成请求资源路径的匹配

修改web.xml文件,将更多的servlet配置节点删除,只保留一个节点即可,代码如下:

copytextpop-up
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.4"
  3.     xmlns="http://java.sun.com/xml/ns/j2ee"
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  6.     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  7.     <servlet>
  8.         <servlet-name>someServlet</servlet-name>
  9.         <servlet-class>web.SomeServlet</servlet-class>
  10.     </servlet>
  11.     <servlet-mapping>
  12.         <servlet-name>someServlet</servlet-name>
  13.         <url-pattern>*.do</url-pattern>
  14.     </servlet-mapping>
  15. </web-app>
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  <servlet>  <servlet-name>someServlet</servlet-name>  <servlet-class>web.SomeServlet</servlet-class>  </servlet>  <servlet-mapping>  <servlet-name>someServlet</servlet-name>  <url-pattern>*.do</url-pattern>  </servlet-mapping></web-app>

1.3.3. 分析请求资源后分发

配置完web.xml文件后,不同请求都会发送到Web.SomeServlet来处理,要想起到分发的作用,则需要分析调过来的请求中具体的请求目标是什么。使用如下代码逻辑来完成分发动作。

copytextpop-up
  1. package web;
  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 SomeServlet extends HttpServlet{
  11.     public void service(HttpServletRequest request,
  12.              HttpServletResponse response)     throws
  13. ServletException,IOException{
  14.         //获得请求资源路径
  15.         String uri = request.getRequestURI();
  16.         System.out.println("uri:" + uri);
  17.         if(uri.equals("/test/list.do")){
  18.             System.out.println("进行员工列表的处理...");
  19.         }else if(uri.equals("/test/add.do")){
  20.             System.out.println("添加员工的处理...");
  21.         }
  22.     }
  23. }
package web;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class SomeServlet extends HttpServlet{public void service(HttpServletRequest request,      HttpServletResponse response) throws ServletException,IOException{//获得请求资源路径String uri = request.getRequestURI();System.out.println("uri:" + uri);if(uri.equals("/test/list.do")){System.out.println("进行员工列表的处理...");}else if(uri.equals("/test/add.do")){System.out.println("添加员工的处理...");}}}

1.4. Servlet的生命周期

1.4.1. 什么是Servlet生命周期

Servlet容器如何创建Servlet对象、如何为Servlet对象分配、准备资源、如何调用对应的方法来处理请求以及如何销毁Servlet对象的整个过程即Servlet的生命周期。

1.4.2. 生命周期的四个阶段

阶段一、实例化

实例化阶段是Servlet生命周期中的第一步,由Servlet容器调用Servlet的构造器创建一个具体的Servlet对象的过程。而这个创建的时机可以是在容器收到针对这个组件的请求之后,即用了才创建;也可以在容器启动之后立刻创建实例,而不管此时Servlet是否使用的上。使用如下代码可以设置Servlet是否在服务器启动时就执行创建。

copytextpop-up
  1. <servlet>
  2.     <servlet-name>someServlet</servlet-name>
  3.     <servlet-class>test/SomeServlet</servlet-class>
  4.         <load-on-startup>1</load-on-startup>
  5. </servlet>
  6. <servlet-mapping>
  7.     <servlet-name>someServlet</servlet-name>
  8.     <url-pattern>/*</url-pattern>
  9. </servlet-mapping>
<servlet><servlet-name>someServlet</servlet-name><servlet-class>test/SomeServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping> <servlet-name>someServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>

配置文件中的load-on-startup节点用于设置该Servlet的创建时机。

当其中的值大于等于0时,表示容器在启动时就会创建实例

小于0时或没有指定时,代表容器在该Servlet被请求时再执行创建

正数的值越小,优先级越高,应用启动时就越先被创建。

阶段二、初始化

Servlet在被加载实例化之后,必须要初始化它。在初始化阶段,init()方法会被调用。这个方法在javax.servlet.Servlet接口中定义。其中,方法以一个ServletConfig类型的对象作为参数。ServletConfig对象由Servlet引擎负责创建,从中可以读取到事先在web.xml文件中通过<init-param>节点配置的多个name-value名值对。ServletConfig对象还可以让Servlet接受一个ServletContext对象。

一般情况下,init方法不需要编写,因GenericServlet已经提供了init方法的实现,并且提供了getServletConfig方法来获得ServletConfig对象。

注:init方法只被执行一次。

以下代码为在servlet配置中,增加初始化参数

copytextpop-up
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.4"
  3.     xmlns="http://java.sun.com/xml/ns/j2ee"
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  6.     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  7.     <servlet>
  8.         <servlet-name>someServlet</servlet-name>
  9.         <servlet-class>test/SomeServlet</servlet-class>
  10.             <init-param>
  11.                 <param-name>debug</param-name>
  12.                 <param-value>true</param-valule>
  13.             </init-param>
  14.     </servlet>
  15.     <servlet-mapping>
  16.         <servlet-name>someServlet</servlet-name>
  17.         <url-pattern>/*</url-pattern>
  18.     </servlet-mapping>
  19. </web-app>
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  <servlet><servlet-name>someServlet</servlet-name><servlet-class>test/SomeServlet</servlet-class><init-param><param-name>debug</param-name><param-value>true</param-valule></init-param></servlet><servlet-mapping> <servlet-name>someServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping></web-app>

使用以下代码可以读取Servlet配置中增加的初始化参数

copytextpop-up
  1. package test;
  2.  
  3. import java.io.IOException;
  4.  
  5. import javax.servlet.ServletConfig;
  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 SomeServlet extends HttpServlet{
  12.  
  13.     public void service(HttpServletRequest request,
  14.              HttpServletResponse response)
  15.      throws ServletException,IOException{
  16.         System.out.println("SomeServlet's service...");
  17.         ServletConfig config = getServletConfig();
  18.         String debug = config.getInitParameter("debug");
  19.         System.out.println("debug:" + debug);
  20.     }
  21. }
package test;import java.io.IOException;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class SomeServlet extends HttpServlet{public void service(HttpServletRequest request,              HttpServletResponse response)                       throws ServletException,IOException{System.out.println("SomeServlet's service...");ServletConfig config = getServletConfig();String debug = config.getInitParameter("debug");System.out.println("debug:" + debug);}}

阶段三、就绪

Servlet被初始化以后就处于能够响应请求的就绪状态。每个对Servlet的请求由一个ServletRequest对象代表,Servlet给客户端的响应由一个ServletResponse对象代表。当客户端有一个请求时,容器就会将请求与响应对象转给Servlet,以参数的形式传给service方法。service方法由javax.servlet.Servlet定义,由具体的Servlet实现。

阶段四、销毁

Servlet容器在销毁Servlet对象时会调用destroy方法来释放资源。通常情况下Servlet容器停止或者重新启动都会引起销毁Servlet对象的动作,但除此之外,Servlet容器也有自身管理Servlet对象的准则,整个生命周期并不需要人为进行干预。

1.4.3. Servlet接口

在ServletAPI中最重要的是Servlet接口,所有Servlet都会直接或间接的与该接口发生联系,或是直接实现该接口,或间接继承自实现了该接口的类。

该接口包括以下四个方法:

  • init(ServletConfig config)
  • service(ServletRequest req,ServletResponse res)
  • destroy( )

在最开始制定Servlet规范时,设计者希望这套规范能够支持多种协议的组件开发,所以Servlet接口是最重要的一个接口。虽然我们写的程序中编写的Servlet都是继承自HttpServlet,但本质上都是对该接口的实现,因为HttpServlet就是针对Servlet这个接口的一个抽象的实现类。可以理解为HttpServlet是支持HTTP协议的分支的一部分。设计Servlet接口中的service方法时,也是希望该方法能够处理多种协议下的请求及响应,所以参数类型是ServletRequest,而在HttpServlet这个支持HTTP协议的分支中,service方法的参数则变成了HttpServletRequest和HttpServletResponse,这两个类分别继承于ServletRequest和ServletResponse,也就是对这两个类的一个具体协议的包装,区别是增加了很多与HTTP协议相关的使用API。

制定的这种规范在实际使用中发现,并不会扩展为HTTP协议之外,所以有了过度设计的缺陷,也为在编写HTTP协议的Web应用时添加了一些不必要的操作。

1.4.4. Servlet涉及到的抽象类

Servlet API中另一个重要的类就是GenericServlet这个抽象类,它对Servlet接口中的部分方法(init和destroy)添加了实现,使得开发时只需要考虑针对service方法的业务实现即可。

HttpServlet又是在继承GenericServlet的基础上进一步的扩展,一个是public voidinit(ServletConfig config),另一个是 public void init()。他们有如下的关系: init(ServletConfig config)方法由tomcat自动调用,它读取web工程下的web.xml,将读取的信息打包传给此参数,此方法的参数同时将接收的信息传递给GenericServlet类中的成员变量config,同时调用init()。以后程序员想重写init方法可以选择init(ServletConfig config)或者init(),但是选择init(ServletConfig config)势必会覆盖此方法已实现的内容,没有为config变量赋值,此后若是调用getServletConfig()方法返回config时会产生空指针异常的,所以想重写init(ServletConfig config)方法,必须在方法体中第一句写上 super.init(config),为了防止程序员忘记重写super.init(config)方法sun公司自动为用户生成一个public void init()的方法。GenericServlet具体的定义如下所示

copytextpop-up
  1. GenericServlet{
  2. ServletConfig config;
  3. public void init()
  4. { } //此方法什么也没做,可以说是为编程人员预留的接口
  5. public void init(ServletConfig config)
  6. {
  7. this.config=config;
  8. this.init();
  9. }
  10. getServletConfig()
  11. {
  12.      return config;
  13. }
  14. }
GenericServlet{       ServletConfig config;       public void init()       {    }   //此方法什么也没做,可以说是为编程人员预留的接口      public void init(ServletConfig config)      {          this.config=config;          this.init();      }     getServletConfig()     {      return config;     }}

1.5. ServletContext

1.5.1. 什么是Servlet上下文

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用,是一个全局的环境变量。该应用中的任何组件,在任何时候都可以访问到该对象,所以Servlet上下文具有唯一性。

1.5.2. 如何获得Servlet上下文

获取该对象的方式有以下四种:

  • GenericeServlet(HttpServlet)提供的 getServletContext()
  • ServletConfig提供的 getServletContext()
  • HttpSession提供的 getServletContext()
  • FilterConfig提供的 getServletContext() 

1.5.3. Servlet上下文的作用及特点

Servlet上下文的作用:

  • 在Web应用范围内存取共享数据:如 setAttribute(),getAttribute()
  • 访问Web应用的静态资源:如getRealPath(String path)
  • 跨多个请求、用户和Servlet

例如,以下是两个Servlet的完整代码,实现了跨Servlet的数据共享

copytextpop-up
  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3.  
  4. import javax.servlet.*
  5. import javax.servlet.http.*
  6.  
  7. public class SomeServlet extends HttpServlet {
  8.  
  9.     public void service(HttpServletRequest request,
  10. HttpServletResponse response)
  11.              throws ServletException, IOException {
  12.         response.setContentType("text/html;charset=utf-8");
  13.         PrintWriter out = response.getWriter();
  14.         ServletContext sctx = getServletContext();
  15.         sctx.setAttribute("name", "Lisa");
  16.         out.close();
  17.     }
  18.  
  19. }
  20. import java.io.IOException;
  21. import java.io.PrintWriter;
  22.  
  23. import javax.servlet.*
  24. import javax.servlet.http.*
  25.  
  26. public class OtherServlet extends HttpServlet {
  27.     public void service(HttpServletRequest request,
  28. HttpServletResponse response)
  29.              throws ServletException, IOException {
  30.         response.setContentType("text/html;charset=utf-8");
  31.         ServletContext sctx =    getServletContext();
  32.         String name = (String) sctx.getAttribute("name");
  33.         out.close();
  34.     }
  35.  
  36. }
import java.io.IOException;import java.io.PrintWriter;import javax.servlet.*import javax.servlet.http.*public class SomeServlet extends HttpServlet {public void service(HttpServletRequest request, HttpServletResponse response)              throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();ServletContext sctx = getServletContext();sctx.setAttribute("name", "Lisa");out.close();}}import java.io.IOException;import java.io.PrintWriter;import javax.servlet.*import javax.servlet.http.*public class OtherServlet extends HttpServlet {public void service(HttpServletRequest request, HttpServletResponse response)              throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");ServletContext sctx =getServletContext();String name = (String) sctx.getAttribute("name");out.close();}}

1.6. Servlet线程安全问题

1.6.1. 为什么会有线程安全问题

当浏览器访问服务器的通讯模块SomeServlet时,会启动一个线程T1来进行一系列的创建动作来处理这个请求。一般的web服务器的编程模型如下:

copytextpop-up
  1.                 while(flag){
  2.                     Socket s = ss.accept();
  3.                     Thread t = new Thread(s);
  4.                     t.start();
  5.                 }
while(flag){Socket s = ss.accept();Thread t = new Thread(s);t.start();}

如果刚好同时也有一个请求来访问SomeServlet,但是服务器只有一个servlet实例,所以服务器会启动线程T2,此时就有可能产生T1和T2同时访问someservlet的情况,如果要修改属性就会有安全隐患

1.6.2. 如何保证Servlet线程安全

使用synchronized对代码加锁即可。代码结构如下

copytextpop-up
  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3.  
  4. import javax.servlet.*;
  5. import javax.servlet.*;
  6.  
  7. public class SomeServlet extends HttpServlet {
  8.         private int count = 0;
  9.         public void service(HttpServletRequest request,
  10. HttpServletResponse response)
  11.              throws ServletException, IOException {
  12.         synchronized(this){
  13.             count ++;
  14.             try {
  15.                 Thread.sleep(1000);
  16.             } catch (InterruptedException e) {
  17.                 e.printStackTrace();
  18.             }
  19.             System.out.println(
  20.                     Thread.currentThread().getName()
  21.                     + ":" + count);
  22.         }
  23.         
  24.     }
  25.  
  26. }
代码