JavaWeb开发与代码的编写(二)

JavaWeb开发与代码的编写(二)

Http协议

  HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。

  HTTP协议的版本:HTTP/1.0、HTTP/1.1

  在HTTP1.0协议中,客户端与web服务器建立连接后,只能获得一个web资源。
  在HTTP1.1协议,允许客户端与web服务器建立连接后,在一个连接上获取多个web资源。

HTTP请求包括的内容

  客户端连上服务器后,向服务器请求某个web资源,称之为客户端向服务器发送了一个HTTP请求。

一个完整的HTTP请求包括如下内容:一个请求行、若干消息头、以及实体内容
范例:

  

HTTP请求的细节——请求行

  请求行中的GET称之为请求方式,请求方式有:POST、GET、HEAD、OPTIONS、DELETE、TRACE、PUT,常用的有: GET、 POST
  用户如果没有设置,默认情况下浏览器向服务器发送的都是get请求,例如在浏览器直接输地址访问,点超链接访问等都是get,用户如想把请求方式改为post,可通过更改表单的提交方式实现。
  不管POST或GET,都用于向服务器请求某个WEB资源,这两种方式的区别主要表现在数据传递上:如果请求方式为GET方式,则可以在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔,例如:GET /mail/1.html?name=abc&password=xyz HTTP/1.1
  GET方式的特点:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。
  如果请求方式为POST方式,则可以在请求的实体内容中向服务器发送数据,Post方式的特点:传送的数据量无限制。

HTTP请求的细节——消息头

  HTTP请求中的常用消息头

  accept:浏览器通过这个头告诉服务器,它所支持的数据类型
  Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
  Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
  Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
  Host:浏览器通过这个头告诉服务器,想访问哪台主机
  If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
  Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的  防盗链
  Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接

例如:

 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, 
     application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
 Referer: http://localhost:8080/JavaWebDemoProject/Web/2.jsp
 Accept-Language: zh-CN
 User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3)
 Accept-Encoding: gzip, deflate
 Host: localhost:8080
 Connection: Keep-Alive

HTTP响应

HTTP响应包括的内容

  一个HTTP响应代表服务器向客户端回送的数据,它包括: 一个状态行、若干消息头、以及实体内容 。

  
范例:

 HTTP/1.1 200 OK
 Server: Apache-Coyote/1.1
 Content-Type: text/html;charset=ISO-8859-1
 Content-Length: 105
 Date: Tue, 27 May 2014 16:23:28 GMT 
 <html>
     <head>
         <title>Hello World JSP</title>
     </head>
     <body>
         Hello World!
 
     </body>
 </html>

HTTP响应的细节——状态行

    状态行格式: HTTP版本号 状态码 原因叙述<CRLF>
      举例:HTTP/1.1 200 OK
  状态码用于表示服务器对请求的处理结果,它是一个三位的十进制数。响应状态码分为5类,如下所示:
  

HTTP响应细节——常用响应头

  HTTP响应中的常用响应头(消息头)
  Location: 服务器通过这个头,来告诉浏览器跳到哪里
  Server:服务器通过这个头,告诉浏览器服务器的型号
  Content-Encoding:服务器通过这个头,告诉浏览器,数据的压缩格式
  Content-Length: 服务器通过这个头,告诉浏览器回送数据的长度
  Content-Language: 服务器通过这个头,告诉浏览器语言环境
  Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
  Refresh:服务器通过这个头,告诉浏览器定时刷新
  Content-Disposition: 服务器通过这个头,告诉浏览器以下载方式打数据
  Transfer-Encoding:服务器通过这个头,告诉浏览器数据是以分块方式回送的
  Expires: -1  控制浏览器不要缓存
  Cache-Control: no-cache  
  Pragma: no-cache

在服务端设置响应头来控制客户端浏览器的行为

设置Location响应头,实现请求重定向

 package gacl.http.study;
 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 ServletDemo01 extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
 
         response.setStatus(302);//设置服务器的响应状态码
         /**
          *设置响应头,服务器通过 Location这个头,来告诉浏览器跳到哪里,这就是所谓的请求重定向
          */
         response.setHeader("Location", "/JavaWeb_HttpProtocol_Study_20140528/1.jsp");
     }
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

  当在浏览器中使用URL地址"http://localhost:8080/JavaWeb_HttpProtocol_Study_20140528/servlet/ServletDemo01"访问ServletDemo01时,就可以看到服务器作出响应后发送到浏览器的状态码和响应头信息,如下图所示:

  

  服务器返回一个302状态码告诉浏览器,你要的资源我没有,但是我通过Location响应头告诉你哪里有,而浏览器解析响应头Location后知道要跳转到/JavaWeb_HttpProtocol_Study_20140528/1.jsp页面,所以就会自动跳转到1.jsp,如下图所示:

  

设置Content-Encoding响应头,告诉浏览器数据的压缩格式

 package gacl.http.study;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.zip.GZIPOutputStream;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
 /**
  *这个小程序是用来演示以下两个小知识点
  *1、使用GZIPOutputStream流来压缩数据
  *2、设置响应头Content-Encoding来告诉浏览器,服务器发送回来的数据压缩后的格式
  */
 public class ServletDemo02 extends HttpServlet {
 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
         String data = "abcdabcdabcdabcdabcdabcdab" +
                 "cdabcdabcdabcdabcdabcdabcdabcdabc" +
                 "dabcdabcdabcdabcdabcdabcdabcdabc" +
                 "dabcdabcdabcdabcdabcdabcdabcdabcdab" +
                 "cdabcdabcdabcdabcdabcdabcdabcdabcdab" +
                 "cdabcdabcdabcdabcdabcdabcdabcdabcdab" +
                 "cdabcdabcdabcdabcdabcdabcdabcdabcdab" +
                 "cdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
        System.out.println("原始数据的大小为:" + data.getBytes().length);         
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
         GZIPOutputStream gout = new GZIPOutputStream(bout); //buffer
         gout.write(data.getBytes());
         gout.close();
         //得到压缩后的数据
         byte g[] = bout.toByteArray();
         response.setHeader("Content-Encoding", "gzip");
         response.setHeader("Content-Length",g.length +"");
         response.getOutputStream().write(g);
     }
 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

服务器发给浏览器的响应信息如下:

浏览器支持的压缩格式有:

设置content-type响应头,指定回送数据类型

 package gacl.http.study;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  public class ServletDemo03 extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         /**
          * 浏览器能接收(Accept)的数据类型有: 
          * application/x-ms-application, 
          * image/jpeg, 
          * application/xaml+xml, 
          * image/gif, 
          * image/pjpeg, 
          * application/x-ms-xbap, 
          * application/vnd.ms-excel, 
          * application/vnd.ms-powerpoint, 
          * application/msword, 
          */
         response.setHeader("content-type", "image/jpeg");//使用content-type响应头指定发送给浏览器的数据类型为"image/jpeg"
         //读取位于项目根目录下的img文件夹里面的WP_20131005_002.jpg这张图片,返回一个输入流
         InputStream in = this.getServletContext().getResourceAsStream("/img/WP_20131005_002.jpg");
         byte buffer[] = new byte[1024];
         int len = 0;
         OutputStream out = response.getOutputStream();//得到输出流
         while ((len = in.read(buffer)) > 0) {//读取输入流(in)里面的内容存储到缓冲区(buffer)
             out.write(buffer, 0, len);//将缓冲区里面的内容输出到浏览器
         }
     }
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

服务器发给浏览器的响应信息如下:

ServletDemo03的运行结果如下图所示:

在浏览器中显示出了图片

设置refresh响应头,让浏览器定时刷新

 package gacl.http.study;
 
 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 ServletDemo04 extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         /**
          * 设置refresh响应头,让浏览器每隔3秒定时刷新
          */
         // response.setHeader("refresh", "3");
         /**
          * 设置refresh响应头,让浏览器3秒后跳转到http://www.baidu.com
          */
         response.setHeader("refresh", "3;url='http://www.baidu.com'");
        response.getWriter().write("gacl");
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

设置content-disposition响应头,让浏览器下载文件

 package gacl.http.study; 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream; 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse; 
 public class ServletDemo05 extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         /**
          * 设置content-disposition响应头,让浏览器下载文件
          */
         response.setHeader("content-disposition", "attachment;filename=xxx.jpg");
         InputStream in = this.getServletContext().getResourceAsStream("/img/1.jpg");
         byte buffer[] = new byte[1024];
         int len = 0;
         OutputStream out = response.getOutputStream();
         while ((len = in.read(buffer)) > 0) {
             out.write(buffer, 0, len);
         }
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 
 }

在浏览器中访问ServletDemo05就会弹出文件下载框,如下图所示:

  

 

Servlet简介

 Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
  Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
  1、编写一个Java类,实现servlet接口。
  2、把开发好的Java类部署到web服务器中。
  按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet

 

Servlet的运行过程

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
  ①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
  ②装载并创建该Servlet的一个实例对象。 
  ③调用Servlet实例对象的init()方法。
  ④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  ⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。 

Servlet调用图

Servletè°ç¨å¾

 

在Eclipse中开发Servlet

第一种方式:继承HttpServlet

Servlet接口实现类

  Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。

  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,如下图所示:

  

  

  

这样,我们就通过Eclipse帮我们创建好一个名字为ServletDemo1的Servlet,创建好的ServletDemo01里面会有如下代码:

 package gacl.servlet.study; 
 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 ServletDemo1 extends HttpServlet {
     /**
      * The doGet method of the servlet. <br>
      *
      * This method is called when a form has its tag value method equals to get.
      * 
      * @param request the request send by the client to the server
      * @param response the response send by the server to the client
      * @throws ServletException if an error occurred
      * @throws IOException if an error occurred
     */
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
           response.setContentType("text/html");
         PrintWriter out = response.getWriter();
         out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
         out.println("<HTML>");
         out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
         out.println("  <BODY>");
         out.print("    This is ");
         out.print(this.getClass());
         out.println(", using the GET method");
         out.println("  </BODY>");
         out.println("</HTML>");
         out.flush();
         out.close();
     } 
     /**
      * The doPost method of the servlet. <br>
      *
      * This method is called when a form has its tag value method equals to post.
      * 
      * @param request the request send by the client to the server
      * @param response the response send by the server to the client
      * @throws ServletException if an error occurred
      * @throws IOException if an error occurred
      */
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
        response.setContentType("text/html");
         PrintWriter out = response.getWriter();
         out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
         out.println("<HTML>");
         out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
         out.println("  <BODY>");
         out.print("    This is ");
         out.print(this.getClass());
         out.println(", using the POST method");
         out.println("  </BODY>");
         out.println("</HTML>");
         out.flush();
         out.close();
     }
 }

  这些代码都是Eclipse自动生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>两对标签,这两对标签是配置ServletDemo1的,如下图所示:

然后我们就可以通过浏览器访问ServletDemo1这个Servlet,如下图所示:

  

第二种方式:实现接口 Servlet

第一步:创建一个JavaWeb项目,并创建一个servlet类-----HelloServlet,实现接口 Servlet

import java.io.IOException;
 
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloServlet implements Servlet{
    //只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
    public HelloServlet() {
        System.out.println("构造器 HelloServelt()...");
    }
     
    //该方法用于初始化Servlet,就是把该Servlet装载入内存
    //只被调用一次,在创建好实例后立即被调用
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化方法 init()...");
    }
     
    //被多次调用,每次请求都会调用service方法。实际用于响应请求的
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("执行方法主体 service()...");
    }
    //只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
    @Override
    public void destroy() {
        System.out.println("servlet 销毁时调用方法 destroy()...");
    }
 
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
 
    @Override
    public String getServletInfo() {
        return null;
    }
}

第二步:在 web.xml 文件中配置上面创建的 HelloServlet 映射关系

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      id="WebApp_ID" version="3.0">
  <!--在tomcat 服务器中运行时,如果不指名访问文件名,默认的根据项目名访问文件顺序如下配置  -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
   
  <!--给创建的 Servlet 配置映射关系  -->
  <servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>com.ys.servlet.HelloServlet</servlet-class>
                    <!--servlet的完整名称--> 
  </servlet>
   
  <servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
                <!-- 与上面配置的 servlet-name 名字要对应,一个servlet可以有多个 servlet-mapping  -->
    <url-pattern>/hello</url-pattern> 
                <!--访问路径-->
  </servlet-mapping>
</web-app>

第三步:将项目部署在 tomcat 服务器,如何部署请看这篇文章:http://www.cnblogs.com/ysocean/p/6893446.html,然后启动服务器

  这里我们项目的结构为:

    

   ①、我们直接通过项目名来访问,由于我们在 web.xml 文件中配置了 <welcome-file-list>,那么会依次找下面配置的文件,我们只创建了一个 index.jsp,那么就会访问这个JSP 文件

  

  ②、通过在 web.xml 文件中配置的<url-pattern>/hello</url-pattern>  来访问

  

  我们可以看控制台打印内容如下:

  

  如果我们不断的刷新  http://localhost:8080/ServletImprove/hello 这个访问链接,那么控制台如下:

  

Servlet 的生命周期

  我们通过上面的实例,可以看到也就是只有第一次才会执行 构造器和 init() 方法,后面每次点击都只调用 service() 方法。那这是为什么呢?

  

上面这幅图可以这样理解:

  1、客户端向 Web 服务器发送请求,服务器查询 web.xml 文件配置。根据请求信息找到对应的 Servlet。

  2、Servlet 引擎检查是否已经装载并创建了该 Servlet 的实例对象,如果有,则直接执行第4步,否则执行第3步,

  3、Web 服务器加载 Servlet,并调用 Servlet 构造器(只会调用一次),创建 Servlet 的实例对象。并调用 init() 方法,完成 Servlet 实例对象的初始化(只会调用一次)。

  4、Web 服务器把接收到的 http 请求封装成 ServletRequest 对象,并创建一个 响应消息的 ServletResponse 对象,作为 service() 方法的参数传入。(每一次访问都会调用一次该方法)

  5、执行 service() 方法,并将处理信息封装到 ServletResponse 对象中返回

  6、浏览器拆除 ServletResponse 对象,形成 http 响应格式,返回给客户端。

  7、Web 应用程序停止或者重新启动之前,Servlet 引擎将卸载 Servlet实例,并在卸载之前调用 destory() 方法

 

创建 Servlet 的三种方法

  第一种:就是我们上面写的 实现接口 Servlet

  第二种:由于实现接口我们需要实现里面所有的方法,里面有一些方法我们可能并不想实现,那么我们就继承 GenericServlet 类

import java.io.IOException;
 
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloServlet extends GenericServlet{
    //只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
    public HelloServlet() {
        System.out.println("构造器 HelloServelt()...");
    }
    //该方法用于初始化Servlet,就是把该Servlet装载入内存
    //只被调用一次,在创建好实例后立即被调用
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化方法 init()...");
    }
     
    //被多次调用,每次请求都会调用service方法。实际用于响应请求的
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("执行方法主体 service()...");
    }
    //只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
    @Override
    public void destroy() {
        System.out.println("servlet 销毁时调用方法 destroy()...");
    }
 
}

 第三种:通常我们浏览器发出的请求都是 http 请求,那么请求方式可能有多种,比如 get,post,而我们在处理请求的时候都是在 service() 方法中,这种方式显然不够明确。那么我们通常是 继承 HttpServlet 类

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 HelloServlet extends HttpServlet{
    //只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
    public HelloServlet() {
        System.out.println("构造器 HelloServelt()...");
    }
    //该方法用于初始化Servlet,就是把该Servlet装载入内存
    //只被调用一次,在创建好实例后立即被调用
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化方法 init()...");
    }
    //处理 post 请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     
    }
    //处理get请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     
    }
     
    //只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
    @Override
    public void destroy() {
        System.out.println("servlet 销毁时调用方法 destroy()...");
    }
 
     
}

  其实上面三种方法,后面两种都是对 Servlet 类的封装,我们可以看 API,其实 HttpServlet 是继承 GenericServlet的。

    

  而 GenericServlet 又是实现 Servlet 接口的

    

  

 

Servlet开发注意细节

Servlet访问URL映射配置

  由于客户端是通过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-name>ServletDemo1</servlet-name>
     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
   </servlet>
   <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
     <url-pattern>/servlet/ServletDemo1</url-pattern>
   </servlet-mapping>

  同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。 例如:

  <servlet>
     <servlet-name>ServletDemo1</servlet-name>
     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
   </servlet> 
   <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
     <url-pattern>/servlet/ServletDemo1</url-pattern>
   </servlet-mapping>
  <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
     <url-pattern>/1.htm</url-pattern>
   </servlet-mapping>
    <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
     <url-pattern>/2.jsp</url-pattern>
   </servlet-mapping>
    <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
     <url-pattern>/3.php</url-pattern>
   </servlet-mapping>
    <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/4.ASPX</url-pattern>
   </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使用*通配符映射  

在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是"*.扩展名",另一种格式是以正斜杠(/)开头并以"/*"结尾。例如:

  

  <servlet>
     <servlet-name>ServletDemo1</servlet-name>
     <servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
   </servlet> 
    <servlet-mapping>
     <servlet-name>ServletDemo1</servlet-name>
   <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 的多线程问题

  我们通过 Servlet 的生命周期可以知道,Servlet 类的构造器只会在第一次访问的时候调用,后面的请求都不会再重新创建 Servlet 实例。即 Servlet 是单例,那么既然是单例的,那就要注意多线程访问所造成的安全问题。如下:

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloServlet extends GenericServlet{
    //多线程共享资源
    private int i = 0;
     
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        i++;
        //为了使多线程访问安全问题更加突出,我们增加一个延时程序
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);     
    }
     
}

  我们用两个浏览器,输入 http://localhost:8080/ServletImprove/hello,然后一起访问,不断刷新,结果如下:

    

结果分析:显然,我们用两个浏览器访问,便相当于两个线程,第一个访问,已经执行了 i++,但是还没来得及打印 i 的值,就马上就睡眠了;接着第二个浏览也来访问,执行 i++,那么i的值相当于增加加了两次1,然后这两个浏览器输出最终结果。这便造成了多线程访问共享资源造成冲突。

不存在线程安全问题的代码:

 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 ServletDemo3 extends HttpServlet {
 
     
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         
         /**
          * 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗
          * i变量被多个线程并发访问,但是没有线程安全问题,因为i是doGet方法里面的局部变量,
          * 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,
          * 各个线程操作的都是自己的i变量,所以不存在线程安全问题
          * 多线程并发访问某一个方法的时候,如果在方法内部定义了一些资源(变量,集合等)
          * 那么每一个线程都有这些东西,所以就不存在线程安全问题了
          */
         int i=1;
         i++;
         response.getWriter().write(i);
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         doGet(request, response);
     } 

那么在 Servlet 中如何处理呢? 

  第一种方法:使用同步代码块  

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloServlet extends GenericServlet{
    //多线程共享资源
    private int i = 0;
     
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        synchronized (this) {
            i++;
            //为了使多线程访问安全问题更加突出,我们增加一个延时程序
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);     
        }
    }
     
}

 结果:

    

分析:这种办法虽然能解决多线程同步问题,但是如果 延时程序特别长,那么会造成访问假死的现象。即第一个线程访问结果没有出来,第二个线程就会一直卡死,出不来结果。

但是编写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(过时的)。  

第二种办法:实现接口 SingleThreadModel

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SingleThreadModel;
 
public class HelloServlet extends GenericServlet implements SingleThreadModel{
    //多线程共享资源
    private int i = 0;
     
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        i++;
        //为了使多线程访问安全问题更加突出,我们增加一个延时程序
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);     
    }
     
}

  结果:

    

  分析:SingleThreadModel 接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销,在现在的Servlet开发中基本看不到SingleThreadModel的使用,这种方式了解即可,尽量避免使用。

第三种办法:避免使用实例变量

  线程安全问题很大一部分是由于实例变量造成的,那么我们只要在 Servlet 里面不定义任何的实例变量,那么就不会有线程安全的问题。因为在 Java 内存模型中,方法中的临时变量是在栈上分配空间,而且每个线程都有自己的私有栈空间,不会造成线程安全问题。

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloServlet extends GenericServlet{
     
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        int i = 0;
        i++;
        //为了使多线程访问安全问题更加突出,我们增加一个延时程序
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);     
    }
     
}

 结果:

  

 

Servlet与普通Java类的区别  

  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应用创建必要的数据库表和数据。

 

Servlet 和 JSP 的区别

  ①、JSP 的本质就是 Servlet,JSP 经过编译后就会变为一个类似 Servlet 的Java文件

  ②、Servlet 基本是JAVA程序代码构成,擅长于流程控制和事务处理,当然也可以用来生成html代码,但是通过Servlet来生成动态网页很不直观.

  ③、JSP由HTML代码和JSP标签构成,可以方便地编写动态网页,当然里面也可以编写 Java代码,但是整体看上去不够优雅。而且比较麻烦

  所以:JSP侧重于视图,Servlet主要用于控制逻辑。

我们可以看一个 JSP 文件,index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    index.jsp
</body>
</html>

经过编译后:很显然下面的代码结构和 Servlet 是差不多的

package org.apache.jsp;
 
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {
  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();
  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
  private javax.el.ExpressionFactory _el_expressionfactory;
  private org.apache.tomcat.InstanceManager _jsp_instancemanager;
  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }
 
  public void _jspInit() {
    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
  }
 
  public void _jspDestroy() {
  }
 
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;
    try {
      response.setContentType("text/html; charset=ISO-8859-1");
      pageContext = _jspxFactory.getPageContext(this, request, response,
                null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;
 
      out.write("\r\n");
      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\r\n");
      out.write("<title>Insert title here</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("\tindex.jsp\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try { out.clearBuffer(); } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

  JSP 页面的九个隐含对象:

  ①、request:HttpServletRequest的一个对象,封装请求信息

      ②、pageContext:页面的上下文,是PageContext的一个对象,可以从该对象中获取其它8个隐含对象。

      ③、session:代表浏览器和服务器的一次会话,是HttpSession 的一个对象

      ④、application:代表当前WEB应用,是ServletContext对象

      ⑤、config:当前JSP对应Servlet的ServletConfig对象

      ⑥、out:JspWriter对象,调用out.prinln()可以直接把字符串打印到浏览器上

      ⑦、page:指向当前JSP对应的Servlet对象的应用,但为Object类型,只能调用 Object 类的方法

      ⑧、exception:在声明了page指令的isErrorPage="true"时,才可以使用

 

缺省Servlet

  如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。 
  凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。 例如:

  <servlet>
     <servlet-name>ServletDemo2</servlet-name>
     <servlet-class>gacl.servlet.study.ServletDemo2</servlet-class>
     <load-on-startup>1</load-on-startup>
   </servlet>   
   <!-- 将ServletDemo2配置成缺省Servlet -->
   <servlet-mapping>
     <servlet-name>ServletDemo2</servlet-name>
     <url-pattern>/</url-pattern>
   </servlet-mapping>

 当访问不存在的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>
  <!-- The mapping for the default servlet -->
     <servlet-mapping>
         <servlet-name>default</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>

  当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。

 

Servlet 的转发和重定向

  重定向:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
         
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        response.sendRedirect("index.jsp");//重定向
    }

  转发:

HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        //response.sendRedirect("index.jsp");
        request.getRequestDispatcher("/index.jsp").forward(request, response);//转发

我们再看看浏览器访问:同时输入 http://localhost:8080/ServletImprove/hello

  重定向变为:

     

  转发为:

    

本质区别:转发只发出了一次请求,而重定向发出了两次请求

  ①.转发:地址栏是初次发出请求的地址
         重定向:地址栏不再是初次发出的请求地址,地址栏为最后响应的那个地址

   ②.转发:在最终的Servlet中,request对象和中转的那个request是同一个对象
         重定向:在最终的Servlet中,request对象和中转的那个request不是同一个对象

  ③.转发:只能转发给当前WEB应用的资源
         重定向:可以重定向到任何资源
                response.sendRedirect("http://www.baidu.com");是可以的
                转发就不行

   ④.转发:/  代表的是当前WEB应用的根目录(http://localhost:8080/项目名称/)
         重定向: / 代表的是当前WEB站点的根目录(http://localhost:8080/

注意:这两条跳转语句不能同时出现在一个页面中,否则会报IllegalStateException - if the response was already committed

 

Servlet 的过滤器

  ①、什么是 过滤器?

     JavaWEB 的一个重要组件,可以对发送到 Servlet 的请求进行拦截,并对响应也进行拦截
  ②、如何实现一个过滤器?

第一步:创建一个过滤器类,实现 Filter 接口

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class HelloFilter implements Filter{
    public HelloFilter() {
        System.out.println("构造器 HelloFilter()...");
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init()...");
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("doFilter()...");
    }
 
    @Override
    public void destroy() {
        System.out.println("destroy()...");
    }
 
}

第二步:在 web.xml 文件中配置过滤器

<!--给创建的过滤器配置关系  -->
  <filter>
    <filter-name>helloFilter</filter-name>
    <filter-class>com.ys.filter.HelloFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>helloFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 这表示可以拦截任何请求 -->
  </filter-mapping>

  启动服务器:我们发现还没发送请求,过滤器的 构造方法和 init() 方法就已经开始运行了

  服务器启动成功之后,我们输入任意连接,比如

    

  每刷新一次,控制台都会打印 doFilter()...

  

总结:生命周期和 Servlet 的类似。只不过其构造方法和初始化方法是在容器启动时就调用了,而其 doFilter() 方法则是在每次请求的时候调用。故过滤器可以对请求进行拦截过滤。可以用来进行权限设置,对传输数据进行加密等等操作。

 

ServletConfig讲解

1.1、配置Servlet初始化参数

  在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。

例如:

  <servlet>
      <servlet-name>ServletConfigDemo1</servlet-name>
      <servlet-class>gacl.servlet.study.ServletConfigDemo1</servlet-class>
      <!--配置ServletConfigDemo1的初始化参数 -->
      <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>

1.2、通过ServletConfig获取Servlet的初始化参数

  当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,我们通过ServletConfig对象就可以得到当前servlet的初始化参数信息。

例如:

 import java.io.IOException;
 import java.util.Enumeration;
 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 ServletConfigDemo1 extends HttpServlet { 
     /**
      * 定义ServletConfig对象来接收配置的初始化参数
      */
     private ServletConfig config;
     
     /**
      * 当servlet配置了初始化参数后,web容器在创建servlet实例对象时,
      * 会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,
      * 将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以
      * 得到当前servlet的初始化参数信息。
      */
     @Override
     public void init(ServletConfig config) throws ServletException {
         this.config = config;
     } 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         //获取在web.xml中配置的初始化参数
         String paramVal = this.config.getInitParameter("name");//获取指定的初始化参数
         response.getWriter().print(paramVal);
         
         response.getWriter().print("<hr/>");
         //获取所有的初始化参数
         Enumeration<String> e = config.getInitParameterNames();
         while(e.hasMoreElements()){
             String name = e.nextElement();
            String value = config.getInitParameter(name);
             response.getWriter().print(name + "=" + value + "<br/>");
         }
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     } 
 }

运行结果如下:

  

 

ServletContext对象

  WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
  ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
  由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。

多个Servlet通过ServletContext对象实现数据共享

  范例:ServletContextDemo1和ServletContextDemo2通过ServletContext对象实现数据共享

  import java.io.IOException;
  import javax.servlet.ServletContext;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
 
 public class ServletContextDemo1 extends HttpServlet { 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         String data = "xdp_gacl";
         /**
          * ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,
          * 可以通过ServletConfig.getServletContext方法获得ServletContext对象。
          */
         ServletContext context = this.getServletConfig().getServletContext();//获得ServletContext对象
         context.setAttribute("data", data);  //将data存储到ServletContext对象中
     }
 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         doGet(request, response);
     }
 }
 import java.io.IOException;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse; 
 public class ServletContextDemo2 extends HttpServlet { 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         ServletContext context = this.getServletContext();
         String data = (String) context.getAttribute("data");//从ServletContext对象中取出数据
         response.getWriter().print("data="+data);
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         doGet(request, response);
     }
 }

  先运行ServletContextDemo1,将数据data存储到ServletContext对象中,然后运行ServletContextDemo2就可以从ServletContext对象中取出数据了,这样就实现了数据共享,如下图所示:

  

  获取WEB应用的初始化参数

在web.xml文件中使用<context-param>标签配置WEB应用的初始化参数,如下所示:

 <?xml version="1.0" encoding="UTF-8"?>
 <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
     http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
     <display-name></display-name>
     <!-- 配置WEB应用的初始化参数 -->
     <context-param>
         <param-name>url</param-name>
         <param-value>jdbc:mysql://localhost:3306/test</param-value>
     </context-param>
 
     <welcome-file-list>
         <welcome-file>index.jsp</welcome-file>
     </welcome-file-list>
 </web-app>

 获取Web应用的初始化参数,代码如下:

 import java.io.IOException;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse; 
 public class ServletContextDemo3 extends HttpServlet { 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         ServletContext context = this.getServletContext();
         //获取整个web站点的初始化参数
         String contextInitParam = context.getInitParameter("url");
        response.getWriter().print(contextInitParam);
     }
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         doGet(request, response);
     } 
 }

运行结果:

  

用servletContext实现请求转发

ServletContextDemo4
 import java.io.IOException;
 import java.io.PrintWriter;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse; 
 public class ServletContextDemo4 extends HttpServlet { 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         String data = "<h1><font color='red'>abcdefghjkl</font></h1>";
        response.getOutputStream().write(data.getBytes());
         ServletContext context = this.getServletContext();//获取ServletContext对象
         RequestDispatcher rd = context.getRequestDispatcher("/servlet/ServletContextDemo5");//获取请求转发对象(RequestDispatcher)
         rd.forward(request, response);//调用forward方法实现请求转发
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
     }
 }
ServletContextDemo5
 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 ServletContextDemo5 extends HttpServlet { 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         response.getOutputStream().write("servletDemo5".getBytes());
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

 运行结果:

  

  访问的是ServletContextDemo4,浏览器显示的却是ServletContextDemo5的内容,这就是使用ServletContext实现了请求转发

 

利用ServletContext对象读取资源文件

  项目目录结构如下:

   

代码范例:使用servletContext读取资源文件

 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.text.MessageFormat;
 import java.util.Properties;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
  /**
  * 使用servletContext读取资源文件
  * 
  * @author gacl
  * 
  */
 public class ServletContextDemo6 extends HttpServlet {
 
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException { 
         /**
          * response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
          * 这样就不会出现中文乱码了
          */
         response.setHeader("content-type","text/html;charset=UTF-8");
         readSrcDirPropCfgFile(response);//读取src目录下的properties配置文件
         response.getWriter().println("<hr/>");
         readWebRootDirPropCfgFile(response);//读取WebRoot目录下的properties配置文件
         response.getWriter().println("<hr/>");
         readPropCfgFile(response);//读取src目录下的db.config包中的db3.properties配置文件
         response.getWriter().println("<hr/>");
         readPropCfgFile2(response);//读取src目录下的gacl.servlet.study包中的db4.properties配置文件
         
     }
 
     /**
      * 读取src目录下的gacl.servlet.study包中的db4.properties配置文件
      * @param response
      * @throws IOException
      */
     private void readPropCfgFile2(HttpServletResponse response)
             throws IOException {
         InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/gacl/servlet/study/db4.properties");
         Properties prop = new Properties();
         prop.load(in);
         String driver = prop.getProperty("driver");
         String url = prop.getProperty("url");
         String username = prop.getProperty("username");
         String password = prop.getProperty("password");
         response.getWriter().println("读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
         response.getWriter().println(
                 MessageFormat.format(
                         "driver={0},url={1},username={2},password={3}", 
                          driver,url, username, password));
     }
 
     /**
      * 读取src目录下的db.config包中的db3.properties配置文件
      * @param response
      * @throws FileNotFoundException
      * @throws IOException
      */
     private void readPropCfgFile(HttpServletResponse response)
             throws FileNotFoundException, IOException {
         //通过ServletContext获取web资源的绝对路径
         String path = this.getServletContext().getRealPath("/WEB-INF/classes/db/config/db3.properties");
         InputStream in = new FileInputStream(path);
         Properties prop = new Properties();
         prop.load(in);
         String driver = prop.getProperty("driver");
          String url = prop.getProperty("url");
         String username = prop.getProperty("username");
         String password = prop.getProperty("password");
         response.getWriter().println("读取src目录下的db.config包中的db3.properties配置文件:");
         response.getWriter().println(
                  MessageFormat.format(
                          "driver={0},url={1},username={2},password={3}", 
                         driver,url, username, password));
      }
  
      /**
      * 通过ServletContext对象读取WebRoot目录下的properties配置文件
      * @param response
       * @throws IOException
      */
     private void readWebRootDirPropCfgFile(HttpServletResponse response)
             throws IOException {
          /**
          * 通过ServletContext对象读取WebRoot目录下的properties配置文件
          * “/”代表的是项目根目录
          */
         InputStream in = this.getServletContext().getResourceAsStream("/db2.properties");
         Properties prop = new Properties();
         prop.load(in);
         String driver = prop.getProperty("driver");
         String url = prop.getProperty("url");
         String username = prop.getProperty("username");
         String password = prop.getProperty("password");
         response.getWriter().println("读取WebRoot目录下的db2.properties配置文件:");
         response.getWriter().print(
                MessageFormat.format(
                         "driver={0},url={1},username={2},password={3}", 
                         driver,url, username, password));
     }
 
     /**
      * 通过ServletContext对象读取src目录下的properties配置文件
      * @param response
      * @throws IOException
      */
    private void readSrcDirPropCfgFile(HttpServletResponse response) throws IOException {
         /**
          * 通过ServletContext对象读取src目录下的db1.properties配置文件
          */
         InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db1.properties");
         Properties prop = new Properties();
         prop.load(in);
         String driver = prop.getProperty("driver");
         String url = prop.getProperty("url");
         String username = prop.getProperty("username");
         String password = prop.getProperty("password");
         response.getWriter().println("读取src目录下的db1.properties配置文件:");
         response.getWriter().println(
                 MessageFormat.format(
                         "driver={0},url={1},username={2},password={3}", 
                         driver,url, username, password));
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 
 }

运行结果如下:

  

代码范例:使用类装载器读取资源文件

  3 import java.io.FileOutputStream;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.io.OutputStream;
  7 import java.text.MessageFormat;
  8 import java.util.Properties;
  9 
 10 import javax.servlet.ServletException;
 11 import javax.servlet.http.HttpServlet;
 12 import javax.servlet.http.HttpServletRequest;
 13 import javax.servlet.http.HttpServletResponse;
 14 
 15 /**
 16  * 用类装载器读取资源文件
 17  * 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
 18  * @author gacl
 19  *
 20  */
 21 public class ServletContextDemo7 extends HttpServlet {
 22 
 23     public void doGet(HttpServletRequest request, HttpServletResponse response)
 24             throws ServletException, IOException {
 25         /**
 26          * response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
 27          * 这样就不会出现中文乱码了
 28          */
 29         response.setHeader("content-type","text/html;charset=UTF-8");
 30         test1(response);
 31         response.getWriter().println("<hr/>");
 32         test2(response);
 33         response.getWriter().println("<hr/>");
 34         //test3();
 35         test4();
 36         
 37     }
 38     
 39     /**
 40      * 读取类路径下的资源文件
 41      * @param response
 42      * @throws IOException
 43      */
 44     private void test1(HttpServletResponse response) throws IOException {
 45         //获取到装载当前类的类装载器
 46         ClassLoader loader = ServletContextDemo7.class.getClassLoader();
 47         //用类装载器读取src目录下的db1.properties配置文件
 48         InputStream in = loader.getResourceAsStream("db1.properties");
 49         Properties prop = new Properties();
 50         prop.load(in);
 51         String driver = prop.getProperty("driver");
 52         String url = prop.getProperty("url");
 53         String username = prop.getProperty("username");
 54         String password = prop.getProperty("password");
 55         response.getWriter().println("用类装载器读取src目录下的db1.properties配置文件:");
 56         response.getWriter().println(
 57                 MessageFormat.format(
 58                         "driver={0},url={1},username={2},password={3}", 
 59                         driver,url, username, password));
 60     }
 61 
 62     /**
 63      * 读取类路径下面、包下面的资源文件
 64      * @param response
 65      * @throws IOException
 66      */
 67     private void test2(HttpServletResponse response) throws IOException {
 68         //获取到装载当前类的类装载器
 69         ClassLoader loader = ServletContextDemo7.class.getClassLoader();
 70         //用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件
 71         InputStream in = loader.getResourceAsStream("gacl/servlet/study/db4.properties");
 72         Properties prop = new Properties();
 73         prop.load(in);
 74         String driver = prop.getProperty("driver");
 75         String url = prop.getProperty("url");
 76         String username = prop.getProperty("username");
 77         String password = prop.getProperty("password");
 78         response.getWriter().println("用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
 79         response.getWriter().println(
 80                 MessageFormat.format(
 81                         "driver={0},url={1},username={2},password={3}", 
 82                         driver,url, username, password));
 83     }
 84     
 85     /**
 86      * 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
 87      */
 88     public void test3() {
 89         /**
 90          * 01.avi是一个150多M的文件,使用类加载器去读取这个大文件时会导致内存溢出:
 91          * java.lang.OutOfMemoryError: Java heap space
 92          */
 93         InputStream in = ServletContextDemo7.class.getClassLoader().getResourceAsStream("01.avi");
 94         System.out.println(in);
 95     }
 96     
 97     /**
 98      * 读取01.avi,并拷贝到e:\根目录下
 99      * 01.avi文件太大,只能用servletContext去读取
100      * @throws IOException
101      */
102     public void test4() throws IOException {
103         // path=G:\Java学习视频\JavaWeb学习视频\JavaWeb\day05视频\01.avi
104         // path=01.avi
105         String path = this.getServletContext().getRealPath("/WEB-INF/classes/01.avi");
106         /**
107          * path.lastIndexOf("\\") + 1是一个非常绝妙的写法
108          */
109         String filename = path.substring(path.lastIndexOf("\\") + 1);//获取文件名
110         InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/01.avi");
111         byte buffer[] = new byte[1024];
112         int len = 0;
113         OutputStream out = new FileOutputStream("e:\\" + filename);
114         while ((len = in.read(buffer)) > 0) {
115             out.write(buffer, 0, len);
116         }
117         out.close();
118         in.close();
119     }
120 
121     public void doPost(HttpServletRequest request, HttpServletResponse response)
122             throws ServletException, IOException {
123 
124         this.doGet(request, response);
125     }
126 
127 }

 运行结果如下:

  

 

在客户端缓存Servlet的输出

  对于不经常变化的数据,在servlet中可以为其设置合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能。例如:

 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 ServletDemo5 extends HttpServlet {      
public void doGet(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {
         String data = "abcddfwerwesfasfsadf";
         /**
          * 设置数据合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能
          * 这里是将数据的缓存时间设置为1天
          */
         response.setDateHeader("expires",System.currentTimeMillis() + 24 * 3600 * 1000);
         response.getOutputStream().write(data.getBytes());
     } 
     public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
         this.doGet(request, response);
     }
 }

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值