分享
 
 
 

第十讲 JSP与Servlet

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

课前索引

1 . 动态网页和静态网页有什么区别?

2 . 什么是 Web 应用程序?

3 . 什么是 Servlet ,和一般 Java 程序有什么区别?

4 . 什么是 JSP ,和 Java 是什么关系?

5 . JSP 和 Servlet 有哪些异同点?

6 . JSP 和 Servlet 的主要用途?

10 . 1 Servlet 的概念、配置与运行

10.1.1 Java Servlet 的概念

Java Servlet 是一个专门用于编写网络服务器应用程序的 Java 组件。所有基于 Java 的服务器端编程都是构建在 Servlet 之上的。在 J2EE 中 Servlet 已经是一个标准的组件。

在 J2EE 中跟 Servlet 相关的一个包是 javax.servlet ,其中最基本的 Servlet 被声明为一个接口 javax.servlet: Interface Servlet ,这是 Servlet 最高层次的一个抽象,它是和网络协议无关的。同样在 javax.servlet 中,实现了一个类 javax.servlet: class GenericServlet ,这个类实现了 Servlet 接口,也是和协议无关的。而这个类是构建其他和协议相关的 Servlet 子类型的通用的父类(至少 HttpServlet 是从它继承而来的,从它的名字也能看出这一点)。

也就是说 Servlet 所适用的网络协议可以是多种多样的,比如 HTTP , FTP , SMTP , TELNET 等,但是就目前而言, 只有 HTTP 服务已经形成了标准的 Java 组件。对应的软件包有两个 javax.servlet.http 和 javax.servlet.jsp ,分别对应我们要讲解的 Servlet 和 JSP 编程。 我们通常所说的 Servlet 编程主要就是指针对 HTTP 的 Servlet 编程,用到的就是 javax.servlet.http 包中的类(典型的就是 HttpServlet 类),实际上 Java Servlet 编程的概念要更广一些,在这里我们也就约定俗成的使用 Servlet 来指代 HTTP Servlet 的编程,这点读者是需要了解的。 由于 JSP 最终都是要经过 JSP 引擎转换成 Servlet 代码的,而且 Servlet 编程和一般的 Java 编程是没有大的区别的,只需要了解一定的规范即可, 所以我们在这里先讲解 Servlet 的编程,这样对以后理解 JSP 是很大的有好处的,尽管在使用的时候可能 JSP 更为简单一些。

目前, Servlet 引擎一般是第三方的插件,它通过一定的方法连接到 Web 服务器, Servlet 引擎把它识别为 Servlet 请求的那些 HTTP 请求截获下来处理,而其他的 HTTP 请求由 Web 服务器按照通常的方式来处理, Servlet 引擎会装载合适的 Servlet 到内存中,如果 Servlet 还没有运行的话,会分配一个可以使用的线程来处理请求,再把 Servlet 的输出返回到发出请求的 Web 客户机。

Java Servlet 和 Java Applet 正好是相对应的两种程序类型, Applet 运行在客户端,在浏览器内执行,而 Servlet 在服务器内部运行,通过客户端提交的请求启动运行 ,读者在学习过程可以作简单的比较。

由于 Servlet 是用 Java 编写的,所以它与生俱来就有跨平台的特性,因此 Servlet 程序的设计完全和平台是无关的,同样的 Servlet 完全可以在 Apache , IIS 等不同 Web 服务器上执行,不管底层的操作系统是 Windows , Solaris , Mac , Linux 还是其他的能支持 Java 的操作系统。

Servlet 是跟普通的 Java 程序一样,是被编译成字节码后由 JVM 执行的。相比传统的 CGI ,尽管 CGI 是用本地代码直接执行的,但是由于每次客户端发出请求,服务器必须启动一个新的程序来处理请求, 这就把高负载强加给了服务器资源,尤其如果 CGI 使用脚本语言编写时,如 perl ,服务器还必须启动语言解释程序,程序越多,占用的内存就越多,消耗 CPU 也越多,严重影响系统性能。

Servlet 运行于 Servlet 引擎管理的 Java 虚拟机中,被来自客户机的请求所唤醒,与 CGI 不同的是, 在虚拟机中只要装载一个 Servlet 就能够处理新的请求,每个新请求使用内存中那个 Servlet 的相同副本, 所以效率比 CGI 来得高。如果采用服务器端脚本,如 ASP , PHP ,语言解释程序是内置程序,因此可以加快服务器的运行,但是效率还是比不上准编译的 Servlet 。实际的使用也已经证明, Servlet 是效率很高的服务器端程序,很适合用来开发 Web 服务器应用程序。

Java Servlet 有着十分广泛的应用。不光能简单的处理客户端的请求,借助 Java 的强大的功能,使用 Servlet 还可以实现大量的服务器端的管理维护功能,以及各种特殊的任务,比如,并发处理多个请求,转送请求,代理等

10.1.3 Servlet 的运行环境

典型的 Servlet 运行环境有 JSWDK , Tomcat , Resin 等,这几个都是免费的软件,适合用来学习 Servlet 和 JSP 。它们都自带一个简单的 HTTP Server ,只需简单配置即可投入使用,你也可以把它们绑定到常用的 Web 服务器上,如 Apache , IIS 等,提供小规模的 Web 服务。还有一些商业的大中型的支持 Servlet 和 JSP 的 Web 服务器,如 JRun , Web Sphere , Web Logic 等等,配置比较复杂,并不适合初学者。但是功能较为强大,有条件的读者可以一试。

10.1.7 Servlet 的编译

Servlet 的编译和一般的 Java 程序是完全一样的,在使用 javac 编译的时候不需要任何特殊的参数。只要 Servlet 的编写是正确的,编译完后生成的 Class 文件就可以做为 Servlet 来运行了。

简单示例:

import java.io.*;

import java.util.*;

import javax.servlet.http.*;

import javax.servlet.*;

//导入必要的包

public class HelloServlet extends HttpServlet {

//所有Servlet必须从HttpServlet派生

public void doGet (HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException

//doGet()是这个Servlet的核心,真正处理请求的地方

{

res.setContentType("text/html");

//设置相应的类型为text/html

PrintWriter pw = res.getWriter();

//从HttpServletResponse得到输出流

pw.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");

pw.println("<head>");

pw.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">");

pw.println("<!-- The Servlet expression tags interpolate script variables into the HTML -->");

pw.println("<title>Hello, world!</title>");

pw.println("</head>");

pw.println("<body bgcolor=#cc99dd>");

pw.println("<h1>Hello, world!</h1>");

pw.println("</body>");

//上面的语句都是向客户端打印HTML文本

pw.close();

//关闭HttpServletResponse,使Web服务器知道相应结束

}

public HelloServlet() {} //构造函数,可以不要

}

10 . 2 Servlet 的应用实例

10.2.1 Servlet 与表单交互的方法

表单是 HTML 中使用最广泛的传递信息的手段。搞清楚 Servlet 与表单的交互,就在客户端与服务器之间架起了一座桥梁。 Servlet 使用 HttpServlet 类中的方法与表单进行交互。在 HttpServlet 类中有几个未完全实现的方法,你可以自己定义方法的内容,但是必须正确使用方法名称以使 HTTP Server 把客户请求正确的映射到相应的函数上。

doHeader 用于处理 HEADER 请求

doGet 用于处理 GET 请求,也可以自动的支持 HEADER 请求

doPost 用于处理 POST 请求

doPut 用于处理 PUT 请求

doDelete 用于处理 DELETE 请求

HttpServlet 的 Service 方法,当它接收到一个 OPTIONS 请求时,它会自动调用 doOptions 方法,当接收到一个 TRACE 请求时调用 doTrace 。 DoOptions 默认执行方式是自动决定什么样的 HTTP 被选择并返回哪个信息。

在使用这些方法时必须带两个参数。第一个包含来自客户端的数据 HttpServletRequest 。第二个参数包含客户端的相应 HttpServletResponse 。

一个 HttpServletRequest 对象提供请求 HTTP 头部数据,也允许获取客户端的数据。怎样获取这些数据取决于 HTTP 请求方法。

不管何种 HTTP 方式,都可以用 getParameterValues 方法返回特定名称的参数值。

( HttpServletRequest,HttpServletResponse 接口分别继承于 ServletRequest 和 ServletResponse 接口, getParameterValues 和 getWriter 方法都是其祖先接口中的方法)

对于 HTTP GET 请求的方式, getQueryString 方法将会返回一个可以用来解剖分析的参数值。

对于用 HTTP POST , PUT 和 DELETE 请求的方式, HttpServletRequest 有两种方法可以选择: 如果是文本数据,你能通过 getReader 的方法得到 BufferedReader 获取数据;如果是二进制数据,可以通过 getInputStream 方法得到 ServletInputStream 获取数据。

为了相应客户端,一个 HttpServletResponse 对象提供返回数据给用户的两个方法: 一种是用 getWriter 方法得到一个 PrintWriter ,用于返回文本数据;另一种方法是用 getOutputStream 方法得到 ServletOutputStream ,用于返回二进制数据 。在使用 Writer 或 OutputStream 之前应先设置头部( HttpServletResponse 中有相应的方法),然后用 Writer 或 OutputStream 将相应的主体部分发给用户。完成后要关闭 Writer 或 OutputStream 以便让服务器知道相应已经结束。

例: PrintWriter out = response.getWriter();

out.println("Request URI: " + request.getRequestURI()+"<br>");

我们知道在同一台机器上,所有的编码方式都是一样的,一般中文平台是 gb2312 ,英文平台是 ISO-8859-1 ,但是网络上的两台机器并不能保证他们的编码方式都是一样的,这时候就有可能出现乱码的问题。在进行 HTTP 网络传输的时候,统一采用的编码方式是 ISO-8859-1 ,这时候如果还是按照本地编码来传输就会出现问题,这也是 Servlet 在实现网络传输的时候一个不完美的地方,它不会自动进行本地编码到 ISO-8859-1 的转换,所以直接打印的话就会出现乱码。原理上讲任何出现打印字符串的地方,都是需要进行编码转换的,但是西文字符在不同字符集下对应相同的编码,以在打印西文字符的时候就不需要转换了。在 Servlet 后继的规范中可能会改变这种麻烦的状况。不同的是,从网络提交的表单数据, Servlet 是自动把它转换成本地编码的,所以程序中得到的 name 字符串变量是 gb2312 编码的,同样需要进行转换后才能在客户端正确打印。

字符编码转换常用的方法是

String native_encoded = " 中文字符串 ";

// 本地编码的字符串

Byte[] byte_array = native_encoded.getBytes();

// 得到本地编码的字节数组

String net_encoded = new String(native_encoded, "ISO-8859-1");

// 生成 ISO-8859-1 编码的字符串

例: out.println(new String(new String("<td> 你的姓名: </td>").getBytes(),"ISO-8859-1"));

10.2.3 用 Servlet 控制会话

会话状态的维持是开发 Web 应用所必须面对的问题,有多种方法可以来解决这个问题,如使用 Cookies , hidden 类型的表单域,或直接把状态信息加到 URL 中等,还有 Servlet 本身提供了一个 HttpSession 接口来支持会话状态的维持 ,在这里我们主要介绍基于这个接口的会话状态的管理。

Session 的发明是为了填补 HTTP 协议的局限。请注意 HTTP 协议是如何工作的 -- 用户发出请求,服务器作出响应,这种用户端和服务器端的联系就是离散的,非连续的。 HTTP 协议不能提供允许服务器跟踪用户请求的功能。在服务器端完成响应用户的请求之后,服务器不能继续与该浏览器继续保持连接。从服务器这端来看,每一个请求都是独立的,因此 HTTP 协议被认为是无状态协议,当用户在多个主页间切换时,服务器无法知道他的身份。 Session 的出现就是为了弥补这个局限。利用 Session ,您就可以当一个用户在多个主页间切换的时候也能保存他的信息。这样很多以前根本无法去做的事情就变得简单多了。

在访问者从到达某个特定的主页到离开为止的那段时间,每个访问者都会单独获得一个 Session 。

Java Servlet 定义了一个 HttpSession 接口,实现的 Session 的功能,在 Servlet 中使用 Session 的过程如下:

( 1 ) 使用 HttpServletRequest 的 getSession 方法得到当前存在的 session ,如果当前没有定义 session ,则创建一个新的 session ,还可以使用方法 getSession ( true )

( 2 ) 写 session 变量。可以使用方法 HttpSession.setAttribute ( name , value )来向 Session 中存储一个信息。也可以使用 HttpSession.putValue ( name , value ),但这个方法已经过时了。

( 3 ) 读 Session 变量。可以使用方法 HttpSession.getAttribute ( name )来读取 Session 中的一个变量值,如果 name 是一个没有定义的变量,那么返回的是 null 。需要注意的是,从 getAttribute 读出的变量类型是 Object ,必须使用强制类型转换,比如:

String uid = (String) session.getAttribute("uid");

也可以使用 HttpSession.getValue ( name ),但是这个方法也已经过时了。

( 4 ) 关闭 session ,当时用完 session 后,可以使用 session.invalidate() 方法关闭 session 。但是这并不是严格要求的。因为, Servlet 引擎在一段时间之后,自动关闭 seesion 。

HttpSession session = request.getSession(true);

//参数true是指在没有session时创建一个新的

Date created = new Date(session.getCreationTime());

//得到session对象创建的时间

out.println("ID " + session.getId()+"<br>");

//得到该session的id,并打印

out.println("Created: " + created+"<br>");

//打印session创建时间

session.setAttribute("UID","12345678");

//在session中添加变量UID=12345678

session.setAttribute("Name","Tom");

//在session中添加变量Name=Tom

10.2.4 Servlet 的生命周期

跟客户端的 Applet 相似, Servlet (这里 Servlet 的概念又回到了最原始的含义)也遵循严格的生命周期。在每个 Servlet 实例的生命中有三种类型的事件,这三种事件分别对应于由 Servlet 引擎所唤醒的三个方法:

1 . init() 。当 Servlet 第一次被装载时, Servlet 引擎调用这个 Servlet 的 init() 方法,只调用一次。如果某个 Sevlet 需要特殊的初始化需要。那么 Servlet 编写人员可以重写该方法来执行初始化任务。这是一个可选的方法。如果某个 Servlet 不需要初始化,那么默认情况下将调用它父类的 init 方法。系统保证,在 init 方法成功完成以前,是不会调用 Servlet 去处理任何请求的。

2 . service() 。这是 Servlet 最重要的方法,是真正处理请求的地方。对于每个请求, Servlet 引擎将调用 Servlet 的 service 方法,并把 Servlet 请求对象和 Servlet 响应对象最为参数传递给它。

3 . destroy() 。这是相对于 init 的可选方法,当 Servlet 即将被卸载时由 Servlet 引擎来调用,这个方法用来清除并释放在 init 方法中所分配的资源。

Servlet 的生命周期可以被归纳为以下几步:

( 1 ) 装载 Servlet ,这一项操作一般是动态执行的。然而, Servlet 通常会提供一个管理的选项,用于在 Servlet 启动时强制装载和初始化特定的 Servlet

( 2 ) Server 创建一个 Servlet 实例

( 3 ) Server 调用 Servlet 的 init 方法

( 4 ) 一个客户端请求到达 Server

( 5 ) Server 创建一个请求对象

( 6 ) Server 创建一个响应对象

( 7 ) Server 激活 Servlet 的 service 方法,传递请求和响应对象作为参数

( 8 ) service 方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息

( 9 ) service 方法使用响应对象的方法。将响应传回 Server ,最终到达客户端。 Service 方法可能激活其他方法以处理请求。如 doGet , doPost 或其他程序员自己开发的方法

( 10 ) 对于更多的客户端请求, Server 创建新的请求和响应对象,仍然激活此 servlet 的 service 方法,将这两个对象作为参数传递给它,如此重复以上的循环,但无需再次调用 init 方法, Servlet 一般只初始化一次

( 11 ) 当 Server 不再需要 Servlet 时,比如当 Server 要关闭时, Server 调用 Servlet 的 destroy

10 . 3 JSP 简介

10.3.1 JSP 的概念

JSP(Java Server Pages?) 是 Sun Microsystems 公司倡导、许多公司参与一起建立的一种动态网页技术标准。它在 HTML 代码中,插入 JSP 标记 (tag) 及 Java 程序片段 (Scriptlet), 构成 JSP 页面,其扩展名为 .jsp 。当客户端请求 JSP 文件时, Web 服务器执行该 JSP 文件,然后以 HTML 的格式返回给客户。前面已经提到过 JSP 只是构建在 Servlet 之上的高层次的动态网页标准 ,因此,从概念上将,相对 Servlet 而言, JSP 并没有什么新的东西,如果读者对前面的 Servlet 已经十分的了解,那么 JSP 的概念可说跟 Servlet 是完全一样的,只不过在实现方法上稍有不同。

10.3.2 JSP 的优点

1. 一次编译,多次、多处运行 , 代码的执行效率高

JSP 的脚本语言是 JAVA 语言,因此它具有 JAVA 语言的一切特性。同时, JSP 也支持现在大部分平台。 当 JSP 第一次被请求时, JSP 页面转换成 servlet ,然后被编译成 .calss 文件 ,以后(除非页面有改动或 Web 服务器被重新启动)再有客户请求该 JSP 页面时, JSP 页面不被重新编译,而是直接执行已编译好的 .class 文件,因此执行效率特别高。

2. 组件的重用性

可重用的、跨平台的 JavaBeans 和 EJB ( Enterprise JavaBeans )组件,为 JSP 程序的开发提供方便,我们可以将复杂的处理程序(如页面中需要显示的动态内容及对数据库的操作)放到组件中。可以多次使用这些组件,极大的减少了在 JSP 页面中重写重复的代码。

3. 将内容的生成和显示进行分离

使用 JSP 技术, Web 页面开发人员可以使用 HTML 或者 XML 标识来设计和格式化最终页面。使用 JSP 标识或者脚本来生成页面上的动态内容。生成动态内容的语句一般被封装在 JavaBean 组件、 EJB 组件或 JSP 脚本段中。这样,页面的设计人员和页面的编程人员可以同步进行。同时在客户端通过查看源文件,看不到 JSP 标识的语句,更看不到 JavaBean 和 EJB 组件,也可以保护源程序的代码。

10.3.3 JSP 的运行方式

JSP 一般的运行方式为:当服务器启动后,当 Web 浏览器端发送过来一个页面请求时, Web 服务器先判断是否是 JSP 页面请求。如果该页面只是一般的 HTML/XML 页面请求,则直接将 HTML/XML 页面代码传给 Web 浏览器端。如果请求的页面是 JSP 页面,则由 JSP 引擎检查该 JSP 页面,如果该页面是第一次被请求、或不是第一次被请求但已被修改,则 JSP 引擎将此 JSP 页面代码转换成 Servlet 代码,然后 JSP 引擎调用服务器端的 Java 编译器 javac.exe 对 Servlet 代码进行编译,把它变成字节码 (.class) 文件,然后再调用 JAVA 虚拟机执行该字节码文件,然后将执行结果传给 Web 浏览器端。如果该 JSP 页面不是第一次被请求,且没有被修改过,则直接由 JSP 引擎调用 JAVA 虚拟机执行已编译过的字节码 .class 文件,然后将结果传送 Web 浏览器端 。上面是一张 JSP 运行的示意图。

(例如:在 resin 服务器中,将一个 add.jsp 文件放在 doc/ 目录下,当在浏览器中参看该页面时 http://127.0.0.1:8080/add.jsp 。系统会在 doc\WEB-INF\work\_jsp 目录下生成 _add__jsp.class , _add__jsp.java , _add__jsp.java.smap 三个文件。)

从这里我们已经不难看出 JSP 和 Servlet 的关系, JSP 引擎负责把 JSP 页面翻译成 Servlet ,因此 JSP 在底层完全就是 Servlet (指原始概念上的 Servlet ,而不是 HttpServlet )。前面我们提到 JSP 编程对应 javax.servlet.jsp ,更确切的讲,这个包是供 JSP 引擎使用的,它在做翻译的时候需要用到这个包,我们在编写 JSP 页面的时候是不需要涉及这个包的使用的。

为什么有了 Servlet 还要在高层实现一个 JSP 呢?这个问题跟 Servlet 本身编写的烦杂程度有关,如果用 Servlet 来控制页面外观的话,将是一件十分头疼的事情,使用 JSP 就把烦杂的打印任务交给了 JSP 引擎,程序员可以把精力集中到逻辑控制上面。在后面还会有进一步的比较。

10.3.8 JSP 指令 (1)

下面我们开始讲解 JSP 的语法。从本质上讲 JSP 还是 Java 程序,因为它最终还是会被翻译成 Servlet 进而编译成 class 文件执行。但是由于 JSP 是嵌入式的 Java 程序,有些特殊的符号还是需要学习的。下面我们一一列举,读者不必深究,多使用之后就会熟悉。

1 . HTML 注释

该注释在客户端可通过查看源文件的方法看到。

JSP 语法: <!-- 注释 [ <%= 表达式 %> ] -->

例 1

<!-- This file displays the user login screen -->

在客户端页面源程序中显示为:

<!-- This file displays the user login screen -->

例 2

<!- 页面调用日期为:

<%= (new java.util.Date()).toLocaleString() %> -->

在 客户端页面源程序中 显示为:

<!-- 页面调用日期为 :January 1, 2000 -->

描述

可以在注释中使用任何有效的 JSP 表达式。表达式是动态的,当用户第一次调用该页面或该页面后来被重新调用时,该表达式将被赋值。 JSP 引擎对 HTML 注释中的表达式执行完后,其执行的结果将直接插入到表达式显示的地方。然后该结果和 HTML 注释中的其它内容一起输出到客户端。 <!-- … --> 之间的内容在浏览器上是看不到,但可以在客户端可通过查看源文件的方法看到该注释。

2 . JSP 注释

JSP 注释作为 JSP 页面的文档资料,但是该注释在客户端通过查看源文件的方法是看不到的。即该注释不发送到客户端。

JSP 语法: <%-- 注释 --%>

注意:在 JSP 注释中,不能使用 --%> ,但是如果你一定要使用的话,可以通过使用 --%\> 来避开。

3. 声明

在JSP页面脚本语言中声明变量或方法

JSP语法:<%! 声明;[声明;]+…… %>

例子

<%! int i =8; %>

<%! int n, m, k,j; %>

<%! String s = new String("hello"); %>

描述

在JSP文件中,一次可以声明一个或多个变量和方法,它们之间用分号隔开。在声明时使用JAVA语言时,必须符合JAVA语言规范。

注意:

(i) 变量必须先声明,然后才能使用。

(ii) 声明必须以分号结尾,但是在表达式中则不用。

(iii) 通过page指令导入的包中已声明的变量或方法,可以在主JSP文件中直接使用它们。

(iv) 一般情况下,一个声明的有效范围是本页面。但是,如果JSP源文件用<jsp:include>指令或include指令包含一些静态文件,声明的有效范围将会扩大,也就是说:声明在该静态文件中也有效。但是如果JSP源文件中用<jsp:include>指令包含进来一个动态文件,主JSP文件中的声明在该动态文件中将不起作用。

4. 表达式

在JSP脚本语言中,可以使用任何有效的表达式。

JSP语法:<%= 表达式 %>

例子1:

<%! String s = new String("hello"); %>

<font color="blue"><%=s%></font>

例子2:

随机显示一个数字:

<font color="blue"><%=java.lang.Math.random()%></font>

描述

表达式用于求值,然后其值转化为字符串,而且插入在JSP文件中显示该表达式的地方。而且可以在文本中使用表达式。表达式标签能包含符合JAVA语言规范的任何有效表达式,但是要注意: 不能使用分号作为表达式的结尾,然而,在脚本段 <%……%>标签中,表达式要求以分号作为结尾。

5. 脚本段

在JSP页面脚本语言中,包含一段有效的代码片段。

JSP语法:<% 代码段 %>

例1:

<%=java.lang.Math.random()%>

<%

for(int i=0;i<8;i++)

{ out.println(i); }

%>

<%

long n=6666;

application.setAttribute("maxNumber",Long.toString(n));

out.println(application.getAttribute("maxNumber"));

%>

描述

在脚本段能声明多个变量和方法。能使用任何有效的表达式。能使用任何JSP隐含的对象或任何用<jsp:useBean>标签声明的对象。能使用页面语言中任何有效的语句,如果使用Java语言,必须符合JAVA语言程序规范。

说明: 假如脚本段有输出语句,则输出内容被存储在 out对象中,通过out对象输出到JSP页面中。

10.3.8 JSP 指令 (2)

1 . Include 指令

该指令用于包含一个文本或代码的文件。

JSP语法:<%@ include file="relativeURL"%>

例子:

<body >

random.jsp中的随机显示的数为:

<%@ include file="random.jsp" %>

</body>

random.jsp文件中内容为:

<%=java.lang.Math.random()*10000%>

在页面中显示为:

random.jsp中的随机显示的数为: 2148.093521070482

描述

Include指令在JSP文件中插入一个包含文本和代码的文件。被包含的文件可以是JSP文件,HTML文件或文本文件。如果被包含的文件是JSP文件,则JSP引擎编译完该JSP文件后,执行的结果将插入到主JSP文件中Include指令所在的位置。如果被包含的文件是HTML文件或文本文件,则JSP引擎不对其进行编译,直接将其内容插入到主JSP文件中Include指令所在的位置。该包含是静态包含,即被包含的文件处理完,而且结果也插入到主JSP文件完毕,主JSP文件将继续执行include指令下面的内容。

注意:

(1)被包含的文件中不能含有<html>,</html>,<body>,或</body>标签。因为被包含的文件的全部内容将被插入到JSP文件中include指令所在的地方,这些标签将会同JSP文件中已有的同样的标签发生冲突。

(2)假如被包含的文件发声变化,主JSP页面将被重新编译。

属性:

file="relativeURL"

file是被包含文件的路径名。 其值是一个URL的一部分,没有协议、端口号或域名. 如:

"error.jsp"

"/templates/onlinestore.html"

"/beans/calendar.jsp"

如果相对URL以"/"开始,这个路径是相对于JSP应用上下文而言的,JSP应用上下文是存储在application对象中的javax.servlet.ServletContext对象。如果相对URL以目录或文件名开始,这个路径是相对于当前JSP文件所在的路径而言的。

2. Page 指令

定义整个JSP页面的全局属性。

JSP语法:

<%@ page

[ language="java"]

[ extends="package.class"]

[ import= "{ package.class | package.* }, ..." ]

[ session="true|false" ]

[ buffer="none|8kb|sizekb" ]

[ autoFlush="true|false" ]

[ isThreadSafe="true|false" ]

[ info="text"]

[ errorPage="relativeURL"]

[ contentType="mimeType [ ;charset=characterSet ]" |

"text/html ; charset=ISO-8859-1" ]

[ isErrorPage="true|false"] %>

例:

<%@ page contentType="text/html;charset=gb2312" %>

<%@ page import="java.sql.*, java.lang.*" %>

<%@ page buffer="8kb" autoFlush="false" %>

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

描述:

Page指令的作用范围是整个JSP文件和该JSP文件用include指令或<jsp:include>包含进来的任何静态文件,整个JSP文件和这些静态文件一起叫做一个"平移单元". 注意:Page指令不适用于任何动态的包含文件。你可以在一个"平移单元"使用多个Page指令。但是每一个属性你只能使用一次,除了import(因为import属性和JAVA程序语言的import语句很类似,你能够多次使用它,就象在JAVA语言中你能够多次使用import命令一样)。不论你将Page指令放到JSP文件或被包含的文件的任何一个位置,它的作用范围都是整个"平移单元".然而,一个好的编成风格是常常将它放到文件的顶部.

language="java"

在JSP文件的脚本段、声明和表达式中使用的语言。现只允许用"JAVA"。

extends="package.class"

该属性用于指明JAVA类文件超类全名。使用该属性时要小心,因为,它会限制JSP引擎编译文件的能力。

import= "{ package.class | package.* }, ..."

JSP文件中导入的一个或多个用逗号隔开的包的列表。这些包(和它们的类)可以在JSP文件的脚本段、表达式、声明和标签中使用。你必须将import属性放到调用被到入的类的标签前面。如果你想导入多个包,你可以在import后面用逗号将每个包隔开即可,或你可以在一个JSP文件中使用多个import.

session="true|false"

该属性用于确定JSP页面是否使用HTTP session.假如这个值是true, 则sesssion引用的是当前或新的session. 假如这个值是false,则在JSP文件中,不能使用session对象。默认值是true.

buffer="none|8kb|sizekb"

设置buffer缓冲区的大小,当out对象将被编译的JSP页面向客户Web浏览器端输出时使用。它的默认值是8kb.假如你指定了buffer缓冲区的大小,这个输出量将不低于你指定的大小。

autoFlush="true|false"

该属性指出:当缓冲区充满时,是否自动刷新。如果值为true(默认值为true),则自动刷新。如果值为false,则当缓冲区溢出时,将会产生错误。而且如果buffer属性的值设为none,autoFlush属性的值不能设为false.

isThreadSafe="true|false"

假设这个值设为true,多个线程能同时访问一个JSP页面。假如这个值为false,对一个JSP页面,JSP引擎一次只能响应一个用户的请求。默认值为true。

info="text"

关于作者、版本和著作权等信息,可以通过javax.sevlet.Servlet.getServletInfo()方法查到该信息的详细情况。

errorPage="relativeURL"

该属性用于设置处理例外事件的JSP文件的路径名。假如这个路径名以"/"开始,则这个路径名是JSP应用文档根目录而言的,而且由Web服务器决定。否则,这个路径是相对于当前JSP文件而言的。

isErrorPage="true|false"

JSP文件是否显示错误页面。 如果这个值为true,你可以使用例外exception对象。如果这个值为false(默认值也为false),在JSP文件中,不能使用exception对象。

contentType="mimeType [ ;charset =characterSet ]" |

"text/html;charset=ISO-8859-1"

JSP文件中使用的MIME type和character encoding(字符编码)是用于响应客户端的请求。你可以使用JSP引擎中有效的任何MIME type或character set(字符集).默认的MIME type是text/html,而且默认的字符集合是ISO-8859-1。

3 . <jsp:forward> 元素

将客户端的请求转交给一个HTML文件、JSP文件或脚本段处理。

JSP语法:<jsp:forward page="{ relativeURL | <%= expression %> }" />

例子:

<jsp:forward page="/dong/hello.jsp" />

描述:

<jsp:forward>标签将请求对象从一个JSP文件转交给另一个文件处理。

特别注意: JSP引擎对主JSP页面<jsp:forward>下面的代码不再执行。

说明:如果JSP文件的输出被设置为缓冲输出(即使用默认的Page指令值或直接设置缓冲区的buffer大小),则在请求被转交之前,缓冲区被清空。如果输出被设置为非缓冲输出(即用Page指令设置buffer=none),而且输出区中已经有内容,则使用<jsp:forward>元素,将会导致非法例外。

属性

page="{ relativeURL | <%= expression %> }"

该属性用于设置将要转交的文件的相关URL.

该URL不能包含协议名、端口号或域名,相对于当前JSP文件来说的。如果它是绝对地址(以"/"开始),该路径由你的Web或应用服务器决定。

4 . <jsp:getProperty>

取得Bean属性的值,以便在结果页面中显示。

JSP语法:<jsp:getProperty name="beanInstanceName" property="propertyName"/>

例:

Bean的程序代码为:

package AccessDatabase;

public class Readdate

{

private String username="changcheng";

public String void getUsername(){return username; }

}

JSP文件的内容为:

<html><body>

<jsp:useBean id="init" scope="page" class="AccessDatabase.readdate" />

从Bean中取得属性名为username的值为:

<jsp:getProperty name=" init " property="username" />

</body></html>

执行后显示结果为:

从Bean中取得属性名为user的值为:changcheng

描述:

在使用<jsp:getProperty>前,你必须使<jsp:useBean>元素创建或调用一个Bean实例。<jsp:getProperty>标签是用于取得JavaBeans属性值,相当于调用Bean中的某个属性的getXXX()方法。

属性:

name="beanInstanceName"

在<jsp:useBean>标签中声明的Bean实例的名字。

property="propertyName"

Bean属性的名字。

说明: 使用<jsp:getProperty>元素时如上例中:<jsp:getProperty name=" init " property="username" />,username必须是Bean(Readdate)中的属性,且该Bean中要有getUsername()方法,否则编译时会出错。

5 . <jsp:include>

在JSP文件中,包含一个静态或动态文件.

JSP语法<jsp:include page="{ relativeURL | <%= expression %>}" flush="true" />

例子:

<jsp:include page="jsp/dadi.jsp" />

<jsp:include page="hello.html" />

<jsp:include page="/index.html" />

属性:

<jsp:include>标签允许你包含一个静态文件或动态文件。一个静态文件被执行后,它的内容插入在主JSP页面中。一个动态文件对请求作出响应,而且将执行结果插入到JSP页面中。

<jsp:include>标签能处理两种文件类型,当你不知道这个文件是静态或动态的文件时,使用该标签是非常方便的。

当include动作执行完毕后,JSP引擎将接着执行JSP文件剩下的文件代码。

page="{ relativeURL | <%= expression %>}"

该属性指出被包含文件相关URL;该URL不能包含协议名、端口号或域名。该URL是绝对或相对于当前JSP文件来说的。如果它是绝对地址(以"/"开始),该路径由你的Web或应用服务器决定

flush="true"

在JSP文件中,常设置flush="true",因为它不是一个默认值。

6. <jsp:plugin>

下载一个plugin插件到客户端以便执行applet或Bean

JSP语法:

<jsp:plugin

type="bean|applet"

code="classFileName"

codebase="classFileDirectoryName"

[ name="instanceName"]

[ archive="URIToArchive, ..." ]

[ align="bottom|top|middle|left|right" ]

[ height="displayPixels"]

[ width="displayPixels"]

[ hspace="leftRightPixels"]

[ vspace="topBottomPixels"]

[ jreversion="JREVersionNumber | 1.1"]

[ nspluginurl="URLToPlugin"]

[ iepluginurl="URLToPlugin"]>

[ <jsp:params>

[ <jsp:param name="parameterName" value="parameterValue"/>]+

</jsp:params> ]

[ <jsp:fallback> text message for user </jsp:fallback> ]

</jsp:plugin>

例子:

<jsp:plugin type=applet code="Molecule.class" codebase="/html">

<jsp:params>

<jsp:param name="molecule" value="molecules/benzene.mol" />

</jsp:params>

<jsp:fallback>

<p>Unable to load applet</p>

</jsp:fallback>

</jsp:plugin>

<jsp:params>

<jsp:params>元素在计算机启动时,给applet或Bean传递参数及其值。如果plugin没有启动,〈jsp:fallback>元素将给使用者提供一条信息。如果plugin启动了但是applet或Bean没有启动,plugin常常弹出一个window窗口,该用户解释出错的原因。

属性:

type="bean|applet"

plugin将执行的对象的类型。你必须指定Bean或applet,因为这个属性没有默认值。

code="classFileName"

plugin将执行的JAVA类文件的名字。在文件名后面必须跟上.class扩展名。文件名是相对于codebase属性中命名的目录。

codebase="classFileDirectoryName"

包含applet代码的目录名的相对或绝对地址。如果没有提供该值,调用<jsp:plugin>的JSP文件的路径将被使用。

name="instanceName"

Bean或applet实例的名字,相同的JSP文件通过它可以相互通信。

archive="URIToArchive, ..."

一个用逗号隔开的路径列表,该路径列表指出用类装载器装载的类文件的位置。类装载器是位于codebase属性中命名的目录。

align="bottom|top|middle|left|right"

applet或Bean中显示的图象的位置是相对于JSP结果页面中的这样的行来说的,该行是JSP文件中包含<jsp:plugin>的行。不同值的显示结果如下:

bottom使图象的底部与下一文本行的基线对齐。

Top使图象的顶部与下一文本行的顶部对齐。

Middle使图象的垂直中心与下一文本行的基线对齐。

Left使图象左对齐.

Right使图象右对齐.

height="displayPixels" width="displayPixels"

applet或Bean以像素为单位显示的初始高度和宽度,不包括applet或Bean弹出的任何窗口和对话框.

hspace="leftRightPixels" vspace="topBottomPixels"

applet或Bean以像素为单位显示的图象的左右(或上下)之间间距的大小。一般是一个非常小的非零值。

jreversion="JREVersionNumber|1.1"

applet或Bean要求的JAVA运行时环境(JRE)的版本。默认的值是1.1

nspluginurl="URLToPlugin"

用户使用Netscape Navigator浏览器时需下载JRE插件的URL。它是完整的URL地址,有协议名、端口号和域名。

iepluginurl="URLToPlugin"

用户使用Internet Explorer浏览器时需下载JRE插件的URL.它是完整的URL地址,有协议名、端口号和域名。

<jsp:params>[ <jsp:param name="parameterName" value="parameterValue"/>]+</jsp:params>

传递给applet或Bean的参数和值。为了传递多个参数和值,你可以在〈jsp:params>元素中使用多个<jsp:param>标签. Applets取得参数是通过java.applet.Applet.getParameter方法。

<jsp:fallback> text message for user </jsp:fallback>

如果plugin插件不能使用时,显示给用户的文本信息

7 . <jsp:setProperty>

设置Bean的一个或多个属性值。

JSP语法:

<jsp:setProperty name="beanInstanceName"

{

property= "*" | property="propertyName" [ param="parameterName"] | property="propertyName" value="{ string | <%= expression %> }"

}

/>

例子:

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

<jsp:setProperty name="init" property="username" />

<jsp:setProperty name="init" property="username" value="lili" />

属性:

<jsp:setProperty>标签用于设置JavaBean组件中的属性值。在你使用<jsp:setProperty>元素前,你必须使用<jsp:useBean>标签声明这个Bean.在<jsp:setProperty>中的name的值必须和在〈jsp:useBean>中的id的值一致.

一般设置属性的值有三中方法:

(i)使用<jsp:setProperty name="beanInstanceName" property="*" />方法 ,即可将用户请求中的所有值(这些值一般是客户表单中的元素的值,且作为参数存储在request对象中)和Bean中的相匹配的属性赋值。此时,Bean中属性的名字必须和客户端表单中元素的名字一样。

(ii)使用<jsp:setProperty name="beanInstanceName" property="propertyName" [ param="parameterName"] />方法, 用请求对象中一个特定的值和Bean中相匹配的属性赋值或不相匹配的属性赋值。

(iii)使用<jsp:setProperty name="init" property="username" value="{ string | <%= expression %> }" />方法,用字符串的值或表达式的值直接设置为Bean的属性

属性和用法: name="beanInstanceName"

在<jsp:useBean>标签中被创建或调用的Bean的实例名. Name的值必须和<jsp:useBean>中的id的值一致。

property="*"

该属性用于一次性设置Bean属性的值。客户端表单中元素的值一次性赋予Bean中相匹配的属性赋值。另外,如果Bean有一个属性没有和它对应的表单元素,则这个属性将不被设置。

property="propertyName" [ param="parameterName"]

当用表单中一个元素的值给Bean中一个属性赋值,而且元素名和属性名不一样时,则必须用param指定一个参数。

8 . <jsp:useBean>

调用或创建一个指定名字和使用范围的Bean.

JSP语法:

<jsp:useBean

id="beanInstanceName"

scope="page|request|session|application"

{ class="package.class"| type="package.class"|

class="package.class" type="package.class"|

beanName="{ package.class | <%= expression %> }" type="package.class"

}

{ /> |

> other tags </jsp:useBean>

}

例子:

<jsp:useBean id="init" scope="page" class="chinaa.basket" />

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

<jsp:useBean id="hello" scope="session" class="dadi.reg" >

<jsp:setProperty name="hello" property="n" value="45" />

</jsp:useBean>

属性:

<jsp:useBean>标签首先调用一个指定的名字和使用范围的Bean,如果这个Bean不存在,则创建该 Bean。

属性和用法

id="beanInstanceName"

被调用或创建的Bean的名字.你可以在JSP文件的表达式、脚本段中使用该变量名。如果该Bean已经被另一个<jsp:useBean>创建,id值必须同原来的<jsp:useBean>标签中使用的id号相同.

scope="page|request|session|application"

定义Bean存在的范围。默认值是page。Bean的必须在其指定的范围内使用,否则使用它,将会出错。

class="package.class"

指定Bean的存放位置即存在哪个包中的哪个类。该类不能是抽象类,而且有一个公共的、无参数构造函数。包和类名是严格区分大小写的。

class="package.class" type="package.class"

类型type的值可以和类、该类的超类或该类实现的接口的类型一样。

以上就是编写 JSP要用到的一些语法,读者不必也不可能一下子掌握,在需要的时候进行查询即可,在使用中会自然而然的熟练起来。

10.3.9 JSP 中的隐藏对象 (1)

由于 JSP 是嵌入式的语言,不能显式的把一些必须的参数传递进来,比如 Request 对象, Response 对象等,所以在 JSP 规范中提供了几个隐含的对象来实现折椅功能。所谓隐含的对象,就是大家约定好使用一个名字来指代某个特定的对象,在编写 JSP 的时候不用显式的声明就能使用,由 JSP 引擎负责在解释的时候把隐含对象加入到解释完的 .java 文件中。常用的隐含对象有 application, session, request, response, out, page, exception, pageContext 。

1 . session 对象

前面在Servlet部分已经提到,过当客户第一次访问Web服务器发布目录(一个Web服务器有一个或多个"发布目录")下的网页文件时,Web服务器会自动创建一个session对象,并为其分配唯一的ID号,客户可以将其需要的一些信息保存到该session对象,以便需要时使用。session对象就是指通过getSession法办法得到的对象,在JSP中是隐含对象,关于session对象的使用读者可以参见Servlet API

2 . application 对象

当Web服务器启动时,Web服务器会自动创建application对象。Application对象一旦创建,它将一直存在,直到Web服务器关闭。因此,application对象可以实现多客户间的数据共享。

一个Web服务器常常有多个发布目录,当Web服务器启动时,它自动为每个发布目录都创建一个application对象,这些application对象各自独立,而且和发布目录一一对应。

application的生命周期:从Web服务器启动到Web服务器关闭。

application在生命周期内的作用范围:在同一个发布目录A下的所有网页文件中,都可以对"和发布目录A对应"的application对象进行操作,而且访问发布目录A的所有客户都共用一个application对象。因此,当在该application中存放数据信息时,所有访问该发布目录A的客户都能够对其进行访问,实现了多客户之间的数据共享。

application对象的基类是:javax.servlet.ServletContext类。可以用该类中的getServletContext()方法取得application。具体的使用方法参见Servlet API。

3 . request 对象

request对象主要用于取得客户在表单中提交的数据信息及多个网页之间数据信息传递等。同时通过它也可以取得Web服务器的参数。跟Servlet参数中的Request对象是相对应的。

request对象的基类为:javax.servlet.ServletRequest

如果传输协议是http,则是javax.servlet.HttpServletRequest

具体的使用方法参见Servlet API。

4 . respose 对象

respose对象主要用于向客户端输出信息,响应客户端的请求。跟Servlet参数中的Response对象是相对应的。

respose对象的基类是:javax.servlet.ServletResponse

如果传输协议是http.则为javax.servlet.HttpServletResponse.

具体的使用方法参见Servlet API。

5 . out 对象

out对象用于向客户端输出数据。

out对象基类是:javax.servlet.JspWriter类,跟Servlet中由HttpServletResponse得到的PrintWriter略有不同,但是都是从

Writer继承而来,所以基本上还是一样的。

具体的使用方法参见Servlet API。

6 . page 对象

page对象是当前JSP页面本身的一个实例。它的类型是:java.lang.Object。

其方法就是Object类中的方法。如:Class getClass()返回一个对象在运行时所对应的类的表示,从而可以得到相应的信息。String toString()返回当前对象的字符串表示。page对象在当前页面中可以用this代替。

7 . exception 对象

当JSP页面在执行过程中发生例外或错误时,会自动产生exception对象。

在当前页面用<%@ page isErrorPage="true" %>设置后,就可以使用该exception对象,来查找页面出错信息。

exception对象的类型是:java.lang.Exception类.

exception对象的常用方法为:

String getMessage()

返回页面的出错信息,如果没有则返回null

void printStackTrace()

以标准错误输出流的形式,打印并显示当前exception对象及其执行轨迹.

10 . 4 JSP 和 Servlet 的结合使用

在使用 JSP技术开发网站时,不强调使用Servlet。,这是为什么呢?Servlet的应用是没有问题的,它非常适合服务器端的处理和编程。但是如果用Servlet处理大量的HTML文本,那么将是一件及其繁琐的事情。这种事情更适合机器去做,否则就是浪费程序员的体力。所以Servlet更适合处理后端的事务,前端的效果用JSP来实现更为合适。

早期的JSP标准给出了两种使用JSP的方式。 这些方式都可以归纳为 JSP模式1和JSP模式2,主要的差别在于处理大量请求的位置不同。 在模式 1中,JSP页面独自响应请求并将处理结果返回客户。这里仍然有视图和内容的分离,因为所有的数据都依靠bean来处理。尽管模式1可以很好的满足小型应用的需要,但却不能满足大型应用的需要。大量使用模式1,常常会导致页面被嵌入大量的Script和Java代码。特别是当需要处理的商业逻辑很复杂时,情况会变得很严重。也许这对于Java程序员来说,这不是大问题。但是如果开发者是前台界面设计人员,在大型项目中,这是很常见的,则代码的开发和维护将出现困难。在任何项目中,这样的模式多少总是会导致定义不清的响应和项目管理的困难。下图是模式1的示意图:

JSP模式2是一种面向动态内容的实现,结合了Servlet和JSP技术。它利用两种技术原有的优点,采用JSP来表现页面,采用Servlet来完成大量的处理,Servlet扮演一个控制者的角色,并负责响应客户请求。接着,Servlet创建JSP需要的Bean和对象,再根据用户的行为 , 决定将哪个 JSP页面发送给用户。特别要注意的是,JSP页面中没有任何商业处理逻辑,它只是简单的检索Servlet先前创建的Beans或者对象,再将动态内容插入预定义的模板。

从开发的观点来看,这一模式具有更清晰的页面表现,清楚的开发者角色划分,可以充分的利用开发小组中的界面设计人员。事实上,越是复杂的项目,使用模式2的好处就越突出。

使用模式 2,JSP和Servlet可以在功能最大幅度的分开。正确的使用模式2,导致一个中心化的控制Servlet,以及只完成显示的JSP页面。另一方面,模式2的实现很复杂,因此,在简单应用中,可以考虑使用模式1。 下面是模式 2的示意图:

在 Web 的建设中到底使用哪一种模式是很重要的事前规划,只有积累了大量的经验才能作出正确而有效的选择。总之网站建设不光是技术的问题,总体的规划是很重要的,读者在学习 JSP 和 Servlet 的同时更应该注意从中吸取思想的精华,技术是会过时的,而思想却是永远有价值的。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有