前言和体系结构

Web服务器做些什么

  • Web服务器接收客户请求,然后向客户返回一些结果。
  • 如果没有请求的资源,则返回404 Not Found错误。

Web客户端做些什么

  • Web客户端允许用户请求服务器上的某个资源,并且向用户显示请求的结果。

客户和服务器都知道HTML和HTTP

  • HTML告诉浏览器怎样向用户显示内容。
  • HTTP是Web上客户和服务器之间进行通信所用的协议。
  • 服务器使用HTTP向客户端发送HTML。

HTML速成指南

标记描述
<!-- -->注释
<a>锚点——通常用来放一个超链接
<align>对齐
<body>定义文本体的边界
<br>行分隔
<center>将内容居中
<form>定义一个表单
<h1>一级标题
<head>定义文档首部的边界
<html>定义HTML文档的边界
<input type>在表单中定义一个输入组件
<p>一个新段落
<title>HTML文档的标题

什么是HTTP协议?

  • HTTP是TCP/IP的上层协议。TCP负责确保从一个网络节点向另一个网络节点发送的文件能作为一个完整的文件到达目的地,尽管在具体传送过程中这个文件可能会分解为小块传输。IP是一个底层协议,负责把数据块(数据包)沿路移动/路由到目的地。HTTP则是另一个网络协议,有一些Web特定的特征,不过它要依赖于TCP/IP从一处向另一处完整地传送请求和响应。HTTP会话的结构是一个简单的请求/响应序列:浏览器发出请求,服务器做出响应。
  • 请求流的关键要素:HTTP方法、URL、表单参数。
  • 响应流的关键要素:状态码、内容类型、内容。

HTML是HTTP响应的一部分

  • HTTP响应可以包含HTML
  • HTTP还会在响应中包含的内容前面增加首部信息。

请求

  • 请求最常用的类型为GET和POST。
  • GET是最简单的HTTP方法,它的主要任务就是要求服务器获得一个资源并把资源发回来。
  • POST可以请求某个东西,与此同时向服务器发送一些表单数据。

最好使用POST发送数据

  • GET中的总字符数是有限的(取决于服务器)。
  • 用GET发送的数据会追加到URL的后面,在浏览器地址栏中显示出来,所以你发送的数据会完全暴露。
  • 如果使用POST而不是GET,用户就不能对一个表单提交建立书签。

HTTP GET请求剖析

GET /socket.io/1/xhr-polling/cjJ4rr2VUk0aTC0JKKvG?t=1493719041095 HTTP/1.1\r\n
Host: s1-im-notify.csdn.net\r\n
Connection: keep-alive\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/5.0.2.2000 Chrome/47.0.2526.73 Safari/537.36\r\n
Origin: http://write.blog.csdn.net\r\n
Accept: */*\r\n
DNT: 1\r\n
Referer: http://write.blog.csdn.net/postlist\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN\r\n
  • GET /socket.io/1/xhr-polling/cjJ4rr2VUk0aTC0JKKvG?t=1493719041095 HTTP/1.1\r\n为请求行
    • GET:HTTP方法
    • /socket.io/1/xhr-polling/cjJ4rr2VUk0aTC0JKKvG:Web服务器上资源的路径。
    • ?t=1493719041095:在GET请求中,参数(如果有)会追加到请求URL第一部分的后面,以”?”开头。各参数之间用”&”分隔。
    • HTTP/1.1:Web浏览器所请求的协议的版本。
  • 其余部分为请求首部:

    Host: s1-im-notify.csdn.net\r\n
    Connection: keep-alive\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/5.0.2.2000 Chrome/47.0.2526.73 Safari/537.36\r\n
    Origin: http://write.blog.csdn.net\r\n
    Accept: */*\r\n
    DNT: 1\r\n
    Referer: http://write.blog.csdn.net/postlist\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN\r\n

    Accept表示可以接受的MIME类型。

HTTP POST请求剖析

POST /pollmessage HTTP/1.1\r\n
Host: p.f.360.cn\r\n
Accept: */*\r\n
Connection: Keep-Alive\r\n
Cache-Control: no-cache\r\n
Content-Length: 358\r\n
Content-Type: application/x-www-form-urlencoded\r\n
\r\n
[Full request URI: http://p.f.360.cn/pollmessage]
[HTTP request 1/1]
[Response in frame: 150751]
  • POST /pollmessage HTTP/1.1\r\n:请求行
    • POST:HTTP方法
    • /pollmessage:资源在Web服务器上的路径
    • HTTP/1.1:Web浏览器请求的协议的版本
  • 请求首部:

    Host: p.f.360.cn\r\n
    Accept: */*\r\n
    Connection: Keep-Alive\r\n
    Cache-Control: no-cache\r\n
    Content-Length: 358\r\n
    Content-Type: application/x-www-form-urlencoded\r\n
  • 消息体(负载):

    [Full request URI: http://p.f.360.cn/pollmessage]
    [HTTP request 1/1]
    [Response in frame: 150751]

HTTP 响应剖析

  • HTTP响应包括一个首部和一个体。
  • 首部信息告诉浏览器使用了什么协议,请求是否成功,以及体中包括何种类型的内容。
  • 体包含了让浏览器显示的具体内容。

    HTTP/1.1 200 OK\r\n
    Server: nginx\r\n
    Date: Tue, 02 May 2017 13:01:11 GMT\r\n
    Content-Type: application/octet-stream\r\n
    Content-Length: 160\r\n
    Connection: close\r\n
    Cache-Control: no-cache\r\n
    pragma: no-cache\r\n
    \r\n
    [HTTP response 1/1]
    [Time since request: 0.117763000 seconds]
    [Request in frame: 150745]
    • HTTP/1.1:Web服务器使用的协议的版本
    • 200:响应的HTTP状态码
    • OK:状态码的响应文本
    • Content-Type: application/octet-stream:MIME类型。MIME类型告诉浏览器要接收的数据是什么类型,这样浏览器才能知道如何显示这些数据。响应中必须有的一个首部。
    • HTTP响应首部:

      HTTP/1.1 200 OK\r\n
      Server: nginx\r\n
      Date: Tue, 02 May 2017 13:01:11 GMT\r\n
      Content-Type: application/octet-stream\r\n
      Content-Length: 160\r\n
      Connection: close\r\n
      Cache-Control: no-cache\r\n
      pragma: no-cache\r\n
    • 体:

      [HTTP response 1/1]
      [Time since request: 0.117763000 seconds]
      [Request in frame: 150745]

URL

http://code.thunisoft.com:80/media/asr-server/blob/master/asrServer/readme/interface/接口文档.md
  • http://:协议,告诉服务器使用什么通信协议。
  • code.thunisoft.com:服务器:所请求物理服务器的唯一名。这个名字映射到一个唯一的IP地址。IP地址是一个数字,形式为”xxx.yyy.zzz.aaa”。在这里也可以指定一个IP地址而不是服务器名,不过服务器名更容易记。
  • 80:端口,URL的这一部分是可选的。一个服务器可以支持多个端口。一个服务器应用由端口标识。如果在URL中没有指定的端口,默认端口则是端口80,而且很幸运,这正是Web服务器的默认端口。
  • media/asr-server/blob/master/asrServer/readme/interface/接口文档.md:路径:所请求资源在服务器上的路径。
  • 接口文档.md:所请求内容的名字。这一部分是可选的,如果没有这一部分,大多数Web服务器都会默认地查找index.html。

端口

  • TCP端口只是一个数字而已。
  • 端口是一个16位数字,标识服务器硬件上一个特定的软件程序。
  • 端口并不表示一个可以插入物理设备的位置。它们只是表示服务器应用的“逻辑”数而已。
  • 从0~1023的TCP端口号已经保留,由一些众所周知的服务使用。你自己的定制服务器程序不要使用这些端口。
  • 常用服务器应用TCP端口号:

    应用端口
    FTP21
    Telnet23
    SMTP25
    Time37
    HTTP80
    POP3110
    HTTPS443

静态页面与动态页面

  • Web服务器只能提供静态页面,静态页面只是原封不动地待在目录中。服务器找到静态页面,并把它原样传回给客户。每个客户看到的东西都一样。
  • 动态页面,或者叫即时页面,是由辅助应用产生的,产生后交给服务器,再由服务器交给客户。即时页面在请求到来之前并不存在,创建这样一个HTML页面就像是搭建一个“空中楼阁”。请求到来后,辅助应用“具体写出”HTML,Web服务器再把这个HTML交回给客户。
  • 辅助应用可以使CGI和Servlet。

我的第一个Servlet

  • 创建一个目录树:

    /project1
        /src
        /classes
        /etc
  • 编写一个Ch1Servlet.java的servlet,把它放在src目录中:

    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class Ch1Servlet extends HttpServlet {
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            PrintWriter out = resp.getWriter();
            java.util.Date today = new java.util.Date();
            out.println("<html>" + 
                "<body>" +
                "<h1 align=center>HF\'s Chapter1 Servlet</h1>" +
                "<br>" + today + "</body>" + "</html>");
        }
    }
  • 创建一个部署文件(deployment descriptor,DD),名为web.xml,把它放在etc目录中。(可以从tomcat的例子中拷贝一份出来修改)

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      version="3.1">
    
      <servlet>
        <servlet-name>Chapter1 Servlet</servlet-name>
        <servlet-class>Ch1Servlet</servlet-class>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>Chapter1 Servlet</servlet-name>
        <url-pattern>/Serv1</url-pattern>
      </servlet-mapping>
    
    </web-app>
  • 在以后的Tomcat目录下建立这个目录树:

    /tomcat
        /webapps
            /ch1
                /WEB-INF
                    /classes
  • 从project1目录编译servlet:

    javac -classpath "G:\Tomcat 8.5\lib\servlet-api.jar" -d classes src/Ch1Servlet.java
  • 把Ch1Servlet.class文件复制到WEB-INF/classes,再把web.xml文件复制到WEB-INF。

  • 启动Tomcat
  • 打开浏览器,键入:

    http://localhost:8080/ch1/Serv1
  • 每次更新servlet类或部署文件都需要关闭Tomcat。

JSP

  • 实际上,在servlet的out.println()里格式化HTML确实不太好。于是引入JSP。
  • JSP页面就像是一个HTML页面,只不过可以把Java和与Java有关的东西放在这个页面中。

高层体系结构

什么是容器?

  • Servlet没有main()方法。它们受控于另一个Java应用,这个Java应用称为容器。Tomcat就是这样一个容器。如果Web服务器应用得到一个指向某sevlet的请求,此时服务器不是把这个请求给servlet本身,而是交给部署该servlet的容器。要由容器向servlet提供HTTP请求和相应,而且要由容器调用servlet的方法,如doPost()或doGet()。

容器能提供什么?

  • 通信支持:利用容器提供的方法,你能轻松地让servlet与Web服务器对话。
  • 生命周期管理:容器控制着servlet的生与死。
  • 多线程支持:容器会自动地为它接收的每个servlet请求创建一个新的Java线程。
  • 声明方式实现安全:利用容器,可以使用XML部署描述文件来配置(和修改)安全性,而不必将其硬编码写到servlet类代码中。
  • JSP支持。

容器如何处理请求

  1. 用户点击一个链接,其URL指向一个servlet
  2. 容器“看出来”这个请求要的是一个servlet,所以容器创建两个对象:HttpServletResponse,HttpServletRequest。
  3. 容器根据请求中的URL找到正确的servlet,为这个请求创建或分配一个线程,并把请求和响应对象传递给这个servlet线程。
  4. 容器调用servlet的service()方法。根据请求的不同类型,service()方法会调用doGet()或doPost()方法。
  5. doGet()或doPost()方法生成动态页面,并把这个页面“填入”响应对象。
  6. 线程结束,容器把响应对象转换为一个HTTP响应,把它发回给客户,然后删除请求和响应对象。

代码里有什么?

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

// 99.9999%的servlet都是HttpServlet
public class Ch1Servlet extends HttpServlet {

    // 在实际中,99.9%的servlet都会覆盖doGet()或doPost()方法
    // servlet从这里拿到容器创建的请求和响应对象的引用
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 在servlet从容器得到的响应对象中,可以拿到一个PrintWriter。
        // 使用这个PrintWriter能够将HTML文本输出到响应对象
        PrintWriter out = resp.getWriter();
        java.util.Date today = new java.util.Date();
        out.println("<html>" + 
            "<body>" +
            "<h1 align=center>HF\'s Chapter1 Servlet</h1>" +
            "<br>" + today + "</body>" + "</html>");
    }
}

一个servlet可以有3个名字

  1. URL名:客户看到HTML中对应一个servlet的URL。
  2. 部署名:部署人员知道的秘密的内部名
  3. 实际的文件名

建立servlet名的映射,这有助于改善应用的灵活性和安全性。

使用部署描述文件将URL映射到servlet

  • <servlet>: 内部名映射到完全限定类名
  • <servlet-mapping>: 内部名映射到公共URL名
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

  <!-- <servlet>元素告诉容器哪些类文件属于一个特定Web应用 -->
  <servlet>
    <!-- <servlet-name>元素用于把一个<servlet>元素绑定到一个
      特定的<servlet-mapping>元素。最终用户绝对看不到这个名,
      这个名只在这个部署描述文件的其他部分中使用。 -->
    <servlet-name>Chapter1 Servlet</servlet-name>
    <!-- 在<servlet-class>里放置类的完全限定名(但是不要加".class"扩展名) -->
    <servlet-class>Ch1Servlet</servlet-class>
  </servlet>

  <!-- <servlet-mapping>映射URL和servlet -->
  <servlet-mapping>
    <servlet-name>Chapter1 Servlet</servlet-name>
    <!-- <url-pattern>里是客户看到(并使用)的servlet名。
      这是一个虚构的名字,并不是具体servlet的名字 -->
    <url-pattern>/Serv1</url-pattern>
  </servlet-mapping>

</web-app>
  • 还可以使用部署描述文件对Web应用的其他方面进行定制,包括安全角色,错误页面,标记库,初始配置信息等。
  • 部署描述文件(DD)提供了一种“声明”机制来定制Web应用,而无需修改源代码。

DD优点

  • 尽量少改动已经测试过的源代码。
  • 即使你手上并没有源代码,也可以对应用的功能进行调整。
  • 不用重新编译和测试任何代码,也可以让你的应用适应不同的资源。
  • 可以更容易地维护动态安全信息,如访问控制列表和安全角色。
  • 非程序员也可以修改和部署你的Web应用。

MVC

  • 模型-视图-控制器(MVC)就是把业务逻辑从servlet中抽出来,把它放在一个“模型”中,所谓模型就是一个可重用的普通Java类。模型是业务数据(如购物车的状态)和处理该数据的方法(规则)的组合。

Servlet & JSP世界汇总的MVC

  • 控制器(Servlet):从请求获得用户输入,并明确这些输入对模型有什么影响。告诉模型自行更新,并且让视图(JSP)能得到新的模型状态。
  • 模型(Java类):包含具体的业务逻辑和状态。换句话说模型知道用什么规则来得到和更新状态。系统中只有这部分与数据库通信。
  • 视图(JSP):负责表示方面。它从控制器得到模型的状态(不过不是直接得到;控制器会把模型数据放在视图能找到的一个地方)。另外视图还要获得用户输入,并交给控制器。

要点

  • 容器为Web应用提供了通信支持、声明周期管理、多线程支持、声明方式安全,以及JSP支持。
  • 容器创建一个请求对象和一个响应对象,servlet可以用这些对象得到有关请求的信息,并把信息发送给客户。
  • 典型的servlet是一个扩展了HttpServlet的类,而且覆盖了一个或多个服务方法(doGet(),doPost()),分别对应于浏览器调用的HTTP方法。
  • 部署人员可以吧servlet类映射到一个URL,这样客户可以使用这个URL来请求该servlet。部署名可以与实际的类文件名完全不同。

J2EE

  • J2EE应用服务器包括一个Web容器和一个EJB容器。Tomcat是一个Web容器,而不是一个完整的J2EE应用服务器。

MVC实战

构建一个真正的(小)Web应用

四大步骤

  1. 分析用户的视图,以及高层体系结构。
  2. 创建用于开发这个项目的开发环境。
  3. 创建用于部署这个项目的部署环境。
  4. 对Web应用的各个组件完成迭代式的开发和测试。

啤酒顾问

场景

  • 我们的Web应用是一个啤酒顾问。用户访问这个应用,先问一个问题,然后会得到一条有用的啤酒建议。

开发流程

第一步 分析用户的视图,以及高层体系结构
  • 啤酒顾问有两个页面:
    1. HTML页面,用户选择页面,生成HTTP POST请求,并把用户的颜色选择作为一个参数发送。
    2. JSP页面,基于用户的选择给出建议。
  • 体系结构
    1. 客户请求得到form.html页面。
    2. 容器找到并获得form.html页面。
    3. 容器把这个页面返回给浏览器,用户再在浏览器上回答表单上的问题。
    4. 浏览器把请求数据发送给容器。
    5. 容器根据URL查找正确的servlet,并把请求传递给这个servlet。
    6. servlet调用BeerExpert寻求帮助。
    7. 这个“专家”类返回一个回答,servlet把这个回答增加到请求对象。
    8. servlet把请求转发给JSP。
    9. JSP从请求对象得到回答。
    10. JSP为容器生成一个页面。
    11. 容器把这个页面返回给心满意足的用户。
第二步 创建用于开发这个项目的开发环境
/projects
    /web
        /beerV1
            /etc
                web.xml  -- 配置文件
            /lib  -- 在这里放第三方JAR文件
            /src  -- 所有Java代码都放在src目录下
                /com
                    /cynhard
                        /web  -- 在这里放控制组件
                            BeerSelect.java
                        /model  -- 在这里放模型组件
                            BeerExpert.java
            /classes  -- 与src对应,放置相应的.class文件
                /com
                    /cynhard
                        /web
                            BeerSelect.class
                        /model
                            BeerExpert.class
            /web  -- 静态和动态视图组件放在这里
                result.jsp
                form.html
第三步 创建用于部署这个项目的部署环境
---------------- 特定于Tomcat的部分 ----------------
/Tomcat 8.5  -- Tomcat主目录
    /webapps
        /Beer-v1  -- Web应用的名字,在Tomcat解析URL是用到。
----------------- Servlet规范的部分 ----------------
            form.html
            result.jsp
            /WEB-INF
                web.xml  -- 必须放在WEB-INF中
                /lib
                /classes
----------------- 特定于应用的部分 ------------------
                    /com  -- 这个包结构与开发环境中使用的结构完全相同
                        /cynhard
                            /web
                                BeerSelect.class
                            /model
                                BeerExpert.class
第四步 对Web应用的各个组件完成迭代式的开发和测试
  • 这一步又分为5个小步骤:
    • 4.1 构建和测试用户最初请求的HTML表单。
    • 4.2 构建控制器servlet的第一个版本,并用HTML表单测试这个控制器。
    • 4.3 为专家/模型类构建一个测试类,然后构建并测试专家/模型类本身。
    • 4.4 把servlet升级到第2版。这个版本增加了一个功能,可以调用模型类来得到啤酒建议。
    • 4.5 构建JSP,把servlet升级到第3版本(再增加一个功能,可以把表示分派到JSP完成),然后再测试整个应用。
4.1 构建和测试用户最初请求的HTML表单
  • 4.1.1 编写开始页面

    <html>
        <body>
            <h1 align="center">Beer Selection Page</h1>
            <form method="POST" action="SelectBeer.do">
                Select beer characteristics<p>
                Color:
                <!-- size表示下拉类表中可见选项的数目 -->
                <select name="color" size="1">
                    <option value="light"> light </option>
                    <option value="amber"> amber </option>
                    <option value="brown"> brown </option>
                    <option value="dark"> dark </option>
                </select>
                <br><br>
                <center>
                    <input type="SUBMIT">
                </center>
            </form>
        </body>
    </html>
  • 4.1.2 部署和测试开始页面

    1. 在开发环境中创建HTML。将上面的HTML页面保存到开发环境中的 /beerV1/web/form.html文件中。
    2. 把这个文件复制到部署环境:/webapps/Beer-v1/form.html。
    3. 在开发环境中创建DD,保存到开发环境中的 /beerV1/etc/web.xml中。

      <?xml version="1.0" encoding="ISO-8859-1"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">
      
          <servlet>
              <servlet-name>Ch3 Beer</servlet-name>
              <servlet-class>com.cynhard.web.BeerSelect</servlet-class>
          </servlet>
      
          <servlet-mapping>
              <servlet-name>Ch3 Beer</servlet-name>
              <url-pattern>/SelectBeer.do</url-pattern>
          </servlet-mapping>
      
      </web-app>
    4. 把DD文件拷贝到部署环境:/webapps/Beer-v1/WEB-INF/web.xml。

    5. 启动Tomcat。
    6. 测试页面。在浏览器输入:http://localhost:8080/Beer-v1/form.html
4.2 构建控制器servlet的第一个版本,并用HTML表单测试这个控制器
  • 4.2.1 控制器servlet的第1版

    • 第一版只要确保HTML页面能适当的调用servlet,而且servlet能正确的接收HTML参数就可以了。
    • 在开发环境的src/com/cynhard/web目录下创建BeerSelect.java,内容如下:

      package com.cynhard.web;  // 确保与前面创建的开发结构和部署结构匹配
      
      import javax.servlet.*;
      import javax.servlet.http.*;
      import java.io.*;
      
      // HttpServlet扩展了GenericSevlet,GenericServlet则实现了Servlet接口
      public class BeerSelect extends HttpServlet {
          // 我们使用doPost来处理HTTP请求,因为HTTP表单指出:method=POST
          public void doPost(HttpServletRequest request, 
                  HttpServletResponse response) throws IOException {
              response.setContentType("text/html");
              PrintWriter out = response.getWriter();
              out.println("Beer Selection Advice<br>");
              String c = request.getParameter("color");
              out.println("<br>Got beer color " + c);
          }
      }
  • 4.2.2 编译、部署和测试控制器servlet

    • 编译servlet

      javac -classpath "G:\Tomcat 8.5\lib\servlet-api.jar";classes;. -d classes src\com\cynhard\web\BeerSelect.java

      -d 选项告诉编译器,把.class文件放在适当包结构中的classes目录下。

    • 部署servlet
      将开发环境的BeerSelect.class拷贝到部署环境中: webapps/Beer-v1/WEB-INF/classes/com/cynhard/web/BeerSelect.class

    • 测试servlet

      • 重启tomcat
      • 浏览器中输入:http://localhost:8080/Beer-v1/form.html
      • 选择一种啤酒颜色,提交。
      • 如果servlet正常运行,就会在浏览器中看到结果。
4.3 为专家/模型类构建一个测试类,然后构建并测试专家/模型类本身
  • 4.3.1 模型规范
    • 包应当是 com.example.model。
    • 其目录结构应当是 /WEB-INF/classes/com/model。
  • 4.3.2 为模型构建测试类
  • 4.3.3 构建和测试模型

    package com.cynhard.model;
    import java.util.*;
    
    public class BeerExpert {
        public List getBrands(String color) {
            List<String> brands = new ArrayList<String>();
            if (color.equals("amber")) {
                brands.add("Jack Amber");
                brands.add("Red Moose");
            } else {
                brands.add("Jail Pale Ale");
                brands.add("Gout Stout");
            }
            return brands;
        }
    }

    编译 :

    javac -d classes -Xlint:unchecked src\com\cynhard\model\BeerExpert.java

    将边编译后的BeerExpert.class放到部署目录:

    \webapps\Beer-v1\WEB-INF\classes\com\cynhard\model
4.4 把servlet升级到第2版,调用模型类
  • 第2版servlet代码

    package com.cynhard.web;
    
    import com.cynhard.model.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.util.*;
    
    public class BeerSelect extends HttpServlet {
        public void doPost(HttpServletRequest request, 
                HttpServletResponse response) throws IOException {
    
            String c = request.getParameter("color");
            // 实例化BeerExpert类,调用getBrands()
            BeerExpert be = new BeerExpert();  
            List result = be.getBrands(c);
    
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            out.println("Beer Selection Advice<br>");
    
            // 打印建议
            Iterator it = result.iterator();
            while (it.hasNext()) {
                out.print("<br>try: " + it.next());
            }
        }
    }
  • 编译:

    javac -classpath "G:\Tomcat 8.5\lib\servlet-api.jar";classes;. -d classes src\com\cynhard\web\BeerSelect.java
  • 部署和测试

    • 将编译的.class文件复制到部署环境:

      \webapps\Beer-v1\WEB-INF\classes\com\cynhard\web
    • 重启tomcat。
    • 重新打开页面。
4.5 构建JSP,把servlet升级到第3版本
  • 4.5.1 新建JSP,保存在开发环境下:\beerV1\web\result.jsp

    <!-- 这是一个“页面指令” -->
    <%@ page import="java.util.*" %>
    
    <html>
    <body>
    <h1 align="center">Beer Recommendations JSP</h1>
    <p>
    
    <!-- <% %> 标记里有一些标准Java代码(这称为scriptlet) -->
    <%
        List styles = (List)request.getAttribute("styles");
        Iterator it = styles.iterator();
        while (it.hasNext()) {
            out.print("<br>try: " + it.next());
        }
    %>
    </body>
    </html>

    将其拷贝到部署目录

    \webapps\Beer-v1
  • 4.5.2 改进servelt,调用JSP

    package com.cynhard.web;
    
    import com.cynhard.model.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.util.*;
    
    public class BeerSelect extends HttpServlet {
        public void doPost(HttpServletRequest request, 
                HttpServletResponse response) 
                throws IOException, ServletException {
    
            String c = request.getParameter("color");
            // 实例化BeerExpert类,调用getBrands()
            BeerExpert be = new BeerExpert();  
            List result = be.getBrands(c);
    
            // 为请求对象增加一个属性,供JSP使用。
            request.setAttribute("styles",  result);
            // 为JSP实例化一个请求分派器。
            RequestDispatcher view = request.getRequestDispatcher("result.jsp");
            // 使用请求分派器要求容器准备好JSP,并向JSP发送请求和响应。
            view.forward(request, response);
        }
    }

    编译、部署和测试:和之前的一样,这里略过。

请求和响应

servlet的生命周期

  • 时序图

    Created with Raphaël 2.1.2 Web容器 Web容器 Servlet类 Servlet类 Servlet对象 Servlet对象 加载类 初始化servlet(构造函数运行) init() service() 处理客户请求(doGet(),doPost()) destory()
  • 声明周期中的三大重要时刻

    方法何时调用作用是否覆盖
    init()servlet实例创建后,并在
    servlet能为客户请求提供
    服务之前,容器对servlet
    调用init()。
    使你在sevlet处理客户请求
    之前有机会对其初始化。
    有可能。
    service()客户请求到来时,容器会
    开始或分配一个新线程,
    调用service()。
    查看请求类型,决定调用
    doGet()或者doPost()。
    不需要。
    doGet()和/或doPost()service()根据请求的HTTP
    方法调用doGet()或doPost()
    在这里写业务逻辑代码。至少覆盖其一。
  • service()方法总是在自己的栈中调用。容器运行多个线程来处理对一个servlet的多个请求。

init()

  • init()总是在第一个service()调用之前完成。
    • 容器启动时找到servlet类文件。
    • 容器启动时或第一个请求到来时时加载servlet类。
    • 只有初始化之后才会调用service()。
  • init()使一个普通对象变成servlet。
  • init()之前容器产生ServletConfig对象和ServletContext对象,可以在init()中访问这些对象。

ServletConfig对象

  • 每个servlet都有一个ServletConfig对象
  • 用于向servlet传递部署时信息。
  • 用于访问ServletContent。
  • 参数在部署描述文件中配置。

ServletContext对象

  • 每个Web应用有一个ServletContent对象。
  • 用于访问Web应用参数。
  • 相当于一种应用公告栏,可以在这里放置消息,应用的其他部分可以访问这些消息。
  • 用于得到服务器信息,包括容器名和容器版本,以及所支持的API的版本等。

service(),doGet(),doPost()

  • HTTP请求方法确定究竟运行doGet()还是doPost()。

GET和POST的区别

  • POST有一个体。参数显示在体里,长度不受限制。GET参数显示在请求的URL后面,有长度限制。
  • GET请求可以建立书签,POST不能。
  • GET用于得到东西。POST用来修改服务器上的某些东西。
  • 幂等:任意多次执行所产生的影响均与一次执行的影响相同。GET是幂等的,POST不是幂等的。

如何确定浏览器发送的是GET还是POST

  • 超链接是GET
  • form中method="POST" 是POST,method=”GET”或者没有method都是GET。

HttpServletRequest

  • 获取参数

    • 获取单个参数:

      String param1 = request.getParameter("param1");
      String param2 = request.getParameter("param2");
    • 如果参数是数组:

      String[] param = request.getParameterValues("param")
  • 客户的平台和浏览器信息

    String client = request.getHeader("User-Agent");
  • 与请求相关的cookie

    Cookie[] cookies = request.getCookies();
  • 与客户相关的会话(session)

    HttpSession session = request.getSession();
  • 请求的HTTP方法

    String method = request.getMethod();
  • 请求的输入流

    InputStream input = request.getInputStream();

HttpServletResponse

  • 大多数情况下,使用响应只是为了向客户发回数据。会对响应调用两个方法:setContentType()和getWriter()。在此之后,只需要完成I/O将HTML(或其他内容)写至流。不过你也可以使用响应设置其他首部、发送错误,以及增加cookie。
  • response.setContentType()指定MIME类型。内容类型是HTTP响应中必须有的一个HTTP首部。
  • 可以用PrintWriter对象或OutputStream对象输出数据,PrintWriter是对OutputStream的封装,用来写入文本数据。OutputStream用来写入二进制数据。PrintWriter通过getWriter()方法得到。OutputStream对象由getOutputStream()方法得到:

    PrintWriter writer = response.getWriter();
    OutputStream out = response.getOutputStream();
  • setHeader()设置响应头部,addHeader()增加响应头部。setHeader()会覆盖现有的值。addHeader()会增加另外一个值。

重定向
  • 通过sendRedirect()重定向。重定向交给浏览器请求重定向的URL。
  • 重定向是一个字符串,如果不以/开头,则重定向以当前目录为基路径。如果以/开头,则重定向以Web应用为基路径。

    • 不以/开头
    Created with Raphaël 2.1.2 开始 原始URL: http://www.cynhard.com/myApp/cool/bar.do 重定向:sendRedirect("foo/stuff.html") 结果: http://www.cynhard.com/myApp/cool/foo/stuff.html End
    • /开头
    Created with Raphaël 2.1.2 开始 原始URL: http://www.cynhard.com/myApp/cool/bar.do 重定向:sendRedirect("/foo/stuff.html") 结果: http://www.cynhard.com/foo/stuff.html End
  • 不能在写到响应之后再调用sendRedirect()。

请求分派
  • 请求分派交给服务器上的其他部分处理。

    RequestDispatcher view = request.getRequestDispatcher("result.jsp");
    view.forward(request, response);

复习:servlet声明周期和API

  • 容器要加载类、调用servlet的无参构造函数,并调用servlet的init()方法,从而初始化servlet。
  • init()方法在servlet一生中只调用一次,往往在servlet为客户请求提供服务器之前调用。
  • init()方法使servlet可以访问ServletConfig和ServletContent对象,servlet需要从这些对象得到有关servlet配置和web应用的信息。
  • 容器通过调用servlet的destory()方法来结束servlet的生命。
  • servlet一生的大多数时间都是为某个客户请求运行service()方法。
  • 对servlet的每个请求都在一个单独的线程中运行!任何特定servlet类都只有一个实例。
  • 你的servlet类一般都会扩展javax.servlet.http.HttpServlet,并由此继承service()方法中的一个实现,它取一个HttpServletRequest和一个HttpServletResponse作为参数。
  • HttpServlet扩展了javax.servlet.GenericServlet,这是一个抽象类,实现了大多数基本servlet方法。
  • GenericServlet实现了Servlet接口。
  • Servlet相关的类都在以下两个包中:javax.servlet或javax.servlet.http。
  • 可以覆盖init()方法,而且必须覆盖一个服务方法(doGet()、doPost()等)。

复习:HTTP和HttpServletRequest

  • HttpServlet的doGet()和doPost()方法取一个HttpServletRequest和一个HttpServletResponse作为参数。
  • service()方法根据HTTP请求的HTTP方法(GET、POST等)来确定运行doGet()还是doPost()。
  • POST请求有一个体;GET请求没有,不过GET请求可以把请求参数追加到请求URL的后面(有时成为“查询串”)。
  • GET请求本质上讲(根据HTTP规范)是幂等的。它们应当能多次运行而不会对服务器产生任何副作用。GET请求不应修改服务器上的任何东西。但是你也可以写一个非幂等的doGet()方法(不过这是很糟糕的做法)。
  • POST本质上讲不是幂等的,所以要由你来适当地设计和编写代码,如果客户错误地把一个请求发送了两次,你也能正确地加以处理。
  • 如果HTML表单没有明确地指出“method=POST”,请求就会作为一个GET请求发送,而不是POST请求。如果你的servlet中没有doGet(),这个请求就会失败。
  • 可以用getParameter(“paramname”)方法从请求得到参数。返回值总是一个String。
  • 如果对应一个给定的参数名有多个参数值,要使用getParameterValues(“paramname”)方法来返回一个String数组。
  • 从请求对象还可以得到其他东西,包括首部、cookie、会话、查询串和输入流。

复习:HttpServletResponse

  • 使用响应向客户发回数据。
  • 对响应对象(HttpServletResponse)调用的最常用的方法是setContentType()和getWriter()。
  • 要当心——很多开发人员都认为应该是getPrintWriter(),但实际上得到书写器的方法是getWriter()。
  • 利用getWriter()方法可以完成字符I/O,想流写入HTML(或其他内容)。
  • 还可以使用响应来设置首部、发送错误,以及增加cookie。
  • 在实际中,可能会使用JSP发送大多数HTML响应,但仍有可能使用一个响应流向客户发送二进制数据(如JAR文件)。
  • 要得到二进制流,需要在响应上调用getOutputStream()。
  • setContentType()方法告诉浏览器如何处理随响应到来的数据。常见的内容类型为”text/html”、”application/pdf”和”image/jpeg”。
  • 不用记住内容类型(也称为MIME类型)。
  • 可以使用addHeader()或setHeader()设置响应首部。二者区别是这个首部是否已经是相应的一部分。如果是,setHeader()会替换原来的值,而addHeader()会向现有的响应增加另一个值。如果响应中原来没有这个首部,setHeader()和addHeader()的表现完全一样。
  • 如果你不想对一个请求做出响应,可以把请求重定向到另一个URL。浏览器会负责把新请求发送到你提供的URL。
  • 要重定向一个请求,需要在响应上调用sendRedirect(aStringURL)。
  • 不能在响应已经提交之后才调用sendRedirect()!换句话说,如果已经向流中写入了东西,再想重定向则为时已晚。
  • 请求重定向与请求分派完全是两码事。请求分派在服务器端发生,而重定向在客户端进行。请求分派吧请求传递给服务器上的另一个组件(通常在同一个Web应用中)。请求重定向只是告诉浏览器去访问另一个URL。

属性和监听者

ServletConfig

  • Servlet初始化参数:

    • 在DD文件中:

          <servlet>
              <servlet-name>Ch3 Beer</servlet-name>
              <servlet-class>com.cynhard.web.BeerSelect</servlet-class>
              <init-param>
                  <param-name>myEmail</param-name>
                  <param-value>cynhard85@126.com</param-value>
              </init-param>
          </servlet>
    • 在servlet代码中:

      writer.println(getServletConfig().getInitParameter("myEmail"));
  • 在servlet初始化之前不能使用servlet初始化参数。

  • 容器初始化一个servlet时,会为这个servlet建一个唯一的ServletConfig。容器从DD“读出”servlet初始化参数交给ServletConfig,然后把ServletConfig传递给servlet的init()方法。
  • servlet初始化参数只能读一次——就是在容器初始化servlet的时候。

ServletContext

  • 上下文初始化参数

    • DD中:

      <servlet>
          <servlet-name>Ch3 Beer</servlet-name>
          <servlet-class>com.cynhard.web.BeerSelect</servlet-class>
      </servlet>
      
      <context-param>
          <param-name>myEmail</param-name>
          <param-value>cynhard85@126.com</param-value>
      </context-param>
    • Servlet中:

      out.println(getServletContext().getInitParameter("myEmail"));
  • <context-param>是针对整个应用的,因此放在所有<servlet>之外。

  • 上下文是针对整个应用的,因此所有servlet和JSP都可以访问。
  • 每个servlet有一个ServletConfig,每个Web应用有一个ServletContext。
  • 在分布式引用中,每个JVM有一个ServletConfig。
  • 要把初始化参数认为是部署时常量!可以在运行时得到这些初始化参数,但是不能设置。

ServletContextListener

  • ServletContextListener使用

    • listener 代码:

      package com.cynhard.web;
      
      import javax.servlet.*;
      
      public class MyServletContextListener implements ServletContextListener {
          public void contextInitialized(ServletContextEvent event) {
              ServletContext sc = event.getServletContext();
              // ...
          }
          public void contextDestroyed(ServletContextEvent event) {
              // ...
          }
      }
    • DD:

      <listener>
          <listener-class>
              com.cynhard.MyServletContextListener
          </listener-class>
      </listener>
  • <listener>不放在<servlet>元素内部。

8个监听者

场景监听者事件类型
你想知道一个Web应用
上下文中是否增加、删
除或替换了一个属性
javax.servlet.ServletContextAttributeListenerServletContextAttributeEvent
你想知道有多少个并发
用户。也就是说,你想
跟中活动的会话。
javax.servlet.http.HttpSessionListenerHttpSessionEvent
每次请求到来时你都想
知道,以便建立日志记
录。
javax.servlet.ServletRequestListenerServeltRequestEvent
增加、删除或替换一个
请求属性时你希望能够
知道。
javax.servlet.ServletRequestAttributeListenerServletRequestAttributeEvent
你有一个属性类(这个
类表示的对象将放在一
个属性中),而且你希
望这个类型的对象在绑
定到一个会话或从会话
删除时得到通知。
javax.servlet.http.HttpSessionBindingListenerHttpSessionBindingEvent
增加、删除或替换一个
会话属性时你希望能够
知道。
javax.servlet.http.HttpSessionAttributeListenerHttpSessionBindingEvent
你想知道是否创建或撤
销了一个上下文
javax.servlet.ServletContextListenerServletContextEvent
你有一个属性类,而且希
望这个类型的对象在其
绑定的会话迁移到另一
个JVM时得到通知。
javax.servlet.http.HttpSessionActivationListenerHttpSessionEvent

属性

  • 属性就像是钉到公告栏上的一个对象。有人在公告栏上贴公告,以便其他人看到。
  • 属性和参数

    2属性参数
    类型应用/上下文
    请求
    会话
    应用/上下文初始化参数
    请求参数
    Servlet初始化参数
    设置方法setAttribute(String name, Object value)不能设置应用和Servlet初始化参数。它们都在DD中设置。
    返回类型ObjectString
    获取方法getAttribute(String name)getInitParameter(String name)
  • 属性的三个作用域:上下文、请求和会话

  • 属性API

    Object getAttribute(String name)
    void setAttribute(String name, Object value)
    void removeAttribute(String name)
    Enumeration getAttributeNames()
  • 上下文作用域不是线程安全的。多个servlet可能有多个线程。
  • 不能通过同步服务方法的方式保护上下文属性。而是对上下文加锁。

    // 错误!上下文对象仍可以通过其他servlet访问
    public synchronized void doGet(...)
    // 正确,对上下文加锁
    synchronized(getServeltContext()) {
        //...
    }
  • 会话属性同样不是线程安全的,需要保护:
HttpSession session = request.getSession();
synchronized(session) {
    // ...
}
  • 可以实现SingleThreadModel来保证servlet一次只得到一个请求。(SingleThreadModel已废弃)
  • 只有请求属性和局部变量是线程安全的。

RequestdDispatcher

// 从ServletRequest得到RequestDispatcher
// 参数如果以/开头,则从Web应用的根开始找
// 如果不是以/开头,则从当前位置(请求发生的位置)开始找
RequestDispatcher view = request.getRequestDispatcher("result.jsp");

// 从ServletContext得到RequestDispatcher
// 路径必须以/开头,只能从Web应用的根开始找
ReqeustDispatcher view = getServletContext().getRequestDispaatcher("/result.jsp");

// 在RequestDispatcher上调用forward()分派请求
view.forward(request, response);

会话管理

  • HttpSession对象可以保存跨同一个客户多个请求的会话状态。换句话说,与一个特定客户的整个会话期间,HttpSession会持久存储。对于会话期间客户做的所有请求,从中得到的所有信息都可以用HttpSession对象保存。

如何实现会话

  • 对客户的第一个请求,容器会生成一个唯一的会话ID,并通过响应把它返回给客户。客户再在以后的每一个请求中发回这个会话ID。容器看到ID后,就会找到匹配的会话,并把这个会话与请求关联。
  • 容器必须以某种方式把会话ID作为响应的一部分交给客户,而客户必须把会话ID作为请求的一部分返回。最简单而且最常用的方式是通过cookie交换这个会话ID信息。
  • 容器几乎会做cookie的所有工作。

获取会话

  • request.getSession()获取Session。
  • session.isNew()返回是否新建的Session。
  • request.getSession(fase)获取已有的会话。如果得到返回HtteSession,否则返回null。

如果不能使用cookie,如何完成会话?

  • 有些客户会进禁止cookie,因此无法用cookie传送SessionID。会话的isNew()方法总是返回true。
  • 如果客户不接受cookie,可以把URL重写作为一条后路。URL重写能够取得置于cookie中的会话ID,并把会话ID附加到访问应用的各个URL的最后。假设有一个Web页面,其中每个链接都有一些额外的信息(会话ID)附加到URL的组后。当用户点击这种“enhanced”(改进)链接时,到达容器的请求会在会后携带着这个额外信息,容器会取下请求URL中这个额外部分,并用它查找匹配的会话。
  • 如果不能用cookie,而且只有告诉响应要对URL编码,URL重写才能奏效。
  • 容器如何判断cookie是否工作?当容器看到一个getSession()调用时,而且没有从客户的请求得到会话ID,容器就知道它必须尝试与客户建立一个新的会话。此时,容器并不知道cookie是否工作,所以向客户返回第一个响应时,会同时尝试cookie和URL重写这两种做法。如果客户接受cookie,则下一个请求时会带着ID cookie,容器找到这个ID cookie,并忽略URL重写。如果找不到这个ID cookie,则容器开启URL重写。
  • URL重写API:

    response.encodeURL("/BeerTest.do")
    response.encodeRedirectURL("/BeerTest.do")
  • 不能对静态页面完成URL重写。因此必须在运行时动态生成HTML。

  • 要使用URL重写,页面必须是动态生成的。可以用JSP来完成URL重写。<c:URL>标签可以很容易的完成这个工作。
  • URL重写以开发商特定的方式处理。我们无需知道。
  • URL重写是自动的,但是只有当你对URL完成了编码时它才奏效。必须通过响应对象的一个方法(可以是encodeURL()或encodeRedirectURL())来运行所有URL,其他的所有事情都由容器来完成。
  • URL编码有响应处理。
  • 要点

    • 在写至响应的HTML中,URL重写把会话ID增加到其中所有URL的最后。
    • 会话ID作为请求URL最后的“额外”信息再通过请求返回。
    • 如果客户不接受cookie,URL重写会自动发生,但是必须显式地对所有URL编码。
    • 要对一个URL编码,需要调用response.encodeURL(aString)。

      out.println("<a href='"
          + response.encodeURL("/BeerTest.do")
          + "'>click me</a>");
    • 没有办法对静态页面完成自动的URL重写,所以,如果你依赖于会话,就必须使用动态生成的页面。

关键的HttpSession方法

它做什么你用它做什么
getCreationTime()返回第一次创建会话的时间。得出这个会话有多“老”。你可能想把某些会话的寿命限制为一个固定的时间。例如,你可能会说“一旦登陆,就必须在10分钟之内完成这个表单……”
getLastAccessedTime()返回容器最后一次得到包含这个会话ID的请求后过去了多长时间(毫秒数)得出客户最后一次访问这个会话是什么时候。可以用这个方法来确定客户是否已经离开很长时间,这样就可以向客户发出一封email,询问他们是否还回来。或者可以调用invalidate()结束会话。
setMaxInactiveInterval()指定对于这个会话客户请求的最大间隔时间(秒数)。如果已经过去了指定时间,而客户未对这个会话做出任何请求,就会导致会话被撤销。可以用这个方法减少服务器中无用的会话。
getMaxInactiveInterval()返回对于这个会话客户请求的最大间隔时间(秒数)。得出这个会话可以保持多长时间不活动但仍“存活”。可以使用这个方法来判断一个不活动的客户在会话撤销之前还有多长的“寿命”。
invalidate()结束会话。当前存储在这个会话中的所有会话属性也会解除绑定。如果客户已经不活动,或者你知道会话已经结束,可以用这个方法杀死(撤销)会话。会话实例本身可能由容器回收,但是这一点我们并不关心。置无效意味着会话ID不再存在,而且属性会从会话对象删除。

设置会话超时

  • 在DD中配置,以分钟为单位
<session-config>
    <session-timeout>15</session-timeout>
</session-config>
  • 设置某一个会话的超时
session.setMaxInactiveInterval(20 * 60);
  • 可以使用cookie在服务器和客户之间交换名/值String对。服务器把cookie发送给客户,客户再在以后的每个请求中发回这个cookie。客户的浏览器退出时,会话cookie就会消失,但是你可以告诉cookie在客户端上待得更久一些,甚至在浏览器关闭之后还持久保存。

利用Servlet API使用Cookie

  • 创建一个新Cookie

    Cookie cookie = new Cookie("username", name);
  • 设置cookie在客户端上存活多久

    // 以秒为单位,如果-1,则浏览器退出时cookie消失。
    cookie.setMaxAge(30 * 60);
  • 把cookie发送到客户

    response.addCookie(cookie);
  • 从客户请求得到cookie(或多个cookie)

    Cookie[] cookies = request.getCookies();

使用JSP

  • JSP最终会变成一个完整的servlet在Web应用中运行。它与其他的servlet非常相似,只不过这个servlet类由容器为你编写。转换和编译只发生一次。
  • JSP中可以使用scriptlet放入常规的Java代码,所谓scriptlet,就是放在<%…%>标记中的Java代码。
  • 指令有3种:page、include和taglib。
  • Java代码放在带百分号的尖括号中间:<%和%>。而指令会为元素开始记号再增加一个字符:@

Scriptlet

<% out.println(""); %>

使用page指令导入包

  • 导入单个包,指令最后没有分号

    <%@ page import="foo.*" %>
  • 导入多个包,逗号分隔

    <%@ page import="foo.*,java.util.*" %>

表达式

  • <%= %>
  • 表达式会成为out.print()的参数。
  • 表达式没有分号。
<%= Counter.getCount() %>

声明

  • <%! %>
  • JSP声明总是在类中定义,而且置于服务方法(或任何其他方法)之外。
  • 变量和方法都可以声明。
  • 需要分号结束。

    <%! int count = 0; %>

隐式对象

API隐式对象
JspWriterout
HttpServeltRequestrequest
HttpServletResponseresponse
HttpSessionsession
ServletContextapplication
ServletConfigconfig
Throwableexception
PageContextpageContext
Objectpage

注释

<!-- HTML注释 -->
<%-- JSP注释 --%>

初始化JSP

  • DD

    <servlet>
        <servlet-name>MyTestInit</servlet-name>
        <jsp-file>/TestInit.jsp</jsp-file>
        <init-param>
            <param-name>email</param-name>
            <param-value>cynhard85@126.com</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyTestInit</servlet-name>
        <url-parttern>/TestInit.jsp</url-parttern>
    </servlet-mapping>
  • 覆盖jspInit()

<%!
    public void jspInit() {
        ServletConfig sConfig = getServletConfig();
        String emailAddr = sConfig.getInitParameter("email");
        ServletContext ctx = getServletContext();
        ctx.setAttribute("mail", emailAddr);
    }
%>

JSP中的属性

servlet中JSP中
应用getServletContext().setAttribute(“foo”, barObj);application.setAttribute(“foo”, barObj);
请求request.setAttribute(“foo”, barObj);request.setAttribute(“foo”, barObj);
会话request.getSession().setAttribute(“foo”, barObj);session.setAttribute(“foo”, barObj);
页面不适用!pageContext.setAttribute(“foo”, barObj);

使用PageContext得到属性

<%-- 设置一个页面作用域属性 --%>
<% Float one = new Float(42.5); %>
<% pageContext.setAttribute("foo", one); %>

<%-- 获得一个页面作用域属性 --%>
<%= pageContext.getAttribute("foo"); %>

<%-- 使用pageContext设置一个会话作用域属性 --%>
<% Float two = new Float(22.4); %>
<% pageContext.setAttribute("foo", two, PageContext.SESSION_SCOPE); %>

<%-- 使用pageContext获得一个会话作用域属性 --%>
<%= pageContext.getAttribute("foo", PageContext.SESSION_SCOPE); %>
<%-- 等价于: --%>
<%= session.getAttribute("foo"); %>

<%-- 使用pageContext获得一个应用作用域属性 --%>
<%= pageContext.getAttribute("mail", PageContext.APPLICATION_SCOPE); %>
<%-- 等价于: --%>
<%= application.getAttribute("mail"); %>

<%-- 使用pageContext,即使不知道作用域也可以查找一个属性 --%>
<%-- 查找顺序:page->request->session->application --%>
<%= pageContext.findAttribute("foo"); %>

指令

page指令

<%@ page import="foo.*" session="false" %>

定义页面的特定的属性,以及这个页面是否要有隐式的会话对象。

属性
属性意义
import定义Java import语句
isThreadSafe定义生成的servlet是否需要实现SingleThreadModel(bad idea)。
contentType定义JSP响应的MIME类型。
isELIgnored定义转换这个页面时是否忽略EL表达式。
isErrorPage定义当前页面是否是另一个JSP的错误页面。
errorPage定义一个资源的URL,如果有未捕获到的Throwable,就会发送到这个资源。
language定义scriptlet、表达式和声明中使用的脚本语言。
extendsJSP会变成一个servlet类,这个属性则定义了这个类的超类。
session定义页面是否有一个隐式的session对象。默认值为true。
buffer定义隐式out对象(JspWriter的引用)如何处理缓存。
autoFlush定义缓存的输出是否自动刷新输出。默认值是true。
info定义放到转换后页面中的串,这样就能使用所生成servlet继承的getServletInfo()方法来得到这个信息。
pageEncoding定义JSP的字符编码。默认为“ISO-8859-1”

taglib指令

<%@ taglib tagdir="/WEB-INF/tags/cool" prefix="cool" %>

定义JSP可以使用的标记库。

include指令

<%@ include file="wickedHeader.html" %>

定义在转换时增加到当前页面的文本和代码。

EL表达式

  • 用EL表达式替代scriptlet。
    • 不应该要求Web页面设计人员必须懂Java。
    • JSP中的Java代码很难修改和维护。
  • EL和Java比较:

    • EL:${application.mail}
    • Java:<%= application.getAttribute("mail") %>
  • 让JSP禁用脚本元素(scriptlet、Java表达式或声明):

    <jsp-config>
        <sp-property-group>
            <url-parttern>*.jsp</url-parttern>
            <scripting-invalid>true</scripting-invalid>
        </sp-property-group>
    </jsp-config>
  • 忽略EL(默认是启用的):

    • 通过DD:

      <jsp-config>
          <sp-property-group>
              <url-parttern>*.jsp</url-parttern>
              <el-ignored>true</el-ignored>
          </sp-property-group>
      </jsp-config>
    • 通过page指令:

      <%@ page isELIgnored="true" %>

动作

  • 标准动作:
<jsp:include page="wickedFooter.jsp" />
  • 其他动作:
<c:set var="rate" value="32" />

无脚本的JSP

使用bean

  • 如果一个类Person有两个方法getProperty()和setProperty(),根据JavaBean规范,就称Person有property的属性。
  • Bean应用:

    • servlet中:

      Person person = new Person();
      person.setName("Gopher");
      request.setAttribute("person", person);
    • jsp中:

      <jsp:useBean id="person" class="com.cynhard.model.Person" scope="request" />
      <jsp:getProperty name="person" property="name" />

      分解:
      <jsp:useBean id="person" class="com.cynhard.model.Person" scope="request" />

      • <jsp:useBean>标识标准动作
      • id="person"声明bean对象的标识符。这对应于以下servlet代码中所用的名:request.setAttribute("person", p);
      • class="com.cynhard.model.Person"声明bean对象的类类型。
      • scope="request"标识这个bean对象的属性作用域。

      <jsp:getProperty name="person" property="name" />

      • jsp:getProperty标识标准动作
      • name="person"标识具体的bean对象。这与<jsp:useBean>标记的“id”值匹配。
      • property="name"标识属性中的性质名(也就是bean类中获取方法和设置方法对应的性质)。
  • <jsp:useBean>如果找不到属性对象,则会创建一个。

  • 可以用<jsp:setProperty>设置属性。

    <jsp:setProperty name="person" property="name" value="Cynhard" />
  • 利用<jsp:useBean>体,可以有条件地运行代码,只有找不到bean属性,而且创建了一个新bean是才会运行体中的代码。

    <!-- 只有request没有person属性时,才会设置person的name。 -->
    <jsp:useBean id="person" class="com.cynhard.model.Person" scope="request">
        <jsp:setProperty name="person" property="name" value="Cynhard" />
    </jsp:useBean>
  • Bean标记会自动转换基本类型的性质

Bean法则

  • 必须提供public无参构造函数。
  • 必须按照命名约定来命名公共的获取和设置方法:getProperty()、isProperty()、setProperty()
  • 设置方法的参数类型和获取方法的返回类型必须一致。
  • 性质名和类型是有获取方法和设置方法得出,而不是得自于类中的一个成员。
  • 结合JSP使用时,性质类型必须是String,或者是一个基本类型。

<jsp:useBean>增加一个type属性

  • class为对象类型。type为引用类型。

    <jsp:useBean id="person" type="foo.Person" class="foo.Employee" scope="page">
  • type可以是class类型、抽象类型或者是一个接口,只要能用作为bean对象class类型的声明引用类型,都可以指定为type。

  • 如果使用了type,但没有class,bean必须已经存在。如果使用了class(有或没有type),class不能是抽象类,而且必须有一个无参数的公共构造函数。
  • type是你声明的类型(可以使抽象类),class是你要实例化的类(必须是具体类):type x = new class()

scope属性默认为“page”

<jsp:useBean id="person" class="foo.Employee" scope="page"/>

等价于:

<jsp:useBean id="person" class="foo.Employee"/>

param属性

  • 利用param属性,可以把bean的性质值设置为一个请求参数的值。只需指定请求参数!

    • TestBean.jsp:

      <jsp:useBean id="person" type="foo.Person" clas="foo.Employee">
          <jsp:setProperty name="person" property="name" param="userName"/>
      </jsp:useBean>
    • 直接获取userName的值:

      <form acton="TestBean.jsp">
          name: <input type="text" name="userName"/>
          ...
      </form>
  • 如果请求参数名与bean性质名匹配,就不需要在<jsp:setProperty>标记中为该性质指定值。

    • TestBean.jsp,不再需要param。

      <jsp:useBean id="person" type="foo.Person" clas="foo.Employee">
          <jsp:setProperty name="person" property="name"/>
      </jsp:useBean>
    • 直接获取userName的值:

      <form acton="TestBean.jsp">
          name: <input type="text" name="name"/>
          ...
      </form>
  • 如果所有请求参数名都与bean性质名匹配,则<jsp:setProperty>中可以用property=*来获取所有请求参数的值。

    <jsp:setProperty name="person" property="*"/>

EL表达式

  • 使用EL,打印嵌套性质变得非常容易……换句话说,可以很轻松地打印性质的性质。

    <!-- 访问person的dog属性的name属性 -->
    ${person.dog.name}
  • EL表达式总是放在大括号里,而且前面有一个美元符前缀。

  • 表达式中第一个命名变量可以是一个隐式对象,也可以是一个属性。

    • EL隐式对象:pageScope requestScope sessionScope applicationScope param paramValues header headerValues cookie initParam pageContext。在所有隐式对象中,只有pageContext不是Map。这是pageContext对象的实际引用。
    • 属性:页面作用域中的属性、请求作用域中的属性、会话作用域中的属性、应用作用域中的属性。
  • 使用点号操作符访问性质和映射值:

    • 点号左边的变量要么是一个Map(有键),要么是一个bean(有性质)。不论变量是一个隐式对象还是一个属性,都是如此。
    • pageContext隐式对象是一个bean,它有获取方法,所有其他隐式对象都是Map。
    • 如果对象是一个bean,但是指定的性质不存在,会抛出一个异常。

使用方括号

  • []就像是更好的点号:

    ${person["name"]}
  • []左边可以是List、数组、Map、Bean。[]里面可以是键、索引、属性名。

  • 数组和List中的String索引会强制转换为int。如果中括号左边是一个数组或List,而且索引是一个String直接量,那么这个索引会强制转换为int。

点号和方括号

  • 对于bean和Map,这两个操作符都可以用。
  • 如果不是String直接量,就会计算。

    ${musicMap[Ambient]}  // 查找一个名为"Ambient"的属性。
    ${musicMap["Ambient"]}
  • 方括号内可以使用嵌套表达式

    ${musicMap[MusicType[0]]}
  • 点号后面不能接索引:${foo.1}是不行的。如果不能用作为Java代码中的变量名,就不能把它放在点号后面。

EL中的请求参数

  • HTML中:

    <form action="TestBean.jsp">
        Name: <input type="text" name="name">
        ID#: <input type="text" name="empID">
        First food: <input type="text" name="food">
        Second food: <input type="text" name="food">
        <input type="submit">
    </form>
  • JSP中:

    Request param name is: ${param.name}<br>
    Request param empID is: ${param.empID}<br>
    Request param food is: ${param.food}<br> <!-- 得到food中的第一个值 -->
    First food request param: ${paramValues.food[0]}<br>
    Second food request param: ${paramValues.food[1]}<br>
    Request param name: ${paramValues.name[0]}

EL获取更多信息

  • 得到“host”首部:

    ${header["host"]}
    ${header.host}
  • 使用requestScope会得到请求属性,而不是request性质。要得到request性质,需要通过pageContext。

    ${pageContext.request.method}

得到Cookie和初始化参数

  • 打印“useName”Cookie的值:

    • 脚本:

      <%
          Cookie[] cookies = request.getCookies();
          for (int i = 0; i < cookies.length; i++) {
              if ((cookies[i].getName()).equals("userName")) {
                  out.println(cookies[i].getValue());
              }
          }
      %>
    • EL:

    ${cookie.userName.value}
  • 打印一个上下文初始化参数:

    • DD:

      <context-param>
          <param-name>mainEmail</param-name>
          <parma-value>cynhard85@126.com</param-value>
      </context-param>
    • 脚本:

      <%= application.getInitParameter("mainEmail") %>
    • EL:

      ${initParam.mainEmail}

EL函数

  1. 编写有一个公共静态方法的Java类。

    • 这只是一个普通的Java类。这个方法必须是公共的,而且是一个静态方法。
    • 放在WEB-INF/classes中

      package foo;
      public class DiceRoller {
          public static int rollDice() {
              return (int)((Math.random() * 6) +1);
          }
      }
  2. 编写一个标记库描述文件(TLD)。

    • 对于一个EL函数,TLD提供了定义函数的Java类与调用函数的JSP之间的一个映射。这样一来,函数名和具体的方法名可以使不同的。
    • 它必须放在WEB-INF目录或者某个子目录中。
    • 确保JSP中taglib指令包含的uri属性与TLD中的<uri>元素匹配。

      <?xml version="1.0" encoding="ISO-8859-1" ?>
      <!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
      <taglib>
        <tlib-version>1.2</tlib-version>
        <uri>DiceFunctions</uri>
        <function>
          <name>rollIt</name>
          <function-class>foo.DiceRoller</function-class>
          <function-signature>int rollDice()</function-signature>
        </function>
      </taglib>
  3. 在JSP中放一个taglib指令。

    • taglib指令告诉容器,“我想使用这个TLD,另外,在JSP中,使用这个TLD中的一个函数时,我想用这个名字作为前缀……”

      <%@ taglib prefix="mine" uri="DiceFunctions"%>
      <html><body>
      ${mine:rollIt()}
      </body></html>
  4. 使用EL调用函数。

    • ${prefix:name()}

EL操作符

  • 算术操作符
    加法 +
    减法 -
    乘法 *
    除法 /和div
    取模 %和mod

  • 逻辑操作符
    与 &&和and
    或 ||和or
    非 !和not

关系操作符
等于 == 和eq
不等于 !=和ne
小于 <和lt
大于>和gt
小于等于 <=和le
大于等于 >=和ge

EL能妥善地处理null值

  • EL能友好地处理null。它能处理unknown或null值,即使找不到表达式中指定名的属性/性质/键,也会显示页面。
  • 在算术表达式中,EL把null值看作是“0”。
  • 在逻辑表达式中,EL把null值看作是“false”。

EL复习

  • EL表达式总是用大括号括起,而且前面有一个美元符号($)前缀:${expression}
  • 表达式中第一个命名变量要么是一个隐式对象,要么是某个作用域(页面作用域、请求作用域、会话作用域或应用作用域)中的一个属性。
  • 点号操作符允许你使用一个Map键或一个bean性质名来访问值,例如,使用${foo.bar}可以得到bar的值,在此bar是Map foo的Map键名,或者bar是bean foo的一个性质。放在点号操作符右边的东西必须遵循Java的标识符命名规则!(换句话说,必须以一个字母,下划线或美元符号开头,第一个字符后面可以有数字,但是不能邮其他字符。)
  • 点号右边只能放合法的Java标识符。例如:${foo.1}就不允许。
  • []操作符比点号功能更强大,因为利用[]可以访问数组和List,可以把包含命名变量的表达式放在中括号里,而且可以做任意层次的嵌套,只要你受得了。
  • 例如,如果musicList是一个ArrayList,可以用${musicList[0]}${musicList["0"]}来访问列表中的第一个值。EL并不关心列表索引加不加引号。
  • 如果中括号里的内容没有用引号引起来,容器就会进行计算。如果确实放在引号里,而且不是一个数组或List的索引,容器就会把它看作是性质或键的直接量名。
  • 除了一个EL隐式对象(PageContext)外,其他EL隐式对象都是Map。从这些Map隐式对象可以得到任意4个作用域中的属性、请求参数值、首部值、cookie值和上下文初始化参数。非映射的隐式对象pageContext,它是PageContext对象的一个引用。
  • 不要把隐式EL作用域对象(属性的Map)与属性所绑定的对象混为一谈。换句话说,不要把requestScope隐式对象与具体的JSP隐式对象request混淆。访问request对象只有一条路,就是通过pageContext隐式对象来访问。(不过,想从请求得到的一些东西通过其他EL隐式对象也可以得到,包括param/paramValues,header/headerValues和cookie)
  • EL函数允许你调用一个普通Java类中的公共静态方法。函数名不一定与具体的方法名匹配!例如,${foo:rollIt()}并不意味着包含函数的类中肯定有一个名为rollIt()的方法。
  • 使用一个TLD(标记库描述文件)将函数名(例如rollIt())映射到一个具体的静态方法。使用<function>元素声明一个函数,包括函数的<name>(rollIt()),完全限定类<function-class>,以及<function-signature>,其中包括返回类型以及方法名和参数表。
  • 要在JSP中使用函数,必须使用taglib指令声明一个命名空间。在taglib指令中放一个prefix属性,告诉容器你要调用的函数在哪个TLD里能找到。例如:<%@ taglib prefix="mine" uri="/WEB-INF/foo.tld"%>

可重用的模板组件

  • include指令:<%@ include file="Header.jsp"%>
  • <jsp:include>标准动作:<jsp:include page="Header.jsp">
  • include指令在转换时发生。<jsp:include>标准动作在运行时发生。
  • include指令在转换时插入“Header.jsp”的源代码。而<jsp:include>标准动作在运行时插入“Header.jsp”的相应。
  • 采用include指令,被包含页面的源代码将成为有include指令的“外围”页面的一部分。
  • include指令和<jsp:include>是位置敏感的。
  • 不要把开始和结束HTML和Body标记放在可重用部件中。设计和编写布局模板部件时(如页眉、导航条等),要假设他们会包含在其他页面中。

使用<jsp:param>定制包含的内容

包含的页面:

<jsp:include page="Header.jsp">
    <jsp:param name="subTitle" value="Hello"/>
</jsp:include>

被包含的页面:

<strong>${param.subTitle}</strong>

<jsp:forward>标准动作

<% if (request.getParameter("useName") == null) { %>
    <jsp:forward page="HandleIt.jsp"/>
<% } %>

如果发生了转发,转发前些的所有内容都不会出现。

JSTL

<c:if test="${empty param.userName}">
    <jsp:forward page="HandleIt.jsp"/>
</c:if>

Bean相关标准动作复习

  • <jsp:useBean>标准动作会定义一个变量,它可能是一个现有bean属性的引用,如果还不存在这样一个bean,则会创建一个新的bean,这个变量就是新bean的引用。
  • <jsp:useBean>必须有一个“id”属性,这个属性声明了JSP中引用bean时所用的变量名。
  • 如果<jsp:useBean>中没有“scope”属性,作用域默认为页面(page)作用域。
  • “class”属性是可选的,它声明了创建一个新bean时使用的类类型。这个类型必须是公共的、非抽象的,而且有一个公共的无参数构造函数。
  • 如果在<jsp:useBean>中放了一个“type”属性,bean必须能强制转换成这种类型。
  • <jsp:useBean>标记可以有一个体,体中的内容会有条件地运行,只有当创建一个新的bean作为<jsp:useBean>的结果时,才会运行体中的内容[这说明,指定(或默认)作用域中不存在有该“id”的bean]。
  • <jsp:useBean>体的主要作用是使用<jsp:setProperty>设置新bean的性质。
  • <jsp:setProperty>必须有一个name属性(它要与<jsp:useBean>的“id”匹配),还要有一个“property”属性。“property”属性必须是一个具体的性质名,或者是通配符“*”。
  • 如果没有包含“value”属性,只有当一个请求参数的名与性质名匹配时,容器才会设置性质值。如果“property”属性使用通配符(*),容器会为所有与某个请求参数名匹配的性质设置值。(其他性质不受影响)
  • 如果请求参数名与性质名不同,但是你想把性质的值设置为请求参数值,可以在<jsp:setProperty>标记中使用“param”属性。
  • <jsp:setProperty>动作使用自省将“性质”匹配到一个JavaBean设置方法。如果性质是“*”,JSP将迭代处理所有请求参数来设置JavaBean性质。
  • 性质值可以是String或基本类型,<jsp:setProperty>标准动作会自动完成转换。

包含复习

  • 通过使用两种包含机制之一(include指令或<jsp:include>标准动作),可以利用可重用的组件创建页面。
  • include指令在转换时完成包含,只发生一次。所以如果包含的内容一经部署后不太可能改变,使用include指令就很合适。
  • include指令实际上只是复制被包含文件中的所有内容,把它粘贴到有include指令的页面中。容器把所有被包含文件的内容合并起来,只编译一个文件来生成servlet。在运行时,有include指令的页面将成为一个“大”页面,就像是你自己把所有源代码键入到一个文件中一样。
  • <jsp:incldue>标准动作在运行时把被包含页面的响应包含到原页面中。所以如果包含的内容在部署之后可能更新,include标准动作就很适用,此时不适用include指令。
  • 这两种机制都能包含静态HTML页面,也可包含动态元素(例如,有EL表达式的JSP代码)。
  • include指令是唯一一个对位置敏感的指令,所包含的内容会插入到页面中include指令所在的位置。
  • include指令和include标准动作的属性命名不一致,指令使用“file”属性,而标准动作使用“page”属性。
  • 在你的可重用组建中,一定要去掉开始和结束标记。否则,生成的输出会有嵌套的开始和结束标记,对于这种嵌套的开始和结束标记,并非所有浏览器都能处理。设计和构造可重用部件时,要知道它们会包含/插入到别的页面中。
  • 可以在<jsp:include>的体中使用<jsp:param>标准动作设置(或替换)请求参数,用来定制所包含的文件。
  • <jsp:param>也可以用在<jsp:forward>标记的体中。
  • <jsp:param>只能放在<jsp:include><jsp:forward>标准动作中。
  • 如果<jsp:param>中使用的参数名已经有一个值(作为请求参数),新值会覆盖原来的值。否则,就会向请求增加一个新的请求参数。
  • 对被包含资源有一些限制:它不能改变相应状态码或设置首部。
  • <jsp:forward>标准动作可以把请求转发到同一个Web应用中的另一个资源(就像使用RequestDispatcher一样)。
  • 发生转发时,会首先清空响应缓冲区!请求转发到的目标资源会先清空输出。所以转发前写至相应的所有内容都会清掉。
  • 如果在转发之前先提交响应(例如,通过调用out.flush()),会把刷新输出的内容发送给客户,但是仅此而已。不会发生转发,原页面的余下部分不会得到处理。

JSTL

<c:out>

  • <c:out>用于输出,可以通过escapeXml指定是否转义XML字符,默认值为true

    <c:out value='${pageContext.currentTip}' escapeXml='true'/>
  • <c:out>可以指定默认值:

    <c:out value='${user}' default='guest'/>

<c:forEach>

  • <c:forEach>用于循环:

    <c:forEach var="movie" items="${movieList}">
        <tr><td>${movie}</td></tr>
    </c:forEach>
  • <c:forEach>可以嵌套

<c:if>

<c:if test="${useType eq 'member'}">
    <jsp:include page="inputComments.jsp"/>
</c:if>

<c:choose><c:when><c:otherwise>

<c:choose>
    <c:when test="${user} == 'admin'">
        ...
    </c:when>
    <c:when test="${user} == 'guest'">
        ...
    </c:when>
    <c:other>
        ...
    </c:other>
</c:choose>

<c:set>

  • “var”用于设置属性。可以有体也可以没体。

    <c:set var="userLevel" scope="session" value="Cowboy"/>
    
    <c:set var="userLevel" scope="session">
        Cowboy
    </c:set>
    • scope是可选的。
  • “target”用于设置bean性质或Map值。可以有体也可以没体。

    <c:set target="${PetMap}" property="dogName" value="Clover"/>
    
    <c:set target="${person}" property="name">
        ${foo.name}
    </c:set>
  • <c:set>中不能同时有“var”和“target”属性。

  • “scope”是可选的,如果没有这个属性,则默认为页面(page)作用域。
  • 如果“value”为null,“var”指定的属性将被删除!
  • 如果“var”指定的属性不存在,则会创建一个属性,但仅当“value”不为null时才会创建新属性。
  • 如果“target”表达式为null,容器会抛出一个异常。
  • “target”中要放入一个能解析为实际对象的表达式。如果放入一个String直接量(表示bean或Map的“id”名),这是不行的。换句话说,“target”并非用来放bean或Map的属性名,而是用于指定具体的属性对象。
  • 如果“target”表达式不是一个Map或bean,容器会抛出一个异常。
  • 如果“target”表达式是一个bean,但是这个bean没有与“property”匹配的性质,容器就会抛出一个异常。记住EL表达式${bean.notAProperty}也会抛出一个异常。

<c:remove>

  • 删除属性:

    <c:remove var="userStatus" scope="request">

<c:import><c:param>

  • 包含内容

    <c:import url="http://www.cynhard.com/index.html"/>
  • 可以到包含应用之外的内容

  • <c:param>定制包含的内容
<c:import url="Header.jsp">
    <c:param name="subTitle" value="We are good"/>
</c:import>

<c:url>

  • 在需要时重写URL

    <a href="<c:url value='inputComments.jsp'/>">Click here</a>
  • URL需要编码时,可以在体内写参数

    <c:url value="/inputComments.jsp" var="inputURL">
        <c:param name="firstName" value="${first}"/>
        <c:param name="lastName" value="${last}"/>
    </c:url>

错误页面

通过page指令建立错误页面

  • 错误页面(errorPage.jsp):

    <%@ page isErrorPage="true"%>
  • 处理页面:

    <%@ page errorPage="errorPage.jsp" %>

DD中配置错误页面

  • 通过异常配置错误页面

    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/errorPage.jsp</location>
    </error-page>
  • 通过状态码配置错误页面

    <error-page>
        <error-code>404</error-code>
        <location>/notFoundError.jsp</location>
    </error-page>

获取错误对象

<%@ page isErrorPage="true"%>
...
${pageContext.exception}

<c:catch>捕获异常

  • 捕获并吞掉异常

    <c:catch>
        <% int x = 10/0; %>
    </c:catch>
  • 获取异常对象

<c:catch var="myException">
    <% int x = 10/0; %>
</c:catch>
${myException.message}

定制标记库

TLD文件解析

  • TLD文件

    <?xml version="1.0" encoding="ISO-8859-1"?>
    
    <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
    
    <taglib>
        <tlibversion>1.0</tlibversion>
        <jspversion>1.1</jspversion>
        <shortname>Tag Library for Openfire 2.0</shortname>
        <uri>http://www.igniterealtime.org/</uri>
        <info>Tab Library for Openfire Admin Console</info>
        <tag>
            <description>some thing</description>
            <name>tabs</name>
            <tagclass>org.jivesoftware.admin.TabsTag</tagclass>
            <bodycontent>JSP</bodycontent>
            <info />
            <attribute>
                <name>css</name>
                <required>false</required>
                <rtexprvalue>true</rtexprvalue>
            </attribute>
            <attribute>
                <name>currentcss</name>
                <required>false</required>
                <rtexprvalue>true</rtexprvalue>
            </attribute>
        </tag>
    </taglib>
  • 解析

    <tlibversion>1.0</tlibversion>
    • 必要,开发人员方这个标记来声明标记库的版本。
    <shortname>Tag Library for Openfire 2.0</shortname>
    • 必要,主要由工具使用
    <uri>http://www.igniterealtime.org/</uri>
    • 必要,taglib指令中使用的唯一名
    <description>some thing</description>
    • 可选,但是有这个标记确实很有好处
    <name>tabs</name>
    • 必要!标记中的名字
    <tagclass>org.jivesoftware.admin.TabsTag</tagclass>
    • 必要!容器根据此找到实现tag功能的类。
    <bodycontent>JSP</bodycontent>
    • 必要!说明标记的体中应该是什么内容。
    <attribute>
        <name>css</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    • 标记的属性

      <name>css</name>
      <required>false</required>
      • 指定属性名,required说明该属性是否必须
      <rtexprvalue>true</rtexprvalue>
      • 指定属性是否运行时表达式(也就是说,不必非得是String直接量)

使用定制标记

  • jsp

    <%@ taglib prefix="mine" uri="http://www.igniterealtime.org/"%>
    ...
    <mine:tabs css="${userName}"%>
  • 解析

    prefix="mine"
    • 指定前缀
    uri="http://www.igniterealtime.org/"
    • 对应TLD中的uri:<uri>http://www.igniterealtime.org/</uri>
    mine:tabs
    • mine对应前缀,tabs对应TLD中的tag名字:<name>tabs</name>
    css="${userName}"
    • css对应TLD中的属性名:<name>css</name>
    • 由于TLD中指定css属性可以为运行时表达式:<rtexprvalue>true</rtexprvalue>,因此这里可以放置表达式:${useName}

定制标记处理器

// SimpleTagSupport实现了定制标记所要完成的工作。
public class AdvisorTagHandler extends SimpleTagSupport {
    // JSP使用TLD中声明的名字调用标记时,容器会调用doTag()。
    public void doTag() throws JspException, IOException {
        // ...
    }

    // 容器调用这个方法将值设置为标记属性的值。它使用JavaBean性质命名约定得出应该向setUser()方法发送一个“user”属性。
    public void setUser(String user) {
    }
}

<rtexprvalue>

  • 如果没有<rtexprvalue><rtexprvalue>的值为false,则不能在属性值中用表达式。
  • 属性的值允许rtexprvalue时,可以使用3种表达式。

    • EL表达式

      <mine:advice user="${userName}"/>
    • 脚本表达式

      <mine:advide user='<%= request.getAttribute("username") %>' />
    • <jsp:attribute>标准动作

      <mine:advice>
          <jsp:attribute name="user">${userName}</jsp:attribute>
      </mine:advice>

标记体

  • <bodycontent>的取值

    • <bodycontent>empty</bodycontent> 不能有体
    • <bodycontent>scriptless</bodycontent> 不能有脚本元素(scriptlet、脚本表达式和声明),但是可以是模板文本和EL,还可以是定制和标准动作。
    • <bodycontent>tagdependent</bodycontent>标记体要看作是纯文本,所以不会计算EL,也不会触发标记/动作。
    • <bodycontent>JSP</bodycontent> 能放在JSP中的东西都能放在这个标记体中
  • 没有体的标记,有3种调用方法

    • 空标记: <mine:advice user="${userName}"/>
    • 开始和结束标记之间没有内容的标记:<mine:advice user="${userName}"></mine:advice>
    • 在开始和结束之间只有<jsp:attribute>标记

      <mine:advice>
          <jsp:attribute name="user">${userName}</jsp:attribute>
      </mine:advice>

taglib<uri>

  • 容器会查找TLD中<uri>与taglib指令中uri值之间的匹配。uri不一定是具体标记处理器的位置。

TLD部署位置

  • 容器会在4个位置查找TLD:
    1. 在WEB-INF目录中查找
    2. 在WEB-INF的一个字目录中查找
    3. 在WEB-INF/lib下一个JAR文件中的META-INF目录中查找
    4. 在WEB-INF/lib下一个JAR文件中的META-INF目录的子目录中查找

使用多个标记库

  • 确保taglib uri名是唯一的。
  • 不要使用保留的前缀。(jsp,jspx,java,javax,servlet,sun,sunw)
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐