java基础 - 2

JAVA编程语言和JDBC

编写正确且遵守规范的Java程序,可以无需重新编译就在任何启用Java技术的平台上运行。Java编程语言彻底地进行了规定。根据定义,启用Java技术的平台必须支持已知的核心库。java.sql包或javax.sql包或者JDBC就是这样一个库,它们可以视为ODBC的可移植版本,且其本身就是重大的标准。JAVA编程语言和JDBC一起使用,可以给编写数据库应用程序提供正确的可移植性解决方案。注意:虽然可移植的应用程序和标准数据库界面都是重大的成果,但不要忘记,因为历史,竞争,有时是没有意思的原因,各种数据库并没有彻底地进行标准化。这可能意味着,必须根据(甚至同一平台上的)特定数据库的性能或内在的调整来寻找"最低公分母"。无论采用标准的SQLODBCJDBC,或其他解决方案,都存在这个问题。最后需要指明的是JDBC驱动程序就是JAVA类,它实现JDBC驱动程序接口,并可以为特别的数据库转换程序(一般是SQL)请求。无疑,驱动程序在这里起了重要作用。大多数的数据库供应商现在都提供驱动程序,以实现特定系统的JDBC API。这些通常都是免费提供的。第三方驱动程序也可以获得,成本从免费到费用浩大的都有。

JDBC编程的核心包为java.sql包,其结构如下图:

JDBC编程的步骤

第一:加载驱动程序

为了与特定的数据库相连,JDBC必须加载相应的驱动程序。如:

try {

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

// 加载Oracle的驱动程序

Class.forName("oracle.jdbc.driver.OracleDriver");

// 加载Microsoft SQL Server的驱动程序 Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

第二:要将"驱动程序"传递到DriverManager,然后获得"连接"

DriverManager类的getConnection(String url,String user, String password)方法用于建立与某个数据库的连接。每个JDBC驱动程序使用一个专门的JDBC URL作为自我标识的一种方法。

JDBC URL的格式为:jdbc : <子协议名> : <子名称>

子协议(sub-protocol)与JDBC驱动程序有关,可以是odbc,oracle,db2mysqlmicrosoft等等,根据实际的JDBC驱动程序厂商而不同。数据库定位器(database locator)是与驱动程序有关的指示器,用于唯一指定应用程序要和哪个数据库进行交互。根据驱动程序的类型,该定位器可能包括主机名,端口和数据库系统名。

try{

String url="jdbc:odbc:myodbc";

Connection con=DriverManager.getConnection(url);

// 或者

Connection con=

DriverManager.getConnection(url,user,password);

}catch(SQLException e){

e.printStackTrace();

}

// 1.Microsoft SQL ServerURL

url="jdbc:Microsoft:sqlserver://192.168.0.1:1433;databasename=mydb";

127.0.0.1 也可以用字符串 "localhost"代替

// 2.Oracle URL

url="jdbc:oracle:thin:@192.168.0.1:1521:goudan";

第三:创建语句,Statement ,PreparedStatement,CallableStatement,并将它们用于更新数据库或执行查询。

Statement 对象用于将 SQL 语句发送到数据库中。实际上有三种 Statement 对象,它们都作为在给定连接上执行 SQL语句的对象:StatementPreparedStatement( 继承Statement )和 CallableStatement(继承PreparedStatement)。它们都专用于发送特定类型的 SQL 语句: Statement 对象用于执行不带参数的简单 SQL语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句;CallableStatement对象用于执行对数据库已存储过程的调用。

第四:查询返回包含有已请求数据的ResultSet,该ResultSet是按类型检索的。

ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。

第五:DatabaseMetaDataResultSetMetaData接口可以用来提供有关数据库或ResultSet的信息。

实例分析

1:通过ODBC建立连接

? 配置WINDOWSODBC详细步骤参考 <<ODBC连接步骤.doc>>

? 代码如下:

import java.sql.*;

public class ODBCTest {

public static void main(String[] args) {

Connection con = null;

try {

// 加载ODBC驱动

1Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

// 通过驱动管理器(DriverManager)获得连接

2con = DriverManager.getConnection("jdbc:odbc:myodbc",

"pubuse","123");

// 如果连接不成功,就会出现异常,不会执行下面这个语句

System.out.println("connect success!");

} catch (Exception e) {// 如果出现异常,会打印堆栈里异常的信息

e.printStackTrace();

} finally {// 用完后,关闭连接,释放资源

try {

if (con != null) // 防止出现内存泄露

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

2:通过SQLSERVER 提供的驱动程序获得连接

? 下面通过SQLSERVER提供的三个驱动获得连接。三个jar文件的名字分别为:msbase.jar,mssqlserver.jar,msutil.jar 然后设置classpath环境变量指向这三个路径。假如这三个文件放在d:\mssqldriver 目录下,如下图所示:

? 然后在系统环境变量classpath里面设置这三个jar文件,这样的话就可以在任何的dos命令窗口里面使用。

也可以在一个dos窗口里面设置。这样的话是只在该窗口下有效。

? 部分代码如下:

………

try {

// 加载SQLSERVER的驱动程序Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

// 通过驱动来获得一个连接

con = DriverManager.getConnection(

"jdbc:microsoft:sqlserver://localhost:1433;"

+ "databasename=pubs", "sa", "");

System.out.println("connect success!");

} catch (Exception e) {

e.printStackTrace();

}

………

3:通过ORACLE提供的驱动程序获得连接

? 下面介绍通过ORACLE提供的驱动程序获得连接。只有一个jar文件:classes12.jar 同上设置环境变量classpath 指向该jar文件。

部分代码如下

………

try {

// 加载ORACLE9i的驱动程序

Class.forName("oracle.jdbc.driver.OracleDriver");

// 获得连接 oracle数据库的端口号:1521 数据服务器的名字叫itjob

// 登陆的用户名为system,密码为:system (默认密码为manager)

con = DriverManager.getConnection(

"jdbc:oracle:thin:@127.0.0.1:1521:itjob",

"system","system");

System.out.println("con ok");

} catch (Exception e) {

e.printStackTrace();

}

………

4:通过数据源获得连接

使用JDBC API的一个主要好处就是独立于数据库的编程,因为大部分JDBC应用程序都可以被轻易地转换到不同的数据库。然而,仍然有两个主要的内容与特定的数据库有关,即JDBC Driver类和JDBC URL

随着JDBC API的升级,数据源技术的引入,可以提高数据库访问的灵活性。本质上,DataSource对象表示一个特殊的数据源。除了将数据库和专门的JDBC驱动程序信息封装到一个单独的,标准化的对象中之外,数据源可以作为Connection工厂,并为设置和获取DataSource对象进行成功操作所需要的特定属性提供方法。DataSource对象可能需要的一些标准属性包括:

? databaseName

? serverName

? portNumber

? username

? password

使用数据源的另一个好处就是和安全有关的敏感信息,如用户名,密码甚至数据库服务器只在一处编码,这可以由系统管理员完成。虽然和DataSource对象的交互可以用图形应用程序完成,但真正看到运行的示例是有指导性的。虽然DataSource对象的概念很简单,但为了在Java应用程序中使用,DataSource对象是使用Java名称和目录接口(JNDI)来引用的。

下面首先介绍下JNDI的相关概念。JNDI是个JAVA API,它包括名称和目录服务器的概念,道理和JDBC与数据库进行通讯的概念差不多。如:硬盘通过与磁道和扇区打交道工作,但用户只关心文件名和目录,文件系统管理名称服务,该服务将给定的文件名和硬盘上特定的位置相关联。另一个简单的示例就是Web,多数用户只关心Web站点的名称,如www.5itjob.com,并不关心底层的IP地址。然而,TCP/IP通讯是通过使用IP地址,而不是人类能看懂的名称进行的。两个表示法之间的转换是通过DNS(域名系统,Domain Name System)完成的。虽然JNDI用自己的方式提供了一个丰富和有用的API,我们的需求却简单得多。简短地说,我们需要知道怎样做四件事:

? 创建名称并将其绑定到一个Java对象

? 查询名称以检索Java对象

? 删除一个名称

? 重新绑定名称到一个新的Java对象

首先要先加载数据源的驱动程序。让环境变量classpath指向下面几个jar文件

2,代码如下:

import java.util.Hashtable;

import javax.naming.*;

import javax.naming.directory.*;

import java.sql.*;

import javax.sql.*;

import com.microsoft.jdbcx.sqlserver.SQLServerDataSource;

public class JNDIServer {

// First we define the relevant parameters for this datasource

private String serverName = "192.168.0.1";

private int portNumber = 1433;

private String login = "student";

private String password = "student";

private String databaseName = "mydb";

private String filePath = "jdbc/mydatasource";

public JNDIServer() {

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

try {

// 创建初始化上下文环境

Context ctx = new InitialContext(env);

// 如果filePath已经绑定过了,那么先解除绑定

ctx.unbind(filePath);

// 创建SQLServerDataSource

SQLServerDataSource ds = new SQLServerDataSource();

// 设置数据源的参数

ds.setServerName(serverName);

ds.setPortNumber(portNumber);

ds.setDatabaseName(databaseName);

ds.setUser(login);

ds.setPassword(password);

ds.setDescription("JDBC DataSource Connection");

// 绑定 JDBC 数据源

ctx.bind(filePath, ds);

ctx.close();

System.out.println("DataSource Created Success!");

} catch (Exception e) {

e.printStackTrace();

}

}

public static void main(String args[]) {

new JNDIServer();

}

}

运行成功后,会在控制台打印:DataSource Created Success!

同时在运行该类所在的磁盘根目录下生成".bindings"文件,如下图:

数据源创建好之后,下面开始使用数据源。

代码如下:

import java.util.Hashtable;

import javax.naming.*;

import java.sql.*;

import javax.sql.*;

public class UseJNDI {

public static void main(String args[]) {

Connection con = null;

try {

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);

// 获得数据源对象

DataSource ds =

(DataSource) ctx.lookup("jdbc/mydatasource");

// 通过数据源对象获得一个连接

con = ds.getConnection();

// 如果连接不成功,就会出现异常,不会执行下面这个语句

System.out.println("connect success!");

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (con != null)// 用完连接后,要关闭释放

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

下面是删除JNDI的引用的代码:

import java.util.Hashtable;

import javax.naming.*;

import java.sql.*;

import javax.sql.*;

public class DeleteJNDI {

public static void main(String[] args) {

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

try {

Context ctx = new InitialContext(env);

ctx.unbind("jdbc/pubs");

ctx.close();

System.out.println("delete succeed!");

} catch (Exception ex) {

System.err.println("ERROR: " + ex.getMessage());

}

}

}

5:通过连接池获得连接

首先请根据我们在上面创建数据库连接的经验,来思考下列问题:

? 为什么需要连接池?

? 什么是PooledConnection?

? 初始化连接池

? 使用连接池

当使用DriverManager或者DataSource方法来获取数据库连接时,每个对新数据库连接的请求都会导致很大的开销。如果频繁地获取新的连接,将会影响性能问题。这在Web服务器端编程的时候尤为明显。请求一个新的Connection对象会带来大量的开销和很多潜在的错误。为了最小化开销,为什么在我们使用完数据库连接后不是重新使用它们,而是删除它们呢?JDBC设计者在创建接口ConnectionPoolDataSource时使用这种流行的设计模式,这允许您创建数据库连接池,其中的连接在关闭后可以重用,而不是删除。

PooledConnection是一个特殊类型的数据库连接,在关闭时不会被删除,不象常规的Connection对象(当常规的连接不再被使用时,垃圾收集器能删除它们)。相反,PooledConnection被缓存以备将来再次使用,从而可能带来大幅度的性能提升。使用数据库连接池几乎和使用DataSource对象一样。首先,不是创建一个实现DataSource接口的类的实例,而是创建了一个实现了ConnectionPoolDataSource接口的类的实例。可以像以前一样使用JNDI来绑定这个新的数据源到一个名称。要实际使用池化的数据源对象,调用ConnectionPooledDataSource(它接下来会建立数据库连接)上的getPooledConnection()得到一个PooledConnection对象。要创建将使用的Connection对象,调用PooledConnection对象(而不是以前的DriverManagerDataSource对象)上的getConnection().这种方法的一个额外的好处是,因为是自动处理,用ConnectionPool管理一定数量的数据库连接方便多了。如果你的客户机许可限制了能够同时连接到数据库的客户机的数目,这种自动化可能非常重要。

初始化连接池。下面的例子我们将使用SQLServer2000数据库和i-net软件的第三方驱动程序来创建PooledDataSource.所有与数据库相关的代码都包含在初始化或绑定过程中。

首先应该在classpath里面再指向名字为Merlia.jarjar文件。

2,代码如下:

import com.inet.tds.PDataSource;

import java.util.Hashtable;

import javax.naming.*;

import com.microsoft.jdbcx.sqlserver.SQLServerDataSource;

public class JNDIofPooledDataSourceTest {

public static void main(String[] args) {

String serverName = "192.168.0.1";

String databaseName = "mydb";

String userName = "student";

String password = "student";

// 通过下面的名字可以获得一个池化的连接

String filePath = "jdbcPool/mydatasource";

int portNumber = 1433;

int poolSize = 10; // We want to create a pool with 10 connections.

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

try {

Context ctx = new InitialContext(env);

// 创建一个池化的数据源对象

PDataSource ds = new PDataSource();

ds.setServerName(serverName);

ds.setPortNumber(portNumber);

ds.setDatabaseName(databaseName);

ds.setUser(userName);

ds.setPassword(password);

ds.setDescription("i-Net PDataSource");

ds.setMaxPoolSize(poolSize);

// 绑定池化的数据源

ctx.bind(filePath, ds);

ctx.close();

System.out.println("PooledDataSource Created Success!");

} catch (Exception ex) {

System.err.println("ERROR: " + ex.getMessage());

}

}

}

一旦初始化了PooledDataSource,就能够在Java应用程序中使用它来创建数据库连接池。请看下面的例子:

import java.util.Hashtable;

import javax.naming.*;

import java.sql.*;

import javax.sql.*;

import java.io.*;

public class UseJNDIOfPooledDataSource {

public static void main(String[] args) {

Connection con = null;

try {

String filePath = "jdbcPool/mydatasource";

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);

// 通过JNDI获得池化的数据源

ConnectionPoolDataSource ds =

(ConnectionPoolDataSource) ctx .lookup(filePath);

// 通过池化的数据源获得池化的连接

PooledConnection pcon = ds.getPooledConnection();

// 通过池化的连接获得连接

con = pcon.getConnection();

// 如果连接不成功,就会出现异常,不会执行下面这个语句

System.out.println("connect success!");

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (con != null)// 用完连接后,要关闭释放

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

6:总结数据库连接的各种方式

下面通过综合的例子来说明各种数据库的连接。

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.Hashtable;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.sql.ConnectionPoolDataSource;

import javax.sql.DataSource;

import javax.sql.PooledConnection;

public class DBCon {

// 通过JDBC-ODBC桥获得连接

public static Connection getOdbcCon(String datasourcename, String name,String password)

throws ClassNotFoundException, SQLException {

String url = "jdbc:odbc:";

Connection con = null;

con = DriverManager.getConnection(url + datasourcename, name, password);

return con;

}

// 通过SQLSERVER的三个驱动,连接SQLSERVER2000数据库

public static Connection getSQLServerCon(String name, String password)

throws ClassNotFoundException, SQLException {

String url = "jdbc:microsoft:sqlserver://127.0.0.1:1433";

Connection con = null;

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

con = DriverManager.getConnection(url, name, password);

return con;

}

// 通过ORACLE的驱动,连接ORACLE数据库

public static Connection getOracleCon(String name, String password)

throws ClassNotFoundException, SQLException {

Connection con = null;

Class.forName("oracle.jdbc.driver.OracleDriver");

// 获得连接 oracle数据库的端口号为:1521 数据服务器的名字叫goudan(作者外号)

String url = "jdbc:oracle:thin:@127.0.0.1:1521:goudan";

con = DriverManager.getConnection(url, name, password);

return con;

}

// 通过数据源获得连接

public static Connection getConnectionFromDataSource(String filePath)

throws javax.naming.NamingException, SQLException {

Connection con = null;

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);

DataSource ds = (DataSource) ctx.lookup(filePath);

con = ds.getConnection();

return con;

}

// 通过连接池获得连接

public static Connection getConnectionFromPooledDataSource(String filePath)

throws javax.naming.NamingException, SQLException {

Connection con = null;

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,

"com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);

ConnectionPoolDataSource ds = (ConnectionPoolDataSource) ctx

.lookup(filePath);

PooledConnection pc = ds.getPooledConnection();

con = pc.getConnection();

return con;

}

}

内容总结

? JDBCJava Database Connectivity)是Sun提供的一套数据库编程接口API,由Java语言编写的类、接口组成;通过这些类和接口JAVA可以方便的访问各种数据库中的数据。

? JDBC连接数据库的四种方式

? JDBC-ODBC桥,简单易用,只适用于WINDOW平台,适用于学习JDBC

? 部分Java驱动程序(native-API partly-Java Driver),少用到。

? 网络驱动程序(net-protocol all-java driver(JDBC Proxy)),应用比较广。

? 纯Java驱动程序(native-protocal all-Java driver,它直接与数据库进行通讯,最好的一种连接。

? JDBC的编程步骤

? 加载驱动程序;

? 建立到数据库的连接对象Connection

? 创建语句,Statement ,PreparedStatement,

CallableStatement,并将它们用于更新数据库或执行查询;

? 查询返回包含有已请求数据的ResultSet

? 通过SqlServer,Oracle的驱动程序连接数据库;

? JDBC数据源,连接池技术的实现原理及其优点

? 数据源技术的引入,可以提高数据库访问的灵活性

? 使用数据源,和安全有关的敏感信息,如用户名,密码甚至数据库服务器地址等信息只在一处编码。

? 使用连接池可以提高连接数据库的效率。

独立实践

1 写一个SQLSERVER连接池的实现,访问pub数据库。

2 写一个连接ORACLE数据库的实现,用scott用户登陆,操作emp

3 从控制台输入注册信息(用户名,密码,确认密码),写到数据库里面。

4 从控制台输入用户名和密码,在数据库中并验证其有效性。(提示:需要编写的类有:User, Validation,DbUtil)。

5 登陆成功后,从控制台输入一个表名,并从控制台打印该表里的数据。

第二十章:高级JDBC

学习目标

? 使用DDL,DML语言对数据库进行基本操作

? 预编译语句

? 使用事务

? 事务的级别控制

? 使用存储过程

? 操作元数据

? 可滚动的和可更新的结果集

? 批处理更新

? 字符大对象CLOB

? 二进制大对象BLOB

? RowSet 新特性

使用DDL,DML语言对数据库进行基本操作。

? 创建表并插入数据及修改数据:

import java.sql.Connection;

import java.sql.Statement;

public class CreateTable {

public static void main(String[] args) {

Connection con = null;

try {

// 通过连接池来获得一个连接

con = DBCon .getConnectionFromPooledDataSource("jdbcPool/mydatasource");

// 创建语句对象

Statement st = con.createStatement();

// 创建表的SQL语句

String sql = "create table student(id int,name char(30),age int)";

// 执行完SQL语句的结果

boolean b = st.execute(sql);

if (b) {

System.out.println("create success");

} else {

System.out.println("create fail");

}

// 插入数据到student

sql = "insert into student values(1,'andy',47)"

+ "insert into student values(2,'jacky',53)"

+ "insert into student values(3,'周润发',51)"

+ "insert into student values(4,'谢贤',60)";

// 执行完SQL语句的结果

b = st.execute(sql);

if (b) {

System.out.println("insert success");

} else {

System.out.println("create fail");

}

// 更新表数据

sql = "update student set name='刘德华' where id=1";

int rows = st.executeUpdate(sql);

// 如果更新成功,rows肯定是大于1的值

if (rows > 0)

System.out.println("update success");

else

System.out.println("update fail");

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

查询数据库里的数据

Statement对象的executeQuery()方法一般用于执行一个select语句,它只返回一个结果集,要想把查询结果最后显示给用户,必须对ResultSet对象进行处理。ResultSet对象包括一个由查询语句返回的一个表,这个表中包含所有的查询结果。对ResultSet对象的处理必须逐行进行。ResultSet对象维持一个指向当前行的指针(类似于Iterator的用法)。最初,这个指针指向第一行之前。ResultSetnext()方法使这个指针移向下一行。因此,第一次使用next()方法将指针指向结果集的第一行,这是可以对第一行的数据进行处理。处理完毕后,使用next()方法,将指针移向下一行,继续处理第二行数据。next()方法的返回值是一个boolean值,若为true,则说明指针成功地移向下一行,可以对该行进行处理。若返回值是false,则说明没有下一行,即结果集已经处理完毕。按从左至右的顺序对各列进行处理可以获得较高的执行效率。ResultSet接口的getXXX()方法可以从某列中获得结果,XXX表示JDBC的数据类型。

请看下例:

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.Statement;

public class ResultSetTest {

public static void main(String[] args) {

Connection con = null;

try {

con = DBCon

.getConnectionFromPooledDataSource("jdbcPool/mydatasource");

Statement st = con.createStatement();

String query = "select id,name from student";

// 获得一个结果集

ResultSet rs = st.executeQuery(query);

// 获得结果集的元数据(表及相关的信息)

ResultSetMetaData rsmt = rs.getMetaData();

// 得到结果集有几列

int num = rsmt.getColumnCount();

String[] columns = new String[num];

// 列的序号是从1开始的

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

columns[i] = rsmt.getColumnName(i + 1);

// 先输出列名

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

System.out.print(columns[i] + " ");

// 输出列名之后换行

System.out.println();

// 取出结果

while (rs.next()) {

// 输出每一行的值

for (int i = 1; i <= num; i++) {

String temp = rs.getString(i);

System.out.print(temp + " ");

}

System.out.println();

}

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

// 用完后要关闭连接,释放资源

if (con != null)

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

输出结果为:(Eclipse环境)

预编译语句(PreparedStatement

Statement对象在每次执行SQL语句时都将该语句传给数据库,在多次执行同一语句时,这样做效率较低。这时可以使用PreparedStatement对象.如果数据库支持预编译,它可以将SQL语句传给数据库作预编译,以后每次执行这个SQL语句时,速度就可以提高很多。如果数据库不支持预编译,则在语句执行时,才将其传给数据库。这对用户来说是透明的。PreparedStatement对象的SQL语句还可以接受参数。在语句中指出需要接受哪些参数,然后进行预编译。在每一次执行时,可以给SQL语句传输不同的参数,这样就大大提高了灵活性。PreparedStatement接口是Statement接口派生的子接口,因此它可以使用Statement接口中的方法。

代码

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.Statement;

public class PrepareStatementTest {

public static void main(String[] args) {

Connection con = null;

try {

con = DBCon

.getConnectionFromPooledDataSource("jdbcPool/mydatasource");

// 创建修改表的PrepareStatement SQL语句

String sql = "update student set name=? where id=?";

// 创建预编译语句对象

PreparedStatement st = con.prepareStatement(sql);

String[] names = new String[] { "梁朝伟", "贝壳汗母", "小罗", "霍元甲" };

for (int i = 0; i < names.length; i++) {

st.setString(1, names[i]);

st.setInt(2, i + 1);

st.executeUpdate();

}

st.close();

// 打印执行完SQL语句的结果

Statement stq = con.createStatement();

// 定义一个查询的SQL语句

String query = "select id,name from student";

// 获得一个结果集

ResultSet rs = stq.executeQuery(query);

// 获得结果集的元数据(表及的信息)

ResultSetMetaData rsmt = rs.getMetaData();

// 得到有几列,保存在num变量里

int num = rsmt.getColumnCount();

String[] columns = new String[num];

// 列的序号是从1开始的

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

columns[i] = rsmt.getColumnName(i + 1);

// 先输出列名

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

System.out.print(columns[i] + " ");

// 输出列名之后换行

System.out.println();

// 取出结果

while (rs.next()) {

// 输出每一行的值

for (int i = 1; i <= num; i++) {

String temp = rs.getString(i);

System.out.print(temp + " ");

}

System.out.println();

}

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

使用事务

下面介绍如何把一系列语句组织成一个事务?如果事务中所有命令都能正确执行,就可以提交这个事务;否则,如果事务中有一个命令出现错误,回滚这个事务,并返回到提交之前的状态,好像什么也没有发生。把命令组合成事务的主要原因是保证数据库的完整性。对于一个事务而言,要么事务中语句全部得到正确执行,事务就可被提交了,要么它中间出现错误。后一种情况,可以调用rollback()方法,数据库将自动放弃上一次提交事务以来的全部变化。一个数据库连接的缺省模式是autocommit模式,每个SQL命令一执行就会提交给数据库。一旦某个命令已提交,就不能把它回退。可以用Connection接口的getAutocommit()方法,检验数据库的目前自动提交模式设置。用命令con.setAutoCommit(false)方法关闭自动提交模式。用con.commit()命令提交事务。用con.rollback()回滚一个事务。

代码

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.SQLException;

import javax.naming.NamingException;

public class UseTran {

public static void main(String[] args) {

Connection con = null;// 方法里临时变量要手动初始化

PreparedStatement updateAge = null;

String updateString = "update student "

+ "set age = ? where name like ?";

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

updateAge = con.prepareStatement(updateString);

int[] age = { 45, 39, 25, 96 };

String[] names = { "梁朝伟%", "贝壳汗母%", "小罗%", "霍元甲%" };

int len = age.length;

// 设置事务提交模式为非自动提交

con.setAutoCommit(false);

for (int i = 0; i < len; i++) {

updateAge.setInt(1, age[i]);

updateAge.setString(2, names[i]);

updateAge.executeUpdate();

}

// 上面执行的语句,如果不出现异常则提交 SQL 语句

con.commit();

System.out.println("update success!");

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException: " + ex.getMessage());

if (con != null) {

try {

System.err.print("Transaction is being ");

System.err.println("rolled back");

// 如果出现异常则事务回滚

con.rollback();

} catch (SQLException excep) {

System.err.print("SQLException: ");

System.err.println(excep.getMessage());

}

}

} finally {// 不管发生不发生异常,要关闭连接,释放资源

try {

if (con != null) {

con.close();

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

事务的级别控制

SQL术语中,事务是逻辑工作单元(logical unit of work,LUW)构成的一个或多个语句。这在某种含义上意味着,一切都是事务。不过,通常而言,术语事务用来表示或全或无的系列操作;也就是说,要么一切十分成功,要么什么也没有发生。典型的事务是从银行帐户提款,并存放到另一个。只要提款完成,金额就消失了。另一个范例是复式薄记记帐法中的借方和贷方:借方和贷方都必须完成。

在单用户模式,事务非常容易理解-它们只是和保存或忘记应用程序的状态有关。在多用户模式中,事务变得复杂多了。多用户事务的经典说明是银行帐户,其中一个应用程序试图在借记帐户,同时另一个应用程序试图贷记同一个帐户。在前面已经介绍过多线程编程(并发编程)。类似的,事务的根本问题是保持两个事务相互隔离,否则一个应用程序就可能影响另一个,从而导致错误的程序状态。当处理多个访问相同数据的用户时,通常可能出现三种问题:

? 脏读:当应用程序使用了被另一个应用程序修改过的数据,而这个数据处于未提交状态时,就会发生脏读。第二个应用程序随后会请求回滚被其修改的数据。第一个事务使用的数据就会被损坏,或者"变脏"

? 不可重复的读:当一个事务获得了数据,而该数据随后被一个单独的事务所更改时,若第一个事务再次读取更改后的数据,就会发生不可重复的读。这样,第一个事务进行了一个不可重复的读。

? 虚读:当事务通过某种查询获取了数据,另一个事务修改了部分该数据,原来的事务第二次获取该数据时,就会发生虚读。第一个事务现在会有不同的结果集,它可能包含虚读。

为了解决与"多个线程请求相同数据"相关的问题,事务之间用锁相互隔开。多数主流的数据库支持不同类型的锁;因此,JDBC API支持不同类型的事务,它们由Connection对象指派或确定。在JDBC API中可以获得下列事务级别:

? TRANSACTION_NONE 说明不支持事务

? TRANSACTION_READ_UNCOMMITTED说明在提交前一个事务可以看到另一个事务的变化。这样脏读,不可重复的读和虚读都是允许的。

? TRANSACTION_READ_COMMITTED说明读取未提交的数据是不允许的。这个级别仍然允许不可重复的读和虚读产生。

? TRANSACTION_REPEATABLE_READ说明事务保证能够再次读取相同的数据而不会失败,但虚读仍然会出现。

? TRANSACTION_SERIALIZABLE是最高的事务级别,它防止脏读,不可重复读和虚读。

为什么不是所有事务都运行在TRANSACTION_SERIALIZABLE模式以保证最高程度的数据完整性呢?问题在于,和处理多线程编程有关的问题相似,事务保护的级别越高,性能损失就越大。假定您的数据库和JDBC驱动程序支持这个特性,则给定一个Connection对象,您可以明确地设置想要的事务级别:con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

您还可以确定当前事务的级别:

int transactionIsolation = con.getTransactionIsolation();

if (transactionIsolation == Connection.TRANSACTION_NONE)

System.out.println("当前事务级别为:TRANSACTION_NONE");

代码:

import java.sql.Connection;

import java.sql.SQLException;

import javax.naming.NamingException;

public class TranTest {

public static void main(String[] args) {

String jndiname = "jdbcPool/mydatasource";

Connection con = null;

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

int transactionIsolation = con.getTransactionIsolation();

if (transactionIsolation == Connection.TRANSACTION_NONE) {

System.out.println("当前事务级别为:TRANSACTION_NONE");

} else if (transactionIsolation == Connection.TRANSACTION_READ_COMMITTED) {

System.out.println("当前事务级别为:TRANSACTION_READ_COMMITTED");

} else if (transactionIsolation == Connection.TRANSACTION_READ_UNCOMMITTED) {

System.out.println("当前事务级别为:TRANSACTION_READ_UNCOMMITTED");

} else if (transactionIsolation == Connection.TRANSACTION_REPEATABLE_READ) {

System.out.println("当前事务级别为:TRANSACTION_REPEATABLE_READ");

} else if (transactionIsolation == Connection.TRANSACTION_SERIALIZABLE) {

System.out.println("当前事务级别为:TRANSACTION_SERIALIZABLE");

}

con.setAutoCommit(false);

con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

transactionIsolation = con.getTransactionIsolation();

if (transactionIsolation == Connection.TRANSACTION_NONE) {

System.out.println("当前事务级别为:TRANSACTION_NONE");

} else if (transactionIsolation == Connection.TRANSACTION_READ_COMMITTED) {

System.out.println("当前事务级别为:TRANSACTION_READ_COMMITTED");

} else if (transactionIsolation == Connection.TRANSACTION_READ_UNCOMMITTED) {

System.out.println("当前事务级别为:TRANSACTION_READ_UNCOMMITTED");

} else if (transactionIsolation == Connection.TRANSACTION_REPEATABLE_READ) {

System.out.println("当前事务级别为:TRANSACTION_REPEATABLE_READ");

} else if (transactionIsolation == Connection.TRANSACTION_SERIALIZABLE) {

System.out.println("当前事务级别为:TRANSACTION_SERIALIZABLE");

}

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException : " + ex.getMessage());

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

System.out.println("程序执行结束!");

}

}

使用存储过程

很多数据库都支持在数据库内部执行的函数。这种方法有几个好处,包括更快的性能和改进的安全性。这些函数称为存储过程。存储过程是用来封装SQL语句来完成一个完整的业务功能,类似于面向对象里面方法的概念。虽然它们通常是用SQL编写的,但也可以用数据库支持的任何编程语言编写。随着Java语言日趋流行,几个数据库厂商---OracleOracle数据库)和IBMdb2数据库)都起用了Java语言创建存储过程,还可以在不同数据库之间移动存储过程。

存储过程可以支持三种类型的参数:IN,OUTINOUT,这对于存储过程在数据库内部真正能做什么来说,带来了很大的灵活性。不管存储过程是用什么语言编写的,它都能以一种标准的方式从Java应用程序调用。

首先,您需要创建一个CallableStatement对象。为了标识存储过程和过程需要的参数的类型和数量,还要允许使用三种类型的调用。下面的清单说明了这三种类型(假定我们正在调用一个名为StudentList的存储过程):

? {call StudentList}如果过程不需要参数

? {call StudentList(?,?)}如果过程需要两个参数

? {?=call StudentList(?,?)}如果参数需要两个参数并返回一个

要想使用存储过程,首先应该创建一个存储过程!

代码的实现

import java.sql.Connection;

import java.sql.SQLException;

import java.sql.Statement;

import javax.naming.NamingException;

public class CreateStoredProceduresofSQLServer {

public static void main(String[] args) {

Connection con = null;

Statement stmt = null;

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

stmt = con.createStatement();

// 1.创建存储过程show_students

String createProcedure1 = "create procedure show_students " + "as " + "select id, name,age " + "from students " + "order by id";

// 删除数据库中存在的同名过程

stmt.executeUpdate("if exists(select name from sysobjects "

+ "where name='show_students'and type='p') "

+ "drop procedure show_students");

stmt.executeUpdate(createProcedure1);

// 2.创建储存过程onestudent

String createProcedure2 = "create procedure onestudent "

+ "@stu_id int = null, " + "@name varchar(20) output, "

+ "@age int output " + "as " + "if @stu_id = null "

+ "BEGIN "

+ " PRINT 'ERROR: You must specify a stu_id value.' "

+ " RETURN "

+ "END "

+

// Get the sales for the specified cof_name and " +

// assign it to the output parameter. " +

"SELECT @name = name, @age = age " + "FROM coffees "

+ "WHERE id = @stu_id " + "RETURN ";

stmt.executeUpdate("if exists(select name from sysobjects "

+ "where name='onestudent'and type='p') "

+ "drop procedure onestudent");

stmt.executeUpdate(createProcedure2);

// 3.创建函数

String createProcedure3 = "CREATE FUNCTION pubuse.ageofstu "

+

// Input cof_name

"(@stu_name varchar(20)) "

+ "RETURNS int "

+ // return sales

"AS " + "BEGIN " + " DECLARE @age int "

+ " SELECT @age = age " + " FROM student "

+ " WHERE name like @stu_name " + " RETURN @age "

+ "END ";

stmt.executeUpdate("if exists(select name from sysobjects "

+ "where name='ageofstu') "

+ "drop function pubuse.ageofstu");

stmt.executeUpdate(createProcedure3);

stmt.close();

con.close();

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException : " + ex.getMessage());

}

System.out.println("程序执行结束!");

}

}

下面是使用存储过程的代码:

import java.sql.CallableStatement;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Types;

import javax.naming.NamingException;

public class InvokeStoreProcdureofSQLServer {

public static void main(String[] args) {

Connection con = null;

String jndiname = "jdbcPool/mydatasource";

// 定义调用存储过程和函数的 SQL 语句

String callSQL1 = "{call show_students}";

String callSQL2 = "{call onestudent(?,?,?)}";

String callSQL3 = "{? = call ageofstu(?)}";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

// 调用第 1 个存储过程

CallableStatement cs = con.prepareCall(callSQL1);

ResultSet rs = cs.executeQuery();

System.out.println("第一个存储过程调用结果");

while (rs.next()) {

String id = rs.getString(1);

String name = rs.getString(2);

String age = rs.getString(3);

System.out.println(id + " " + name + " " + age);

}

// 调用第 2 个存储过程

cs = con.prepareCall(callSQL2);

cs.setString(1, "2");

cs.registerOutParameter(2, Types.CHAR);

cs.registerOutParameter(3, Types.INTEGER);

cs.execute();

String name = cs.getString(2);

int age = cs.getInt(3);

System.out.println("第二个存储过程调用结果");

System.out.println("This student's name is " + name

+ " and age is " + age);

// 调用函数

cs = con.prepareCall(callSQL3);

cs.setString(2, "小罗");

cs.registerOutParameter(1, Types.INTEGER);

cs.execute();

age = cs.getInt(1);

System.out.println("函数调用结果");

System.out.println("This student's name is " + age + ".");

cs.close();

con.close();

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException : " + ex.getMessage());

}

System.out.println("调用结束!");

}

}

操作元数据

JDBC Meta Data(元数据)----描述数据的数据。它的接口有:

? DatabaseMetaData(数据库元数据)

? ResultSetMetaData(结果集元数据)

DatabaseMetaData(数据库元数据)

在对数据源进行连接以后,得到一个Connection对象,可以从这个对象获得有关数据源的各种信息,包括关于数据库中的各个表,表中的各个列,数据类型和存储过程等各方面的信息。根据这些信息,JDBC程序可以访问一个事先并不了解的数据库。获取这些信息的方法都是在DatabaseMetaData的对象上实现的,而DatabaseMetaData对象是在Connection对象之上获得的。

下面的语句可以在一个连接的基础上创建一个DatabaseMetaData 对象:

DatabaseMetaData dm=con.getMetaData();

数据库的一些常用信息可通过DatabaseMetaData对象的下列方法获得。

? getURL()//返回一个String对象,代表数据库的URL.

? getUserName()//返回此连接使用的数据库的用户名

? isReadOnly()//返回一个boolean值,指示数据库是否只允许读操作。

? getDatabaseProduceName()//返回数据库的产品名称

? getDatabaseProduceVersion()//返回数据库的版本号

? getDriverName()//返回驱动程序的名称。

? getDriverVersion()//返回驱动程序的版本号

代码示例:

import java.sql.Connection;

import java.sql.DatabaseMetaData;

import java.sql.ResultSet;

import java.sql.SQLException;

import javax.naming.NamingException;

public class DataBaseMetaDataTest {

public static void main(String[] args) {

Connection con = null;

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

// 测试数据库信息

DatabaseMetaData dm = con.getMetaData();

System.out.println("1. 数据库的基本信息");

System.out.println("Database is " + dm.getDatabaseProductName());

System.out.println("Database version is "

+ dm.getDatabaseProductVersion());

System.out.println("JDBC Driver is " + dm.getDriverName());

System.out.println("JDBC driver version is "

+ dm.getDriverVersion());

// 获取数据库中目录(数据库)的信息

System.out.println("2. 数据库中目录(数据库)的信息");

ResultSet catalogs = dm.getCatalogs();

while (catalogs.next()) {

System.out.println(catalogs.getString(1));

}

// 获取数据库中模式的信息

System.out.println("3. 数据库中模式的信息");

ResultSet schemas = dm.getSchemas();

while (schemas.next()) {

System.out.println(schemas.getString(1));

}

// 获取数据库中各个表的情况

System.out.println("4. 数据库中各个表的信息");

ResultSet tables = dm.getTables("pubs", null, null, null);

while (tables.next()) {

for (int i = 0; i < 5; i++) {

System.out.print(tables.getString(i + 1));

System.out.print(" | ");

}

System.out.println();

}

// 获取数据库表中各个列的信息

System.out.println("5. 数据库表中各个列的信息");

ResultSet columns = dm.getColumns(null, null,

"student", null);

while (columns.next()) {

for (int i = 0; i < 18; i++) {

System.out.print(columns.getString(i + 1));

System.out.print(" | ");

}

System.out.println();

}

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException : " + ex.getMessage());

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

e.printStackTrace();

}

}

System.out.println("程序结束!!!");

}

}

ResultSetMetaData(结果集元数据)

根据结果集的元数据,可以得到一个查询结果集关于查询表中列的个数,各个列名,类型以及各个列的宽度等。ResultSetMetaData的对象可以由ResultSet对象的getMetaData()方法得到。

ResultSetMetaData对象的常用方法如下:

? ResultSet rs=stmt.executeQuery();

? ResultSetMetaData rsmd=rs.getMetaData();

? rsmd.getColumnCount()//返回ResultSet对象的列数。

? rsmd.getColumnDisplaySize(int column);//返回column指定的列的最大宽度。

? rsmd.getColumnLabel(int column)//返回column指定列的标签。

? rsmd.getColumnName(int column)//返回column指定列的列名。

请看下例:

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

import java.sql.Statement;

import javax.naming.NamingException;

public class ResultSetMetaDataTest {

public static void main(String[] args) {

Statement stmt = null;

ResultSet rs = null;

Connection con = null;

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

stmt = con.createStatement();

String querySQL1 = "select * from student";

rs = stmt.executeQuery(querySQL1);

// 提取结果集的元数据:

ResultSetMetaData rsmd = rs.getMetaData();

int colCount = rsmd.getColumnCount();

String[] columnNames = new String[colCount];

String[] columnLabels = new String[colCount];

int[] columnTypes = new int[colCount];

for (int i = 0; i < colCount; i++) {

columnNames[i] = rsmd.getColumnName(i + 1);

columnLabels[i] = rsmd.getColumnLabel(i + 1);

columnTypes[i] = rsmd.getColumnType(i + 1);

}

System.out.println();

System.out.println("提取的数据如下:");

System.out.println();

System.out .println("----------------------");

for (int i = 0; i < colCount; i++) {

System.out.print(columnLabels[i] + "\t");

}

System.out.println();

for (int i = 0; i < colCount; i++) {

System.out.print(columnNames[i] + "\t");

}

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.err.println("SQLException: " + ex.getMessage());

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

}

}

System.out.println("程序结束!!!");

}

}

可滚动的和可更新的结果集

可滚动的结果集表示在结果集中前后滚动显示数据。可更新的结果集表示可以通过结果集来更新数据库里的数据。

可滚动的ResultSet可以使用以下方法:

? absolute()

? afterLast()

? beforeFirst()

? first()

? getRow()

? isAfterLast()

? isBeforeFirst()

? isFirst()

? isLast()

? last()

? moveToCurrentRow()//仅对可更新的ResultSet有效

? previous()

? relative(int rows)//将游标移动相对量的行,或者正方向或者负方向

为了让查询返回可滚动的结果集,你必须用

Statement stmt=con.createStatement(type,concurrency);

得到一个不同的Statement对象。

对于PreparedStatement,调用

PreparedStatement stmt=con.preparedStatement(command,type,concurrency);

例如,如果你只想在一个结果总来回滚动,而不想编辑它的数据,用下列语句:

Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

所有调用方法:

ResultSet rs=stmt.executeQuery(query);

返回的结果集都是可滚动的,一个可滚动的结果集用一个光标指示目前的位置。

ResultSet类型值

? TYPE_FORWARD_ONLY 结果集不可滚动

? TYPE_SCROLL_INSENSITIVE 结果集可滚动,但不反映数据库的变化

? TYPE_SCROLL_SENSITIVE 结果集可滚动,并反映数据库的变化

ResultSet同步值

? CONCUR_READ_ONLY 不能用结果集更新数据库

? CONCUR_UPDATABLE 可以用结果集更新数据库

如果你想编辑ResultSet类型数据,并让这些变化在数据库中自动体现出来,就需要创建一个可更新的结果集。可更新的结果集不一定是可滚动的,但如果想让用户编辑数据,通常这个结果集也是可滚动的。为了得到可更新的结果集,构造语句如下:

Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

接着,调用executeQuery()就可返回可更新的结果集。

注意:不是所有的查询都可返回可更新的结果集。如果某个查询涉及多个表,那么它的返回结果可能是不可更新的。但如果查询只涉及一个表或涉及用主关键字连接的多个表,那么返回结果是可更新的,这可以用ResultSetgetConcurrency()方法验证。

SQL类型相对应,JDBC 2.0为每个数据类型提供了与它相应的updateXxx()方法,如updateDouble(),updateString()等等。

updateXxx()方法只能改变当前光标的记录值,而不能改变数据库中数据。调用updateRow()方法把修改后的数据保存到数据库中。如果移动到另一行之前,没有调用updateRow()方法,本行中所有的更新将被撤消。也可以调用cancelRowUpdates()方法,撤消本行中的所有修改。

例如:

String query="select * from student";

ResultSet rs=stmt.executeQuery(query);

while(rs.next()){

double age=rs.getInt("age");

rs.updateInt("age",age+1);

rs.updateRow();

往数据库中添加一个新记录:

? 首先用moveToInsertRow()方法把光标移动到指定位置(它称为插入行)。

? 接着,调用 updateXxx()方法,在插入位置创建一个新行。

? 最后,调用insertRow()方法,把这个新行发送给数据库。插入完成后,调用

moveToCourrentRow()方法把光标移回调用moveToInsertRow()方法以前的位置。

例:

rs.moveToInsertRow();

rs.updateString("name","李雨春");

rs.updateInt("age",21);

rs.insertRow();

rs.moveToCurrentRow();

调用下面的方法,可以删除位于光标下面的一行:

rs.deleteRow();

deleteRow() 方法会立即把这行从结果集和数据库中删除。

代码的实现

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import javax.naming.NamingException;

public class ResultSetDemo {

public static void main(String[] args) {

Connection con = null;

Statement stmt = null;

ResultSet rs = null;

String querySQL = "select name,age from student";

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,

ResultSet.CONCUR_UPDATABLE);

rs = stmt.executeQuery(querySQL);

// 显示修改之前的数据

System.out.println("修改之前的数据:");

while (rs.next()) {

for (int i = 1; i <= 2; i++) {

System.out.print(rs.getObject(i));

System.out.print(" ");

}

System.out.println();

}

// 过年了,把记录里年龄都加1,虽然写一个SQL语句就可以实现

// update student set age=age+1

// 但是为了测试rs的用法我们还使用JDBC

rs.beforeFirst();

while (rs.next()) {

int age = rs.getInt("age");

rs.updateInt("age", age + 1);

rs.updateRow();

}

// 显示修改后的数据

rs = stmt.executeQuery(querySQL);

System.out.println("修改之后的数据:");

while (rs.next()) {

for (int i = 1; i <= 2; i++) {

System.out.print(rs.getObject(i));

System.out.print(" ");

}

System.out.println();

}

// 增加一条数据

rs.moveToInsertRow();

rs.updateString("name", "李雨春");

rs.updateInt("age", 21);

rs.insertRow();

rs.moveToCurrentRow();

// 显示增加后的数据

System.out.println("增加之后的数据:");

rs = stmt.executeQuery("select name,age from student");

while (rs.next()) {

for (int i = 1; i <= 2; i++) {

System.out.print(rs.getObject(i));

System.out.print(" ");

}

System.out.println();

}

// 删除最后一行数据

if (rs.last())

rs.deleteRow();

System.out.println("删除之后的数据:");

rs = stmt.executeQuery(querySQL);

while (rs.next()) {

for (int i = 1; i <= 2; i++) {

System.out.print(rs.getObject(i));

System.out.print(" ");

}

System.out.println();

}

stmt.close();

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} catch (SQLException ex) {

System.out.println("\n--- SQLException caught ---\n");

while (ex != null) {

System.out.println("Message: " + ex.getMessage());

System.out.println("SQLState: " + ex.getSQLState());

System.out.println("ErrorCode: " + ex.getErrorCode());

ex = ex.getNextException();

System.out.println();

}

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

}

}

System.out.println();

System.out.println("End.");

}

}

打印结果如图

批处理更新

批处理更新功能可以一次向数据库提交多个更新操作,要求数据库进行处理。一起提交多个更新(而非一个一个单独地提交更新)在某些情况下将大大提高性能。

可以利用Statement,PreparedStatement,CallableStatement对象来提交批处理更新。

代码的实现

import java.sql.BatchUpdateException;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import javax.naming.NamingException;

public class BatchUpdateTest {

public static void main(String[] args) {

Connection con = null;

Statement stmt = null;

String jndiname = "jdbcPool/mydatasource";

try {

con = DBCon.getConnectionFromPooledDataSource(jndiname);

stmt = con.createStatement();

con.setAutoCommit(false);

stmt.addBatch("insert into student values('拉登',56)");

stmt.addBatch("insert into student values('张学友',49)");

stmt.addBatch("insert into student values('刘德华',51)");

stmt.addBatch("insert into student values('猪八戒',128)");

int[] updateCounts = stmt.executeBatch();

con.commit();

for (int i = 0; i < updateCounts.length; i++) {

System.out.println("Line" + (i + 1) + " update counts is : "

+ updateCounts[i]);

}

con.setAutoCommit(true);

ResultSet uprs = stmt.executeQuery("select * from student");

System.out.println("table student after insertion:");

while (uprs.next()) {

String name = uprs.getString("name");

int age = uprs.getInt("age");

System.out.print(name + " " + age);

System.out.println();

}

uprs.close();

stmt.close();

} catch (BatchUpdateException b) {

System.err.println("SQLException: " + b.getMessage());

System.err.println("SQLState: " + b.getSQLState());

System.err.println("Message: " + b.getMessage());

System.err.println("Vendor: " + b.getErrorCode());

System.err.print("Update counts: ");

int[] updateCounts = b.getUpdateCounts();

for (int i = 0; i < updateCounts.length; i++) {

System.err.print(updateCounts[i] + " ");

}

} catch (SQLException ex) {

System.err.println("SQLException: " + ex.getMessage());

System.err.println("SQLState: " + ex.getSQLState());

System.err.println("Message: " + ex.getMessage());

System.err.println("Vendor: " + ex.getErrorCode());

} catch (NamingException ex) {

System.err.println("Name Not Bound : " + ex.getMessage());

} finally {

try {

if (con != null)

con.close();

} catch (Exception e) {

}

}

System.out.println("程序执行结束!");

}

}

打印结果如图

字符大对象CLOB

mssqlserver 2000里面并没有字符大对象这个数据类型,但是可以通过nchar类型来代替字符大对象数据,但是只能存放3k字符以下的。在ORACLE数据库里面存在字符大对象CLOB数据类型。该数据类型用来存放文本类型的数据。

SQL代码:

ORACLE的实现,在运行该例子之前,首先把ORACLE的驱动文件classes12.jar加到classpath里面。然后打开ORACLE数据库,建立一张表。SQL语句如下:

CREATE TABLE MESSAGES(ID VARCHAR2(20),TITLE VARCHAR2(30),AUTHOR VARCHAR(20),MESSAGE CLOB);

JAVA代码:

package com.app;

import java.io.File;

import java.io.FileInputStream;

import java.io.OutputStream;

import java.sql.ResultSet;

import java.sql.Statement;

import java.sql.Connection;

import oracle.sql.CLOB;

public class OracleClob {

public static void main(String[] args) {

String jndiname = "jdbcPool/mydatasource";

Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);

String insertSQL = "Insert INTO messages VALUES('1001','my message', 'xsw', empty_clob())";

try {

con.setAutoCommit(false);

Statement stmt = con.createStatement();

stmt.execute(insertSQL);

String cmd = "SELECT message FROM messages WHERE id='1001'";

ResultSet rset = stmt.executeQuery(cmd);

rset.next();

CLOB clob = (CLOB) rset.getClob(1);

File file = new File("myfile.txt");

FileInputStream fis = new FileInputStream(file);

OutputStream outstream = clob.getAsciiOutputStream();

int size = clob.getBufferSize();

byte[] buffer = new byte[size];

int length = -1;

while ((length = fis.read(buffer)) != -1)

outstream.write(buffer, 0, length);

fis.close();

outstream.close();

con.commit();

con.setAutoCommit(true);

} catch (Exception e) {

e.printStackTrace();

}

}

}

下面看下MSSQLSERVER数据库的实现(首先在查询分析器里面运行下列SQL语句:

CREATE TABLE messages (

id char(30),

title char(40) ,

author char(20) ,

message ntext

)

然后运行下面的代码:

package com.app;

import java.io.File;

import java.io.FileInputStream;

import java.sql.Connection;

import java.sql.PreparedStatement;

public class MSSQLClob {

public static void main(String[] args) {

String jndiname = "jdbcPool/mydatasource";

Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);

String insertSQL = "Insert INTO messages VALUES(?,?,?,?)";

PreparedStatement stmt = con.prepareStatement(insertSQL);

File file = new File("myfile.txt");

FileInputStream fis = new FileInputStream(file);

stmt.setString(1, "1001");

stmt.setString(2, "my message");

stmt.setString(3, "xsw");

stmt.setAsciiStream(4, fis, (int) file.length());

stmt.executeUpdate();

fis.close();

}

}

二进制大对象BLOB

该数据类型是用来存放图片文件的。在MSSQLSERVER 2000没有该数据类型,那么可以用binary数据类型代替,但是该数据只能存放小7k的图片。在ORACLE数据库里,有BLOB数据类型,来直接存放图片文件,直接操作该数据类型。对不支持BLOB数据类型的MSSQLSERVER 2000数据库,我们可以用二进制流进行读取图片文件。

ORACLE的实现:

SQL语句:

CREATE TABLE AUTHORS(AUTHOR VARCHAR2(40),PHOTO BLOB)

JAVA代码:

package com.app;

import java.io.File;

import java.io.FileInputStream;

import java.io.OutputStream;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.Statement;

import oracle.sql.BLOB;

public class OracleBlob {

public static void main(String[] args) {

String jndiname = "jdbcPool/mydatasource";

Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);

String insertSQL = "Insert INTO authors VALUES('1001', empty_blob())";

try {

con.setAutoCommit(false);

Statement stmt = con.createStatement();

stmt.execute(insertSQL);

String cmd = "SELECT photo FROM authors WHERE author='1001'";

ResultSet rset = stmt.executeQuery(cmd);

rset.next();

BLOB blob = (BLOB) rset.getBlob(1);

File file = new File("mypic.jpg");

FileInputStream fis = new FileInputStream(file);

OutputStream outstream = blob.getAsciiOutputStream();

int size = blob.getBufferSize();

byte[] buffer = new byte[size];

int length = -1;

while ((length = fis.read(buffer)) != -1)

outstream.write(buffer, 0, length);

fis.close();

outstream.close();

con.commit();

con.setAutoCommit(true);

} catch (Exception e) {

e.printStackTrace();

}

}

}

MSSQLSERVER 2000的实现

SQL语句:

CREATE TABLE authors(

author char(50) ,

photo image NOT NULL

)

代码:

package com.app;

import java.io.File;

import java.io.FileInputStream;

import java.sql.Connection;

import java.sql.PreparedStatement;

public class MSSQLBlob {

public static void main(String[] args) {

String jndiname = "jdbcPool/mydatasource";

Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);

String insertSQL = "Insert INTO authors VALUES(?,?)";

PreparedStatement stmt = con.prepareStatement(insertSQL);

File file = new File("mypic.jpg");

FileInputStream fis = new FileInputStream(file);

stmt.setString(1, "1001");

stmt.setBinaryStream(2, fis, (int) file.length());

stmt.executeUpdate();

fis.close();

}

}

RowSet 新特性

Java 5Java Database Connectivity (JDBC)方面加强了支持,其中加入了新的包javax.sql.rowsetjavax.sql.rowset.serialjavax.sql.rowset.spi。从RowSet接口继承规定了五个新的接口:

? CachedRowSet:可以不用与数据源建立长期的连接,只有当从数据库读取数据或是往数据库写入数据的时候才会与数据库建立连接,它提供了一种轻量级的访问数据库的方式,其数据均存在内存中。

? JdbcRowSet:对ResultSet的对象进行包装,使得可以将ResultSet对象做为一个JavaBeans 组件。

? FilteredRowSet:继承自CachedRowSet,可以根据设置条件得到数据的子集。

? JoinRowSet:继承自CachedRowSet,可以将多个RowSet对象进行SQL Join语句的合并。

? WebRowSet:继承自CachedRowSet,可以将WebRowSet对象输出成XML格式。

CachedRowSet

CachedRowSet可以通过调用populate(ResuletSet rs)来生成数据,一旦获得数据,CachedRowSet就可以断开与数据库的连接,直到往数据库写入数据的时候才需建立连接。

下面的代码演示了如何根据ResultSet建立一个CachedRowSet对象:

package com.itjob;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import javax.sql.rowset.CachedRowSet;

import com.sun.rowset.CachedRowSetImpl;

public class CachedRSTest {

private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";

private final static String URL = "jdbc:odbc:myodbc";

private final static String USER = "student";

private final static String PASSWORD = "student";

public static void main(String[] args) throws InstantiationException,

IllegalAccessException, ClassNotFoundException, SQLException {

Class.forName(DRIVER);

Connection con = DriverManager.getConnection(URL, USER, PASSWORD);

Statement stmt = con.createStatement();

ResultSet rs = stmt.executeQuery("SELECT * FROM student");

CachedRowSet crset = new CachedRowSetImpl();

crset.populate(rs);

rs.close();

stmt.close();

con.close();

System.out.println("name");

while (crset.next()) {

System.out.println(crset.getString("name"));

}

}

}

JdbcRowSet

JdbcRowSet功能与ResultSet类似,与CachedRowSet不同,JdbcRowSet在操作时保持与数据库的连接。可以将与数据库连接的URL,用户名,密码以及执行的SQL语句通过setXXX形式绑定。另外,JdbcRowSet返回的结果默认是可以上下滚动和可更新的,当然这需要数据库厂商提供的JDBC Driver支持。下面的代码演示了如何通过set方法设定数据库连接参数,以及如何操作JdbcRowSet对象。

package com.itjob;

import java.sql.SQLException;

import javax.sql.rowset.JdbcRowSet;

import com.sun.rowset.JdbcRowSetImpl;

public class JdbcRSDemo {

private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";

private final static String URL = "jdbc:odbc:myodbc";

private final static String USER = "student";

private final static String PASSWORD = "student";

public static void main(String[] args) throws SQLException, ClassNotFoundException {

Class.forName(DRIVER);

JdbcRowSet jrs = new JdbcRowSetImpl();

// 设置连接数据库的URL

jrs.setUrl(URL);

// 设置连接数据库的用户名

jrs.setUsername(USER);

// 设置连接数据库的密码

jrs.setPassword(PASSWORD);

// 设置执行数据库的SQL语句

jrs.setCommand("select * from student");

jrs.execute();

System.out.println("name");

while (jrs.next()) {

System.out.println(jrs.getString("name"));

}

jrs.close();

}

}

FilteredRowSet

FilteredRowSet接口中规定了可以设定过滤器,其过滤接口为Predicate接口,必须实现Predicate接口中的evaluate方法。具体的代码如下:

package com.itjob;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import javax.sql.rowset.CachedRowSet;

import javax.sql.rowset.FilteredRowSet;

import com.sun.rowset.CachedRowSetImpl;

import com.sun.rowset.FilteredRowSetImpl;

public class FilteredRSDemo {

private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";

private final static String URL = "jdbc:odbc:myodbc";

private final static String USER = "student";

private final static String PASSWORD = "student";

public static void main(String[] args) throws InstantiationException,

IllegalAccessException, ClassNotFoundException, SQLException {

Class.forName(DRIVER).newInstance();

Connection con = DriverManager.getConnection(URL, USER, PASSWORD);

Statement stmt = con.createStatement();

ResultSet rs = stmt.executeQuery("SELECT * FROM student");

FilteredRowSet frs = new FilteredRowSetImpl();

frs.populate(rs);

con.close();

MyDBFilter filter = new MyDBFilter(1, 4);

frs.setFilter(filter);

System.out.println("name");

while (frs.next()) {

System.out.println(frs.getString("name"));

}

frs.close();

}

}

package com.itjob;

import java.sql.SQLException;

import javax.sql.RowSet;

import javax.sql.rowset.CachedRowSet;

import javax.sql.rowset.Predicate;

class MyDBFilter implements Predicate {

private int low;

private int high;

public MyDBFilter(int low, int high) {

this.low = low;

this.high = high;

}

public boolean evaluate(RowSet rs) {

CachedRowSet crs=(CachedRowSet)rs;

//如果idlowhigh之间返回真

try {

int idValue = crs.getInt("id");

if (low < idValue && idValue < high) {

return true;

}

} catch (SQLException e) {

}

return false;

}

public boolean evaluate(Object arg0, int arg1) throws SQLException {

return false;

}

public boolean evaluate(Object arg0, String arg1) throws SQLException {

return false;

}

}

WebRowSet

WebRowSet继承自CachedRowSet,支持XML格式的查询,更新等操作,下面的代码将WebRowSet对象输出成XML格式到文件。

package com.itjob;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import javax.sql.rowset.WebRowSet;

import com.sun.rowset.WebRowSetImpl;

public class WebRSDemo {

private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";

private final static String URL = "jdbc:odbc:myodbc";

private final static String USER = "student";

private final static String PASSWORD = "student";

public static void main(String[] args) throws InstantiationException,

IllegalAccessException, ClassNotFoundException, SQLException, IOException {

Class.forName(DRIVER).newInstance();

Connection con = DriverManager.getConnection(URL, USER, PASSWORD);

Statement stmt = con.createStatement();

ResultSet rs = stmt.executeQuery("SELECT * FROM student");

// 关闭与数据库的连接

con.close();

WebRowSet wrs = new WebRowSetImpl();

wrs.populate(rs);

FileOutputStream out = new FileOutputStream("data.xml");

wrs.writeXml(out);

wrs.close();

}

}

内容总结

? 对数据库进行SQL语句进行基本操作(包括create, insert, update,delete

? 使用预编译语句可以提高数据库访问效率及加强安全性

? 可以在应用程序级别使用事务

? 通过事务的级别控制,可以满足不同的业务方面的需求

? 使用存储过程优化查询,提高查询效率

? 操作元数据

? 可滚动的和可更新的结果集

? 批处理更新

? 字符大对象CLOB的用法

? 二进制大对象BLOB用法

独立实践

1 连接oracle数据库,建立个可更新的结果集,对scott用户下的emp表进行批处理更新年龄,姓名等字段。

2 连接sql 2000数据库,使用pubs数据库,建立一个带参数的方法,该方法通过输入表名,能动态打印出该表的记录(包括列名)。

3 写一个保存人简历的程序(有图片)。不要求显示输入和输出,全部通过代码来操作,但是要求从数据库可以看到记录。

4 通过事务操作来实现两个账户转账的过程。(要求自己建表)

5 综合所学知识,对scott用户下的emp表进行增、删、改、查操作。

第二十一章:XML基础

学习目标

? XML的概念

? 定义XML文档

? 命名空间(Naming Space

? XML 文档规则

? DTD

? SCHEMA

? 样式表(XSL

XML的概念

XML,称为可扩展标记语言(Extensible Markup Language),用来创建自定义标记的标记语言。它由万维网协会(W3C)创建,用来克服 HTML(即超文本标记语言(Hypertext Markup Language),它是所有网页的基础)的局限。和 HTML 一样,XML 基于 SGML ― 标准通用标记语言(Standard Generalized Markup Language)。XML 是为 Web 设计的。

XML是以数据为中心的,用来标记数据,说明数据是什么的。

应用领域为:

? 信息存储和交换领域,类似于数据管理系统,来存储数据。这种数据处理与硬件,软件没有太大的关系,就是一个文本文件,具有可移植性。处理XML数据的时候,通过JAVA代码写的解析器就可以存取XML数据。

? 在J2EE服务器用的比较多,在服务器端通过XML文件来进行处理数据业务。配置文件几乎全是XML文件。在EJBHibernateJAVA热门技术领域,XML的应用更加重要。

XMLHTML相同点:

? XMLHTML都是基于文本的标记语言。

XMLHTML差别:

? 在功能上最大的区别就是XML是用来存储、传输以及交换数据的,HTML是用来格式化和显示数据的。

? 在内容上的最大的区别是HTML里面的标记(markup)都是定义好的,XML里面的标记都是自定义的。比如:对于那些HTML中的标记中形如"用斜体显示数据"<i>…</i>)的地方,XML标签则表现为程序中的一个字段名。它为一个数据定义一个标记(label)来表示该数据。(如:<name>andy lau</name>)。注意:由于标识数据可以让你看出其中的意义(如何解释它,应该如何处理它),XML有时也被描述为一种能够指明数据意义(语义)的机制。

HTML是最成功的标记语言。您几乎可以在任何设备(从掌上电脑到大型机)上查看最简单的HTML标记,甚至可以用合适的工具将HTML标记转换成语音和其它格式。既然HTML成功了,为什么W3C(万维网协会)还要创建XML呢?为了回答这个问题,请查看下面这个文档:

<p>

<b>张三</b>

<br>

<br>西安电子科技大学:电子信息工程

</p>

HTML的问题在于它是为人设计的。即使不用浏览器查看上面的HTML文档,您和我也会知道那是某个人的信息。作为高级动物---人来讲:您和我具有理解大多数文档的含义和意图的智慧。但是遗憾的是机器不能做到。尽管这个文档中的标记告诉浏览器如何显示该信息,但标记没有告诉浏览器信息是什么?!您和我都知道它是一个人的信息,但计算机并不知道。要显示HTML,浏览器只需遵守HTML文档中的指令即可。段标记告诉浏览器在新的一行显示,并且通常在前面有一个空行,而两个换行标记则告诉浏览器前进到下一行,并且行之间没有空行。尽管浏览器出色地将文档格式化,但计算机仍不知道这个代表某个人的信息。

如下图在浏览器里的显示:

为了完成对样本HTML文档的讨论,请考虑从该信息中提取出该人是学哪个专业的?请看下面的算法:

从该文档中找到有两个<br>标记的段落,那么学校就是第二个换行标记的后面的第一个冒号后面的内容,尽管该算法对该文档起作用,但是不具备通用性。如果文档格式一改变,那么该算法就不生效了。

现在让我们来看一个样本XML文档。使用XML,您可以给文档中的标记赋予某种涵义。更重要的是,计算机也容易处理这样的信息。

请看下例:

<person>

<name>张三</name>

<sex></sex>

<daxue>西安电子科技大学</daxue>

<zhuanye>电子信息工程</zhuanye>

</person>

现在算法编写又简单又具有通用性:

我们只需要找到<zhuanye></zhuanye>标记之间的内容(技术上称为<zhuanye>元素),就可以从该文档抽取邮政编码。

定义XML文档

有三个通用术语用来描述XML文档的组成部分:标记,元素和属性。

? 标记是左尖括号(<)和右尖括号(>)之间的文本。有开始标记(例如:<zhuanye>)和结束标记(例如:</zhuanye>)。

? 元素是开始标记,结束标记以及位于二者之间的所有内容。在上面的样本中,<person>元素包含四个子元素<name>,<sex>,<daxue>,<zhuanye>.

? 属性是一个元素的开始标记中的名称-值对。如:

<tizhong danwei='kg'>80</tizhong> danwei <tizhong>元素的属性。

定义XML基本语法要求:

? XML文档必须包含在一个单一元素中。这个单一元素称为根元素,它包含文档中所有文本和所有其它元素。如:

<aihao>

play majiang

</aihao>

XML文档包含在一个单一元素<aihao>中。

下面是一个不包含单一根元素的文档:

<aihao>play pike</aihao>

<aihao>play majiang</aihao>

不管该文档可能包含什么信息,XML解析器都会拒绝它。

? XML元素不能重叠。下面是一些不合乎规则的标记:

<person>刘德华<age>28</person></age>

如果您在<person>元素中开始了<age>元素,则必须在<person>元素中结束<age>元素。

如:<person>刘德华<age>49</age></person>

? 不能省去任何结束标记。在下面的XML文档里面标记是不合乎规则的。因为没有任何结束标记。

<person>刘德华

<age>49

? 空元素:如果一个元素根本不包含标记,则称为空元素。如:

<tizhong danwei="kg"></tizhong>

<tizhong danwei="kg"/> 是等价的。

? 元素是区分大小写的 在HTML中,<b><B>是相同的;在XML中,它们是不同的。如果您试图用</B>标记去结束<b>元素,那么将会出错。

<b>我爱深圳市计算机行业协会!</B> ---------错误

<b>我爱深圳市计算机行业协会!</b> ---------正确

属性的规则:

? 属性必须有值

? 属性值必须用引号括起

请看下例:

<tizhong danwei></tizhong> ---错误(属性无值)

<tizhong danwei=kg></tizhong> --错误(属性值没有用引号括起)

<tizhong danwei='kg'></tizhong>----正确

<tizhong danwei="kg"></tizhong>----正确

属性值可以用单引号括起,也可以用双引号括起,但是要始终保持一致。如果属性值包含单引号或双引号,则您可以使用另一种引号来括起该值。如:

description="zhangsan's JAVA is very well",或者使用实体"代表双引号,使用实体'代表单引号。实体是一个符号(如:",XML解析器会用其它文本代替该符号(如: ")。

XML声明: 大多数XML文档以XML声明作为开始,它向解析器提供了关于文档的基本信息。XML 声明通常在XML 文档的第一行出现。XML 声明不是必选项,但是如果使用XML 声明,必须在文档的第一行,前面不得包含任何其他内容或空白。

? 下所示:

<?xml version="1.0" encoding="gb2312" standalone="no"?>

<?xml version="1.1" encoding="utf-8" ?>

声明最多可以包含三个名称-值对。

? version是使用的XML版本;目前最新版本为1.1

? encoding是该文档所使用的字符集。如没有指定encoding,XML解析器会假定字符在utf-8字符集中,这是一个几乎支持世界上所有语言的字符和象形文字的unicode标准。

? standalone(可以是yesno)定义了是否可以在不读取任何外部实体或外部DTD文档的情况下处理该文档。因为standalone="no"是缺省值,所以您很少会在XML声明中看到standalone

? 注释可以出现在文档的任何位置;它们甚至可以出现在根元素的前面或后面。注释以<!-- 开始,以 --> 结束。注释不能在结束部分以外包含双连字符 --;除此之外,注释可以包含任何内容。最重要的是,注释内的任何标记都忽略;如果您希望除去XML文档的一块较大部分,只需要用注释括住那个部分即可。(要恢复这个注释掉的部分,只需除去注释标记即可。)下面是包含注释的标记:

<!-- 这里是注释 -->

? 处理指令: XML文件还可以包含处理指令(PI Processing Instruction),这些指令可以将命令或信息传给正在处理XML数据的应用。

通常它用来传递信息给解析器的下游程序。一般写法如下:

<?xml:stylesheet href="style.css" type="text/css"?>

命名空间(Naming Space

XML 命名空间提供了一种避免元素命名冲突的方法。

命名冲突

因为XML文档中使用的元素不是固定的,那么两个不同的XML文档使用同一个名字来描述不同类型的元素的情况就可能发生。而这种情况又往往会导致命名冲突。请看下面两个例子

这个 XML 文档在table元素中携带了水果的信息:

<table>

<tr>

<td>Apples</td>

<td>Bananas</td>

</tr>

</table>

这个 XML 文档在table元素中携带了桌子的信息(家具,不能吃的哦):

<table>

<name> African Coffee Table </name>

<width>80</width>

<length>120</length>

</table>

如果上面两个XML文档片断碰巧在一起使用的话,那么将会出现命名冲突的情况。因为这两个片断都包含了<table>元素,而这两个table元素的定义与所包含的内容又各不相同。

使用前缀解决命名冲突问题

下面的XML文档在table元素中携带了信息:

<h:table>

<h:tr>

<h:td>Apples</h:td>

<h:td>Bananas</h:td>

</h:tr>

</h:table>

下面的XML文档携带了家具table的信息:

<f:table>

<f:name>African Coffee Table</f:name>

<f:width>80</f:width>

<f:length>120</f:length>

</f:table>

现在已经没有元素命名冲突的问题了,因为这两个文档对各自的table元素使用了不同的前缀,table元素在两个文档中分别是(<h:table> <f:table>)

通过使用前缀,我们创建了两个不同的table元素。

使用命名空间

下面的XML文档在table元素中携带了信息:

<h:table xmlns:h="http://www.w3.org/TR/html4/">

<h:tr>

<h:td>Apples</h:td>

<h:td>Bananas</h:td>

</h:tr>

</h:table>

下面的XML文档携带了家具table的信息:

<f:table xmlns:f="http://www.w3s.com/furniture">

<f:name>African Coffee Table</f:name>

<f:width>80</f:width>

<f:length>120</f:length>

</f:table>

在上面两个例子中除了使用前缀外,两个table元素都使用了xmlns属性,使元素和不同的命名空间关联到一起。

命名空间属性

命名空间属性一般放置在元素的开始标记处,其使用语法如下所示:

xmlns:namespace-prefix="namespace"

在上面的例子中,命名空间定义了一个Internet 地址:

xmlns:f="http://www.w3s.com/furniture"

W3C 命名规范声明命名空间本身就是一个统一资源标示符,Uniform Resource Identifier (URI)

当我们在元素的开始标记处使用命名空间时,该元素所有的子元素都将通过一个前缀与同一个命名空间相互关联。

注意:用来标识命名空间的网络地址并不被XML解析器调用,XML解析器不需要从这个网络地址中查找信息,该网络地址的作用仅仅是给命名空间一个唯一的名字,因此这个网络地址也可以是虚拟的,然而又很多公司经常把这个网络地址指向一个真实的Web页面,这个地址包含了关于当前命名空间更详细的信息。

可以访问http://www.w3.org/TR/html4/.

统一资源标识符

通用资源标识符Uniform Resource Identifier (URI)是一个标识网络资源的字符串。最普通的URI应该是统一资源定位符Uniform Resource Locator (URL)URL用于标识网络主机的地址。另一方面,另一个不常用的URI是通用资源名字Universal Resource Name (URN)。在我们的例子中,一般使用的是URLs

既然前面的例子使用的URL地址来标识命名空间,我们可以确信这个命名空间是唯一的。

默认的命名空间

定义一个默认的XML命名空间使得我们在子元素的开始标记中不需要使用前缀。他的语法如下所示:

<element xmlns="namespace">

下面的XML文档在table元素中包含了水果的信息:

<table xmlns="http://www.w3.org/TR/html4/">

<tr>

<td>Apples</td>

<td>Bananas</td>

</tr>

</table>

下面的XML文档包含了家具table的信息:

<table xmlns="http://www.w3s.com/furniture">

<name>African Coffee Table</name>

<width>80</width>

<length>120</length>

</table>

使用命名空间

文档开始使用XSL的时候,就会发现命名空间使用的是如此频繁。XSL样式单主要用于将XML文档转换成类似于HTML文件的格式。

如果看一下下面的XSL文档,就会发现有很多标记都是HTML标记。那些标记并不是HTML标记,是加了前缀的XSL,这个XSL前缀由命名空间"http://www.w3.org/TR/xsl"所标识:

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/xsl">

<xsl:template match="/">

<html>

<body>

<table border="2" bgcolor="yellow">

<tr>

<th>Title</th>

<th>Artist</th>

</tr>

<xsl:for-each select="CATALOG/CD">

<tr>

<td>

<xsl:value-of select="TITLE" />

</td>

<td>

<xsl:value-of select="ARTIST" />

</td>

</tr>

</xsl:for-each>

</table>

</body>

</html>

</xsl:template>

</xsl:stylesheet>

XML 文档规则

XML 文档规则是用来规定XML文档有那些标记组成,标记的多少,顺序,属性等方面的内容。也就是规定XML的格式及内容。目的是为了解析器解析该XML文档提供方便。XML的文档规则有两种:DTDXML Schema

DTDDocument Type Definition)文档类型定义

DTD定义可以在XML文档中出现的元素,这些元素出现的次序,它们可以如何相互嵌套以及XML文档结构的其它详细信息。

XML Schema

模式可以定义您能在DTD中使用的所有文档结构,还可以定义数据类型和比DTD更复杂的规则。

因此XML 文档可以分为三个类型:

? 格式良好的:满足XML的基本的语法特点,但是不遵守DTD或者XML Schema

? 有效的文档:即满足了XML的基本的语法特点,又遵守DTD或者 XML Schema

? 无效的文档:不满足XML的基本的语法特点。或者该文档不遵守DTD或者XML Schema

首先我们介绍下DTDDocument Type Definition)文档类型定义

XML强大之处在于可以定制自己的标记来替代预先定义的标记集。这里的最大问题在于如何让处理器明白这些自定义的标记,同时我们常说XML要求文档结构完整,这意味着我们必须在提供XML文件时提供它的语法规范。为此,XML需要有一个文档来定义标记的含义,元素相关的属性等,这样解析器就能够检查输入的数据是否正确。

DTD可以包含在XML文档中,也可以独立为一个文件。一个符合DTD验证的XML文档是有效的文档。

请看下例:(student.dtd)

<?xml version="1.0" encoding="UTF-8"?>

<!ELEMENT students (student*)>

<!ELEMENT student (name,age,address,phone?)>

<!ELEMENT name (#PCDATA)>

<!ELEMENT age (#PCDATA)>

<!ELEMENT address (#PCDATA)>

<!ELEMENT phone (#PCDATA)>

<!ATTLIST age xusui CDATA #IMPLIED>

DTD定义了样本文档中使用的所有元素。它定义了三个元素

? <students>元素包含零个或者多个<student>元素。

? <student>元素包含一个<name>,一个<age>,一个<address>,和一个可选的<phone>元素。也就是说<name>,<age>,<address>元素必须出现,而〈phone〉元素可有可无,但是这些元素必须按先后顺序出现。

? 包含文本的元素:(#PCDATA)表明该元素必须包含经过解析的字符数据,经过解析的字符数据实质上就是不是标记的任何文本,即该元素不能包含自己的子元素)

? 其中<age>元素 有个可选的属性xusui.

尽管DTD相当简单,但它清楚地说明了什么样的元素组合是合乎规则的。<age>元素出现在<name>元素之前就是不合乎规则的。另外,请注意,DTD语法不同于普通的XML语法。(XML Schema 文档本身就是XML)。尽管DTD的语法不同,但您仍可以将普通的注释放到DTD中。

下面的XML文档是满足上面DTD的文档:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE students SYSTEM "D:\java\xml\student.dtd">

<students>

<student>

<name>andy</name>

<age>49</age>

<address>luohu luohukejidasha</address>

</student>

<student>

<name>goudan</name>

<age xusui="当年减出生年加1">28</age>

<address>shanxi xi an</address>

<phone>02988755660</phone>

</student>

</students>

当然也可以把dtd文档写到XML文档内部:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE students [

<!ELEMENT students (student*)>

<!ELEMENT student (name,age,address,phone?)>

<!ELEMENT name (#PCDATA)>

<!ELEMENT age (#PCDATA)>

<!ELEMENT address (#PCDATA)>

<!ELEMENT phone (#PCDATA)>

<!ATTLIST age

xusui CDATA #IMPLIED>

]>

<students>

<student>

<name>andy</name>

<age>49</age>

<address>luohu luohukejidasha</address>

</student>

<student>

<name>goudan</name>

<age xusui="当年减出生年加1">28</age>

<address>shanxi xi an</address>

<phone>02988755660</phone>

</student>

</students>

下面分析把DTD文档写在XML文档内部和写在外部的对比:

如果采用外部DTD的话,就需要有两个文档,第一个文档就是关于DTD的文档,第二个文档就是遵守DTD格式的内容文档。实际上,我们可以建立无穷多个遵守该DTD格式的文档。我们在构造关系数据库中的表的时候,需要定义好表的结构(也就是表包含的字段集合),然后就可以往这个表中放入记录,记录的个数从理论上讲可以是无穷多个的。这里关于表的结构就类似于DTD文档,记录类似于遵守DTD格式的内容文档。

外部DTD的好处是:它可以方便高效地被多个XML文件所共享。你只要写一个DTD文件,就可以被多个XML文件所引用。事实上,当许多组织需要统一他们的数据交换格式时,他们就是通过外部DTD来完成的。这样做不仅简化了输入工作,还保证当你需要对DTD做出改动时,只需改动一个公用的DTD文件,而不用一一去改动每个应用了它的XML文件。不过需要注意,如果DTD的改动不是"向后兼容"的,这时原先根据该DTD编写的那些XML文件可能就会出现问题了。所以在改动DTD时,一定要尽量兼容以前的文档,除非解析器变了。(类似于JAVAJDK的发展,JDK的每个版本的发布都会兼容以前的版本,这样在换JDK时,以前的程序就不要改动了。目前的最新JDK的版本是1.6也叫6.0)

由以上的例子可知,建立合法XML文件的要点之一就是要定义好DTD,然后所有的内容就按照DTD格式进行编写。DTD实际上表现了一个层次关系,你也可以把它理解成一棵树的结构。树中的节点实际就是一个个元素(ELEMENT,一个元素包含其他的元素。

对于一个DTD的定义,最关键的在于它内部的元素和属性的定义。按上面例子的理解,一个DTD文档实际上就是元素定义的集合,而元素可能包含属性,也可能不包含属性。属性可以通过以下的语法进行定义:

<! ATTLIST ElementName

AttributeName Type Default

AttributeName Type Default

>

ATTLIST是一个XML语言的保留关键字,ElementName表示元素的名称,它相当于程序设计语言中变量的名称。一个元素可以包含多个属性,一个属性由三部分构成:AttributeName,TypeDefaultAttributeName表示该属性的名字,Type表明该属性的类型,Default表明该属性的特点。我们先来看一下XML所定义的属性类型的种类,如下表:

类型 具体的含义说明

CDATA 这个类型表明该属性只能包含字符数据,比如刘德华“hibernate”,”小罗等等

ID 该属性的取值必须是唯一的,就像我们每个人都有的身份证号码是唯一的一样,在一个文档内两个ID属性的值不能一样

IDREFIDREFS 这个属性的值实际上就像C++中的指针一样,它是一个指向文档中其他地方声明的ID值,所以,如果在具体的文档中该属性的取值和它所指向的ID值不匹配的话,就会返回错误。IDREFSIDREF类似,但是可以具有由空格分隔的多个引用

ENTITY,ENTITIES ENTITY属性的值必须对应一个在DTD文档内声明的但还没有分析过的实体。ENTITIES属性和ENTITY类似,不同的是它可以包含多个实体,外部实体,参数实体和外部参数实体。可以把实体理解为程序设计语言中的变量

NMTOKENNMTOKENS NMTOKENCDATA的一个子集,它所使用的字符必须是字母,数字,句点,破折号,下划线或冒号。NMTOKENSNMTOKEN类似,不同之处在于它可以包含多个值,每个值之间用空格进行分隔

NOTATION NOTATION的值必须引用已在DTD文档其他地方声明过的某注解名称

NOTATIONenumerated 该属性的值必须匹配NOTATION名称列表中的某个名称。比如,我们已经存在两个NOTATION,一个为beauty,一个为beast;我们可以定义一个属性类型为NOTATIONbeauty|beast

Enumerated 该属性几乎和C++中的枚举变量一样(JDK1.5里面也有枚举类型),我们事先定义好一些值,该属性的值必须匹配所列出的这些值。比如现在有值为美丽,魅力,温柔,体贴,该属性的类型就可以表现为(美丽|魅力|温柔|体贴),实际内容文档必须从这些值中取一个,值之间用“|”进行分隔

Default属性特点可以有四种形式:#REQUIRED,#IMPLIED,#FIXED value,Defaultvalue.下表对这四种形式进行了详细介绍:

值 含义

#REQUIRED 用来告诉XML解析程序,该元素的所有实例都必须有该属性的值,就像数据表中某一个字段为NOT NULL一样

#IMPLIED 表示如果该元素的实例中没有指定该元素的值的话,就忽略该属性,就像在数据表中某一个字段的值可以为NULL一样

#FIXED value 表示包含该属性的元素实例必须在指定列出的值中,比如 一个属性名称为学员:学员 CDATA #FIXED “刘德华

表示属性值只能去刘德华

Defaultvalue 为属性提供一个默认的值比如一个属性名称为学员:学员 CDATA “刘德华如果在该属性的实例中没有包含这个属性的话,解析器就认为该属性的值就是刘德华,如果在该属性的实例中包含了这个属性并赋值了的话,就采用这个赋值

下面的DTD例子用来说明属性类型IDREF/IDREFS的使用:

如下例:<second.dtd>

<?xml version="1.0" encoding="UTF-8"?>

<!ELEMENT family (person+)>

<!ELEMENT person EMPTY>

<!ATTLIST person

relID ID #REQUIRED

parentID IDREFS #IMPLIED

name CDATA #REQUIRED

>

请注意:这里的parentID的类型为IDREFS,这个表明该属性的值必须在XML文档中出现过。如果该属性的值没在XML文档中出现过的话,解析器就认为该文档是不合法的。比如,下面的文档就是一个不合法的文档:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE family SYSTEM "D:\java\xml\second.dtd">

<family>

<person relID="p1" name="李雨春" />

<person relID="p2" name="狗蛋" />

<person relID="p3" name="张曼玉" />

<person relID="p4" parentID="p1 p6 p2" name="小罗" />

</family>

原因是parentID中出现了值 "p6",而这个值并没有在文档中出现过。

元素的定义语法为:

<!ELEMENT NAME CONTENT>

这里ELEMENTXML语言的关键字,NAME表示元素的名称,CONTENT表示元素的类型。它指定了元素起始标记和结束标记之间允许包含的东西。CONTENT可以以下几种形式出现,如下表:

内容 解释

EMPTY 如果一个元素的CONTENT被声明为EMPTY的话,表示该元素不能包含任何子元素和文本

ANY 表示该元素中可以包含任何在DTD中定义的元素内容

#PCDATA 表明该元素可以包含任何字符数据,但是不能在其中包含任何子元素。

假设我们定义元素学员:

<! ELEMENT student #PCDATA>

则下面的实例是正确的:

<student>比耳盖茨</student>

而下面的实例就是错误的:

<student>比耳<B>盖茨</B></student>

因为在其中包含了子元素<B>

一般如果定义元素的CONTENT#PCDATA,最好在其中只加入纯文本字符数据

其他类型 最通常的情况是一个元素本身是由其他元素的集合构成的

在前面的一些例子中,大家一定注意到了"|","+","*"等这些符号,这些符号具体的含义及使用方法见下表:

符号 符号类型 描述 示例 示例说明

() 括号 用来给元素分组 (刘德华|张学友),(小罗|卡罗斯),盖茨 分成三组

| 竖条 表明在列出的对象中选折一个 (爱人|朋友) 表示爱人和朋友必须出现,且只能出现一个

+ 加号 表示该对象最少出现一次,可以出现多次 (student+ 表示student必须出现,可以出现多次

* 星号 表示该对象允许出现任意多次,也可以是零次 (爱好* 爱好可以出现零次到多次

? 问号 表示该对象可以出现,但只能出现一次 (爱人? 爱人可以出现一次或零次

, 逗号 表示对象必须按指定的顺序出现 (name,sex,age) 表示name,sex,age必须顺序出现

有时我们还会在DTD中遇到一些实体的定义,一般分为以下两种情况:

内部实体:

<!ENTITY texing "jdakdjfaldlfldsafdfdslkfdsa"

外部实体:

<!ENTITY texing SYSTEM "http://www.5itjob.com/zhaosheng.xml">

它们在XML文档中的使用是统一的:

computer&texing; </computer>

最后,我们在看个例子:〈shenzhen.dtd>

<?xml version="1.0" encoding="UTF-8"?>

<!ELEMENT shenzhen (nanshan,luohu)>

<!ELEMENT nanshan (#PCDATA)>

<!ATTLIST nanshan mianji CDATA #REQUIRED>

<!ELEMENT luohu (#PCDATA)>

<!ATTLIST luohu mianji CDATA #REQUIRED>

<!ENTITY miaoshu "shen zhen de zui hao de qu">

下面是满足上面DTDXML文档:<testshenzhen.xml>

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE shenzhen SYSTEM "D:\j2se质料整理\xml\shenzhen.dtd">

<shenzhen>

<nanshan mianji="50">

&miaoshu;

</nanshan>

<luohu mianji="60" />

</shenzhen>

XML Schema

上面我们讨论了用DTD来验证XML文档,虽然DTD胜任对XML文档结构的验证,但是它有很多局限。为此,微软提出了Schema的方案来改进DTDSchema从字面意义上来说,可以翻译成架构,它的基本意思是为XML文档制定一种模式。Schema相对于DTD的明显好处是XML Schema文档本身也是XML文档,而不是像DTD一样使用自成一体的语法。这就方便了用户和开发者,因为可以使用相同的工具来处理XML Schema和其他XML信息,而不必专门为Schema使用特殊的工具。Schema简单易懂,懂得XML语法规则的人都可以立刻理解它。Schema的概念提出已久,但W3C的标准最近才出来,相应的应用支持尚未完善,但采用Schema已成为XML发展的一个趋势。

请看下例:<movie.dtd>

<?xml version="1.0" encoding="UTF-8"?>

<!ELEMENT movie (daoyan,zhuyan)>

<!ELEMENT daoyan (#PCDATA)>

<!ELEMENT zhuyan (#PCDATA)>

<movie.xsd>

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"

elementFormDefault="qualified" attributeFormDefault="unqualified">

<xs:element name="daoyan" type="xs:string" />

<xs:element name="zhuyan" type="xs:string" />

<xs:element name="movie">

<xs:complexType>

<xs:choice maxOccurs="2">

<xs:element ref="daoyan" />

<xs:element ref="zhuyan" />

</xs:choice>

</xs:complexType>

</xs:element>

</xs:schema>

<moviedtd.xml>

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE movie SYSTEM "D:\java\xml\movie.dtd">

<movie>

<daoyan>张艺谋</daoyan>

<zhuyan>刘德华</zhuyan>

</movie>

<moviexsd.xml>

<?xml version="1.0" encoding="UTF-8"?>

<movie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="D:\java\xml\movie.xsd">

<daoyan>张艺谋</daoyan>

<zhuyan>刘德华</zhuyan>

</movie>

从上例可看出这两个XML文当格式完全是一样的。只不过采用的模式文档不一样,一个是DTD 一个是 XML Schema

XML Schema中,元素是通过它的名字和类型来确定的。名称就是该元素的名字,类型就像JAVA中的一个变量,有基本类型(int double char等),有复杂类型(类类型)。在Schema中也是一样,类型(type)可以分为两种形式,一种是简单类型,一种是复合类型。简单类型不能包含元素和属性。复杂类型不仅可以包含属性,而且可以在其中嵌套其他的元素,或者可以和其他元素中的属性相关联。

SchemaDTD的区别:

XMLSGML中继承了DTD,并用它来定义内容的模式,验证和组织元素。同时,它也有很多局限,如下所述:

? DTD不遵守XML语法;

? DTD不可扩展;

? DTD不支持命名空间的应用;

? DTD没有提供强大的数据类型支持,只能表示很简单的数据类型。

Schema完全克服了这些弱点,使得基于Web的应用系统交换XML数据更为容易。下面是它所展现的一些新特性:

? Schema完全基于XML语法,不需要再学习特殊的语法;

? Schema能用处理XML文档的工具处理,而不需要特殊的工具;

? Schema大大扩充了数据类型,支持boolean,number,date and times,URI,integer,decimal number 等等

? Schema支持原型,也就是元素的继承。如:我们定义了一个""数据类型,然后可以根据它产生"狼狗""纯狗"两种数据类型;

? Schema支持属性组。我们一般声明一些公共属性,然后可以应用于所有的元素,属性组允许把元素,属性关系放于外部定义,组合;

? 开放性。原来的DTD只能有一个DTD应用于一个XML文档,现在可以有多个Schema运用于一个XML文档。

Schema的数据类型

基本数据类型

下表列出的是最基本的数据类型,被W3C组织所承认:如下表:

数据类型 相关特性 描述

string length,pattern,maxLength,minLength,enumeration,

whiteSpace 表示字符串

boolean pattern,whiteSpace 布尔型

decimal enumeration,pattern,minInclusive,minExclusive,

maxInclusive,maxExclusive 代表特定精度的数字

float pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 表示单精度32位浮点数

double pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 表示双精度64位浮点数

duration pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 表示持续时间

dateTime pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 代表特定的时间

time pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 代表特定的时间,但是是每天重复的

date pattern,enumeration,minInclusive,minExclusive,

maxInclusive,maxExclusive 代表日期

hexBinary length,pattern,maxLength,minLength,enumeration 代表十六进制数

anyURL length,pattern,maxLength,minLength,enumeration 代表一个URL,用来定位文件

NOTATION length,pattern,maxLength,minLength,enumeration 代表NOTATION类型

扩展数据类型

下面这些类型是微软扩充的数据类型,并没有被W3C组织完全采纳。

数据类型 相关特性 描述

ID length,enumeration,pattern,maxLength,

minLength 用于唯一标识元素

IDREF length,enumeration,pattern,maxLength,

minLength 参考ID类型的元素或属性

ENTITY length,enumeration,pattern,maxLength,

minLengthwhiteSpace 实体类型

NMTOKEN length,enumeration,pattern,maxLength,

minLength NMTOKEN类型

NMTOKENS length,enumeration ,maxLength,

minLengthwhiteSpace NMTOKEN类型集

long enumeration,pattern,fractionDigits,

minInclusive,minExclusive,maxInclusive,

maxExclusive,totalDigits,whiteSpace 表示整型数,大小介于-92233720368547758089223372036854775807之间

int enumeration,pattern,fractionDigits,

minInclusive,minExclusive,maxInclusive,

maxExclusive,totalDigits,whiteSpace 表示整型数,大小介于-21474836482147483647之间

short enumeration,pattern,fractionDigits,

minInclusive,minExclusive,maxInclusive,

maxExclusive,totalDigits,whiteSpace 表示整型数,大小介于-3276832767之间

byte enumeration,pattern,fractionDigits,

minInclusive,minExclusive,maxInclusive,

maxExclusive,totalDigits,whiteSpace 表示整型数,大小介于-128127之间

数据类型的相关特性

前面这些数据类型说明中都有一列相关特性,用于说明该列的类型的一些扩充应用。下面是对它们应用的描述:

enumeration

指定的数据集中选折,主要的目的是为了能限定用户的选值。

fractionDigits

限定最大的小数位,主要用于控制精度。

length

指定数据的长度单元

maxExclusive

指定数据的最大值。其他值必须小于它

maxInclusive

指定最大值,其他值可以小于等于它

maxLength

指定长度的最大值

minExclusive

指定最小值,其他值必须大于它。

minInclusive

指定最小值,其他值可以大于等于它。

minLength

指定长度的最小值

pattern

规范数据的显示规范,使得数据必须匹配它的显示方式。

Schema的元素类型

Schema DTD中的元素类型丰富得多,这可使用户和程序都更容易确定元素的种类,下面的表,只是列出了最常用的元素类型。

元素类型 描述

schema 包含一个已经定义的schema

element 声明一个元素

group 把一组元素声明组合在一起,以便它们能够一起被复合类型应用

attribute 声明一个属性

attributeGroup 把一组属性声明组合在一起,以便他们能够一起被复合类型应用

choice 允许唯一的一个元素从一个组中被选择

simpleType 定义一个简单类型,它决定了元素和属性值的约束和相关信息

simpleContent 应用于complexType,对它的内容进行约束和扩展等

complexType 定义一个复合类型,它决定了一组元素和属性值的约束和相关信息

list 从一个特定数据类型的集合中选择定义一个简单类型元素

union 从一个特定简单数据类型的集合中选择定义一个简单类型元素

unique 定义一个属性或元素值,它必须在一个特定范围内

squence 给一组元素一个特定的序列

restriction 定义一个约束条件

Schema的样例如下:

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"

elementFormDefault="qualified" attributeFormDefault="unqualified">

<!--内容-->

</xs:schema>

其中xs命名空间指定的是W3C组织的定义,也就是遵循它的标记集规范。目标命名空间是全局的,指定目标命名元素符合本Schema的定义。elementFormDefault的取值有两个选择:

unqualifiedqualified,前者表示目标命名空间不一定遵循本Schema,后者则必须遵循。attributeFormDefault类似。

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"

elementFormDefault="qualified" attributeFormDefault="unqualified">

<xs:element name="cat" type="xs:string" />

<xs:element name="dog" type="xs:string" />

<xs:element name="tel">

<xs:simpleType>

<xs:restriction base="xs:string">

<xs:pattern value="(([0-5]){2}|([a-z]))?" />

</xs:restriction>

</xs:simpleType>

</xs:element>

<xs:element name="langdog" type="xs:string" substitutionGroup="dog" />

<xs:element name="maodog" type="xs:string" substitutionGroup="dog" />

<xs:element name="pets">

<xs:complexType>

<xs:choice maxOccurs="unbounded">

<xs:element ref="cat" />

<xs:element ref="dog" />

<xs:element ref="tel" />

</xs:choice>

</xs:complexType>

</xs:element>

</xs:schema>

element中有些常用的属性:

name 代表该元素的名字,不能和ref同时使用;

type 代表该元素的类型,可以是已有的简单类型,也可以自定义复合类型,或者应用其他命名空间中的类型定义,不能和ref同时使用;

minOccurs 表示该元素出现的最少次数;

maxOccurs 表示该元素出现的最大次数;

ref 表示该元素参考已经定义的元素;

fixed 表示该元素的值是不能改变的,不能与default一起使用;

default 表示该元素的缺省值;

substitutionGroup 表示这个元素的父类,如狼狗也属于狗这一类。

样式表(XSL

XML是一种程序交换原始数据的简单而标准的方法,然而基于XML的数据本身并不包含数据显示的信息。为了使数据便于阅读理解,我们需要将信息更好地显示。类似于MVC模式,XML文档是M的部分,样式表就是V的部分,也就是说,同一个XML文档可以有多个显示效果(样式表)。样式表就是这样一种为XML文档"化妆"的文档,它是一种专门描述结构文档表现方式的文档,即可以描述这些文档如何在屏幕上显示,也可以描述它们的打印效果,甚至声音效果。使用样式表定义XML文档数据的显示方式,将XML数据与其表达形式分离的做法符合业界提倡的数据和表示层分开的思想(MVC)。这样一来,XML格式文档不会重蹈某些HTML文档(以显示数据为主)结构混杂,内容繁乱的覆辙,XML的编写者也可以集中精力于数据本身,而不受显示方式的细枝末节的影响。不仅如此,样式表还带来另一个好处,即定义不同的样式表可以使相同的数据呈现不同的显示外观,从而适合于不同应用,甚至能够在不同的显示设备上显示。这样,XML数据就可以得到最大程度上的重用性,满足不同的应用需求。迄今为止,W3CWWW标准化组织)已经给出了两种样式表语言的推荐标准:一种是级联样式表CSSCascading Style Sheets),另一种是可扩展样式表语言XSLeXtensible Stylesheet Language)。下面,我们就来讲述这两种样式表。

CSS样式表

级联样式表CSS是一种样式描述规则,是一种针对HTML而提出的样式表语言,现在同样可以很好地应用与描述XML文档的显示。利用CSS,我们可以定义HTMLXML文档中元素的显示效果,包括元素的位置,颜色,背景,边框,字体,排版格式等等。

请看下面的一个XML文档:

<?xml version="1.0" encoding="UTF-8"?>

<?xml-stylesheet type="text/css" href="test.css"?>

<person>

<name>andy</name>

<age>49</age>

<address>中国 香港 铜锣弯</address>

</person>

我们定义显示上面XML文档的CSS样式表(test.css),如下:

person

{

margin-top:.7in;

margin-bottom:.7in;

margin-left:1.5in;

margin-right:1in;

background-color:blue;

display:block

}

name

{

display:block;

font-family:Arial,Helvetica,sans-serif;

font-size:32pt;

width:10em;

color:red

}

age

{

display:block;

font-size=x-large;

color:yellow

}

address

{

display:block;

font-size:20pt;

color:cyan

}

显示效果如下图:

XML中引用一个级联样式表,我们使用如下的处理指令:

<?xml-stylesheet type="text/css" href="样式表路径"?>

我们在示例XML的文档的第二句,引入了样式表。

XML解析器解析XML文档和CSS文件,并执行如下任务:

解析"person.xml",抽取其元素;

使用"test.css"文件,应用样式格式化XML文档中的元素。

XSL样式表

XSL是一种用来转换XML文档的样式表,它包含转换和格式化XML文档的规则。XSL在转换XML文档时分为明显的两个过程:首先转换文档结构,然后将文档格式化输出:如下图:

这两个步骤可以分离开来单独处理,因此在XSL发展过程中逐渐分裂为两种分支:XSLT(结构转换)和XSL-FO(格式化输出)。其中XSL-FO的作用类似于CSSHTML中的作用,由于其发展缓慢而不成熟,W3C至今还未能出台一个得到多方认可的XSL-FO,而且目前主流的浏览器大都不支持XSL-FO,本书不对其做讲解。前面学习XML时,我们已经知道XML是一个完整的树形结构文档。在转换XML文档时可能需要处理其中的一部分(节点)数据,那么如何查找和定位XML文档中的信息呢?XPath就是一种专门用来在XML文档中查找信息的语言,用来描述如何识别,选择,匹配XML文档中的各个构成元件,包括元素,属性,文字内容等。XPath隶属XSLT,因此,我们通常会将XSLT语法和XPath语法混在一起说。用一种比较好理解的解释:如果将XML文档看作一个数据库,XPath就是SQL查询语言;如果将XML文档看成DOS目录结构,XPath就是cd,dir等目录操作命令的集合。XSL包含XSLTXPath的强大功能,从而可以把XML文档转换成任何一种其它格式的文档,例如XML文档,HTML文档,XHTML文档等等,限于目前浏览器的支持能力,在大多数情况下,XSLXML文档转换为一个HTML文档进行显示,本节中我们主要讲述怎样使用XSL样式表将XML文档转换为HTML文档。

XSL的基本结构

为了让大家能够对XSL有一个感性认识,并很快地掌握它的精髓,我们先看一个简单例子。通过剖析这个例子,来掌握XSL的基本结构和一些基本语法。

请看下面的XML文档:

<?xml version="1.0" encoding="UTF-8"?>

<?xml-stylesheet type="text/xsl" href="teststudents.xsl"?>

<students>

<student ID="102376">

<name>李明</name>

<sex></sex>

<java>97</java>

<oracle>69</oracle>

<mssql>76</mssql>

</student>

<student ID="102378">

<name>张一强</name>

<sex></sex>

<java>92</java>

<oracle>83</oracle>

<mssql>62</mssql>

</student>

<student ID="102379">

<name>王亮</name>

<sex></sex>

<java>74</java>

<oracle>91</oracle>

<mssql>71</mssql>

</student>

<student ID="102380">

<name>赵三金</name>

<sex></sex>

<java>89</java>

<oracle>93</oracle>

<mssql>67</mssql>

</student>

</students>

下面是上面的XML文档所引用的teststudents.xsl:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:template match="/">

<html>

<head>

<title>第一期东北JAVA班部分学员</title>

</head>

<body>

<h2 align="center">成绩单</h2>

<xsl:apply-templates select="students" />

</body>

</html>

</xsl:template>

<xsl:template match="students">

<table border="1" cellpadding="0" align="center">

<tbody>

<tr>

<th>姓名</th>

<th>性别</th>

<th>java</th>

<th>oracle</th>

<th>mssql</th>

</tr>

<xsl:for-each select="student">

<tr>

<td>

<xsl:value-of select="name" />

</td>

<td>

<xsl:value-of select="sex" />

</td>

<td>

<xsl:value-of select="java" />

</td>

<td>

<xsl:value-of select="oracle" />

</td>

<td>

<xsl:value-of select="mssql" />

</td>

</tr>

</xsl:for-each>

</tbody>

</table>

</xsl:template>

</xsl:stylesheet>

显示结果如下图: 现在我们来分析一下这段XML代码的执行过程:

在作为XML声明和XSL声明之后,样式表利用<xsl:template><xsl:apply-templates/></xsl:template>声明XSL模板,并调用该模板。

根据<xsl:apply-templates/>,系统最先匹配XML源树的根节点。根节点用"/"表示,它的匹配方法在一对<xsl:template match="/">括起的源码中声明。按照这段代码,首先生成带有样式信息的HTML文档的开头一段代码。

<html>

<head>

<title>第一期东北JAVA班部分学员</title>

</head>

<body>

<h2 align="center">

成绩单

</h2>

下面,系统看到了<xsl:apply-templates select="students"/>的指示,于是,它在XML源树中寻找标记为"students"的节点进行匹配。就像函数调用一样,现在系统跳到了用<xsl:template match="students">括起的"函数"中继续生成下面的HTML代码。

<table border="1" cellpadding="0" align="center">

<tbody>

<tr>

<th>姓名</th>

<th>性别</th>

<th>java</th>

<th>oracle</th>

<th>mssql</th>

</tr>

现在,系统又接到了新的指示<xsl:for-each select="student" >。这条指示要求系统寻找标记为"student"的子节点,对于每一个"student"子树中的内容,系统为其生成表中一行的内容。每一行包含四列,分别把标记为"name","sex","java","oracle","mssql"的节点的内容填进去。对应到本例中的XML数据,生成的HTML代码:

<tr>

<td>李明</td>

<td></td>

< td >97</ td > < td >69</ td >< td >76</ td >

</tr>

<tr>

<td>张一强</td>

<td></td>

< td >92</ td > < td >83</ td >< td >62</ td >

</tr>

<tr>

<td>王亮</td>

<td></td>

< td >74</ td > < td >91</ td >< td >71</ td >

</tr>

<tr>

<td>赵三金</td>

<td></td>

< td >89</ td > < td >93</ td >< td >67</ td >

</tr>

处理完<xsl:for-each select="student" >中的内容,系统继续生成HTML代码:

</tbody>

</table>

至此,系统已经处理完<xsl:template match="students">中的所有内容,可以"函数返回"了。系统返回到<xsl:template match="/">括起的源码中,完成HTML最后两行代码的生成:

</body>

</html>

把上面的HTML代码串起来,就是生成的转换结果文件。

分析上面的例子,我们可以看出一个XSL文件基本上由几部分组成:

因为XSL文档本身是XML文档,因此文档的第一句就是XML声明:

<?xml version="1.0" encoding="UTF-8"?>

接下来就是XSL声明:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:fo="http://www.w3.org/1999/XSL/Format">

<!--模板规则-->

<! -输出模板--

</xsl:stylesheet>

XSL声明中包含模板,模板中定义XML的输出格式。

XSL文档本身是结构完整的XML文档,所以在书写时要注意标签的匹配问题。</xsl:stylesheet>既是XSL的声明语句,也是根元素,必须位于文件的首部,通常也要利用xmlns属性指明XSL的命名空间。样式表中所有的模板都由标签<xsl:template>标明,模板可以说明处理的对象(元素,属性),处理的方式或是转换的结果。此时,我们可以把该标签类似地理解为编程语言中函数的概念。

XSL的基本语法

通过对上面XSL转换XML文档为HTML文档的简单例子的分析,相信大家对XSL的基本语法有了一个初步的认识,下面我们再来详细分析一下XSL的元素语法。

XSL模板:

模板是XSL中最重要的概念之一,XSL文件就是由一个一个的模板组成的,任何一个XSL文件至少包含一个模板。可以把XSL的设计细化成一个个模板,最后再将这些模板组合成一个完整的XSL。这种方法如同程序中模块化设计逐步细化一样,可以使你先从整体上考虑整个XSL的设计,然后将一些表现形式细化成不同的模板,再具体设计这些模块,最后将它们整合在一起。模板中两个主要的元素为:<xsl:apply-templates><xsl:template>, 这两个元素相结合,就如同编程中的函数调用:前者是调用指令,而后者就是函数体。

定义一个模板的基本语法是:

<xsl:template match="pattern">

<!-输出模板-->

</xsl:template>

属性match则控制模板的匹配模式,确定在什么情况下执行此模板。属性match的取值把模板规则与指定的元素或属性相比较,只有指定的节点才会被处理。其中最上层的模板即根节点必须将match取值设定为"/"

<xsl:template match="/">

<!-输出根节点的模板-->

</xsl:template>

如果属性match的取值为"*",那么表示该规则适用于所有的未单独指定处理的元素节点。比如下例中的第二个模板就表示要处理除<mynode>元素之外的所有节点。

<xsl:template match="mynode">

<!-输出模板-->

</xsl:template>

<xsl:template match="*">

<!-输出模板-->

</xsl:template>

此外,XSL中还可以使用路径指示符来指定一些特殊位置的元素与模板相匹配。"//"代表任意深度位置,如<xsl:template match="//node">用来匹配文档中任何位置的<node>元素;而如果是<xsl:template match="my//node">,则表明是匹配<my>元素的后继节点中所有<node>元素。另外一个路径指示符是"/",表示直接的父子节点关系。将刚才例子中的"//"换为"/",就意味着匹配的是<my>元素子节点中的<node>元素。XSL在输出模板中描述输出格式,这些格式可以是各种字符串,标签符号,节点值或者是一些XSL语法结构,如条件判断,循环处理等。模板定义完毕后,就可以在XSL中调用该模板,调用一个模板的语法为:

<xsl:apply-templates select="node"/>

<xsl:apply-templates>用来说明哪一个节点被模板具体处理。你可以将它理解为程序中调用子函数。Select属性用来定义确切的节点名称。<xsl:apply-templates>总是包含在<xsl:template>元素中,像这样:

<xsl:template match="/">

<xsl:apply-templates select="students" />

</xsl:template>

这段代码说明模板匹配整个文档(根节点),具体执行时处理根节点下所有students元素。对于不同的元素需要调用不同的模板进行处理。为了激活样式表中的模板规则,要在根元素模板规则中使用<xsl:apply-templates>元素,这样就会层层作用使整个样式表文件生效。直接使用<xsl:apply-templates>表示不加区分地对当前节点的所有子节点应用模板,而在select属性中书写匹配式则能够限定作用对象:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:fo="http://www.w3.org/1999/XSL/Format">

<!--根模板-->

<xsl:template match="/">

<xsl:apply-templates select="//student" />

</xsl:template>

<!--student模板-->

<xsl:template match="student">

<xsl:apply-templates select="name" />

<xsl:apply-templates select="java" />

</xsl:template>

<!--name模板-->

<xsl:template match="student">

<xsl:value-of />

</xsl:template>

<!--java模板-->

<xsl:template match="java">

<xsl:value-of />

</xsl:template>

</xsl:stylesheet>

上述第一个模板与XML文档中的根元素相匹配,并对根节点以下的所有<student>元素应用模板规则。然后,一旦遇到<student>标签,就对<name>元素和<java>元素分别应用模板规则。最后,经过转换显示的结果是学生姓名和java成绩。

节点选择语句<xsl:value-of >

在许多应用场合中,输出模板中需要使用节点的取值,此时可以根据需要使用<xsl:value-of select="pattern" >语句输出节点值,最直接的使用方式是使用空元素<xsl:value-of />,这样可以输出当前节点及其后继节点的取值。而如果仅仅是想输出指定节点的取值,可以利用select属性进行限定(select属性可以是任意合法的路径表达式)。

<xsl:value-of select="name" >

<xsl:value-of select="//student" >

上述第一个表达式匹配的对象是当前节点的所有子节点中名称为<name>的元素,第二个表达式匹配的对象则是当前节点中所有后继节点中名为<student>的元素。注意:在XSL样式表中必须有一个模板规则与根元素相匹配。例如想在输出文档中显示学生成绩单XML源文档中的name元素的值,可以这样写XSL代码:

<xsl:template match="student">

<xsl:value-of select="name" />

</xsl:template>

其中match="student"定义模板匹配student节点,xsl:value-of语法说明需要输出一个节点的值,而select="name"则定义需要被输出的元素为name。这个过程类似于在数据库里查询一个人的名字。

循环判断语句<xsl:for-each>

XML文档中,如果存在两条以上的数据,必须使用<xsl:for-each >元素指定上层的节点元素,再用<xsl:value-of >设定要输出的内容,才能显示整个XML文档数据。这种循环结构能够遍历整个结果集合,而不必针对每一条结果都单独书写转换规则。它的标准语法格式为:

<xsl:for-each select="student" >

….

</xsl:for-each>

条件判断语句<xsl:if>

<xsl:if>语句是简单地对条件进行判断,结果为真就执行条件内部的规则,因此可以把if条件与简单的布尔表达式联合使用。

<xsl:if>有三种不同的用法,如果要用元素的名称作为匹配条件,语法是:

<xsl:if test="元素名称" >

下面的例子对"name"元素的内容用红色显示:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:template match="/">

<html>

<head>

<title>第一期东北JAVA班部分学员</title>

</head>

<body>

<h2 align="center">成绩单</h2>

<xsl:apply-templates select="students" />

</body>

</html>

</xsl:template>

<xsl:template match="students">

<table border="1" cellpadding="0" align="center">

<tbody>

<tr>

<th>姓名</th>

<th>性别</th>

<th>java</th>

<th>oracle</th>

<th>mssql</th>

</tr>

<xsl:for-each select="student">

<tr>

<td>

<xsl:if test="name">

<font color="red">

<xsl:value-of select="name" />

</font>

</xsl:if>

</td>

<td>

<xsl:value-of select="sex" />

</td>

<td>

<xsl:value-of select="java" />

</td>

<td>

<xsl:value-of select="oracle" />

</td>

<td>

<xsl:value-of select="mssql" />

</td>

</tr>

</xsl:for-each>

</tbody>

</table>

</xsl:template>

</xsl:stylesheet>

显示的XML文件为:

如果用元素内容作为匹配条件,语法是:

<xsl:if test="元素名称[.='元素内容']" >

注意:"."表示当前元素。请看下例,如果名字是叫李明的 显示红颜色。

<xsl:if test="name[.='李明']">

<font color="red">

<xsl:value-of select="name" />

</font>

</xsl:if>

内容总结

? 会写XML文档

? 理解XML命名空间概念

? 掌握DTDSCHEMA的用法

? 会通过使用XSL进行XML的显示

? 知道XML的作用

? 正确使用XML技术

独立实践

? 写一个关于学生课程信息的DTDSHEMA文档

? 根据上面的DTDSCHEMA文档写出XML文档,并定义XSL显示该XML

? 写一个数据库连接的DTDSHEMA文档

? 根据数据库连接的DTDSHEMA文档,写出XML文档,为下一章解析XML,创建数据库连接做准备

? 以下XML 文件不是一個有效的文件,為什麼?

<?xml version = “1.0”?>

<!—A DTD example -->

<!DOCTYPE library [

<!ELEMENT library (branch+, book+)>

<!ELEMENT branch (name, address)>

<!ATTLIST branch branchID ID #REQUIRED>

<!ELEMENT book (#PCDATA)>

<!ATTLIST book inBranch IDREF>

<!ELEMENT name (#PCDATA)>

<!ELEMENT address ( #PCDATA )>

]>

<library>

<branch branchID = “b1”>

<name>理工图书馆</name>

<address>理工大道一号</address>

</branch>

<branch branchID = “b2”>

<name>文法商图书馆</name>

<address>文管大道一号</address>

</branch>

<branch>

<name>总图书馆</name>

</branch>

<book inBranch = “b2”>

如何当个称职的职场新鲜人

</book>

<book inBranch = “b3”>

如何管理資料库

</book>

<book inBranch = “b1”>

JAVA 十天速成手冊

</book>

</library>

修改上面的代码并写出相应的Schema

第二十二章:使用Java解析XML

学习目标

? 解析器的介绍

? 文档对象模型(DOM)解析实例

? SAX解析实例

? DOM4J解析实例

? JDOM解析实例

? JAVA操纵XML 实例讲解

解析器的介绍

XML解析器是读取XML文档并分析其结构的代码。这一部分将介绍XML解析器是如何工作的。将讨论不同类型的XML解析器及何时使用它。

一般而言使用解析器需要以下步骤:

? 创建一个解析器对象

? 使解析器指向您的XML文档

? 处理结果

显然第三步最为复杂。一旦知道了XML文档的内容,比方说,您可能希望生成一个Web页面,创建一个订单或者做一个饼图。这里讨论的常见XML解析工具使这项工作大大简化。

解析器有不同的分类方法:

? 验证和非验证解析器

? 支持一种或多种XML Schema语言的解析器

? 支持Document Object Model (DOM)的解析器

? 支持Simple API for XML (SAX)的解析器

我们已知有三种不同类型的XML文档:

? 结构良好的文档:这类文档符合XML基本规则(属性必须放在引号中,标签必须正确嵌套等)。

? 有效文档:这些结构良好的文档同时还符合文档类型定义(DTD)或XML Schema所定义的规则。

? 无效文档:除以上两种文档外的所有其他文档。

如果您有一个XML文档符合XML的基本规则,那么它就是一个结构良好的文档。如果该文档还满足您的公司所定义的支出帐目文档规则,那么它也是有效的。如果XML解析器发现XML文档不是结构良好的,XML Specification要求解析器报告一个致命错误。

验证解析器:在解析时验证XML文档,而非验证解析器:不验证文档。换句话说,如果一个XML文档是结构良好的,那么非验证解析器并不关心文档是否符合DTD或模式中定义的规则,甚至不关心该文档是否符合DTD或模式中定义的规则,甚至不关心该文档是否有这样的规则。(多数验证解析器都默认关闭验证功能。)

那么为什么要使用非验证解析器呢?有两个很好的理由:速度和效率。XML解析器读取DTD或者模式,建立规则引擎保证XML文档中的每个元素和属性都遵循这些规则,需要做大量的工作。如果您确信一个XML文档是有效的,那么就可以完全跳过验证。根据文档规则复杂程度的不同,这样可以节约相当可观的时间和内存。如果您的代码不够健壮,它从XML文档中得到输入数据,并且该代码要求文档遵循特定的DTD或者模式,那么您可能就不得不验证所有的内容,不论代价多么高,多么浪费时间。

DOMW3C推荐的正式标准。它定义了一个接口,程序能够访问和更新XML文档的结构。如果一个XML解析器声称支持DOM,就意味着它实现了该标准中定义的接口。目前,有三个级别的DOM是正式的推荐标准,被命名为DOM Level 1,DOM Level 2 DOM Level 3。本章中所讨论的DOM功能都是DOM Level 2的一部分。

DOM解析器:当你使用DOM解析器解析一个XML文档时,您得到一棵结构树,它表示XML文档的内容。所有的文本,元素和属性,都在这个树结构中

解析的过程:

解析后的XML文档。 DOM还提供各种不同的功能,可用于分析和操作树的内容和结构。DOM是处理XML数据的传统方法。使用DOM时,数据以树状结构的形式被加载到内存中,所以,DOM解析是一个比较耗费内存的操作。如上图所示,矩形框表示元素节点,椭圆表示文本节点。DOM使用父子关系。例如,在这个例子中,student是具有五个孩子的根元素:三个文本节点(空白),以及两个元素节点nameage。要认识到的一件重要事情是,name age节点实际上具有null值。相反,它们具有文本节点(goudan 28)作为孩子。

DOM以及广义的基于树的处理具有几个优点

首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。

另一方面,在内存中构造这样的树涉及大量的开销。大型文件完全占用系统内存容量的情况并不鲜见。此外,创建一棵DOM树可能是一个缓慢的过程。为了满足该缺点,我们使用SAXSimple API for XMLAPI处理XML文档内容。它的设计目标是占用更少的内存,把更多的工作交给程序员。SAXDOM是互补的,有各自的适用环境。请看下图解析过程:

当使用SAX解析器解析一个XML文档时,解析器在读取文档的过程中会生成一系列的事件。至于如何处理这些事件则取决于您的需求。下面列出了一小部分您在XML文档时可能遇到的事件:

? startDocument事件

? 对于每个元素,在元素开始时有startElement事件,元素结束时有endElement事件

? 如果元素包含内容,对于文本将出现characters事件,对于子元素将出现startElementendElement事件,依此类推

? endDocument事件

如下代码及过程:

<?xml version="1.0" encoding="UTF-8"?>

<student>

<name>goudan</name>

<age>28</age>

</student>

分析这个代码片段的SAX处理器,一般情况下将产生以下事件:

startDocument

startElement (person)

characters (white space)

startElement (name)

characters (goudan)

endElement (name)

characters (white space)

startElement (age)

characters (28)

endElement (age)

characters (white space)

endElement (person)

endDocument

这种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。

另一方面,由于应用程序没有以任何方式存储数据,使用SAX来更改数据或在数据流中往后移是不可能的。尽管SAXDOM提供了许多有用的功能,对于程序员而言有些任务还是太复杂了。延续开源社群有需要就创建工具的历史传统,Java技术专家Jason Hunter Brett McLaughlin缔造了JDOM,这个Java库极大简化了XML文档的处理。和DOM类似,JDOM也提供一个对象树表示XML文档,但是这些对象工作的方式对Java程序员更直观。要记住,JDOM在背后包含使用普通SAXDOM 解析器的适配器; JDOM对所有主要的(和几个次要的)Java XML解析器都提供了适配器,因此不必担心您的Java XML解析器是否支持JDOMJDOM在幕后使用的解析器不需要您的干涉。

一般来说以下情况应使用DOM解析器:

? 需要详细了解文档的结构

? 需要改变文档的结构(也许您需要对元素排序,增加新的元素等等)

? 需要多次引用解析的信息

进一步推广,在以下情况中应使用SAX解析器:

? 内存少

? 只需要XML文档中少量元素或属性

? 解析的信息只使用一次

JDOM的内存使用比DOM少,但是不如SAX好。此外,如果您希望进行验证,JDOM要求您设置底层的解析器,JDOM本身不进行验证。就是说,如果JDOM能够完成您所需要的所有功能,而且速度满足您的需要,它可以简化您的编码工作。

文档对象模型(DOM)解析实例

DOM主要包含下列内容:

DOM文档是以层次结构组织的节点或信息片段的集合。这个层次结构允许开发人员从树中导航寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。

DOM解析过程(下图):

利用JAXPxml文档的内容解析到一个个的Java对象中,只需几行代码就能做到这一点。首先,我们需要建立一个解析器工厂,以利用这个工厂来获得一个具体的解析器对象:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

我们在这里使用DocumentBuilderFacotry的目的是为了创建与具体解析器无关的程序,当DocumentBuilderFactory类的静态方法newInstance()被调用时,它根据一个系统变量来决定具体使用哪一个解析器。又因为所有的解析器都服从于JAXP所定义的接口,所以无论具体使用哪一个解析器,代码都是一样的。所以当在不同的解析器之间进行切换时,只需要更改系统变量的值,而不用更改任何代码。这就是工厂所带来的好处。这个工厂模式的具体实现,可以参看下面的类图

DocumentBuilder db = dbf.newDocumentBuilder();

当获得一个工厂对象后,使用它的静态方法newDocumentBuilder()方法可以获得一个DocumentBuilder对象,这个对象代表了具体的DOM解析器。但具体是哪一种解析器,微软的或者IBM的,对于程序而言并不重要。 然后,我们就可以利用这个解析器来对XML文档进行解析了:

Document doc = db.parse("d:/xml/message.xml");

DocumentBuilderparse()方法接受一个XML文档名作为输入参数,返回一个Document对象,这个Document对象就代表了一个XML文档的树模型。以后所有的对XML文档的操作,都与解析器无关,直接在这个Document对象上进行操作就可以了。而具体对Document操作的方法,就是由DOM所定义。

使用Document对象的getElementsByTagName()方法,我们可以得到一个NodeList对象,一个Node对象代表了一个XML文档中的一个标签元素,而NodeList对象,所代表的是一个Node对象的列表:

NodeList nl = doc.getElementsByTagName("message");

我们通过这样一条语句所得到的是XML文档中所有<message>标签对应的Node对象的一个列表。然后,我们可以使用NodeList对象的item()方法来得到列表中的每一个Node对象:

Node my_node = nl.item(0);

当一个Node对象被建立之后,保存在XML文档中的数据就被提取出来并封装在这个Node中了。在这个例子中,要提取Message标签内的内容,我们通常会使用Node对象的getNodeValue()方法:

String message = my_node.getFirstChild().getNodeValue();

请注意,这里还使用了一个getFirstChild()方法来获得message下面的第一个子Node对象。虽然在message标签下面除了文本外并没有其它子标签或者属性,但是我们坚持在这里使用getFirseChild()方法,这主要和W3CDOM的定义有关。W3C把标签内的文本部分也定义成一个Node,所以先要得到代表文本的那个Node,我们才能够使用getNodeValue()来获取文本的内容。

DOM对象

DOM的基本对象有5个:DocumentNodeNodeListElementAttr。下图表示了基本对象间的关系

Document对象代表了整个XML的文档,所有其它的Node,都以一定的顺序包含在Document对象之内,排列成一个树形的结构,程序员可以通过遍历这颗树来得到XML文档的所有的内容,这也是对XML文档操作的起点。我们总是先通过解析XML源文件而得到一个Document对象,然后再来执行后续的操作。此外,Document还包含了创建其它节点的方法,比如createAttribute()用来创建一个Attr对象。它所包含的主要的方法有: createAttribute(String):用给定的属性名创建一个Attr对象,并可在其后使用setAttributeNode方法来放置在某一个Element对象上面。createElement(String):用给定的标签名创建一个Element对象,代表XML文档中的一个标签,然后就可以在这个Element对象上添加属性或进行其它的操作。

createTextNode(String):用给定的字符串创建一个Text对象,Text对象代表了标签或者属性中所包含的纯文本字符串。如果在一个标签内没有其它的标签,那么标签内的文本所代表的Text对象是这个Element对象的唯一子对象。

getElementsByTagName(String):返回一个NodeList对象,它包含了所有给定标签名字的标签。

getDocumentElement():返回一个代表这个DOM树的根节点的Element对象,也就是代表XML文档根元素的那个对象。

Node对象是DOM结构中最为基本的对象,代表了文档树中的一个抽象的节点。在实际使用的时候,很少会真正的用到Node这个对象,而是用到诸如ElementAttrTextNode对象的子对象来操作文档。Node对象为这些对象提供了一个抽象的、公共的根。虽然在Node对象中定义了对其子节点进行存取的方法,但是有一些Node子对象,比如Text对象,它并不存在子节点。Node对象所包含的主要的方法有:

appendChild(org.w3c.dom.Node):为这个节点添加一个子节点,并放在所有子节点的最后,如果这个子节点已经存在,则先把它删掉再添加进去。

getFirstChild():如果节点存在子节点,则返回第一个子节点,对等的,还有getLastChild()方法返回最后一个子节点。

getNextSibling():返回在DOM树中这个节点的下一个兄弟节点,对等的,还有getPreviousSibling()方法返回其前一个兄弟节点。

getNodeName():根据节点的类型返回节点的名称。

getNodeType():返回节点的类型。

getNodeValue():返回节点的值。

hasChildNodes():判断是不是存在有子节点。

hasAttributes():判断这个节点是否存在有属性。

getOwnerDocument():返回节点所处的Document对象。

insertBefore(org.w3c.dom.Node neworg.w3c.dom.Node ref):在给定的一个子对象前再插入一个子对象。

removeChild(org.w3c.dom.Node):删除给定的子节点对象。

replaceChild(org.w3c.dom.Node neworg.w3c.dom.Nodeold):用一个新的Node对象代替给定的子节点对象。

NodeList对象,代表了一个包含一个或者多个Node的列表。可以简单的把它看成一个Node的数组,我们可以通过方法来获得列表中的元素:

getLength():返回列表的长度。

item(int):返回指定位置的Node对象。

Element对象代表的是XML文档中的标签元素,继承于Node,亦是Node的最主要的子对象。在标签中可以包含有属性,因而Element对象中有存取其属性的方法,而任何Node中定义的方法,也可以用在Element对象上面。

getElementsByTagName(String):返回一个NodeList对象,它包含了在这个标签中其下的子孙节点中具有给定标签名字的标签。

getTagName():返回一个代表这个标签名字的字符串。getAttribute(String):返回标签中给定属性名称的属性的值。在这儿需要主要的是,应为XML文档中允许有实体属性出现,而这个方法对这些实体属性并不适用。这时候需要用到getAttributeNodes()方法来得到一个Attr对象来进行进一步的操作。

getAttributeNode(String):返回一个代表给定属性名称的Attr对象。Attr对象代表了某个标签中的属性。Attr继承于Node,但是因为Attr实际上是包含在Element中的,它并不能被看作是Element的子对象,因而在DOMAttr并不是DOM树的一部分,所以Node中的getparentNode()getpreviousSibling()getnextSibling()返回的都将是null。也就是说,Attr其实是被看作包含它的Element对象的一部分,它并不作为DOM树中单独的一个节点出现。这一点在使用的时候要同其它的Node子对象相区别。

请看下面例子:(message.xml

<?xml version="1.0" standalone="yes"?>

<links>

<link>

<text>sohu</text>

<url newWindow="no">http://www.sohu.com</url>

<author>zhangzhaoyang</author>

<date>

<day>2</day>

<month>1</month>

<year>2004</year>

</date>

<description>zhangzhaoyang's website</description>

</link>

<link>

<text>Java</text>

<url newWindow="no">http://java.sun.com</url>

<author>Sun Microsystems</author>

<date>

<day>3</day>

<month>1</month>

<year>2001</year>

</date>

<description>Sun Microsystem's website.</description>

</link>

<link>

<text>microsoft</text>

<url newWindow="no">http://www.microsoft.com</url>

<author>bill gates</author>

<date>

<day>4</day>

<month>1</month>

<year>2000</year>

</date>

<description>bill's website</description>

</link>

</links>

DOM解析的例子:

下面代码使用DOM解析XML文件

package com.app;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.NodeList;

public class DOMXMLReader {

public static void main(String args[]) {

try {

DocumentBuilderFactory factory = DocumentBuilderFactory

.newInstance();

DocumentBuilder builder = factory.newDocumentBuilder();

Document doc = builder.parse("url.xml");

doc.normalize();

NodeList links = doc.getElementsByTagName("link");

for (int i = 0; i < links.getLength(); i++) {

Element link = (Element) links.item(i);

System.out.print("Content: ");

System.out.println(link.getElementsByTagName("text").item(0)

.getFirstChild().getNodeValue());

System.out.print("URL: ");

System.out.println(link.getElementsByTagName("url").item(0)

.getFirstChild().getNodeValue());

System.out.print("Author: ");

System.out.println(link.getElementsByTagName("author").item(0)

.getFirstChild().getNodeValue());

System.out.print("Date: ");

Element linkdate = (Element) link.getElementsByTagName("date")

.item(0);

String day = linkdate.getElementsByTagName("day").item(0)

.getFirstChild().getNodeValue();

String month = linkdate.getElementsByTagName("month").item(0)

.getFirstChild().getNodeValue();

String year = linkdate.getElementsByTagName("year").item(0)

.getFirstChild().getNodeValue();

System.out.println(day + "-" + month + "-" + year);

System.out.print("Description: ");

System.out.println(link.getElementsByTagName("description")

.item(0).getFirstChild().getNodeValue());

System.out.println();

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

下面代码修改XML文件:

package com.app;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerFactory;

import javax.xml.transform.dom.DOMSource;

import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.Text;

public class DOMXMLWriter {

public static void main(String args[]) {

try {

DocumentBuilderFactory factory = DocumentBuilderFactory

.newInstance();

DocumentBuilder builder = factory.newDocumentBuilder();

Document doc = builder.parse("url.xml");

doc.normalize();

// ---取得变量----

String text = "itjob";

String url = "www.5itjob.com";

String author = "xsw";

String discription = "a good website for it job!";

// -------------

Text textseg;

Element link = doc.createElement("link");

Element linktext = doc.createElement("text");

textseg = doc.createTextNode(text);

linktext.appendChild(textseg);

link.appendChild(linktext);

Element linkurl = doc.createElement("url");

textseg = doc.createTextNode(url);

linkurl.appendChild(textseg);

link.appendChild(linkurl);

Element linkauthor = doc.createElement("author");

textseg = doc.createTextNode(author);

linkauthor.appendChild(textseg);

link.appendChild(linkauthor);

java.util.Calendar rightNow = java.util.Calendar.getInstance();

String day = Integer.toString(rightNow

.get(java.util.Calendar.DAY_OF_MONTH));

String month = Integer.toString(rightNow

.get(java.util.Calendar.MONTH));

String year = Integer.toString(rightNow

.get(java.util.Calendar.YEAR));

Element linkdate = doc.createElement("date");

Element linkdateday = doc.createElement("day");

textseg = doc.createTextNode(day);

linkdateday.appendChild(textseg);

Element linkdatemonth = doc.createElement("month");

textseg = doc.createTextNode(month);

linkdatemonth.appendChild(textseg);

Element linkdateyear = doc.createElement("year");

textseg = doc.createTextNode(year);

linkdateyear.appendChild(textseg);

linkdate.appendChild(linkdateday);

linkdate.appendChild(linkdatemonth);

linkdate.appendChild(linkdateyear);

link.appendChild(linkdate);

Element linkdiscription = doc.createElement("description");

textseg = doc.createTextNode(discription);

linkdiscription.appendChild(textseg);

link.appendChild(linkdiscription);

doc.getDocumentElement().appendChild(link);

TransformerFactory tFactory = TransformerFactory.newInstance();

Transformer transformer = tFactory.newTransformer();

DOMSource source = new DOMSource(doc);

StreamResult result = new StreamResult(new java.io.File("url.xml"));

transformer.transform(source, result);

System.out.println("write complete!");

} catch (Exception e) {

e.printStackTrace();

}

}

}

SAX解析实例

//以下代码统计url.xml文件中每个标签出现的次数

package com.app;

import java.io.File;

import java.util.Enumeration;

import java.util.Hashtable;

import javax.xml.parsers.SAXParser;

import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import org.xml.sax.helpers.DefaultHandler;

public class TagCounter extends DefaultHandler {

// Hashtable用来记录tag出现的次数

private Hashtable tags;

public void startDocument() throws SAXException {

tags = new Hashtable();

}

// 解析完成后的工作

public void endDocument() throws SAXException {

Enumeration e = tags.keys();

while (e.hasMoreElements()) {

String tag = (String) e.nextElement();

int count = ((Integer) tags.get(tag)).intValue();

System.out.println("Tag <" + tag + "> occurs " + count + " times");

}

}

// 对每一个开始元属进行处理

public void startElement(String namespaceURI, String localName,

String rawName, Attributes atts) throws SAXException {

String key = rawName;

Object value = tags.get(key);

if (value == null) {

// 如果是新标签,把它添加在Hastable

tags.put(key, new Integer(1));

} else {

// 如果以前碰到过,得到其计数值,并加1

int count = ((Integer) value).intValue();

count++;

tags.put(key, new Integer(count));

}

}

static public void main(String[] args) {

String filename = null;

filename = "url.xml";

SAXParserFactory spf = SAXParserFactory.newInstance();

SAXParser saxParser = null;

try {

// 创建解析器SAXParser对象

saxParser = spf.newSAXParser();

saxParser.parse(new File(filename), new TagCounter());

} catch (Exception ex) {

ex.printStackTrace();

}

}

}

//以下程序解析url.xml

package com.app;

import java.io.File;

import java.io.IOException;

import java.util.Stack;

import javax.xml.parsers.SAXParser;

import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import org.xml.sax.helpers.DefaultHandler;

public class SAXXMLReader extends DefaultHandler {

Stack tags = new Stack();

// 用于保存解析出来的信息

String text = null;

String url = null;

String author = null;

String description = null;

String day = null;

String year = null;

String month = null;

public void endDocument() throws SAXException {

System.out.println("------解析结束--------");

}

public void startDocument() throws SAXException {

System.out.println("------解析开始--------");

}

public void startElement(String p0, String p1, String p2, Attributes p3)

throws SAXException {

tags.push(p2);

}

public void endElement(String p0, String p1, String p2) throws SAXException {

tags.pop();

if (p2.equals("link"))

parser();

}

public void characters(char[] p0, int p1, int p2) throws SAXException {

// 察看栈顶元素,根据元素名称给对应的变量赋值

String tag = (String) tags.peek();

if (tag.equals("text"))

text = new String(p0, p1, p2);

else if (tag.equals("url"))

url = new String(p0, p1, p2);

else if (tag.equals("author"))

author = new String(p0, p1, p2);

else if (tag.equals("day"))

day = new String(p0, p1, p2);

else if (tag.equals("month"))

month = new String(p0, p1, p2);

else if (tag.equals("year"))

year = new String(p0, p1, p2);

else if (tag.equals("description"))

description = new String(p0, p1, p2);

}

private void parser() {

System.out.print("Content: ");

System.out.println(text);

System.out.print("URL: ");

System.out.println(url);

System.out.print("Author: ");

System.out.println(author);

System.out.print("Date: ");

System.out.println(day + "-" + month + "-" + year);

System.out.print("Description: ");

System.out.println(description);

System.out.println();

}

static public void main(String[] args) {

String filename = "url.xml";

SAXParserFactory spf = SAXParserFactory.newInstance();

SAXParser saxParser = null;

try {

saxParser = spf.newSAXParser();

saxParser.parse(new File(filename), new SAXXMLReader());

} catch (Exception ex) {

ex.printStackTrace();

}

}

}

DOM4J解析实例

D:\\wuhan\\test.xml

<?xml version="1.0" encoding="GB2312"?>

<RESULT>

<VALUE><NO>A1234</NO><NO>A1234aaaaaaaaaa</NO><ADDR>深圳罗湖</ADDR>

</VALUE>

<VALUE>

   <NO>B1234</NO>

   <ADDR>深圳南山</ADDR>

</VALUE>

<VALUE>

   <NO>B1234xxxx</NO>

   <ADDR>深圳xxxxx</ADDR>

</VALUE>

</RESULT>

Java 代码

package com.xml;

import java.io.*;

import java.util.*;

import org.dom4j.*;

import org.dom4j.io.*;

public class Dom4j {

public static void main(String arge[]) {

long lasting = System.currentTimeMillis();

try {

File f = new File("D:\\wuhan\\test.xml");

SAXReader reader = new SAXReader();

Document doc = reader.read(f);

Element root = doc.getRootElement();

Element foo;

for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {

foo = (Element) i.next();

System.out.print("车牌号码:" + foo.elementText("NO"));

System.out.println("车主地址:" + foo.elementText("ADDR"));

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

JDOM解析实例

package com.xml;

import java.io.*;

import java.util.*;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.input.SAXBuilder;

public class JDom {

public static void main(String arge[]) {

long lasting = System.currentTimeMillis();

try {

SAXBuilder builder = new SAXBuilder();

Document doc = builder.build(new File("D:\\wuhan\\test.xml"));

Element foo = doc.getRootElement();

List allChildren = foo.getChildren();

for(int i = 0;i < allChildren.size();i++) {

System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());

System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());

}

} catch (Exception e) {

e.printStackTrace();

}

}}

JAVA操纵XML 实例讲解

JDBC开发中需要连接到不同的数据库,利用XML文件保存不同类型数据库的连接参数,并使用统一的程序解析XML以取得相应的连接参数。

<<Db.xml>>

<?xml version="1.0" encoding="UTF-8"?>

<DBS>

<DB>

<type>Oracle</type>

<driver>oracle.jdbc.driver.OracleDriver</driver>

<url>jdbc:oracle:thin:@localhost:1521:goudan</url>

<user>pubuser1</user>

<password>11111111</password>

</DB>

<DB>

<type>mysql</type>

<driver>org.git.mm.mysql.Driver</driver>

<url>jdbc:mysql://localhost:3306/BookDB</url>

<user>pubuser2</user>

<password>11111111</password>

</DB>

<DB>

<type>sqlserver</type> <driver>com.microsoft.jdbc.sqlserver.SQLServerDriver</driver>

<url>jdbc:Microsoft:sqlserver://127.0.0.1:1433</url>

<user>pubuser3</user>

<password>11111111</password>

</DB>

</DBS>

<<DBTest.java>>

package test.xml;

import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;

public class DBTest {

public DBTest(String type) {

// this.type = type ;

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

try {

DocumentBuilder db = dbf.newDocumentBuilder();// 创建解析器

Document doc = db.parse("Db.xml");// 解析地址

NodeList n1 = doc.getElementsByTagName("DB");// 得到含有DB标签的一个列表

for (int i = 0; i < n1.getLength(); i++) {

Element myNode = (Element) n1.item(i);// 通过item方法获得每个对象

if (myNode.getElementsByTagName("type").item(0).getFirstChild()

.getNodeValue().equals(type)) {

String driver = myNode.getElementsByTagName("driver").item(

0).getFirstChild().getNodeValue();

String url = myNode.getElementsByTagName("url").item(0)

.getFirstChild().getNodeValue();

String user = myNode.getElementsByTagName("user").item(0)

.getFirstChild().getNodeValue();

String password = myNode.getElementsByTagName("password")

.item(0).getFirstChild().getNodeValue();

System.out.println(driver + " " + url + " " + user + " "

+ password);

break;

}

}

} catch (ParserConfigurationException e) {

e.printStackTrace();

} catch (SAXException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] rag) {

DBTest dbt1 = new DBTest("Oracle");

DBTest dbt2 = new DBTest("mysql");

DBTest dbt3 = new DBTest("sqlserver");

}

}

通过JAVA写数据到XML里面

<<persons.xml>>

<?xml version="1.0" encoding="UTF-8"?>

<persons>

<person>

<name>andy</name>

<age>45</age>

<tel>13632940025</tel>

<sex>m</sex>

</person>

</persons>

<<WriteXmlTest.xml>>

package test.xml;

import java.io.File;

import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerConfigurationException;

import javax.xml.transform.TransformerException;

import javax.xml.transform.TransformerFactory;

import javax.xml.transform.dom.DOMSource;

import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.NodeList;

import org.w3c.dom.Text;

import org.xml.sax.SAXException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.ResultSet;

import java.sql.Statement;

import java.util.ArrayList;

public class WriteXmlTest {

public static void main(String[] args) {

try {

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

DocumentBuilder db = dbf.newDocumentBuilder();

Document dc = db.parse("persons.xml");

fang(dc);

} catch (ParserConfigurationException e) {

e.printStackTrace();

} catch (SAXException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

public static void fang(Document doc) {

Element eleroots = doc.getDocumentElement();

Element eleroot = doc.createElement("person");// 创建gen元素

eleroots.appendChild(eleroot);

Element elename = doc.createElement("name");

Text text1 = doc.createTextNode("andy");

elename.appendChild(text1);

eleroot.appendChild(elename);

Element eleage = doc.createElement("age");

Text text2 = doc.createTextNode("45");

eleage.appendChild(text2);

eleroot.appendChild(eleage);

Element eletel = doc.createElement("tel");

Text text3 = doc.createTextNode("13632940025");

eletel.appendChild(text3);

eleroot.appendChild(eletel);

Element elesex = doc.createElement("sex");

Text text4 = doc.createTextNode("m");

elesex.appendChild(text4);

eleroot.appendChild(elesex);

TransformerFactory tff = TransformerFactory.newInstance();

Transformer tf = null;

try {

tf = tff.newTransformer();

} catch (TransformerConfigurationException e) {

e.printStackTrace();

}

DOMSource ds = new DOMSource(doc);

StreamResult sr = new StreamResult(new File("persons.xml"));

try {

tf.transform(ds, sr);

} catch (TransformerException e) {

e.printStackTrace();

}

}

}

内容总结

? 知道解析器的作用

? 理解DOMSAX解析XML原理

? 了解DOMSAX解析的优缺点

? 使用DOMSAX解析XML文档

? 使用DOM4J解析XML文档

? 使用JDOM 解析XML文档

? 能够使用各种解析方式来处理应用程序

? 能够写出XML方式的连接池技术

独立实践

? 写一个XML文档:包括JDBC连接所需要的驱动,url,用户名,密码等

? 使用SAX或者DOM解析上面的XML建立数据库的连接

? 使用DOM4J解析一个判断用户的登陆的操作,用户信息存在XML文档里

? 使用JDOM解析上一章的一个关于学生课程信息的XML文档,并在控制台显示数据

? 使用任何一种解析方式对学生课程信息的XML文档进行写一行增加课程信息的数据。

第二十三章:HTML基础

学习目标

? HTML文档是由HTML元素组成的文本文件,是预定义的正在使用的HTML标签

? HTML中最重要的标签是定义标题元素,段落和换行的标签

? 掌握格式化元素比如加粗和倾斜文本

? 了解HTML中的特殊实体

? 掌握超链接、锚的用法

? 掌握表格的画法以及如何添加表的标题、表头等

? 掌握无序、有序及自定义列表

? 了解并掌握图像标签、alt属性、src属性,以及如何利用图像创建超链接

? 掌握如何在<body>标签中设置网页背景

知识要点

HTML元素

HTML文档是由HTML元素组成的文本文件。

HTML元素是预定义的正在使用的HTML标签。

________________________________________HTML标签

HTML标签用来组成HTML元素。

HTML标签两端有两个包括字符:“<”“>”,这两个包括字符被称为角括号。

HTML标签通常成对出现,比如<b></b>。一对标签的前面一个是开始标签,第二个是结束标签,在开始和结束标签之间的文本是元素内容。

HTML标签是大小写无关的,<b><B>表示的意思是一样的。________________________________________

回忆一下上一页的HTML例子:

<html>

<head>

<title>Title of page</title>

</head>

<body>

This is my first homepage.

<b>This text is bold</b>

</body>

</html>

下面是一个HTML元素:

<b>This text is bold</b>

HTML元素以开始标签<b>起始, 内容是:This text is bold,以结束标签</b>中止。<b>标签的目的是定义一个需要被显示成粗体的HTML元素。

下面也是一个HTML元素:

<body>

This is my first homepage.

<b>This text is bold</b>

</body>

HTML标签以开始标签<body>起始,终止于结束标签</body><body>标签的目的是定义一个HTML元素,使其包含HTML文档的主体。________________________________________为什么使用小写标签?

HTML标签是大小写无关的:<B><b>含义相同。当你上网的时候,你会注意到多数教程在示例中使用大写的HTML标签,我们总是使用小写标签。为什么?

假如你想投入到下一代HTML中,你应该开始使用小写标签。W3C在他们的HTML4建议中提倡使用小写标签,XHTML(下一代HTML)也需要小写标签。________________________________________

标签属性

标签可以拥有属性。属性能够为页面上的HTML元素提供附加信息。

标签<body>定义了HTML页面的主体元素。使用一个附加的bgcolor属性,你可以告诉浏览器:你页面的背景色是红色的,就像这样:

<body bgcolor="red">

标签<table>定义了一个HTML表格。使用一个附加的border属性,你可以告诉浏览器:这个表格是没有边框的,代码是:

<table border="0">

属性通常由属性名和值成对出现,就像这样:name="value"。属性通常是附加给HTML元素的开始标签的。

引号样式

属性值应该被包含在引号中。双引号是最常用的,但是单引号也可以使用。

在很少情况下,比如说属性值本身包含引号,使用单引号就很必要了。

比如:name='John "ShotGun" Nelson'

注意:中文引号跟英文引号是不一样的。上面所指的引号都是英文状态下的引号。

HTML基本标签

HTML中最重要的标签是定义标题元素,段落和换行的标签。

学习HTML的最好方法是编辑运行示例代码。

请自己尝试一下这个例子

一个非常简单的HTML文档:

<html>

<body>

The content of the body element is displayed in your browser.

</body>

</html>

这个例子是一个非常简单的HTML文档,拥有很少的几个HTML标签。它说明了一个主体元素中的文本是如何在浏览器中显示的。

简单的段落:

<html>

<body>

<p>

This is a paragraph.

</p>

<p>

This is a paragraph.

</p>

<p>

This is a paragraph.

</p>

<p>

Paragraph elements are defined by the p tag.

</p>

</body>

</html>

这个例子说明了段落元素中的文本是如何在浏览器中显示的。

标题元素

标题元素由标签<h1><h6>定义。<h1>定义了最大的标题元素,<h6>定义了最小的。

<h1>

This is a heading

</h1>

<h2>

This is a heading

</h2>

<h3>

This is a heading

</h3>

<h4>

This is a heading

</h4>

<h5>

This is a heading

</h5>

<h6>

This is a heading

</h6>

HTML自动在一个标题元素前后各添加一个空行。

段落

段落是用<p>标签定义的。

<p>This is another paragraph</p>

HTML自动在一个段落前后各添加一个空行。

换行

当需要结束一行,并且不想开始新段落时,使用<br>标签。<br>标签不管放在什么位置,都能够强制换行。

<p>

This

<br>

is a para

<br>

graph with line breaks

</p>

<br>标签是一个空标签,它没有结束标记。

注释

注释标签用来在HTML源文件中插入注释。注释会被浏览器忽略。你可以使用注释来解释你的代码,这可以在你以后编辑代码的时候帮助你。

<!-- This is a comment -->

注意:你需要在左括号“<”后面跟一个感叹号,右括号不用。

技巧

当你写下HTML文本的时候,你不能确知在另外一个浏览器中,这些文本将被如何显示。有人用着大的显示器,有的人用的小一些。每次用户调整窗口大小的时候,文本都将被重新格式化。不要想在编辑器中写一些空行和空格来协助排版。

HTML将截掉你文本中的多余空格。不管多少个空格,处理起来只当一个。一点附加信息:在HTML里面,一个空行也只被当作一个空格来处理。

使用空段落<p>来插入空白行是一个坏习惯,请使用<br>标签来替代。(但是不要用<br>标签来创建列表,我们后面会专门学习HTML列表的。)

你也许注意到了段落可以不写结束标记</p>。别依赖它,HTML的下一个版本将不准你漏掉任何一个结束标签。

HTML自动在某些元素前后增加额外的空行,就像在段落和标题元素的前后一样。

我们使用了水平线(<hr>标签)来分隔我们教程的章节。

例如:

多个段落

<html>

<body>

<p>

This paragraph contains a lot of lines in the source code, but the

browser ignores it.

</p>

<p>

This paragraph contains a lot of spaces in the source code, but the

browser ignores it.

</p>

<p>

The number of lines in a paragraph depends on the size of your

browser window. If you resize the browser window, the number of lines

in this paragraph will change.

</p>

</body>

</html>

这个例子说明了段落的一些默认行为。

换行

<html>

<body>

<p>

To break

<br>

lines

<br>

in a

<br>

paragraph,

<br>

use the br tag.

</p>

</body>

</html>

这个例子说明了在HTML文档中换行的使用。

格式

<html>

<body>

<p>

My Bonnie lies over the ocean. My Bonnie lies over the sea. My Bonnie

lies over the ocean. Oh, bring back my Bonnie to me.

</p>

<p>

Note that your browser simply ignores your formatting!

</p>

</body>

</html>

这个例子说明了HTML显示格式的一些问题。

标题元素

<html>

<body>

<h1>

This is heading 1

</h1>

<h2>

This is heading 2

</h2>

<h3>

This is heading 3

</h3>

<h4>

This is heading 4

</h4>

<h5>

This is heading 5

</h5>

<h6>

This is heading 6

</h6>

<p>

Use heading tags only for headings. Don't use them just to make

something bold. Use other tags for that.

</p>

</body>

</html>

这个例子说明了在HTML中显示标题元素的标签。

居中的标题元素

<html>

<body>

<h1 align="center">

This is heading 1

</h1>

<p>

The heading above is aligned to the center of this page. The heading

above is aligned to the center of this page. The heading above is

aligned to the center of this page.

</p>

</body>

</html>

这个例子显示了一个居中的标题元素。

水平线

<html>

<body>

<p>

The hr tag defines a horizontal rule:

</p>

<hr>

<p>

This is a paragraph

</p>

<hr>

<p>

This is a paragraph

</p>

<hr>

<p>

This is a paragraph

</p>

</body>

</html>

这个例子说明了如何插入水平线。

隐藏的注释

<html>

<body>

<!--This comment will not be displayed-->

<p>

This is a regular paragraph

</p>

</body>

</html>

这个例子说明了在HTML文档中如何插入隐藏的注释。

背景色

<html>

<body bgcolor="yellow">

<h2>

Look: Colored Background!

</h2>

</body>

</html>

这个例子说明了如何给页面设置背景色。

HTML格式

HTML定义了很多元素用来格式化输出,比如加粗和倾斜文本。

例如:

格式化文字

<html>

<body>

<b>This text is bold</b>

<br>

<strong> This text is strong </strong>

<br>

<big> This text is big </big>

<br>

<em> This text is emphasized </em>

<br>

<i> This text is italic </i>

<br>

<small> This text is small </small>

<br>

This text contains

<sub> subscript </sub>

<br>

This text contains

<sup>

superscript

</sup>

</body>

</html>

这个例子说明了在HTML里面可以怎样格式化文本。

预格式化文本

<html>

<body>

<pre>

This is

preformatted text.

It preserves both spaces

and line breaks.

</pre>

<p>

The pre tag is good for displaying computer code:

</p>

<pre>

for i = 1 to 10

print i

next i

</pre>

</body>

</html>

这个例子说明了可以怎样用pre标签来控制换行和空格。

计算机输出标签

<html>

<body>

<code>

Computer code

</code>

<br>

<kbd>

Keyboard input

</kbd>

<br>

<tt>Teletype text</tt>

<br>

<samp>

Sample text

</samp>

<br>

<var>

Computer variable

</var>

<br>

<p>

<b>Note:</b> These tags are often used to display

computer/programming code.

</p>

</body>

</html>

这个例子说明了计算机输出标签在显示上的不同。

地址

<html>

<body>

<address>

Donald Duck

<br>

BOX 555

<br>

Disneyland

<br>

USA

</address>

</body>

</html>

这个例子说明了如何用HTML书写一个地址。

缩写和首字母缩略法

<html>

<body>

<abbr title="United Nations">UN</abbr>

<br>

<acronym title="World Wide Web">WWW</acronym>

<p>

The title attribute is used to show the spelled-out version when

holding the mouse pointer over the acronym or abbreviation.

</p>

<p>

This only works for the acronym element in IE 5.

</p>

<p>

This works for both the abbr and acronym element in Netscape 6.2.

</p>

</body>

</html>

这个例子说明了如何处理缩写和首字母缩略。

文字方向

<html>

<body>

<p>

If your browser supports bi-directional override (bdo), the next line

will be written from the right to the left (rtl):

</p>

<bdo dir="rtl">

Here is some Hebrew text

</bdo>

</body>

</html>

这个例子说明了如何改变文字方向。

块引用

<html>

<body>

Here comes a long quotation:

<blockquote>

This is a long quotation. This is a long quotation. This is a long

quotation. This is a long quotation. This is a long quotation.

</blockquote>

Here comes a short quotation:

<q> This is a short quotation </q>

<p>

With the block quote element, the browser inserts line breaks and

margins, but the q element does not render as anything special.

</p>

</body>

</html>

这个例子说明了如何处理大段引用和小块引用。

删除和插入文字

<html>

<body>

<p>

a dozen is

<del>

twenty

</del>

<ins>

twelve

</ins>

pieces

</p>

<p>

Most browsers will overstrike deleted text and underline inserted

text.

</p>

<p>

Some older browsers will display deleted or inserted text as plain

text.

</p>

</body>

</html>

这个例子说明了如何标记被删除或者插入的文本。

查看HTML源文件

在浏览器菜单中选择查看——源文件,将弹出一个窗口,这个页面的HTML代码就在其中。

HTML实体

有些字符,比如说“<”字符,在HTML中有特殊的含义,因此不能在文本中使用。

想要在HTML中显示一个小于号“<”,需要用到字符实体。

字符实体

HTML中,有些字符拥有特殊含义,比如小于号“<”定义为一个HTML标签的开始。假如我们想要浏览器显示这些字符的话,必须在HTML代码中插入字符实体。

一个字符实体拥有三个部分:一个and符号(&),一个实体名或者一个实体号,最后是一个分号(;

想要在HTML文档中显示一个小于号,我们必须这样写:<或者<

使用名字相对于使用数字的优点是容易记忆,缺点是并非所有的浏览器都支持最新的实体名,但是几乎所有的浏览器都能很好地支持实体号。

注意:实体名是大小写敏感的。

下面这个例子能够让你针对HTML实体实践一下。

<html>

<body>

<p>

This is a character entity: {

</p>

<p>

</body>

</html>

不可拆分的空格

HTML中,最常见的字符实体就是不可拆分空格。

通常,HTML会合并你文档中的空格。假如在你的HTML文本中连续写了10个空格,其中9个会被去掉。想要在HTML中插入空格,可以使用实体:

常用的字符实体

显示结果 描述 实体名 实体号

不可拆分的空格

< 小于 < <

> 大于 > >

& and符号 & &

" 引号 " "

' 单引号 '

其它字符实体

显示结果 描述 实体名 实体号

¢ 分 ¢ ¢

£ 英镑 £ £

¥ 人民币元 ¥ ¥

§ 章节 § §

? 版权 © ©

? 注册 ® ®

× 乘号 × ×

÷ 除号 ÷ ÷

HTML链接

HTML使用超级链接来连接到网络上的其他页面。

例如

创建链接

<html>

<body>

<p>

<a href="lastpage.htm"> This text</a> is a link to a page on this Web

site.

</p>

<p>

<a href="http://www.microsoft.com/"> This text</a> is a link to a

page on the World Wide Web.

</p>

</body>

</html>

这个例子说明了在HTML文档中如何创建链接

图片作为链接

<html>

<body>

<p>

You can also use an image as a link:

<a href="lastpage.htm"> <img border="0" src=".\images\next.gif">

</a>

</p>

</body>

</html>

这个例子说明了在HTML文档中如何用图片作为链接。

锚标签和href属性

HTML使用锚标签(<a>)来创建一个连接到其他文件的链接。锚可以指向网络上的任何资源:HTML页面,图像,声音,影片等等。

创建一个锚的语法:

<a href="url">Text to be displayed</a>

锚可以指向网络上的任何资源:HTML页面,图像,声音,影片等等。

标签<a>被用来创建一个链接指向的锚,href属性用来指定连接到的地址,在锚的起始标签<a>和结束标签</a>中间的部分将被显示为超级链接。

这个锚定义了一个到W3Schools的链接:

<a href="http://www.w3schools.com/">Visit W3Schools!</a>

target属性

使用target属性,你可以定义从什么地方打开链接地址。

下面这段代码打开一个新的浏览器窗口来打开链接:

<a href="http://www.w3schools.com/" target="_blank">Visit W3Schools!</a>

锚标签和name属性

name属性用来创建一个命名的锚。使用命名锚以后,可以让链接直接跳转到一个页面的某一章节,而不用用户打开那一页,再从上到下慢慢找。

下面是命名锚的语法:

<a name="label">Text to be displayed</a>

你可以为锚随意指定名字,只要你愿意。下面这行代码定义了一个命名锚:<a name="tips">Useful Tips Section</a>

你应该注意到了:命名锚的显示方式并没有什么与众不同的。

想要直接链接到“tips”章节的话,在URL地址的后面加一个“#”和这个锚的名字,就像这样:

<a name="http://www.w3schools.com/html_links.asp#tips">Jump to the Useful Tips Section</a>

一个链接到本页面中某章节的命名锚是这样写的:<a name="#tips">Jump to the Useful Tips Section</a>

技巧

尽量在子目录路径后面加一个左斜杠。假如你像下面这样写:href="http://www.w3schools.com/html",将会产生向服务器产生两个HTTP请求,因为服务器会在后面追加一个左斜杠,产生一个新的请求,就像这样:href="http://www.w3schools.com/html/"

命名锚通常用来在大型文档的开头创建章节表。这个页面的每个章节被加上一个命名锚,到这些锚的链接被放在页面的顶端。

如果浏览器无法找到指定的命名锚,它将转到这个页面的顶部,而不显示任何错误提示。

例如:

在新浏览器窗口中打开链接

<html>

<body>

<a href="lastpage.htm" target="_blank">Last Page</a>

<p>

If you set the target attribute of a link to "_blank", the link will

open in a new window.

</p>

</body>

</html>

这个例子说明了怎样用打开新窗口的方式来链接到其他页面,这样,访问者不用离开你的页面。

链接到本页面的某个位置

<html>

<body>

<p>

<a href="#C4"> See also Chapter 4. </a>

</p>

<p>

<h2>

Chapter 1

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 2

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 3

</h2>

<p>

This chapter explains ba bla bla

</p>

<a name="C4"><h2>

Chapter 4

</h2>

</a>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 5

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 6

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 7

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 8

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 9

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 10

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 11

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 12

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 13

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 14

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 15

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 16

</h2>

<p>

This chapter explains ba bla bla

</p>

<h2>

Chapter 17

</h2>

<p>

This chapter explains ba bla bla

</p>

</body>

</html>

这个例子说明了如何跳转到一个文档的某部分。

跳出框架

<html>

<body>

<p>

Locked in a frame?

</p>

<a href="http://www.w3schools.com/" target="_top">Click here!</a>

</body>

</html>

这个例子说明了假如你的页面在框架中,如何跳出框架。

创建一个邮件链接

<html>

<body>

<p>

This is a mail link:

<a href="mailto:someone@microsoft.com?subject=Hello%20again">

Send Mail</a>

</p>

<p>

<b>Note:</b> Spaces between words should be replaced by %20 to

<b>ensure</b> that the browser will display your text properly.

</p>

</body>

</html>

这个例子说明了如何链接到一个邮件信息(只有安装了邮件程序才有效)。

创建一个邮件链接2

<html>

<body>

<p>

This is another mailto link:

<a

href="mailto:someone@microsoft.com?cc=someoneelse@microsoft.com&bcc=andsomeoneelse2@microsoft.com&subject=Summer%20Party&body=You%20are%20invited%20to%20a%20big%20summer%20party!">Send

mail!</a>

</p>

<p>

<b>Note:</b> Spaces between words should be replaced by %20 to

<b>ensure</b> that the browser will display your text properly.

</p>

</body>

</html>

这个例子显示了一个完成度更高的邮件链接。

HTML表格

使用HTML可以创建表格。

例如:

<html>

<body>

<p>

Each table starts with a table tag. Each table row starts with a tr

tag. Each table data starts with a td tag.

</p>

<h4>

One column:

</h4>

<table border="1">

<tr>

<td>

100

</td>

</tr>

</table>

<h4>

One row and three columns:

</h4>

<table border="1">

<tr>

<td>

100

</td>

<td>

200

</td>

<td>

300

</td>

</tr>

</table>

<h4>

Two rows and three columns:

</h4>

<table border="1">

<tr>

<td>

100

</td>

<td>

200

</td>

<td>

300

</td>

</tr>

<tr>

<td>

400

</td>

<td>

500

</td>

<td>

600

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何在HTML页面中创建表格。

表格边框

<html>

<body>

<h4>

With a normal border:

</h4>

<table border="1">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With a thick border:

</h4>

<table border="8">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With a very thick border:

</h4>

<table border="15">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子显示了不同的表格边框。

表格

表格是用<table>标签定义的。表格被划分为行(使用<tr>标签),每行又被划分为数据单元格(使用<td>标签)。td表示表格数据Table Data),即数据单元格的内容。数据单元格可以包含文本,图像,列表,段落,表单,水平线,表格等等。

<table border="1">

<tr>

<td>

row 1, cell 1

</td>

<td>

row 1, cell 2

</td>

</tr>

<tr>

<td>

row 2, cell 1

</td>

<td>

row 2, cell 2

</td>

</tr>

</table>

在浏览器中显示如下:

row 1, cell 1 row 1, cell 2

row 2, cell 1 row 2, cell 2

表格和border属性

如果不指定border属性,表格将不显示边框。有时候这很有用,但是多数时候我们希望显示边框。

想要显示一个有边框的表格,需要使用border属性。

<table border="1">

<tr>

<td>

Row 1, cell 1

</td>

<td>

Row 1, cell 2

</td>

</tr>

</table>

表格头

表格头使用<th>标签指定。

<table border="1">

<tr>

<th>

Heading

</th>

<th>

Another Heading

</th>

</tr>

<tr>

<td>

row 1, cell 1

</td>

<td>

row 1, cell 2

</td>

</tr>

<tr>

<td>

row 2, cell 1

</td>

<td>

row 2, cell 2

</td>

</tr>

</table>

在浏览器中显示如下:

Heading Another Heading

row 1, cell 1 row 1, cell 2

row 2, cell 1 row 2, cell 2

表格中的空单元格

在多数浏览器中,没有内容的单元格显示得不太好。

<table border="1">

<tr>

<td>

row 1, cell 1

</td>

<td>

row 1, cell 2

</td>

</tr>

<tr>

<td>

row 2, cell 1

</td>

<td></td>

</tr>

</table>

在浏览器中显示如下:

row 1, cell 1 row 1, cell 2

row 2, cell 1

注意一下空单元格的边框没有显示出来。为了避免这个,可以在空单元格里加入不可分空格来占位,这样边框能正常显示。

<table border="1">

<tr>

<td>

row 1, cell 1

</td>

<td>

row 1, cell 2

</td>

</tr>

<tr>

<td>

row 2, cell 1

</td>

<td>

</td>

</tr>

</table>

在浏览器中显示如下:

row 1, cell 1 row 1, cell 2

row 2, cell 1

技巧

通常很少使用<thead><tbody><tfoot>标签,因为浏览器对它们的支持不好。希望这个在XHTML的未来版本中得到改变。

________________________________________

例如:

没有边框的表格

<html>

<body>

<h4>

This table has no borders:

</h4>

<table>

<tr>

<td>

100

</td>

<td>

200

</td>

<td>

300

</td>

</tr>

<tr>

<td>

400

</td>

<td>

500

</td>

<td>

600

</td>

</tr>

</table>

<h4>

And this table has no borders:

</h4>

<table border="0">

<tr>

<td>

100

</td>

<td>

200

</td>

<td>

300

</td>

</tr>

<tr>

<td>

400

</td>

<td>

500

</td>

<td>

600

</td>

</tr>

</table>

</body>

</html>

这个例子显示了一个没有边框的表格。

表格头

<html>

<body>

<h4>

Table headers:

</h4>

<table border="1">

<tr>

<th>

Name

</th>

<th>

Telephone

</th>

<th>

Telephone

</th>

</tr>

<tr>

<td>

Bill Gates

</td>

<td>

555 77 854

</td>

<td>

555 77 855

</td>

</tr>

</table>

<h4>

Vertical headers:

</h4>

<table border="1">

<tr>

<th>

First Name:

</th>

<td>

Bill Gates

</td>

</tr>

<tr>

<th>

Telephone:

</th>

<td>

555 77 854

</td>

</tr>

<tr>

<th>

Telephone:

</th>

<td>

555 77 855

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何显示表格头。

空单元格

<html>

<body>

<table border="1">

<tr>

<td>

Some text

</td>

<td>

Some text

</td>

</tr>

<tr>

<td></td>

<td>

Some text

</td>

</tr>

</table>

<p>

As you can see, one of the cells has no border. That is because it is

empty. Try to insert a space in the cell. Still it has no border.

</p>

<p>

The trick is to insert a no-breaking space in the cell.

</p>

<p>

No-breaking space is a character entity. If you don't know what a

character entity is, read the chapter about it.

</p>

<p>

The no-breaking space entity starts with an ampersand ("&"),then the

letters "nbsp", and ends with a semicolon (";")

</p>

<p>

</p>

</body>

</html>

这个例子说明了如何使用“ ”来支撑没有内容的单元格。

有标题的表格

<html>

<body>

<h4>

This table has a caption,and a thick border:

</h4>

<table border="6">

<caption>

My Caption

</caption>

<tr>

<td>

100

</td>

<td>

200

</td>

<td>

300

</td>

</tr>

<tr>

<td>

400

</td>

<td>

500

</td>

<td>

600

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何创建一个有标题的表格。

单元格跨行(列)的表格

<html>

<body>

<h4>

Cell that spans two columns:

</h4>

<table border="1">

<tr>

<th>

Name

</th>

<th colspan="2">

Telephone

</th>

</tr>

<tr>

<td>

Bill Gates

</td>

<td>

555 77 854

</td>

<td>

555 77 855

</td>

</tr>

</table>

<h4>

Cell that spans two rows:

</h4>

<table border="1">

<tr>

<th>

First Name:

</th>

<td>

Bill Gates

</td>

</tr>

<tr>

<th rowspan="2">

Telephone:

</th>

<td>

555 77 854

</td>

</tr>

<tr>

<td>

555 77 855

</td>

</tr>

</table>

</body>

</html>

这个例子显示了如何定义跨行或者跨列的单元格。

表格内的其他标签

<html>

<body>

<table border="1">

<tr>

<td>

<p>

This is a paragraph

</p>

<p>

This is another paragraph

</p>

</td>

<td>

This cell contains a table:

<table border="1">

<tr>

<td>

A

</td>

<td>

B

</td>

</tr>

<tr>

<td>

C

</td>

<td>

D

</td>

</tr>

</table>

</td>

</tr>

<tr>

<td>

This cell contains a list

<ul>

<li>

apples

</li>

<li>

bananas

</li>

<li>

pineapples

</li>

</ul>

</td>

<td>

HELLO

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何在元素中显示其他元素。

cellpadding属性

<html>

<body>

<h4>

Without cellpadding:

</h4>

<table border="1">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With cellpadding:

</h4>

<table border="1" cellpadding="10">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何使用cellpadding属性在表格内容和边框之间留出更多空白。

cellspacing属性

<html>

<body>

<h4>

Without cellspacing:

</h4>

<table border="1">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With cellspacing:

</h4>

<table border="1" cellspacing="10">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何使用cellspacing属性来增加单元格间距。

给表格增加背景色或者背景图像

<html>

<body>

<h4>

A background color:

</h4>

<table border="1" bgcolor="red">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

A background image:

</h4>

<table border="1" background="/images/bgdesert.jpg">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何给表格增加背景。

给表格增加背景色或者背景图像

<html>

<body>

<h4>

Cell backgrounds:

</h4>

<table border="1">

<tr>

<td bgcolor="red">

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td background="/images/bgdesert.jpg">

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何给一个或多个单元格增加背景。

给单元格内容设置对齐方式

<html>

<body>

<table width="400" border="1">

<tr>

<th align="left">

Money spent on....

</th>

<th align="right">

January

</th>

<th align="right">

February

</th>

</tr>

<tr>

<td align="left">

Clothes

</td>

<td align="right">

$241.10

</td>

<td align="right">

$50.20

</td>

</tr>

<tr>

<td align="left">

Make-Up

</td>

<td align="right">

$30.00

</td>

<td align="right">

$44.45

</td>

</tr>

<tr>

<td align="left">

Food

</td>

<td align="right">

$730.40

</td>

<td align="right">

$650.00

</td>

</tr>

<tr>

<th align="left">

Sum

</th>

<th align="right">

$1001.50

</th>

<th align="right">

$744.65

</th>

</tr>

</table>

</body>

</html>

这个例子说明了如何使用“align”属性来设置单元格的对齐方式,让表格好看一些。

frame属性

<html>

<body>

<p>

If you see no frames around the tables in these examples, your

browser is too old, or does not support it.

</p>

<h4>

With frame="border":

</h4>

<table frame="border">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="box":

</h4>

<table frame="box">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="void":

</h4>

<table frame="void">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="above":

</h4>

<table frame="above">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="below":

</h4>

<table frame="below">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="hsides":

</h4>

<table frame="hsides">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="vsides":

</h4>

<table frame="vsides">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="lhs":

</h4>

<table frame="lhs">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

<h4>

With frame="rhs":

</h4>

<table frame="rhs">

<tr>

<td>

First

</td>

<td>

Row

</td>

</tr>

<tr>

<td>

Second

</td>

<td>

Row

</td>

</tr>

</table>

</body>

</html>

这个例子说明了如何使用“frame”属性来控制表格周围的边框。

HTML列表

HTML支持有序、无序和自定义列表。

例如:

一个无序列表

<html>

<body>

<h4>

An Unordered List:

</h4>

<ul>

<li>

Coffee

</li>

<li>

Tea

</li>

<li>

Milk

</li>

</ul>

</body>

</html>

这个例子显示了一个无序列表。

有序列表

<html>

<body>

<h4>

An Ordered List:

</h4>

<ol>

<li>

Coffee

</li>

<li>

Tea

</li>

<li>

Milk

</li>

</ol>

</body>

</html>

这个例子显示了一个有序列表。

无序列表

无序列表是一个项目的序列。各项目前加有标记(通常是黑色的实心小圆圈)。

无序列表以<ul>标签开始。每个列表项目以<li>开始。

<ul>

<li>

Coffee

</li>

<li>

Milk

</li>

</ul>

在浏览器中显示如下:

? Coffee

? Milk

无序列表的项目中可以加入段落、换行、图像,链接,其他的列表等等。

有序列表

有序列表也是一个项目的序列。各项目前加有数字作标记。

有序列表以<ol>标签开始。每个列表项目以<li>开始。

<ol>

<li>

Coffee

</li>

<li>

Milk

</li>

</ol>

在浏览器中显示如下:

1. Coffee

2. Milk

有序列表的项目中可以加入段落、换行、图像,链接,其他的列表等等。

自定义列表

自定义列表不是一个项目的序列,它是一系列条目和它们的解释。

有序列表以<dl>标签开始,自定义列表条目以<dt>开始,自定义列表的定义以<dd>开始。

<dl>

<dt>

Coffee

</dt>

<dd>

Black hot drink

</dd>

<dt>

Milk

</dt>

<dd>

White cold drink

</dd>

</dl>

在浏览器中显示如下:

Coffee

Black hot drink

Milk

White cold drink

自定义列表的定义(标签<dd>)中可以加入段落、换行、图像,链接,其他的列表等等。

例如:

有序列表的不同类型

<html>

<body>

<h4>

Numbered list:

</h4>

<ol>

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ol>

<h4>

Letters list:

</h4>

<ol type="A">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ol>

<h4>

Lowercase letters list:

</h4>

<ol type="a">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ol>

<h4>

Roman numbers list:

</h4>

<ol type="I">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ol>

<h4>

Lowercase Roman numbers list:

</h4>

<ol type="i">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ol>

</body>

</html>

这个例子显示了有序列表的不同类型。

无序列表的不同类型

<html>

<body>

<h4>

Disc bullets list:

</h4>

<ul type="disc">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ul>

<h4>

Circle bullets list:

</h4>

<ul type="circle">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ul>

<h4>

Square bullets list:

</h4>

<ul type="square">

<li>

Apples

</li>

<li>

Bananas

</li>

<li>

Lemons

</li>

<li>

Oranges

</li>

</ul>

</body>

</html>

这个例子显示了无序列表的不同类型。

嵌套列表

<html>

<body>

<h4>

A nested List:

</h4>

<ul>

<li>

Coffee

</li>

<li>

Tea

<ul>

<li>

Black tea

</li>

<li>

Green tea

</li>

</ul>

</li>

<li>

Milk

</li>

</ul>

</body>

</html>

这个例子说明了可以怎样嵌套列表。

嵌套列表2

<html>

<body>

<h4>

A nested List:

</h4>

<ul>

<li>

Coffee

</li>

<li>

Tea

<ul>

<li>

Black tea

</li>

<li>

Green tea

<ul>

<li>

China

</li>

<li>

Africa

</li>

</ul>

</li>

</ul>

</li>

<li>

Milk

</li>

</ul>

</body>

</html>

这个例子说明了如何创建一个更复杂化的嵌套列表。

自定义列表:

<html>

<body>

<h4>

A Definition List:

</h4>

<dl>

<dt>

Coffee

</dt>

<dd>

Black hot drink

</dd>

<dt>

Milk

</dt>

<dd>

White cold drink

</dd>

</dl>

</body>

</html>

这个例子说明了如何创建一个自定义列表。

HTML图像

使用HTML可以在文档中显示图像。

例如:

插入图像

<html>

<body>

<p>

An image:

<img src="./images/constr.gif" width="144" height="50">

</p>

<p>

A moving image:

<img src="./images/hackanm.gif" width="48" height="48">

</p>

<p>

Note that the syntax of inserting a moving image is no different from

that of a non-moving image.

</p>

</body>

</html>

这个例子说明了如何在网页中显示图像。

插入非本地图像

<html>

<body>

<p>

An image from W3Schools:

<img src="http://www.w3schools.com/images/ie.gif" width="73"

height="68">

</p>

</body>

</html>

这个例子说明了如何在网页中显示非本地图像。

Img标签和src属性

HTML里面,图像是由<img>标签定义的。

<img>是空标签,意思是说,它只拥有属性,而没有结束标签。

想要在页面上显示一个图像,需要使用src属性。“src”表示的意思。“src”属性的值是所要显示图像的URL

插入图像的语法:

<img src="url">

URL指向图像存储的地址。网站“www.w3schools.com”子目录“images”中的图像“boat.gif”URL如下: “http://www.w3schools.com/images/boat.gif”

当浏览器在文档中遇到img标签时,就放置一个图像。如果把img标签放在两个段落之间,就会先显示一个段落,然后是这个图像,最后是另外一个段落。

alt属性

alt属性用来给图像显示一个交互文本alt属性的值是由用户定义的。

<img src="boat.gif" alt="Big Boat">

“alt”属性在浏览器装载图像失败的时候告诉用户所丢失的信息,此时,浏览器显示这个交互文本来代替图像。给页面上的图像都加上alt属性是一个好习惯,它有助于更好地显示信息,而且,对纯文本浏览器很有用。

技巧

如果一个HTML文档包含10个图像,那么为了正确显示这个页面,需要加载11个文件。加载图像是需要时间的,所以请谨慎使用图像。

例如:

背景图像

<html>

<body background="./images/background.jpg">

<h3>

Look: A background image!

</h3>

<p>

Both gif and jpg files can be used as HTML backgrounds.

</p>

<p>

If the image is smaller than the page, the image will repeat itself.

</p>

</body>

</html>

这个例子说明了在HTML页面中如何添加背景图像。

对齐图像

<html>

<body>

<p>

An image

<img src="./images/xhtml.gif" align="bottom" width="100" height="50">

in the text

</p>

<p>

An image

<img src="./images/xhtml.gif" align="middle" width="100" height="50">

in the text

</p>

<p>

An image

<img src="./images/xhtml.gif" align="top" width="100" height="50">

in the text

</p>

<p>

Note that bottom alignment is the default alignment

</p>

<p>

An image

<img src="./images/xhtml.gif" width="100" height="50">

in the text

</p>

<p>

<img src="./images/xhtml.gif" width="100" height="50">

An image before the text

</p>

<p>

An image after the text

<img src="./images/xhtml.gif" width="100" height="50">

</p>

</body>

</html>

这个例子说明了在文字中如何对齐图像。

浮动图像

<html>

<body>

<p>

<img src="./images/xhtml.gif" align="left" width="100" height="50">

A paragraph with an image. The align attribute of the image is set to

"left". The image will float to the left of this text.

</p>

<p>

<img src="./images/xhtml.gif" align="right" width="100" height="50">

A paragraph with an image. The align attribute of the image is set to

"right". The image will float to the right of this text.

</p>

</body>

</html>

这个例子说明了如何让图像浮动在段落的旁边。

调整图像大小

<html>

<body>

<p>

<img src="./images/hackanm.gif" width="20" height="20">

</p>

<p>

<img src="./images/hackanm.gif" width="45" height="45">

</p>

<p>

<img src="./images/hackanm.gif" width="70" height="70">

</p>

<p>

You can make a picture larger or smaller changing the values in the

"height" and "width" attributes of the img tag.

</p>

</body>

</html>

这个例子说明了如何改变图像的大小。

图像的交互文本

<html>

<body>

<img src="./images/prev.gif" alt="Last Page">

<p>

Text-only browsers will only display the text in the "alt" attribute,

which is "Last Page".Note that if you hold the mouse pointer over the

image it will display the text.

</p>

</body>

</html>

这个例子说明了如何为图像创建交互文本。将鼠标放在图像上,将能够看到这个文本。

图像链接:

<html>

<body>

<p>

You can also use an image as a link:

<a href="back.htm"> <img border="0" src="./images/next.gif">

</a>

</p>

</body>

</html>

这个例子说明了如何使用图像作为链接。

图像地图

<html>

<body>

<p>

Click on one of the planets to watch it closer:

</p>

<img src="./images/planets.gif" width="145" height="126"

usemap="#planetmap">

<map id="planetmap" name="planetmap">

<area shape="rect" coords="0,0,82,126" alt="Sun" href="sun.htm">

<area shape="circle" coords="90,58,3" alt="Mercury"

href="mercury.htm">

<area shape="circle" coords="124,58,8" alt="Venus" href="venus.htm">

</map>

<p>

<b>Note:</b> We use both an

<b>id</b> and a

<b>name</b> attribute in the map tag because some versions of

Netscape don't understand the id attribute.

</p>

</body>

</html>

这个例子说明了如何创建带有可点击区域的图像地图。每个可点击区域是一个超级链接。

将图像转化为图像地图

<html>

<body>

<p>

Move the mouse over the image, and look at the status bar to see how

the coordinates change.

</p>

<p>

<a href="ismap.htm"> <img src="./images/planets.gif" ismap

width="146" height="126"> </a>

</p>

</body>

</html>

这个例子说明了如何将图像转化为图像地图。将鼠标在图像上移动,状态栏将显示坐标。

HTML背景

好的背景能够让网站变得很美妙。

例如:

好的背景和文字颜色

<html>

<body bgcolor="#d0d0d0">

<p>

This is a paragraph. This is a paragraph. This is a paragraph. This

is a paragraph. This is a paragraph.

</p>

<p>

This is another paragraph. This is another paragraph. This is another

paragraph. This is another paragraph.

</p>

</body>

</html>

一个背景色和文字颜色让页面文本更易读的例子。

差的背景和文字颜色

<html>

<body bgcolor="#ffffff" text="yellow">

<p>

This is a paragraph. This is a paragraph. This is a paragraph. This

is a paragraph. This is a paragraph.

</p>

<p>

This is another paragraph. This is another paragraph. This is another

paragraph. This is another paragraph.

</p>

</body>

</html>

一个背景色和文字颜色让页面文本不便阅读的例子。

背景

<body>标签有两个属性可以指定背景。背景可以是颜色,也可以是图像。

bgcolor

bgcolor属性将背景设置为颜色,它的值可以是一个十六进制数、RGB值或者一种颜色名称。

<body bgcolor="#000000">

<body bgcolor="rgb(0,0,0)">

<body bgcolor="black">

上面三条语句都可以把页面背景设置成黑色。

background

background属性将背景设置为图像,它的值是你所要使用图像的URL地址。如果这个图像的大小不及浏览器窗口,它将平铺占满窗口。

<body background="clouds.gif">

<body background="http://www.w3schools.com/clouds.gif">

URL地址可以是相对地址(上面第一条语句),也可以是绝对地址(上面第二条语句)。

________________________________________注意:如果打算使用背景图像,应该牢记:

? 这个图像会使页面加载时间过长吗?小技巧:图像文件的容量尽量不要超过10K

? 这个图像跟页面上其他图像协调吗?

? 这个图像跟页面上的文字颜色协调吗?

? 这个图像平铺了以后看起来还可以吗?

? 这个图像吸引了文字的注意力,喧宾夺主了吗?

________________________________________

技巧

<body>标签的bgcolorbackgroundtext属性在最新的HTML标准(HTML4XHTML)中已被废弃。W3C在他们的推荐中已删除这些属性。在HTML的未来版本中,层叠样式表(CSS)将被用来定义HTML元素的布局和显示属性。

一般的网站很少使用背景图像。

最常用的背景颜色是黑色、白色和灰色。

例如:

好的背景图像

<html>

<body background="./images/background.jpg">

<h3>

Image Background

</h3>

<p>

Both gif and jpg files can be used as HTML backgrounds.

</p>

<p>

If the image is smaller than the page, the image will repeat itself.

</p>

</body>

</html>

一个背景图像和文字颜色让页面文本易于阅读的例子。

好的背景图像2

<html>

<body background="./images/paper.gif">

<h3>

Image Background

</h3>

<p>

Both gif and jpg files can be used as HTML backgrounds.

</p>

<p>

If the image is smaller than the page, the image will repeat itself.

</p>

</body>

</html>

另一个背景图像和文字颜色让页面文本易于阅读的例子。

差的背景图像

<html>

<body background="./images/rock.jpg">

<h3>

Image Background

</h3>

<p>

Both gif and jpg files can be used as HTML backgrounds.

</p>

<p>

If the image is smaller than the page, the image will repeat itself.

</p>

</body>

</html>

一个背景图像和文字颜色让页面文本不便阅读的例子。

内容总结

? 掌握HTML中最重要的标签是定义标题元素,段落和换行的标签,格式化元素比如加粗和倾斜文本,了解HTML中的特殊实体

? 掌握超链接、锚的用法,表格的画法以及如何添加表的标题、表头等。无序、有序及自定义列表。以及如何在<body>标签中设置网页背景。

? 了解并掌握图像标签、alt属性、src属性,以及如何利用图像创建超链接

独立实践

? 写一个自己简历的HTML文档。

? 写一个用表格显示本班学生的HTML文档。

? 通过点击简历的小头像,可以看到显示完整相片的HTML文档。

? 在上面三个HTML文档里用不同的背景图片

? 定义一个个人主页的HTML文档

第二十四章:HTML进阶

学习目标

? 掌握表单的创建方法、提交方式以及表单所包含的文档对象

? 掌握框架的创建方法、框架大小的调整以及如何在不同框架之间创建链接

? 掌握IFrame的使用,如何利用IFame引入其它页面,以及如何在IFrame和包含IFrame的页面之间通信

知识要点

HTML表单

HTML表单用来选择不同种类的用户输入。

例如:

文本框

<html>

<body>

<form>

First name:

<input type="text" name="firstname">

<br>

Last name:

<input type="text" name="lastname">

</form>

</body>

</html>

这个例子说明了在HTML页面中如何创建文本框。在文本框中,用户可以输入文本。

密码框

<html>

<body>

<form>

Username:

<input type="text" name="user">

<br>

Password:

<input type="password" name="password">

</form>

<p>

Note that when you type characters in a password field, the browser

displays asterisks or bullets instead of the characters.

</p>

</body>

</html>

这个例子说明了在HTML页面中如何创建密码框。

表单

表单是一个能够包含表单元素的区域。

表单元素是能够让用户在表单中输入信息的元素(比如文本框,密码框,下拉菜单,单选框,复选框等等)。

表单是用<form>元素定义的。

<form>

<input>

<input>

</form>

Input

最常用的表单标签是<input>标签。Input的类型用type属性指定。最常用的input类型解释如下:

文本框

在表单中,文本框用来让用户输入字母、数字等等。

<form>

First name:

<input type="text" name="firstname">

<br>

Last name:

<input type="text" name="lastname">

</form>

________________________________________注意,表单本身并不可见。另外,在多数浏览器中,文本框的宽度默认是20个字符。________________________________________

单选按钮

当需要用户从有限个选项中选择一个时,使用单选按钮。

<form>

<input type="radio" name="sex" value="male">

Male

<br>

<input type="radio" name="sex" value="female">

Female

</form>

在浏览器中显示如下:

Male

Female

________________________________________注意,各选项中只能选取一个。________________________________________

复选框

当需要用户从有限个选项中选择一个或多个时,使用复选框。

<form>

<input type="checkbox" name="bike">

I have a bike

<br>

<input type="checkbox" name="car">

I have a car

</form>

在浏览器中显示如下:

I have a bike

I have a car

表单的action属性和提交按钮

当用户点击提交按钮的时候,表单的内容会被提交到其他文件。表单的action属性定义了所要提交到的目的文件,该目的文件收到信息后通常进行相关的处理。

<form name="input" action="html_form_action.asp" method="get">

Username:

<input type="text" name="user">

<input type="submit" value="Submit">

</form>

如果在上面这个文本框中输入一些字符,按下提交按钮以后,输入的字符将被提交到页面“action.asp”

例如:

复选框

<html>

<body>

<form>

I have a bike:

<input type="checkbox" name="Bike">

<br>

I have a car:

<input type="checkbox" name="Car">

</form>

</body>

</html>

这个例子说明了在HTML页面中如何创建复选框。用户可以选中,也可以取消选择复选框。

单选按钮

<html>

<body>

<form>

Male:

<input type="radio" checked name="Sex" value="male">

<br>

Female:

<input type="radio" name="Sex" value="female">

</form>

<p>

When a user clicks on a radio-button, the button becomes checked, and

all other buttons with the same name become unchecked

</p>

</body>

</html>

这个例子说明了在HTML页面中如何创建单选按钮。

简单的下拉列表

<html>

<body>

<form>

<select name="cars">

<option value="volvo">

Volvo

<option value="saab">

Saab

<option value="fiat">

Fiat

<option value="audi">

Audi

</select>

</form>

</body>

</html>

这个例子说明了在HTML页面如何创建下拉列表。下拉列表是可以选择的列表。

预选的下拉列表

<html>

<body>

<form>

<select name="cars">

<option value="volvo">

Volvo

<option value="saab">

Saab

<option value="fiat" selected>

Fiat

<option value="audi">

Audi

</select>

</form>

</body>

</html>

这个例子说明了如何创建一个含有预先选定元素的下拉列表。

文本域

<html>

<body>

<p>

This example demonstrates a text-area.

</p>

<textarea rows="10" cols="30">

The cat was playing in the garden.

</textarea>

</body>

</html>

这个例子说明了如何创建文本域(多行文本),用户可以在其中输入文本。在文本域中,字符个数不受限制。

创建按钮

<html>

<body>

<form>

<input type="button" value="Hello world!">

</form>

</body>

</html>

这个例子说明了如何创建按钮。按钮上的文字可以自己定义。

数据周围的标题边框

<html>

<body>

<fieldset>

<legend>

Health information:

</legend>

<form>

Height

<input type="text" size="3">

Weight

<input type="text" size="3">

</form>

</fieldset>

<p>

If there is no border around the input form, your browser is too old.

</p>

</body>

</html>

这个例子说明了如何在数据周围画带有标题的边框。

含有文本框和提交按钮的表单:

<html>

<body>

<form name="input" action="action.asp" method="get">

Enter your first name:

<input type="text" name="FirstName" value="Mickey">

<br>

Enter your last name:

<input type="text" name="LastName" value="Mouse">

<br>

<input type="submit" value="Submit">

</form>

<p>

If you click the "Submit" button, you will send your input to a new

page called action.asp.

</p>

</body>

</html>

这个例子说明了在HTML页面中加入表单。这个表单包含了两个文本框和一个提交按钮。

含有复选框的表单

<html>

<body>

<form name="input" action="action.asp" method="get">

I have a bike:

<input type="checkbox" name="Bike" checked>

<br>

I have a car:

<input type="checkbox" name="Car">

<br>

<input type="submit" value="Submit">

</form>

<p>

If you click the "Submit" button, you send your input to a new page

called action.asp.

</p>

</body>

</html>

这个表单包含了两个复选框和一个提交按钮。

含有单选按钮的表单

<html>

<body>

<form name="input" action="action.asp" method="get">

Male:

<input type="radio" name="Sex" value="Male" checked>

<br>

Female:

<input type="radio" name="Sex" value="Female">

<br>

<input type="submit" value="Submit Now!">

</form>

<p>

If you click the "Submit" button, you will send your input to a new

page called action.asp.

</p>

</body>

</html>

这个表单包含了两个单选按钮和一个提交按钮。

从表单发送电子邮件

<html>

<body>

<form action="MAILTO:someone@w3schools.com" method="post"

enctype="text/plain">

<h3>

This form sends an e-mail to W3Schools.

</h3>

Name:

<br>

<input type="text" name="name" value="yourname" size="20">

<br>

Mail:

<br>

<input type="text" name="mail" value="yourmail" size="20">

<br>

Comment:

<br>

<input type="text" name="comment" value="yourcomment" size="40">

<br>

<br>

<input type="submit" value="Send">

<input type="reset" value="Reset">

</form>

</body>

</html>

这个例子说明了如何从一个表单发送电子邮件。

HTML框架

使用框架,可以在一个浏览器窗口中显示不止一个页面。

例如:

垂直分栏

<html>

<frameset cols="25%,50%,25%">

<frame src="frame_a.htm">

<frame src="frame_b.htm">

<frame src="frame_c.htm">

</frameset>

</html>

这个例子说明了如何创建一个有三个页面的垂直分栏。

水平分栏

<html>

<frameset rows="25%,50%,25%">

<frame src="frame_a.htm">

<frame src="frame_b.htm">

<frame src="frame_c.htm">

</frameset>

</html>

这个例子说明了如何创建一个有三个页面的水平分栏。

如何使用<noframes>标签

<html>

<frameset cols="25%,50%,25%">

<frame src="frame_a.htm">

<frame src="frame_b.htm">

<frame src="frame_c.htm">

<noframes>

<body>

Your browser does not handle frames!

</body>

</noframes>

</frameset>

</html>

这个例子说明了如何使用<noframes>标签。

框架

使用框架,可以在一个浏览器窗口中显示不止一个HTML文档。这样的HTML文档被称为框架页面,它们是相互独立的。

使用框架的不利因素有:

? 网站开发者需要关心更多HTML文档的情况。

? 打印整个页面变得困难。

frameset标签

? <frameset>标签定义了如何将窗口拆分成框架。

? 每个frameset标签定义了一组行和列。

? 行/列的值指明了每个行/列在屏幕上所占的大小。

frame标签

<frame>标签定义了每个框架中放入什么文件。

下面这个例子中,有一个两列的分栏。第一个被设置成窗口宽度的25%,第二个被设置成窗口宽度的75%。页面“frame_a.htm”被放在第一个分栏中,“frame_b.htm”被放在第二个分栏中。

<frameset cols="25%,75%">

<frame src="frame_a.htm">

<frame src="frame_b.htm">

</frameset>

技巧

假如一个框架有可见边框,用户可以拖动边框来改变它的大小。如果不想让用户改变大小,可以在<frame>标签中加入:noresize="noresize"

给不支持框架的浏览器写上<noframes>标签。

例如:

混合框架

<html>

<frameset rows="50%,50%">

<frame src="frame_a.htm">

<frameset cols="25%,75%">

<frame src="frame_b.htm">

<frame src="frame_c.htm">

</frameset>

</frameset>

</html>

这个例子说明了怎样把三个页面以行列混合的方式放在框架中。

使用了noresize="noresize"的框架

<html>

<frameset rows="50%,50%">

<frame noresize="noresize" src="frame_a.htm">

<frameset cols="25%,75%">

<frame noresize="noresize" src="frame_b.htm">

<frame noresize="noresize" src="frame_c.htm">

</frameset>

</frameset>

</html>

这个例子说明了noresize属性。这个框架是不可改变大小的,把鼠标移动到框架边界上,你会发现无法调整大小。

导航框架

<html>

<frameset cols="120,*">

<frame src="frame_link.htm">

<frame src="frame_a.htm" name="showframe">

</frameset>

</html>

这个例子说明了如何创建一个导航框架。导航框架包含了一系列链接,它们的目标页面在第二个框架中。文件“frame_links.htm”包含了三个链接,链接的代码如下:

<a href ="frame_a.htm" target ="showframe">Frame a</a>

<a href ="frame_b.htm" target ="showframe">Frame b</a>

<a href ="frame_c.htm" target ="showframe">Frame c</a>

第二个框架将显示链接到的页面。

内联框架:

<html>

<body>

<iframe src="intro.htm"></iframe>

<p>

Some older browsers don't support iframes.

</p>

<p>

If they don't, the iframe will not be visible.

</p>

</body>

</html>

这个例子说明了如何创建一个内联框架(包含在HTML页面里的框架)。

在框架内跳转到指定章节

<html>

<frameset cols="30%,70%">

<frame src="frame_a.htm">

<frame src="frame_section.htm#C10">

</frameset>

</html>

这个例子显示了两个框架页。其中一个的源是一个文件的指定章节,该章节在文件“frame_section.htm”中使用代码<a name="C10">指定。

使用导航框架跳转到指定章节

<html>

<frameset cols="200,*">

<frame src="frame_linksection.htm">

<frame src="frame_section.htm" name="showframe">

</frameset>

</html>

这个例子显示了两个框架页。左边的导航框架包含了一系列以第二个框架为目标的链接(“frame_linksection.htm”),第二个框架显示链接文件(“frame_section.htm”)。导航框架中的一个链接指向目标文件中的指定章节。文件“frame_link”中的HTML代码是像这样的:

<a href ="frame_section" target ="showframe">没有命名锚的链接</a>

<a href ="frame_section#C10" target ="showframe">有命名锚的链接</a>

IFrame

纵观时下网站,几乎每页都要放Banner,栏目图片,版权等一大堆雷同的东西,这都是为了网站风格统一、广告效应的需要。有没有办法,让这些雷同的东西一次下载后就不用再下载,而只下载那些内容有变化区域的网页内容呢?

答案是:应用Iframe标记!

Iframe标记的使用

提起Iframe,可能早已被很多人忘记。不过,说起它的兄弟Frame大家就不会陌生了。Frame标记即帧标记,我们所说的多帧结构就是在一个浏览器窗口中显示多个HTML文件。现在,我们遇到一种很现实的情况:如有一个教程,是一节一节地上,每页末尾做一个上一节下一节的链接,除了每节教程内容不同之外,页面其它部分内容都是相同的,如果一页一页地做页面,这似乎太让人厌烦了。如果有一种方法让页面其它地方不变,只将教程做成一页一页的内容页,不含其它内容,在点击上下翻页链接时,只改变教程内容部分,其它保持不变。这样,一是省时,另则以后如果教程有个三长两短的变动,也很方便。更重要的是将那些广告Banner、栏目列表、导航等几乎每页的都有的东西只下载一次后就不再下载了。

Iframe标记,又叫浮动帧标记,你可以用它将一个HTML文档嵌入在一个HTML中显示。它不同于Frame标记,最大的区别是这个标记所引用的HTML文件不是与另外的HTML文件相互独立显示,而是可以直接嵌入在一个HTML文件中,与这个HTML文件内容相互融合,成为一个整体;另外,还可以多次在一个页面内显示同一内容,而不必重复写内容,一个形象的比喻即画中画

Iframe标记的使用格式

<Iframe src="URL" width="x" height="x" scrolling="[option]" frameborder="x"></iframe>

src:文件的路径,既可是HTML文件,也可以是文本、JSP等;

widthheight"画中画"区域的宽与高;

scrolling:src的指定的HTML文件在指定的区域不显不完时,滚动选项,如果设置为NO,则不出现滚动条;如为Auto:则自动出现滚动条;如为Yes,则显示;

frameborder:区域边框的宽度,为了让画中画与邻近的内容相融合,常设置为0

比如:

<Iframe src="http://netschool.cpcw.com/homepage" width="250" height="200" scrolling="no" frameborder="0"></iframe>

父窗体与浮动帧之间的相互控制

在脚本语言与对象层次中,包含Iframe的窗口我们称之为父窗体,而浮动帧则称为子窗体,弄清这两者的关系很重要,因为要在父窗体中访问子窗体或相反都必须清楚对象层次,才能通过程序来访问并控制窗体。

在父窗体中访问并控制子窗体中的对象

在父窗体中,Iframe是子窗体,它是document对象的一个子对象,可以直接在脚本中访问子窗体中的对象。

现在就有一个问题:我们怎样来控制这个Iframe。当我们给这个Iframe设置了ID 属性后,就可通过文档对象模型DOMIframe所含的HTML进行一系列控制。

比如在example.htm里嵌入test.htm文件,并控制test.htm里一些标记对象:

<Iframe src="test.htm" id="test" width="250" height="200" scrolling="no" frameborder="0"></iframe>

test.htm文件代码为:

<html>

<body>

<h1 id="myH1">

hello,my boy

</h1>

</body>

</html>

如我们要改变ID号为myH1H1标记里的文字为hello,my dear,则可用:

document.myH1.innerText="hello,my dear"(其中,document可省)

example.htm文件中,Iframe标记对象所指的子窗体与一般的DHTML对象模型一致,对象访问控制方式也是一样的。

在子窗体中访问并控制父窗体中对象

在子窗体中我们可以通过其parent(双亲)对象来访问父窗口中的对象。

example.htm

<html>

<body οnclick="alert(tt.myH1.innerHTML)">

<Iframe name="tt" src="frame1.htm" width="250" height="200"

scrolling="no" frameborder="0"></iframe>

<h1 id="myH2">

hello,my wife

</h1>

</body>

</html>

如果要在frame1.htm中访问ID号为myH2中的标题文字并将之改为"hello,my friend",我们就可以这样写:

parent.myH2.innerText="hello,my friend"

这里parent对象就代表当前窗体(example.htm所在窗体),要在子窗体中访问父窗体中的对象,无一例外都通过parent对象来进行。

Iframe虽然内嵌在另一个HTML文件中,但它保持相对的独立,是一个独立王国,在单一HTML中的特性同样适用于浮动帧中。

试想一下,通过Iframe标记,我们可将那些不变的内容以Iframe来表示,这样,不必重复写相同的内容,这有点象程序设计中的过程或函数,减省了多少繁琐的手工劳动。另外,至关重要的是,它使页面的修改更为可行,因为,不必因为版式的调整而修改每个页面,你只需修改一个父窗体的版式即可了。

________________________________________有一点要注意,Nestscape浏览器不支持Iframe标记,但在时下IE的天下,这似乎也无大碍,广泛采用Iframe标记,既是为自己着想,又为网友节省了网费,何乐而不为。

________________________________________

内容总结

? 熟练掌握表单的创建方法、提交方式以及表单所包含的文档对象

? 熟练掌握框架的创建方法、框架大小的调整以及如何在不同框架之间创建链接

? 掌握IFrame的使用,如何利用IFame引入其它页面,以及如何在IFrame和包含IFrame的页面之间通信

独立实践

? 写一个用户登录的HTML文档

? 把上一章练习里的几个HTML文档通过框架组织起来

? 写一个注册用户信息的HTML文档。要求有普通用户和管理员用户之分

? 使用Iframe来优化上面结个HMTL文档

? 思考:如何改变浏览器的滚动条的样式,如何改变IFrame的滚动条的样式

第二十五章:JavaScript介绍

学习目标

? 了解JavaScript是什么以及它的特点

? 了解JavaScriptJava的区别

? 了解JavaScript基于对象编程的特性、编译方式

? 了解JavaScript的运行环境

? 会编写简单的JavaScript程序

JavaScript语言概况

JavaScript可以使信息和用户之间不仅只是一种显示和浏览的关系,而是实现了一种实时的、动态的、可交式的表达能力。从而基于CGI静态的HTML页面将被可提供动态实时信息,并对客户操作进行反应的Web页面的取代。JavaScript脚本正是满足这种需求而产生的语言。它深受广泛用户的喜爱的欢迎。它是众多脚本语言中较为优秀的一种,它与WWW的结合有效地实现了网络计算和网络计算机的蓝图。无疑Java家族将占领Internet网络的主导地位。因此,尽快掌握JavaScript脚本语言编程方法是我国广大用户日益关心的。

什么是JavaScript

JavaScript是一种基于对象(Object)和事件驱动(Event Driven)并具有安全性能的脚本语言。使用它的目的是与HTML超文本标记语言、Java 脚本语言(Java小程序)一起实现在一个Web页面中链接多个对象,与Web客户交互作用。从而可以开发客户端的应用程序等。它是通过嵌入或调入在标准的HTML语言中实现的。它的出现弥补了HTML语言的缺陷,它是JavaHTML折衷的选择,具有以下几个基本特点:

是一种脚本编写语言

JavaScript是一种脚本语言,它采用小程序段的方式实现编程。像其它脚本语言一样,JavaScript同样已是一种解释性语言,它提供了一个容易的开发过程。

它的基本结构形式与CC++VBDelphi十分类似。但它不像这些语言一样,需要先编译,而是在程序运行过程中被逐行地解释。它与HTML标识结合在一起,从而方便用户的使用操作。

基于对象的语言

JavaScript是一种基于对象的语言,同时以可以看作一种面向对象的。这意味着它能运用自己已经创建的对象。因此,许多功能可以来自于脚本环境中对象的方法与脚本的相互作用。

简单性

JavaScript的简单性主要体现在:首先它是一种基于Java基本语句和控制流之上的简单而紧凑的设计, 从而对于学习Java是一种非常好的过渡。其次它的变量类型是采用弱类型,并未使用严格的数据类型。

安全性

JavaScript是一种安全性语言,它不允许访问本地的硬盘,并不能将数据存入到服务器上,不允许对网络文档进行修改和删除,只能通过浏览器实现信息浏览或动态交互。从而有效地防止数据的丢失。

动态性

JavaScript是动态的,它可以直接对用户或客户输入做出响应,无须经过Web服务程序。它对用户的反映响应,是采用以事件驱动的方式进行的。所谓事件驱动,就是指在主页(Home Page)中执行了某种操作所产生的动作,就称为事件”(Event)。比如按下鼠标、移动窗口、选择菜单等都可以视为事件。当事件发生后,可能会引起相应的事件响应。

跨平台性

JavaScript是依赖于浏览器本身,与操作环境无关,只要能运行浏览器的计算机,并支持JavaScript的浏览器就可正确执行。从而实现了编写一次,走遍天下的梦想。

实际上JavaScript最杰出之处在于可以用小程序做大事。无须有高性能的电脑,软件仅需一个字处理软件和一个浏览器,无须WEB服务器即可在自己的电脑完成所有的事情。

综合所述JavaScript 是一种新的描述语言,它可以被嵌入到 HTML 的文件之中。 JavaScript语言可以做到回应使用者的需求事件 (如: form 的输入) ,而不用任何的网路来回传输资料,所以当一位使用者输入一项资料时,它不用经过传给伺服端 (server)处理,再传回来的过程,而直接可以被客户端 (client) 的应用程式所处理。

JavaScriptJava的区别

虽然JavaScriptJava有紧密的联系,但却是两个公司开发的不同的两个产品。JavaSUN公司推出的新一代面向对象的程序设计语言,特别适合于Internet应用程序开发;而JavaScriptNetscape公司的产品,其目的是为了扩展Netscape Navigator功能,而开发的一种可以嵌入Web页面中的基于对象和事件驱动的解释性语言, 它的前身是Live Script;而Java的前身是Oak语言。下面对两种语言间的异同作如下比较:

基于对象和面向对象

Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象。 JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object Based)和事件驱动(Event Driver)的编程语言。因而它本身提供了非常丰富的内部对象供设计人员使用。

解释和编译

两种语言在其浏览器中所执行的方式不一样。Java的源代码在传递到客户端执行之前,必须经过编译,因而客户端上必须具有相应平台上的仿真器或解释器,它可以通过编译器或解释器实现独立于某个特定的平台编译代码的束缚。

JavaScript是一种解释性编程语言,其源代码在发往客户端执行之前不需经过编译,而是将文本格式的字符代码发送给客户编由浏览器解释执行。

强变量和弱变量

两种语言所采取的变量是不一样的。

Java采用强类型变量检查,即所有变量在编译之前必须声明。如:

Integer x;

String y;

x=1234;

x=4321;

其中X=1234说明是一个整数,Y=4321说明是一个字符串。

JavaScript中变量声明,采用其弱类型。即变量在使用前不需声明,而解释器在运行时检查其数据类型,如:

  x=1234;

  y"4321";

前者说明x为其数值型变量,而后者说明y为字符型变量。

代码格式

Java是一种与HTML无关的格式,必须通过像HTML中引用外部媒体那么进行装载,其代码以字节代码的形式保存在独立的文档中。

JavaScript的代码是一种文本字符格式,可以直接嵌入HTML文档中,并且可动态装载。编写HTML文档就像编辑文本文件一样方便。

嵌入方式

HTML文档中,两种编程语言的标识不同,JavaScript使用<Script>...</Script>来标识,而Java使用<applet>...</applet>来标识。

静态联编和动态联编

Java采用静态联编,即Java的对象引用必须在编译时的进行,以使编译器能够实现强类型检查。

JavaScript采用动态联编,即JavaScript的对象引用在运行时进行检查,如不经编译则就无法实现对象引用的检查。

实例

下面我们通过一个例子,编写第一个JavaScript程序。通过它可说明JavaScript的脚本是怎样被嵌入到HTML文档中的。

test1.html文档:

<html>

<head>

<Script Language="JavaScript">

  alert("这是第一个JavaScript例子!");

  alert("欢迎你进入JavaScript世界!");

  alert("今后我们将共同学习JavaScript知识!");

  </Script>

</head><

</html>

在浏览器中打开test1.html,怎么没有显示啊?

说明:

test.htmlHTML文档,其标识格式为标准的HTML格式;

JavaScript代码由 <Script Language ="JavaScript">...</Script>说明。在标识<Script Language ="JavaScript">...</Script>之间就可加入JavaScript脚本

alert()JavaScript的窗口对象方法,其功能是弹出一个具有OK对话框并显示()中的字符串

通过<!-- ...//-->标识说明:若不认识JavaScript代码的浏览器,则所有在其中的标识均被忽略;若认识,则执行其结果。使用注释这是一个好的编程习惯,它使其他人可以读懂你的语言。

JavaScript </Script> 标签结束。

从上面的实例分析中我们可以看出,编写一个JavaScript程序确实非常容易的。

内容总结

? JavaScript是一种基于对象(Object)和事件驱动(Event Driven)并具有安全性能的脚本语言。

? JavaScriptJava的区别

Java 面向对象 编译 强变量 applet 静态联编

jscript 基于对象 解释 弱变量 直接嵌入 动态联编

独立实战

? 写一个简单的javasript程序,弹出警告窗口,显示欢迎来到javascript世界”;

? 把下列语句加到网页中试试

<Script Language="JavaScript">

document.write("<h1>Hello World!</h1>")

</Script>

? 上网搜索如何用javascript显示当前的日期和弹出一个对话框

? 简述JavaJavaScript之间的区别

? 什么是JavaScriptJavaScript有什么好处?

第二十六章:JavaScript基础

学习目标

? 了解并掌握JavaScript中常用的基本数据类型、变量、常量、操作运算符

? 掌握JavaScript中所有的程序流

? 了解并掌握JavaScript中的常用函数,包括字符串函数、日期函数等

? 了解JavaScript的事件机制,并针对事件编写事件处理程序

基本结构

JavaScript提供脚本语言的编程与Java非常相似,并提供了功能强大的类库。对于已经具备Java语言的人来说,学习JavaScript脚本语言是一件非常轻松愉快的事。

JavaScript代码的加入

<HTML>

<Head>

<Script Language="JavaScript">

document. Write("这是ITJOB学校");

document. close();

</Script>

</Head>

</HTML>

说明:

Document. write()是文档对象的输出函数,其功能是将括号中的字符或变量值输出到窗口;document. close()是将输出关闭。

可将<Script>...</Script>标识放入<head>.. </Head><Body> ...</Body>之间。将JavaScript标识放置<Head>... </Head>在头部之间,使之在主页和其余部分代码之前装载,从而可使代码的功能更强大;可以将JavaScript标识放置在<Body>... </Body>主体之间以实现某些部分动态地创建文档。

基本数据类型

JavaScript脚本语言同其它语言一样,有它自身的基本数据类型、表达式和算术运算符以及程序的基本框架结构。

JavaScript中四种基本的数据类型:数值(整数和实数)、字符串型(用“”号或‘’括起来的字符或数值)、布尔型(使TrueFalse表示)和空值。在JavaScript的基本类型中的数据可以是常量,也可以变量。由于JavaScript采用弱类型的形式,因而一个数据的变量或常量不必首先声明,而是在使用或赋值时确定其数据的类型的。当然也可以先声明该数据的类型,它是通过在赋值时自动说明其数据类型的。

常量

整型常量

JavaScript的常量通常又称字面常量,它是不能改变的数据。其整型常量可以使用十六进制、八进制和十进制表示其值。

实型常量

实型常量是由整数部分加小数部分表示,如12.32193.98 。可以使用科学或标准方法表示:5E74e5等。

布尔值

布尔常量只有两种状态:TrueFalse。它主要用来说明或代表一种状态或标志,以说明操作流程。它与C++是不一样的,C++可以用1或0表示其状态,JavaScript只能用TrueFalse表示其状态。

字符型常量

使用单引号()或双引号()括起来的一个或几个字符。如 "This is a book of JavaScript ""3245""ewrt234234" 等。

空值

JavaScript中有一个空值null,表示什么也没有。如试图引用没有定义的变量,则返回一个Null值。

特殊字符

同C语言一样,JavaScript中同样以有些以反斜杠(/)开头的不可显示的特殊字符。通常称为控制字符。

变量

变量的主要作用是存取数据、提供存放信息的容器。对于变量必须明确变量的命名、变量的类型、变量的声明及其变量的作用域。

变量的命名

JavaScript中的变量命名同其计算机语言非常相似,这里要注意以下两点:

? 必须是一个有效的变量,即变量以字母开头,中间可以出现数字如test1text2等。除下划线(_)作为连字符外,变量名称不能有空格、(+)、(-)、(,)或其它符号。

? 不能使用JavaScript中的关键字作为变量。

JavaScript中定义了40多个类键字,这些关键是JavaScript内部使用的,不能作为变量的名称。如varintdoubletrue不能作为变量的名称。

在对变量命名时,最好把变量的意义与其代表的意思对应起来,以免出现错误。

变量的类型

JavaScript中,变量可以用命令Var作声明:

var mytest;

该例子定义了一个mytest变量。但没有赋予它的值。

Var mytest=”This is a book”

该例子定义了一个mytest变量, 并赋值。

JavaScript中,变量不声明,而在使用时再根据数据的类型来确定其变量的类型。

如:

x=100

y="125"

xy= True

cost=19.5等。

其中x整数,y为字符串,xy为布尔型,cost为实型。

变量的声明及其作用域

JavaScript变量可以在使用前声明,并可赋值。通过使用var关键字对变量声明。对变量声明的最大好处就是能及时发现代码中的错误;因为JavaScript是采用动态编译的,而动态编译是不易发现代码中的错误,特别是变量命名的方面。

对于变量还有一个重要性──那就是变量的作用域。在JavaScript中同样有全局变量和局部变量。全局变量是定义在所有函数体之外,其作用范围是整个函数;而局部变量是定义在函数体之内,只对其该函数是可见的,而对其它函数则是不可见的。

表达式和运算符

表达式

在定义完变量后,就可以对它们进行赋值、改变、计算等一系列操作,这一过程用表达式来完成,表达式是变量、常量、布尔及运算符的集合,表达式可以分为算术表述式、字串表达式、赋值表达式以及布尔表达式等。

运算符

运算符是完成操作的一系列符号,在JavaScript中有算术运算符,如+、-*/等;

有比较运算符如!=、==等; 有逻辑布尔运算符如!(取反)、|||; 有字串运算如+ 、+=等。

JavaScript主要有双目运算符和单目运算符。其双目运算符由下列组成:

操作数1 运算符 操作数2

即由两个操作数和一个运算符组成。如5040"This"+"that"等。单目运算符,只需一个操作数,其运算符可在前或后。

算术运算符

JavaScript中的算术运算符有单目运算符和双目运算符。

双目运算符

+(加) 、-(减)、 *(乘)、 /(除)、 %(取模) 、|(按位或)、&(按位与)<<(左移)、 >>(右移)、 >>>(右移,零填充)。

单目运算符

-(取反)、~(取补)、++(递加1)、--(递减1)。

比较运算符

比较运算符它的基本操作过程是,首先对它的操作数进行比较,尔后再返回一个trueFalse值,有8个比较运算符:

<(小于)>(大于)<=(小于等于)>=(大于等于)==(等于)!=(不等于)

布尔逻辑运算符

JavaScript中增加了几个布尔逻辑运算符:

!(取反)&=(与之后赋值)、 &(逻辑与)、 |=(或之后赋值)、 |(逻辑或)、 ^=(异或之后赋值)、 ^(逻辑异或)、 ?:(三目操作符)、||(或)、==(等于)|=(不等于)

其中三目操作符主要格式如下:

操作数?结果1:结果2

若操作数的结果为真,则表述式的结果为结果1,否则为结果2。

实例

下面是一个跑马灯效果的JavaScript文档。

Test2_1.html

<html>

<head>

<script Language="JavaScript">

var msg="这是一个跑马灯效果的JavaScript文档";

var interval = 100;

var spacelen = 120;

var space10=" ";

var seq=0;

function Scroll() {

len = msg.length;

window.status = msg.substring(0, seq+1);

seq++;

if ( seq >= len ) {

seq = spacelen;

window.setTimeout("Scroll2();", interval );

}

else

window.setTimeout("Scroll();", interval );

}

function Scroll2() {

var out="";

for (i=1; i<=spacelen/space10.length; i++)

out += space10;

out = out + msg;

len=out.length;

window.status=out.substring(seq, len);

seq++;

if ( seq >= len ) { seq = 0; };

window.setTimeout("Scroll2();", interval );

}

Scroll();

</script>

<body>

</body>

</html>

JavaScript程序构成

JavaScript脚本语言的基本构成是由控制语句、函数、对象、方法、属性等,来实现编程的。

程序控制流

在任何一种语言中,程序控制流是必须的,它能使得整个程序减小混乱,使之顺利按其一定的方式执行。下面是JavaScript常用的程序控制流结构及语句:

if条件语句

基本格式

if(表述式)

语句段1;

......

else

语句段2;

.....

功能:若表达式为true,则执行语句段1;否则执行语句段2。

说明:

if -else 语句是JavaScript中最基本的控制语句,通过它可以改变语句的执行顺序。

表达式中必须使用关系语句,来实现判断,它是作为一个布尔值来估算的。

它将零和非零的数分别转化成falsetrue

if后的语句有多行,则必须使用花括号将其括起来。

另外,if语句可以嵌套使用。

for循环语句

基本格式

for(初始化;条件;增量)

语句集;

功能:实现条件循环,当条件成立时,执行语句集,否则跳出循环体。

说明:

初始化参数告诉循环的开始位置,必须赋予变量的初值;

条件:是用于判别循环停止时的条件。若条件满足,则执行循环体,否则跳出。

增量:主要定义循环控制变量在每次循环时按什么方式变化。

三个主要语句之间,必须使用逗号分隔。

while循环

基本格式

while(条件)

语句集;

该语句与For语句一样,当条件为真时,重复循环,否则退出循环。

breakcontinue语句

Java语言相同,使用break语句使得循环从Forwhile中跳出,continue使得跳过循环内剩余的语句而进入下一次循环。

函数

函数为程序设计人员提供了一个丰常方便的能力。通常在进行一个复杂的程序设计时,总是根据所要完成的功能,将程序划分为一些相对独立的部分,每部分编写一个函数。从而,使各部分充分独立,任务单一,程序清晰,易懂、易读、易维护。JavaScript函数可以封装那些在程序中可能要多次用到的模块。并可作为事件驱动的结果而调用的程序。从而实现一个函数把它与事件驱动相关联。这是与其它语言不样的地方。

JavaScript函数定义

function 函数名 (参数,变元){

函数体;

return 表达式;

}

说明:

 当调用函数时,所用变量均可作为变元传递。

 函数由关键字Function定义。

 函数名:定义自己函数的名字。

 参数表,是传递给函数使用或操作的值,其值可以是常量,变量或其它表达式。

 通过指定函数名(实参)来调用一个函数。

 必须使用Return将值返回。

 函数名对大小写是敏感的。

函数中的形式参数

在函数的定义中,我们看到函数名后有参数表,这些参数变量可能是一个或几个。那么怎样才能确定参数变量的个数呢?在JavaScript中可通过arguments.length来检查参数的个数。例:

Function function_Name(exp1,exp2,exp3,exp4)

{

Number =function_Name.arguments.length;

if (Number>1

document.wrile(exp2);

if (Number>2)

document.write(exp3);

if(Number>3)

document.write(exp4);

...

}

事件驱动及事件处理

基本概念

JavaScript是基于对象(object-based)的语言。这与Java不同,Java是面向对象的语言。而基于对象的基本特征,就是采用事件驱动(event-driven)。它是在图形界面的环境下,使得一切输入变的简单化。通常鼠标或热键的动作我们称之为事件(Event),而由鼠标或热键引发的一连串程序的动作,称之为事件驱动(Event Driver)。而对事件进行处理程序或函数,我们称之为事件处理程序(Event Handler)。

事件处理程序

JavaScript中对象事件的处理通常由函数(Function)担任。其基本格式与函数全部一样,可以将前面所介绍的所有函数作为事件处理程序。格式如下:

Function 事件处理名(参数表){

事件处理语句集;

……

}

事件驱动

JavaScript事件驱动中的事件是通过鼠标或热键的动作引发的。它主要有以下几个事件:

单击事件onClick

当用户单击鼠标按钮时,产生onClick事件。同时onClick指定的事件处理程序或代码将被调用执行。通常在下列基本对象中产生:

? button(按钮对象)

? checkbox(复选框)或(检查列表框)

? radio (单选钮)

? reset buttons(重要按钮)

? submit buttons(提交按钮)

例:可通过下列按钮激活change()文件:

<Form> <Input type="button" Value="" onClick="change()"></Form>

onClick等号后,可以使用自己编写的函数作为事件处理程序,也可以使用JavaScript中内部的函数。还可以直接使用JavaScript的代码等。例:

<Input type="button" value=" " οnclick=alert("这是一个例子");

onChange改变事件

当利用texttextarea元素输入字符值改变时发该事件,同时当在select表格项中一个选项状态改变后也会引发该事件。

例:

<Form>

<Input type="text" name="test" value="test"

onCharge="check(this.test)">

</Form>

选中事件onSelect

TextTextarea对象中的文字被选中后,引发该事件。

获得焦点事件onFocus

当用户单击Texttextarea以及select对象时,产生该事件。此时该对象成为前台对象。

失去焦点onBlur

text对象或textarea对象以及select对象不再拥有焦点、而退到后台时,引发该文件,他与onFocas事件是一个对应的关系。

载入文件onLoad

当文档载入时,产生该事件。onLoad一个作用就是在首次载入一个文档时检测cookie的值,并用一个变量为其赋值,使它可以被源代码使用。

卸载文件onUnload

Web页面退出时引发onUnload事件,并可更新Cookie的状态。

实例

下例程序是一个自动装载和自动卸载的例子。即当装入HTML文档时调用loadform()函数,而退出该文档进入另一HTML文档时则首先调用unloadform()函数,确认后方可进入。

<HTML>

<HEAD>

<script Language="JavaScript">

<!--

function loadform(){

alert("这是一个自动装载例子!");

}

function unloadform(){

alert("这是一个卸载例子!");

}

//-->

</Script>

</HEAD>

<BODY OnLoad="loadform()" OnUnload="unloadform()">

<a href="test.htm">调用</a>

</BODY></HTML>

内容总结

? JavaScript的程序结构:可将<Script>...</Script>标识放入<head>.. </Head><Body> ...</Body>之间。将JavaScript标识放置<Head>

? JavaScript的四种基本数据类型:数值(整数和实数)、字符串型(用“”号或‘’括起来的字符或数值)、布尔型(使TrueFalse表示)和空值。

? 变量的声明及其作用域: 声明用var 变量名;变量的作用域分全局和局部。全局变量是定义在所有函数体之外,其作用范围是整个函数;而局部变量是定义在函数体之内,只对其该函数是可见的,而对其它函数则是不可见的。

? 运算符:JavaScript中有算术运算符,如+、-*/等;有比较运算符如!=、==等; 有逻辑布尔运算符如!(取反)、|||; 有字串运算如+ 、+=等。

? JavaScript脚本语言的基本构成是由流程控制语句、函数、对象、方法、属性等组成。

? 事件驱动及事件处理:事件(Event),由鼠标或热键引发的一连串程序的动作,事件驱动(Event Driver)。对事件进行处理程序或函数,叫事件处理程序(Event Handler

? 常见的事件:onClick onChange onBlur onLoad onUnload

独立实践

? 建立javascript的两种方式?

? js的数据类型有哪些 ''""有什么区别?

? js变量命名区分大小写吗 函数名区分大小写吗?

? 写2javascript函数, loadAlert函数在文档加载时提示你的文档已经加载, unloadAlert在浏览器关闭时提示你的文档已经卸载;

? 演示跑马灯的例子

第二十七章:JavaScript进阶

学习目标

? 掌握JavaScript中基于对象的常用内部对象属性、方法的使用

? 掌握自定义对象的创建方法,以及数组的使用方法

? 了解并熟悉JavaScript内部对象系统

? 熟悉并利用窗口对象的输入输出操作

基于对象的JavaScript语言

JavaScript语言是基于对象的(Object-Based),而不是面向对象的(object-oriented)。之所以说它是一门基于对象的语言,主要是因为它没有提供象抽象、继承、重载等有关面向对象语言的许多功能。而把其它语言所创建的复杂对象统一起来,从而形成一个非常强大的对象系统。

虽然JavaScript语言是一门基于对象的,但它还是具有一些面向对象的基本特征。它可以根据需要创建自己的对象,从而进一步扩大JavaScript的应用范围,增强编写功能强大的Web文本文件。

对象的基础知识

对象的基本结构

JavaScript中的对象是由属性(properties)和方法(methods)两个基本的元素的构成的。前者是对象在实施其所需要行为的过程中,实现信息的装载单位,从而与变量相关联;后者是指对象能够按照设计者的意图而被执行,从而与特定的函数相关联。

引用对象的途径

一个对象要真正地被使用,可采用以下几种方式获得:

? 引用JavaScript内部对象

? 由浏览器环境中提供

? 创建新对象

这就是说一个对象在被引用之前,这个对象必须存在,否则引用将毫无意义,而出现错误信息。

有关对象操作语句

JavaScript不是一个纯面向对象的语言,它设有提供面向对象语言的许多功能,因此JavaScript设计者之所以把它你基于对象而不是面向对象的语言,在JavaScript中提供了几个用于操作对象的语句和关键词及运算符。

For...in语句

格式如下:

For(对象属性名 in 已知对象名)

说明:

该语句的功能是用于对已知对象的所有属性进行操作的控制循环。它将一个已知对象的所有属性反复赋给一个变量;而不是使用计数器来实现的。

该语句的优点就是无需知道对象中属性的个数即可进行操作。

例:下列函数是显示数组中的内容:

Function showData(object)

for (var X=0; X<30;X++)

document.write(object[i])

该函数是通过数组下标顺序值,来访问每个对象的属性,使用这种方式首先必须知道数组的下标值,否则若超出范围,则就会发生错误。而使For...in语句,则根本不需要知道对象属性的个数,如下面的例子:

Function showData(object)

for(var prop in object)

document.write(object[prop])

使用该函数时,在循环体中,For自动将的属性取出来,直到最后为此。

this关键词

this是对当前对象的引用,在JavaScript由于对象的引用是多层次,多方位的,往往一个对象的引用又需要对另一个对象的引用,而另一个对象有可能又要引用另一个对象,这样有可能造成混乱,最后自己不知道现在引用的那一个对象,为此JavaScript提供了一个用于将对象指定当前对象的语句this

new运算符

虽然在JavaScript中对象的功能已经是非常强大的了。但更强大的是设计人员可以按照需求来创建自己的对象,以满足某一特定的要求。使用New运算符可以创建一个新的对象。其创建对象使用如下格式:

Newobject=new Object(Parameters table);

其中Newobject创建的新对象:object是已经存在的对象; parameters table参数表;newJavaScript中的命令语句。

如创建一个日期新对象

newData=new Data()

birthday=new Data(December 12.1998)

之后就可使NewDatabirthday作为一个新的日期对象了。

对象属性的引用

对象属性的引用可由下列方式之一实现:

使用点(.)运算符

university.Name=“云南省

university.city=“昆明市

university.Date="1999"

其中university是一个已经存在的对象,NameCityDate是它的三个属性,并通过操作对其赋值。

通过对象的下标实现引用

university[0]=“云南

university[1]=“昆明市

university[2]="1999"

通过数组形式的访问属性,可以使用循环操作获取其值。

function showunievsity(object)

for (var j=0;j<2; j++)

document.write(object[j])

若采用For...in则可以不知其属性的个数后就可以实现:

Function showmy(object)

for (var prop in this)

docament.write(this[prop]);

通过字符串的形式实现

university["Name"]=“云南

university["City"]=“昆明市

university["Date"]="1999"

对象的方法的引用

JavaScript中对象方法的引用是非常简单的。

ObjectName.methods()

实际上methods()=FunctionName方法实质上是一个函数。 如引用university对象中的showmy()方法,则可使用:

document.write(university.showmy())

或:document.write(university)

如引用math内部对象中cos()的方法,如下:

with(math)

document.write(cos(35));

document.write(cos(80));

若不使用with则引用时相对要复杂些:

document.write(Math.cos(35))

document.write(math.sin(80))

常用对象的属性和方法

JavaScript为我们提供了一些非常有用的常用内部对象和方法。用户不需要用脚本来实现这些功能。这正是基于对象编程的真正目的。

JavaScript提供了string(字符串)、math(数值计算)和Date(日期)三种对象和其它一些相关的方法。从而为编程人员快速开发强大的脚本程序提供了非常有利的条件。

常用内部对象

JavaScript中对于对象属性与方法的引用,有两种情况:其一是说该对象是静态对象,即在引用该对象的属性或方法时不需要为它创建实例;而另一种对象则在引用它的对象或方法是必须为它创建一个实例,即该对象是动态对象。

JavaScript内部对象的引用,以是紧紧围绕着它的属性与方法进行的。因而明确对象的静动性对于掌握和理解JavaScript内部对象是具有非常重要的意义。

串对象

string对象:内部静态性。

串对象的属性

该对象只有一个属性,即length。它表明了字符串中的字符个数,包括所有符号。例:

mytest="This is a JavaScript"

mystringlength=mytest.length

最后mystringlength返回mytest字符串的长度为20

串对象的方法

string对象的方法共有19个。主要用于有关字符串在Web页面中的显示、字体大小、字体颜色、字符的搜索以及字符的大小写转换。

其主要方法如下:

锚点anchor():该方法创建如用Html文文件中一样的anchor标记。使用anchor如用Html(A Name="")一样。通过下列格式访问:

string.anchor(anchorName)

有关字符显示的控制方法

big()字体显示, Italics()斜体字显示,bold()粗体字显示,blink()字符闪烁显示,small()字符用小体字显示,fixed()固定高亮字显示、fontsize(size)控制字体大小等。

字体颜色方法:fontcolor(color)

字符串大小写转换

toLowerCase()-小写转换,toUpperCase()大写转换。下列把一个给定的串分别转换成大写和小写格式:

string=stringValue.toUpperCasestring=stringValue.toLowerCase

字符搜索:indexOf[charactor,fromIndex]

从指定formIndtx位置开始搜索charactor第一次出现的位置。

返回字符串的一部分字符串:substring(start,end)

start开始到end的字符全部返回。

 

算术函数的math对象

功能:提供除加、减、乘、除以外的一引些自述运算。如对数,平方根等。

静动性:静态对象

主要属性

math中提供了6个属性,它们是数学中经常用到的常数E、以10为底的自然对数LN10、以2为底的自然对数LN23.14159PI1/2的平方根SQRT1-2,2的平方根为SQRT2

主要方法

绝对值:abs()

正弦余弦值:sin(),cos()

反正弦反余弦 :asin(), acos()

正切反正切:tan(),atan()

四舍五入:round()

平方根:sqrt()

基于几方次的值:pow(base,exponent)

...

日期及时间对象

功能:提供一个有关日期和时间的对象。

静动性:动态性,即必须使用New运算符创建一个实例。例:

MyDate=New Date()

Date对象没有提供直接访问的属性。只具有获取和设置日期和时间的方法。

日期起始值:1770年1月1日00:00:00。

获取日期的时间方法

getYear(): 返回年数

getMonth():返回当月号数

getDate(): 返回当日号数

getDay():返回星期几

getHours():返回小时数

getMintes(:返回分钟数

getSeconds():返回秒数

getTime() : 返回毫秒数

设置日期和时间:

setYear();设置年

setDate():设置当月号数

setMonth():设置当月份数

setHours():设置小时数

setMintes():设置分钟数

setSeconds():设置秒数

setTime ():设置毫秒数

...

JavaScript中的系统函数

JavaScript中的系统函数又称内部方法。它提供了与任何对象无关的系统函数,使用这些函数不需创建任何实例,可直接用。

返回字符串表达式中的值:

方法名:eval(字符串表达式),例:

test=eval("8+9+5/2");

返回字符串ASCII码:

方法名:unEscape (string)

返回字符的编码:

方法名:escape(character)

返回实数:

parseFloat(floustring);

返回不同进制的数:

parseInt(numbestring ,radix)

其中radix是数的进制,numbestring字符串数

创建新对象

使用JavaScript可以创建自己的对象。虽然JavaScript内部和浏览器本身的功能已十分强大,但JavaScript还是提供了创建一个新对象的方法。使其不必像超文本标识语言那样,借助其它多媒体工具,就能完成许多复杂的工作。

JavaScript中创建一个新的对象是十分简单的。首先它必须定义一个对象,接着再为该对象创建一个实例。这个实例就是一个新对象,它具有对象定义中的基本特征。

对象的定义

JavaScript对象的定义,其基本格式如下:

Function Object(属性表)

this.prop1=prop1

this.prop2=prop2

...

this.meth=FunctionName1;

this.meth=FunctionName2;

...

在一个对象的定义中,可以为该对象指明其属性和方法。通过属性和方法构成了一个对象的实例。如下是一个关于University对象的定义:

function university(name,city,creatDate,URL)

this.name=name

this.city=city

this.creatDate=New Date(creatDate)

This.URL=URL

其基本含义如下:

Name-指定一个单位名称。

City单位所在城市。

CreatDate-记载university对象的更新日期。

URL-该对象指向一个网址。

创建对象实例

一旦对象定义完成后,就可以为该对象创建一个实例了:

NewObject=New object();

其中Newobjet是新的对象,Object已经定义好的对象。例:

U1=New university(“云南省昆明市"January 05,199712:00:00","http://www.YN.KM")

U2=New university(“云南电子科技大学昆明”,"January 07,1997 12:00:00","htlp://www.YNKJ.CN")

对象方法的使用

在对象中除了使用属性外,有时还需要使用方法。在对象的定义中,我们看到This.meth=FunctionName语句,那就是为定义对象的方法。实质对象的方法就是一个函数FunctionName,通过它实现自己的意图。

例在university对象中增加一个方法,该方法是显示它自己本身,并返回相应的字串。

<script>

function university(name,city,createDate,URL)

{

this.name=name;

this.city=city;

this.date=new Date(createDate);

this.URL=URL;

this.show=showuniversity;

this.toString=parseToString;

}

function showuniversity()

{

for(var prop in this)

{

document.write(prop + "=" + this[prop] + "<BR>");

}

}

function parseToString()

{

return "name:"+ this.name + "<br>city:" + this.city + "<br>date:" + this.date;

}

U2=new university("深圳计算机行业协会","深圳","January 07,1997 12:00:00","htlp://www.5itjob.CN");

U2.show();

document.write(U2);

</script>

其中this.show就是定义了一个方法showuniversity()

showuniversity()方法是实现university对象本身的显示。

察看对象所有属性的方法

function showProp(object)

{

for(var prop in object)

{

document.write(prop + " = " + object[prop] + "<br>");

}

}

howProp(document);

JavaScript中的数组

? 使用New创建数组

? JavaScript中没有提供像其它语言具有明显的数组类型,但可以通过function定义一个数组,并使用New对象操作符创建一个具有下标的数组。从而可以实现任何数据类型的存储。

定义对象的数组

function arrayName(size){

this.length=size;

for(var X=1; X<=size;X++)

this[X]=0;

reture this;

}

其中arrayName是定义数组的一个名子,Size是有关数组大小的值

从中可以看出,JavaScript中的数组是从1size,这与其它0到size的数组表示方法有所不同,当然你可根据需要将数组的下标由1到size调整到0size-1,可由下列实现:

function arrayName (size)

for (var X=0; X<size;X++)

this[X]=0;

this.lenght=size;

return this;

从上面可以看出该方法是只是调整了this.length的位置,该位置是用于存储数组的大小的。从而调整后的数组的下标将与其它语言一致。但请读者注意正是由于数组下标顺序由1到size,使得JavaScript中的对象功能更加强大。

创建数组实例

一个数组定义完成以后,还不能马上使用,必须为该数组创建一个数组实例:

Myarray=new arrayName(n);

并赋于初值:

Myarray[1]=“字串1

Myarray[2]=“字串2

Myarray[3]=“字串3

Myarray[n]=“字串n”

一旦给数组赋于了初值后,数组中就具有真正意义的数据了,以后就可以在程序设计过程中直接引用。

创建多维数组

function creatMArray(row,col){

var indx=0;

this.length=(row*10)+col

for(var x=1;x<=row;x++)

for(var y=1;y<=col;y++)

indx=(x*10)+y;

this[indx]=””;

}

myMArray=new creatMArray();

之后可通过myMArray[11]myMArray[12]myMArray[13]myMArray[21]

myMArray[22]myMArray[23]

实例

颜色变化的例子。

<html>

<head>

<script>

<!--

function makearray(n) {

this.length = n;

for(var i = 1; i <= n; i++)

this[i] = 0;

return this;

}

hexa = new makearray(16);

function init()

{

for(var i = 0; i < 10; i++)

hexa[i] = i;

hexa[10]="a";

hexa[11]="b";

hexa[12]="c";

hexa[13]="d";

hexa[14]="e";

hexa[15]="f";

}

function hex(i) {

if (i < 0)

return "00";

else if (i > 255)

return "ff";

else return "" + hexa[Math.floor(i/16)] + hexa[i%16];

}

function setbgColor(r, g, b) {

var hr = hex(r);

var hg = hex(g);

var hb = hex(b);

document.bgColor = "#"+hr+hg+hb;

}

function fade(sr, sg, sb, er, eg, eb, step) {

for(var i = 0; i <= step; i++) {

setbgColor( Math.floor(sr * ((step-i)/step) + er * (i/step)),

Math.floor(sg * ((step-i)/step) + eg * (i/step)), Math.floor(sb *

((step-i)/step) + eb * (i/step)));

}

}

function fadein() {

fade(255,0,0,0,0,255,100);

fade(0,0,255,0,255,0,100);

fade(0,255,0, 0,0,0, 100);

}

init();

fadein();

// -->

</script>

<body>

</body>

</html>

使用内部对象系统

使用浏览器的内部对象系统, 可实现与HTML文档进行交互。它的作用是将相关元素组织包装起来,提供给程序设计人员使用,从而减轻编程人的劳动,提高设计Web页面的能力。

浏览器对象层次及其主要作用

除了前面提到过的文档document对象外,Navigator浏览器中还提供了窗口(Window)对象以及历史(History)和位置(Location)对象。

? 浏览器对象(navigator)

提供有关浏览器的信息

? 窗口对象(Windows)

Window对象处于对象层次的最顶端,它提供了处理Navigator窗口的方法和属性。

? 位置对象(location)

Location对象提供了与当前打开的URL一起工作的方法和属性,它是一个静态的对象。

? 历史对象(history)

history对象提供了与历史清单有关的信息。

? 文档对象(document)

document对象包含了与文档元素(elements)一起工作的对象,它将这些元素封装起来供编程人员使用。

编程人员利用这些对象,可以对WWW浏览器环境中的事件进行控制并作出处理。在JavaScript中提供了非常丰富的内部方法和属性,从而减轻了编程人员的工作,提高编程效率。这正是基于对象与面向对象的根本区别所在。在这些对象系统中,文档对象属于非常重要的,它位于最低层,但对于我们实现Web页面信息交互起作关键作用。因而它是对象系统的核心部分。

文档对象功能及其作用

Navigator浏览器中,document文档对象是核心是,同时也是最重要的。见图:

Links Anchor Form Method Prop

链接对象 锚对象 窗体对象 方法 对象

从图中可以看出,document对象的主要作用就是把这些基本的元素(如links,anchor等)包装起来,提供给编程人员使用。从另一个角度看,document对象中又是由属性和方法组成。

document中三个主要的对象

document中主要有:links,anchor,form等三个最重要的对象:

? anchor锚对象:

anchor对象指的是<a name=...> </a>标识在HTML源码中存在时产生的对象。它包含着文档中所有的anchors信息。

? 链接links对象

link对象指的是用<a href=...> </A>标记的连接一个超文本或超媒体的元素作为一个特定的URL

? 窗体(form)对象

窗体对象是文档对象的一个元素,它含有多种格式的对象储存信息,使用它可以在JavaScript脚本中编写程序进行文字输入,并可以用来动态改变文档的行为。通过document.forms[]数组来使得在同一个页面上可以有多个相同的窗体,使用forms[]数组要比使用窗体名字要方便得多。

:下面就是一个使用窗体数组和窗体名字的例子。该程序使得两个窗体中的字段内容保持一致。

<Html>

<head>

</head>

<body>

<form>

<input type=text onChange="document.my.elements[0].value=this.value;">

</form>

<form NAME="my">

<input type=text

onChange="document.forms[0].elements[0].value=this.value;">

</form>

</body>

</html>

其中用了OnChnge事件(当窗体内容改变时激发)。第一个使用窗体名字标识my,第二个使用窗体数组Forms[]。其效果是一致。

文档对象中的attribute属性

document对象中的attribute属性,主要用于在引用Href标识时,控制着有关颜色的格式和有关文档标题、文档原文件的URL以及文档最后更新的日期。这部分元素的主要含义如下:

? 链接颜色:alinkcolor

这个元素主要用于,当选取一个链接时,链接对象本身的颜色就按alinkcolor指定改变。

? 链接颜色:linkcolor

当用户使用<A Href=...> Text string </A>链接后,Textstring的颜色就会按Linkcolor所指定的颜色更新。

? 浏览过后的颜色:VlinkColor

该属性表示的是已被浏览存储为已浏览过的链接颜色。

? 背景颜色:bgcolor

该元素包含文档背景的颜色。

? 前景颜色:fgcolor

该元素包含HTML文档中文本的前景颜色。

文档对象的基本元素

? 窗体属性:

窗体属性是与HTML文档中<Form>...</Form>相对应的一组对象在HTML文档所创建的窗体数,由length指定。通过document.forms.length反映该文档中所创建的窗体数目。

? 锚属性:anchors

该属性中,包含了HTML文档的所有<A> </A>标记为Name=...的语句标识。

所有的数目保存在document.anchors.length中。

? 链接属性:links

链接属性是指在文档中<A>...</A>的由Href=...指定的数目,其链接数目

保存在document.links.length中。

实例

下面我们通过一个例子来说明文档对象的综合应用。

<html>

<head>

</HEAD>

<BOdy>

<Form Name="mytable">

请输入数据:

<Input Type="text" Name="text1" Value="">

</Form>

<A name="Link1" href="test31.htm">链接到第一个文本</a>

<br>

<A name="Link2" href="test32.htm">链接到第二个文本</a>

<br>

<A name="Link2" href="test33.htm">链接到第三个文本</a>

<br>

<A href="#Link1">第一锚点</a>

<A href="#Link2">第二锚点</a>

<A Href="#Link3">第三锚点</a>

<BR>

<Script Language="JavaScript">

document.write("文档有"+document.links.length+"个链接"+"<br>");

document.write("文档有"+document.anchors.length+"个锚点"+"<br>");

document.write("文档有"+document.forms.length+"个窗体");

</script>

</body>

</HTML>

下列程序随机产生每日一语。

<HTML>

<HEAD>

<script Language="JavaScript">

<!--

tips = new Array(6);

tips[0]="每日一语(1";

tips[1]="每日一语(2";

tips[2]="每日一语(3";

tips[3]="每日一语(4";

tips[4]="每日一语(5";

tips[5]="每日一语(6";

index = Math.floor(Math.random() * tips.length);

document.write("<FONT SIZE=8 COLOR=DARKBLUE>" + tips[index]+"</FONT>");

</Script>

</HEAD>

<BODY>

</BODY>

</HTML>

窗口及输入输出

JavaScript是基于对象的脚本编程语言,那么它的输入输出就是通过对象来完成的。其中有关输入可通过窗口(Window)对象来完成,而输出可通过文档(document)对象的方法来实现。

窗口及输入输出

请看下面例子:

<HTML>

<Head>

<script languaga="JavaScript">

var test=window.prompt("请输入数据:");

document.write(test+"JavaScript输入输出的例子");

</script>

</Head>

</HTML>

其中window.prompt()就是一个窗口对象的方法,其基本作用是,当装入Web页面时在屏幕上显示一个具有确定取消的对话框,让你输入数据。

窗口对象

该对象包括许多有用的属性、方法和事件驱动程序,编程人员可以利用这些对象控制浏览器窗口显示的各个方面,如对话框、框架等。在使用应注意以下几点:

? 该对象对应于HTML文档中的<Body><FrameSet>两种标识;

? onloadonunload都是窗口对象属性;

? 在JavaScript脚本中可直接引用窗口对象。如: window.alert("窗口对象输入方法") 可直接使用以下格式: alert("窗口对象输入方法")

窗口对象的事件驱动

窗口对象主要有装入Web文档事件onload和卸载时onunload事件。用于文档载入和停止载入时开始和停止更新文档。

窗口对象的方法

窗口对象的方法主要用来提供信息或输入数据以及创建一个新的窗口。

? 创建一个新窗口open()

使用window.open(参数表)方法可以创建一个新的窗口。其中参数表提供有窗口的主要特性和文档及窗口的命名。

? 具有OK按钮的对话框

alert()方法能创建一个具有OK按钮的对话框。

? 具有OKCancel按钮的对话框

confirm()方法为编程人员提供一个具有两个按钮的对话框。

? 具有输入信息的对话框

prompt()方法允许用户在对话框中输入信息,并可使用默认值,其基本格式如下prompt提示信息,默认值)。 

窗口对象中的属性

窗口对象中的属性主要用来对浏览器中存在的各种窗口和框架的引用,其主要属性有以下几个:

? frames 确文档中帧的数目

frames(帧)作为实现一个窗口的分隔操作,起到非常有用的作用,在使用注意以下几点:

frames属性是通过HTML标识<Frames>的顺序来引用的,它包含了一个窗口中的全部帧数。

帧本身已是一类窗口,继承了窗口对象所有的全部属性和方法。

? parent 指明当前窗口或帧的父窗口。

? defaultstatus:默认状态,它的值显示在窗口的状态栏中。

? status:包含文档窗口中帧中的当前信息。

? top:包括的是用以实现所有的下级窗口的窗口。

? window.指的是当前窗口

? self:引用当前窗口。

输出流及文档对象

JavaScript文档对象中,提供了用于显示关闭、消除、打开HTML页面的输出流。

创建新文档open()方法

使用document.open()创建一个新的窗口或在指定的命令窗口内打开文档。由于窗口对象是所加载的父对象,因而我们在调用它的属性或方法时,不需要加入Window对象。例用Window. open()open()是一样的。

打开一个窗口的基本格式:

Window .open("URL","窗口名字","窗口属性"]

window属性参数是由一个字符串列表项它由逗号分隔,它指明了有关新创建窗口的属性。

参 数 设定值 含 义

toolbar yes/no 建立或不建立标准工具条

location yes/no 建立或不建立位置输入字段

directions yes/no 建立或不建立标准目录按钮

status yes/no 建立或不建立状态条

menubar yes/no 建立或不建立菜单条

scrollbar yes/no 建立或不建立滚动条

revisable yes/no 能否改变窗口大小

width yes/no 确定窗口的宽度

Height yes/no 确定窗口的高度。

在使用Open()方法时,需要注意以下点。

? 通常浏览器窗中,总有一个文档是打开的。因而不需要为输出建立一个新文档。

? 在完成对Web文档的写操作后,要使用或调用close()方法来实现对输出流的关闭。

? 在使用open()来打开一个新流时,可为文档指定一个有效的文档类型,有效文档类型包括text/HTMLtext/giftext/ximtext/plugin等。

write()writeln()输出显示。

该方法主要用来实现在Web页面上显示输出信息。在实际使用中,需注意以下几点:

? writeln()write()唯一不同之处在于在未尾加了一个换符。

? 为了正常显示其输出信息,必须指明<pre> </Pre>标记,使之告诉编辑器。

? 输出的文档类型,可以由浏览器中的有效的合法文本类型所确定。

关闭文档流close()

在实现多个文档对象中,必须使用close()来关闭一个对象后,才能打开另一个文档对象。

清除文档内容clear()

使用该方法可清除已经打开文档的内容。

简单的输入、输出例子

JavaScript中可以非常方便地实现输入输出信息,并与用户进行交互。

JavaScript信息的输入

通过使用JavaScript中所提供的窗口对象方法prompt(), 就能完成信息的输入。该方法提供了最简便的信息输入方式,其基本格式如下:

Window.prompt("提示信", 预定输入信息);

此方法首先在浏览器窗口中弹出一个对话框, 让用户自行输入信息。一旦输入完成后,就返回用户所输入信息的值。例: test=prompt请输入数据:””this is a JavaScript”

实际上prompt()是窗口对象的一个方法。因为缺省情况下所用的对象就是window对象, 所以windows对象可以省略不写。

输出显示

每种语言,都必须提供信息数据的输出显示。JavaScript也是一样,它提供有几个用于信息输出显示的方法。比较常用的有window.alert()document.write和及document.writln()方法。

document.write()方法和document.writeln()方法

documentJavaScript中的一个对象在它中封装许多有用的方法,其中write()writeln()就是用于将文本信息直接输出到浏览器窗口中的方法。 document.write()

document.writeln()

说明:

write()writeln()方法都是用于向浏览器窗口输出文本字串;

二者的唯一区别就是writeln()方法自动在文本之后加入回车符。

window.alert()输出

JavaScript为了方便信息输出,JavaScript提供了具有独立的对话框信息输出─alert()方法。

alert()方法是window对象的一个方法,因此在使用时,不需要写window窗口对象名,而是直接使用就行了。它主要用途用在输出时产生有关警告提示信息或提示用户,一旦用户按确定钮后,方可继续执行其他脚本程序。例:

<HTML>

<HEAD>

<TITLE></TITLE>

</HEAD>

<BODY>

<Script Language="JavaScript">

alert("这是一个JavaScript测试程序");

</Script>

</BODY>

</HTML>

利用输入、输出方法实现交互

JavaScript中,可以利用prompt()方法和write()方法实现与Web页面用户进行交互。例下面就是一个有关实现交互的例子。

Test7_1.htm

<HTML>

<HEAD>

<TITLE></TITLE>

</HEAD>

<BODY>

<Script Language="JavaScript">

<!-- Hide From Other Browsers

document.write("<H1>有关交互的例子");

my=prompt("请输入数据:");

document.write(my+"</H1>");

document.close();

// Stop Hiding from Other Browsers-->

</Script>

</BODY>

</HTML>

从上面程序可以看出:

可通过write()prompt()方法实现交互。

JavaScript脚本语言中可以使用HTML标识语言的代码。从而实现混合编程。其中<H1><Br>就是HTML标识符。

Window案例

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312">

<title>test</title>

<script>

var popUpWin=0;

function popUpWindow(URLStr, left, top, width, height)

{

if(popUpWin)

{

if(!popUpWin.closed) popUpWin.close();

}

popUpWin = open(URLStr, 'popUpWin', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbar=no,resizable=no,copyhistory=yes,width='+width+',height='+height+',left='+left+', top='+top+',screenX='+left+',screenY='+top+'');

}

</script>

</head>

<body>

<input type="button" value="delete" onClick="confirm('delete');">

<input type="button" value="open" onClick="popUpWindow('login.htm',200,200,400,200);">

</body></html>

内容总结

? JavaScript的对象是由属性(properties)和方法(methods)两个基本的元素的构成的,引用对象的途径:引用JavaScript内部对象;由浏览器环境中提供;创建新对象

? 对象操作语句:For...in语句 this new 点(.)运算符

? JavaScript常用对象:string(字符串)、math(数值计算)和Date(日期)三种对象

? 创建新对象首先用function定义一个对象,接着用new 为该对象创建一个实例,通过.运算符访问对象属性和方法。

? JavaScript中的数组:function定义一个数组,并使用New对象操作符创建一个具有下标的数组,通过数组名[下标]访问。

? 浏览器对象层次:文档document对象、窗口(Window)对象、历史(History)和位置(Location)对象

? 重点掌握:document对象的层次结构;window对象的alertpromptconfirm方法

独立实战

? 创建Date对象的一个实例,在一个网页中显示当前的日期(年月日 时分秒)

? 自定义一个student对象,它有name,native,age三个属性,创建该对象的三个实例,在网页中输出

? 弹出一个输入的对话框,输入"javaand java World",把输入的值保存到myvar中,在myvar中统计java出现的次数,并取出第三个到第十个字符,输出到文档

? 网页中制作一个按纽,弹出一个登陆对话框(无工具条、无状态栏、无菜单、无滚动条,长300,宽300

? 随机显示img下的一张图片

? 用户登陆网页有两个输入框,用javascript实现验证功能(这两个输入框内容不能为空,否则提示请输入值)

第二十八章: Servlet

学习目标

? Java Servlet概述

? Java Servlet API

? HTTP协议简介

? Servlet过滤器

? Servlet监听器

Java Servlet概述

Web刚刚开始用于提供服务,服务供应商们就意识到动态内容的需求。最早朝这个方向发展的技术之一Applet,专注于利用客户平台提供动态的用户体验。与此同时,开发人员也开始研究使用服务器平台达到同样的目的。最初,通用网关接口(CGI)脚本是产生动态内容的主要技术。尽管CGI脚本技术被广泛使用,它却存在着许多缺陷,包括平台相关和缺少控制性。为了解决这些缺陷,出现了Java Servlet技术。它以可移植的方式提供了动态的,基于用户的网页内容。

Java Servlet是用Java编程语言实现的类。它扩展了通过请求——响应模式访问的应用程序的服务器端的性能。尽管Servlet可以响应任何类型的请求,但通常它们用来扩展Web服务器端的应用程序。对于这样的应用,Java Servlet技术定义了专用于HTTP协议的Servlet类。类包javax.servletjavax.servlet.http提供了编写Servlet的接口和类。所有的Servlet必须实现定义了生命周期方法的Servlet接口。当实现通用服务时,可以使用或扩展由Java Servlet API提供的GenericServlet类。HttpServlet类提供了像doGetdoPost这样专门用于处理HTTP服务的方法。

Servlet能够做什么?

Servlet是用Java代码编写的服务器方软件程序,用于处理客户机和服务器之间的消息传递。Java Servlet API为请求和响应消息定义了一个标准接口,这样Servlet就可以跨平台和跨不同的Web应用服务器间移植。

Servlet可以通过动态构造一个发回客户机的响应来响应客户机请求。例如:下面是一个响应HTTP请求的Servlet.源代码如下:

package com.servlet;

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 HelloServlet extends HttpServlet {

public HelloServlet() {

super();

}

public void destroy() {

super.destroy(); // Just puts "destroy" string in log

// Put your code here

}

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 and say you hello");

out.println(" </BODY>");

out.println("</HTML>");

out.flush();

out.close();

}

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

public void doPut(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

// Put your code here

}

public void init() throws ServletException {

// Put your code here

}

}

Web.xml配置文件的配置如下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<description>

This is the description of my J2EE component

</description>

<display-name>

This is the display name of my J2EE component

</display-name>

<servlet-name>HelloServlet</servlet-name>

<servlet-class>com.servlet.HelloServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>HelloServlet</servlet-name>

<url-pattern>/servlet/HelloServlet</url-pattern>

</servlet-mapping>

</web-app>

在浏览器里输入:

http://localhost:818/beike/servlet/HelloServlet

运行结果如下图

由于Servlet是用Java编程语言编写的,它们可以访问整个Java API集合。这就使它们非常适合实现复杂的商业应用逻辑,特别是访问企业中其它位置的数据。Java Database Connection(JDBC) API就是一个示例,它允许Java程序访问关系数据库。一般我们不直接在Servelt里面写JDBC编程,而是通过连接池技术或者Hibernate技术来实现JDBC的连接,后面会有综合的例子。由于没有与Servlet关联的图形,因此它不适合访问GUI Java APIAWT/Swing

可以多次调用一个Servlet来响应来自多个客户机的请求。这是一种多线程的工作方式,该Servlet只有一个实例,多个用户通过多线程的访问,来响应给客户的。因此,一个Servlet可以同时处理多个请求,并且可以使这些请求同步。Servlet可以将请求转发到其它服务器和Servlet.

Servlet如何工作?

ServletJ2EEWeb服务器(Web Container)中运行。而应用服务器是一种特殊的Web服务器;它们扩展了Web服务器的功能,还可以处理Enterprise BeansWeb应用程序的请求。Web服务器和应用服务器之间有明显的差异。虽然两者都可以在同一台机器上运行,Web服务器运行的是Servlet代码。服务器自己装入,执行和管理Servlet。服务器使用Java字节码解释器来运行Java程序;这叫做Java虚拟机(JVM)。大型的应用服务器会自带虚拟机,比如:Weblogic。而小型的Web服务器需要使用操作系统里安装好的虚拟机,比如:tomcat

Servlet的生命周期。

Servlet执行的步骤如下:

1 客户机将请求发送给服务器。

2 服务器从浏览器的地址栏获得请求的信息。并根据Web.xml配置文件找到响应的Servet执行。如果找不到,会报404错误。

3 如果是第一次请求,那么会实例化该Servlet,调用init()方法进行初始化,该方法在Servlet 的生命周期里只执行一次。然后分配线程进行响应。如果不是第一次访问,那么会直接分配个线程进行客户的响应。

4Servlet响应之前,服务器会产生request,response对象,并且把客户请求的信息封装到request对象中,然后把这两个对象传递给Servletservice()方法执行。

5 service()根据请求的方式来调用不同的方法执行。比如get请求,service()方法会request,response对象传递给doGet()方法执行,把执行后的结果保存到response对象里面。返回给客户。

6 服务器关闭后,会调用Servletdestroy()方法进行销毁。

下面是Servetl执行的线程执行图:

ServletCGI程序的区别:

CGICommon Gateway Interface)公共网关接口程序也是用来创建响应请求的动态Web内容的。但与CGI相比,Servlet还有自己的优势。Servlet能够提供一个基于组件的,独立于平台的方法来创建Web应用,也没有一般CGI程序的性能限制。Servlet具有以下几个特点:

◆ 可跨平台,跨不同Web服务器进行移植。Servlet使你能够不用编写指定平台的API就能进行服务器端编程。Java Servlet API是标准Java的延伸。

◆ 具有持久性(Persistent)。一个Servlet在装入内存之后,就一直驻留内存,这意味着它能够在请求之间维持系统资源如数据库连接。

◆ 高效的(Efficient)。当一个客户对某个Servlet发出多个请求时,服务器只创建并装入该Servlet一次。每一次重复请求只不过完成商业逻辑处理。CGI过程则对每个请求都装入一个过程,这会大大降低性能。而且,JVM使用lightweight java thread来处理对Servlet的请求,而不是像CGI那样使用一个weighty操作系统进程。

◆ 能够将表示(presentation)与商业逻辑(business logic)分离。这样一来,将一个项目划分为各个独立部分来进行开发和维护变得容易得多了。

◆ 能够获取给定的HTTP的各种调用并且从Java语言本身的继续开发中获益。

Java Servlet API

什么是Java Servlet API? Java Servet API 是一组类,用于定义Web客户机和Web Servlet之间的标准接口。其实,API将请求封装成对象,这样服务器可以将它们传递到Servlet;响应也是这样的封装,因此服务器可以将它们传递回客户机。

Java Servlet API有两个包。Javax.servlet包含了支持普通协议无关的Servlet的类,javax.servlet.http包括了对HTTP协议的特别支持。本课主要介绍HTTP Servlet

Web上使用的HTTP Servlet

Servlet接口类是Java Servlet API的重要抽象概念。这个类定义了管理Servlet以及它与客户机通信的方法。要编写在Web上使用的HTTP Servlet,使用HttpServlet类:

◆ HttpServletRequest对象代表发送到Servlet的客户机请求。这个对象封装了从客户机到服务器的通信。它可以包含关于客户机环境的信息和任何要从Servlet发送到客户机的数据。

◆ HttpServletResponse对象代表从Servlet发回客户机的响应。这通常是动态生成的响应,如HTML页面,并且它是用请求和Servlet访问的其它来源中的数据创建的。

处理HTTP Servlet的关键方法

HttpServlet的子类必须至少覆盖一个方法。通常,Servlet覆盖doGetdoPost方法。GET请求是对Web页面的典型浏览器请求,它是在用户输入URL或使用链接时发出的。POST请求是用户提交指定发出的HTML表单时生成的。HTTP POST方法允许客户机一次将无限长度的数据发送到Web服务器,并且可以在发出信息时使用。

同一个Servlet可以通过让doGet调用doPost,或者反向调用来处理GETPOST

其它常用方法包括:

? Servletservice()方法

? Servlet容器激活了Servletservice()方法,传递HttpServletRequestHttpServletResponse对象同HTTP请求和响应进行交互,service()方法从请求对象中得到必要的信息处理请求,然后使用响应对象的方法创建客户响应根据 HTTP传输方法,service()方法把请求发送给另一个方法,GET请求发送给doGet()方法,POST请求发送给doPost()方法方法之间可以相互调用,doPost()可以调用doGet()方法,大多数人直接调用doGetdoPost

? doPut方法,用于HTTP PUT请求

? doDelete方法,用于HTTP DELETE请求

? init destroy,用于管理为Servlet的使用期限而保留的资源。

? getServletInfo,Servlet用来提供自身信息的方法。

其它相关接口的说明:

javax.servlet.ServletConfig接口:当Servet第一次被装载时,为了向它传递服务设置信息,容器开发者必须实现该接口。

javax.servlet.ServletContext接口:提供给Servlet一些访问它们所运行环境的方法,并允许记录一些重要的事件,由Servlet的编写者来决定记录什么样的数据。

下图是servlet UML

HTTP协议简介

大部分J2EE Web客户端使用HTTP协议和J2EE服务器通讯。HTTP协议定义了客户端可以发送给服务器的请求和服务器可以作为回答的响应。每个请求包含一个URLURL是一个字符串,标识了Web组件或者像HTML页面和图象文件这样的静态资源。

J2EE服务器将HTTP请求转化为一个HTTP请求(Request)对象,并把这个对象传递给URL标识的Web组件。Web组件填充一个HTTP响应(Response)对象,服务器把这个对象转化为HTTP响应,并发送给客户端。

HTTP协议基本概念及其特点

HTTPHyperText Transfer Protocol)是超文本传输协议的简称,是WWW上用于发布信息的主要协议。也可以理解为:在TCP/IP之上的WebRPC(Remote Process Call远程过程调用)。关于HTTP协议更详细的信息,请登陆www.w3.org访问。

HTTP定义了一个客户机/服务器结构的简单事务处理,这里的客户机/服务器也就是浏览器/Web 服务器。简单事务处理由以下几步组成:

客户与服务器建立连接

客户向服务器提交请求

如果请求被接受,那么服务器回送一个应答,应答中至少包括状态编码和该文件的内容。

客户或服务器断开连接。

HTTP的基本特点是:

简单。服务器迅速作出浏览器的应答。

无状态。一个请求到另一个请求不保留任何有关连接的信息。

灵活。允许传送任意类型的数据对象。

无连接。HTTP是一个无连接协议。

HTTP的缺点是每次连接HTTP只完成一次请求。若服务器的一个HTML文件中有许多图象,每传一个图象都要单独建立一次连接。

一个HTTP请求包含请求方法(Request Method),请求URL,头字段(Header Field)和请求体。HTTP1.1定义了下面的请求方法:

GET:获取由请求URL标识的资源。

HEAD:返回由URL标识的头信息。

POST:想Web服务器发送无限制长度的数据。

PUT:存储一个资源到请求的URL

DELETE:删除由URL标识的资源。

OPTIONS:返回服务器支持的HTTP方法。

TRACE:返回TRACE请求附带的头字段。

一个HTTP响应包括响应码,头字段和响应体。HTTP协议要求响应码和所有的头字段都在任何响应体之前返回。

下面是一些常用的状态码:

404:指示请求的资源不可用。

401:指示请求需要HTTP验证。

500:指示在HTTP服务器内部发生错误,不能执行请求。

503:指示HTTP服务器暂时性超载,不能处理当前请求。

HTTP请求报头--Resquest

HttpServletRequest对象封装了来自客户端的全部信息. Servlet容器得到一个请求的时候,建立了一个该类型的对象,并传递给Servlet获取参数方法.

public String getParameter(String name)

public Enumeration getParameterNames()

public String[] getParameterValues(String name)

获取内容信息

getCharacterEncoding()方法返回请求的名称和字符编码风格

用法:request.getCharacterEncoding()

public int getContentLength()方法可返回以字节记数的内容长度,如果长度未知,则返回-1

用法:request.getContentLength()

getContentType()方法返回请求内容的MIME类型,未知返回null

用法:request.getContentType()

获取连接信息

getProtocol()方法获取传输协议及版本

用法:request.getProtocol() (HTTP/1.1)

getRemoteAddr()方法返回因特网协议(IP)地址

用法:request.getRemoteAddr() (122.40.18.09)

getRemoteHost()方法返回主机名

用法:request.getRemoteHost() (122.40.18.09)

getScheme()方法返回模式名

用法:request.getScheme() (http)

getServerName()方法返回服务器名

用法:request.getServerName() (localhost)

getServerPort()方法返回服务器端口号

用法:request.getServerPort() (80)

获取Cookie

以下代码显示了全部可用的Cookie

Cookie[] cookies=request.getCookies();

int cookiesLen=cookies.length()

if(cookiesLen>0){

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

{

String sName=cookies[i].getName();

String sValue=cookies[i]=getValue();

}

}

获取用户请求信息

req.getAuthtype()方法返回认证模式

req.getMethod()方法返回HTTP方法

req.getPathInfo()方法返回路径信息

req.getPathTranslated()方法返回真实传输路径

req.getQueryString()方法返回请求索引串

req.getRequestURI()方法返回请求URI

req.getServletPath()方法返回Servlet路径

req.getAttributeNames()方法返回请求属性集

req.getAttribute(String name)方法返回属性name的信息

HTTP响应报头--Response

HttpServletResponse对象封装了同客户的全部通信,并提供了多种方法,能够访问和操作HTTP报头,属性等

常见的方法

setHeader()方法

addCookie()

sendError()

setContentType()

会话管理

由于HTTP是无状态协议,服务器无法识别来自相同客户端请求的顺序,因此需要采取专门的方法识别该类用户

1.隐藏的表单字段:<input name=“id” type=“hidden” value=“005”>

2.改写URL:向地址栏内URL后追加信息

http://localhost/servlets/srvlt?sql==1009&id=005

以上两个信息可以用request.getParameter(“id”)取出

3. 持久CookieCookie

? 建立Cookie

Date dtLogin=new Date();

? Cookie coLt=new Cookie(“loginTime”,dtLogin.toString());

? response.addCookie(coLt);// 发送给客户端的响应中包含该Cookie

? 访问Cookie,得到用户登录的最后时间

?? for(int i=0; i<reqCookies.length; i++){

? sName=reqCookies[i].getName();

? if(sName!=null && sName.equalsIgnoreCase(“loginTime”))

? sValue=reqCookies[i].getValue();

? }

3.会话跟踪APIHttpSession接口

HttpSession session=request.getSession();

使用HttpSessionsetAttribute()getAttribute()方法把一个属性同会话联系起来

发送请求:能够向另一个Servlet发送请求

ServletContext sc=this.getServletContext();

RequestDispatcher rd=sc.getRequestDispatcher(‘/srvltCom’); If(rd!=null){

try{

rd.forward(req,res); rd.include(req,res);

}

Catch(Exception e){} }

Servlet过滤器

Servlet过滤器是Servlet的一种特殊用法,主要用来完成一些通用的操作。比如编码的过滤,判断用户的登陆状态等等。

Servlet过滤器的适用场合:

A.认证过滤

B.登录和审核过滤

C.图像转换过滤

D.数据压缩过滤

E.加密过滤

F.令牌过滤

G.资源访问触发事件过滤

HXSL/T过滤

I.多用途INTERNET邮件扩展(Mime)类型过滤

Servlet过滤器接口的构成:

所有的Servlet过滤器类都必须实现javax.servlet.Filter接口。这个接口含有3个过滤器类必须实现的方法:

A.init(FilterConfig)

这是Servlet过滤器的初始化方法,读取web.xml文件中Servlet过滤器的初始化参数

B.doFilter(ServletRequest,ServletResponse,FilterChain):完成实际的过滤操作,当请求访问过滤器关联的URL时,Servlet容器将先调用过滤器的doFilter方法。FilterChain参数用于访问后续过滤器

C.destroy()

Servlet容器在销毁过滤器实例前调用该方法,这个方法中可以释放Servlet过滤器占用的资源

Servlet过滤器的创建步骤:

A.实现javax.servlet.Filter接口的servlet

B.实现init方法,读取过滤器的初始化函数

C.实现doFilter方法,完成对请求或过滤的响应

D.调用FilterChain接口对象的doFilter方法,向后续的过滤器传递请求或响应

E.销毁过滤器

F.web.xml中配置Filter

Servlet过滤器的执行流程图如下:

请看下例进行编码的过滤器:CodeFilter.java

package com.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 CodeFilter implements Filter {

FilterConfig config = null;

public void destroy() {

// TODO Auto-generated method stub

config = null;

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException {

// 设置请求和响应的字符编码格式为gb2312

arg0.setCharacterEncoding("gb2312");

arg1.setCharacterEncoding("gb2312");

System.out.println("already code filter.....");

arg2.doFilter(arg0, arg1);

}

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

config = arg0;

}

}

Web.xml配置文件中的主要信息为:

<filter>

<filter-name>code</filter-name>

<filter-class>com.filter.CodeFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>code</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

可以看到上面的配置信息和Servlet的配置格式完全一样。由以上信息可知,当浏览器输入访问该Web应用程序的Web组件(Servlet,Jsp),都会先执行该过滤器,进行字符编码。

下面请看判断用户是否登陆的过滤器:

CheckLoginFilter.java

package com.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;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

public class CheckLoginFilter implements Filter {

FilterConfig config = null;

public void destroy() {

// TODO Auto-generated method stub

config = null;

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException {

// TODO Auto-generated method stub

HttpServletRequest request = (HttpServletRequest) arg0;

HttpSession session = request.getSession(true);

String res = (String) session.getAttribute("islogin");

HttpServletResponse response = (HttpServletResponse) arg1;

if (res == null || !res.equals("true")) {

response.sendRedirect("../error.html");

}

arg2.doFilter(request, response);

}

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

this.config = arg0;

}

}

Web.xml文件中的主要配置为:

<filter>

<filter-name>LoginFilter</filter-name>

<filter-class>com.filter.CheckLoginFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>LoginFilter</filter-name>

<url-pattern>/success/*</url-pattern>

</filter-mapping>

下面以一个servlet做为测试

servletweb.xml配置文件信息为:

<servlet-mapping>

<servlet-name>CodingServlet</servlet-name>

<url-pattern>/success/code</url-pattern>

</servlet-mapping>

由以上配置的信息可知:当地址栏直接输入

http://localhost:818/WebDemo/success/code时,由于格式既符合/* 又符合/success/*,所以两个过滤器都会执行。

执行的结果会转发到error.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>error.html</title>

<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

<meta http-equiv="description" content="this is my page">

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

</head>

<body>

sorry ni mei login

<br>

qing

<a href=http://localhost:818/web122/test.html>login</a>

</body>

</html>

在浏览器运行的结果为下图:

得到上面的结果后,也会从tomcat的控制台打印如下图的信息:

其中第一条信息为Session监听器执行后打印的结果,下面马上会介绍监听器。

Servlet监听器

Servlet监听器主要有以下几种:

ServletRequestListener ,ServletRequestAttributeListener

HttpSessionActivationListener ,HttpSessionBindingListener ,

HttpSessionAttributeListener,HttpSessionListener,

ServletContextListener等等。

这些监听器主要用来监听session,request,application这三个对象里存取数据的变化。当然也有些常用的用途。

下面的例子是用来记数的,记录当前时刻,有多少人在访问我们的网站:

监听器类的定义如下:SessListener.java

package com.listener;

import javax.servlet.ServletContext;

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpSessionActivationListener;

import javax.servlet.http.HttpSessionAttributeListener;

import javax.servlet.http.HttpSessionBindingEvent;

import javax.servlet.http.HttpSessionBindingListener;

import javax.servlet.http.HttpSessionEvent;

import javax.servlet.http.HttpSessionListener;

public class SessListener implements HttpSessionBindingListener,

HttpSessionActivationListener, HttpSessionListener,

HttpSessionAttributeListener {

private int count;

public void valueBound(HttpSessionBindingEvent arg0) {

// TODO Auto-generated method stub

}

public void valueUnbound(HttpSessionBindingEvent arg0) {

// TODO Auto-generated method stub

}

public void sessionDidActivate(HttpSessionEvent arg0) {

// TODO Auto-generated method stub

}

public void sessionWillPassivate(HttpSessionEvent arg0) {

// TODO Auto-generated method stub

}

public void sessionCreated(HttpSessionEvent arg0) {

// TODO Auto-generated method stub

System.out.println("sessionCreated");

count++;

HttpSession session = arg0.getSession();

session.setMaxInactiveInterval(120);

ServletContext application = session.getServletContext();

application.setAttribute("num", count);

}

public void sessionDestroyed(HttpSessionEvent arg0) {

// TODO Auto-generated method stub

System.out.println("sessionDestroyed");

count--;

if (count <= 0)

count = 1;

HttpSession session = arg0.getSession();

ServletContext application = session.getServletContext();

application.setAttribute("num", count);

}

public void attributeAdded(HttpSessionBindingEvent arg0) {

// TODO Auto-generated method stub

}

public void attributeRemoved(HttpSessionBindingEvent arg0) {

// TODO Auto-generated method stub

}

public void attributeReplaced(HttpSessionBindingEvent arg0) {

// TODO Auto-generated method stub

}

}

该监听器在Web.xml的配置文件中的信息为:

<listener>

<listener-class>com.listener.SessListener</listener-class>

</listener>

当用户访问时就会进行人数的统计。关于本章的例子请参考工程WebDemo

内容总结

? Java Servlet是用Java编程语言实现的类。它扩展了通过请求——响应模式访问的应用程序的服务器端的性能。类包javax.servletjavax.servlet.http提供了编写Servlet的接口和类。所有的Servlet必须实现定义了生命周期方法的Servlet接口。

? Servlet的生命周期:

? HTTP协议:超文本传输协议HTTP的基本特点是简单、无状态、灵活、无连接

? HTTP请求报头ResquestHttpServletRequest对象封装了来自客户端的全部请求信息,包含客户机环境的信息和任何要从Servlet发送到服务器的数据,它有各种获取请求信息的方法

? HTTP响应报头ResponseHttpServletResponse对象封装了Servlet发回客户机的响应,通常是动态生成的响应,如HTML页面,并且它是用请求和Servlet访问的其它来源中的数据创建的。它提供了多种方法,能够访问和操作HTTP报头,属性等

? 会话管理:通过隐藏的表单字段、改写URL和持久Cookie三种手段

独立实践

? 在下列范例中,点击“Submit request!”超链接时将以何种HTTP请求形式调用HelloServlet?

<a href=” http://localhost:8080/servlets/HelloServlet” >submit request!</a>

A. GET

B. POST

C. HEAD

D. DELETE

? 以下是HelloServlet的完整内容:

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*

public class HelloServlet extends HttpServlet {

PrintWriter out = null;

public void service(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html");

out = response.getWriter();

}

public void doGet(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

out.println("doGet() is called");

}

public void doPost(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

out.println("doPost() is called");

}

}

如果以下面的方式调用HelloServlet,浏览器将会显示下列哪个信息?

http://localhost:8080/servlet/HelloServlet

? 下面哪个方法可以取得HTTP请求所传递的参数?

A. ServletRequest接口的getAttribute()方法

B. ServletRequest接口的getParameter()方法

C. HttpServletRequest接口的getAttribute()方法

D. HttpServletRequest接口的getParameter()方法

? 查看下列程序代码片段:

1. response.setContextType("image/gif");

2

3. out.close()

如果你希望在ServletHTTP回应中传送一张GIF图片,应该在第2行加入什么程序代码?

? 下列哪个接口所提供的方法可回传一个RequestDispatcher对象(选择两个正确答案)?

A. HttpServletRequest

B. ServletConfig

C. GenericServlet

D. ServletContext

E. ServletRequest

第二十九章: Jsp 技术

学习目标

? JSP介绍

? JSP语法

? JSP内置对象

? 自定义标签

? 标准标签的使用

JSP介绍

JSP(Java Server Page)Servlet的简化设计。注重的是显示方面,而Servlet主要是负责控制,在MVC模式里面,JSP处于视图层,Servlet处于控制器层。

JSP语法

JSP是由模板元素(html/xml等),指令元素,java脚本,动作元素组成的。

JSP的执行原理如下图:

JSP的执行过程如下图:

请看下例:

JSP源文件 test.jsp

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>My JSP 'test.jsp' starting page</title>

</head>

<body>

<%

for (int i = 0; i < 5; i++) {

out.println("hello<br>");

}

%>

</body>

</html>

经编译后的Servlet源文件

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import java.util.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

implements org.apache.jasper.runtime.JspSourceDependent {

private static java.util.Vector _jspx_dependants;

public java.util.List getDependants() {

return _jspx_dependants;

}

public void _jspService(HttpServletRequest request,

HttpServletResponse response) throws java.io.IOException,

ServletException {

JspFactory _jspxFactory = null;

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

try {

_jspxFactory = JspFactory.getDefaultFactory();

response.setContentType("text/html;charset=ISO-8859-1");

pageContext = _jspxFactory.getPageContext(this, request, response,

null, true, 8192, true);

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n<html>\r\n <head>\r\n <title>My JSP 'test.jsp' starting page</title>\r\n </head>\r\n \r\n <body>\r\n ");

for (int i = 0; i < 5; i++) {

out.println("hello<br>");

}

out.write("\r\n </body>\r\n</html>\r\n");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)) {

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

out.clearBuffer();

if (pageContext != null)

pageContext.handlePageException(t);

}

} finally {

if (_jspxFactory != null)

_jspxFactory.releasePageContext(pageContext);

}

}

}

执行完该Servlet生成的结果如下:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>My JSP 'test.jsp' starting page</title>

</head>

<body>

hello<br>

hello<br>

hello<br>

hello<br>

hello<br>

</body>

</html>

页面显示的结果如下图:

模板元素

模板元素主要是指HTML模板,它是JSP引擎里面不执行的部分。这些HTML标记数据直接会传送到客户端的浏览器。关于HTML请找些相关资料,花三个小时就可以搞定了,在此就不多做介绍了。

指令元素

1 页面指令 (page指令)

2 包含指令 (include指令)

3 标签库指令 (taglib指令)

这三个包含在“<%@ %>”卷标里

页面指令

“<%@ page %>”指令作用于整个JSP页面,同样包括静态的包含文件。但是“<%@ page %>”指令不能作用于动态的包含文件,比如 “<jsp:include>”

可以在一个页面中用上多个“<%@ page %>”指令,但是其中的属性只能用一次,不过也有例外,那就是import属性。因为import属性和Java中的import语句类似(参照Java Languageimport语句引入得是Java语言中的类),所以此属性就能多用几次。

无论把“<%@ page %>”指令放在JSP的文件的哪个地方,它的作用范围都是整个JSP页面。不过,为了JSP程序的可读性,以及好的编程习惯,最好还是把它放在JSP文件的顶部。

如:

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

所有属性用法如下:

<%@ 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"]

%>

对主要常用属性的解释:

1language="java"

声明脚本语言的种类,目前只能用"java"

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

需要导入的Java包的列表,这些包作用于程序段,表达式,以及声明。下面的包在JSP编译时已经导入了,所以就不需要再指明了:

java.lang.* javax.servlet.* javax.servlet.jsp.* javax.servlet.http.*

3errorPage="relativeURL"

设置处理异常事件的JSP文件。

4isErrorPage="true | false"

设置此页是否为出错页,如果被设置为true,你就能使用exception对象

exception对象示例,ErrorPage.jsp文件代码

<%@ page isErrorPage="true" %>

<%= exception.getMessage() %><br>

<%= exception.toString()%><br>

请看下例

<%@ page language="java" import="java.util.Date" session="true"

buffer="12kb" autoFlush="true" info="page test" errorPage="error.jsp"

isErrorPage="false" contentType="text/html; charset=gb2312"%>

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

<%@ page isELIgnored="false"%>

<html>

<body>

<h1>

使用page指令的测试页面

</h1>

<%=new Date().toLocaleString()%>

使用表达式语言: \${2>3}:${2>3}

</body>

</html>

显示结果如下:

包含指令

是页面里包含有其他的页面。格式如:<%@ include file=”head.jsp” %>

如下例:

head.jsp

<table height=20 width=100% bgcolor=99ccff>

<tr>

<td align=center>

------head------

</td>

</tr>

</table>

body.jsp

<html>

<body bgcolor=eeffdd>

this is body

<br>

<table height=20 width=100 bgcolor=ff2233>

<tr>

<td align=center>

********body********

</td>

</tr>

</table>

</body>

</html>

footer.jsp

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

<br>

------footer------

<br>

<table height=50 width=100 bgcolor=518000>

<tr>

<td align=center>

<hr>

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

</td>

</tr>

</table>

include.jsp

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

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

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

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

浏览器的运行结果为:

标签库指令

格式

<%@ taglib uri="URIToTagLibrary" prefix="tagPrefix" %>

如下例:testtag.jsp

<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<body>

<c:if test="true">

hello

</c:if>

</body>

</html>

在浏览器里的运行结果为:

脚本元素

声明

声明的格式为:<%! %>

声明是声明实例变量,在转换成Servlet源文件的时候是以属性的形式生成。

表达式

表达式是或者运算值

表达式的格式为:<%= %>

scriptlet脚本

scriptletjava脚本,里面可以直接写java代码。

scriptlet的格式为:<% %>

如下例:jiaoben.jsp

<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>

<html>

<head>

<title>My JSP 'jiaoben.jsp' starting page</title>

</head>

<%! int age = 28; %>

<body>

<%

String mess = "test scriptlet";

out.print(mess + "<br>");

if(age > 18){

%>

成人拉! <br>

<%}%>

该人的年龄为:

<%=age %>

</body>

</html>

转换成Servlet 的代码如下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import java.util.*;

public final class jiaoben_jsp extends org.apache.jasper.runtime.HttpJspBase

implements org.apache.jasper.runtime.JspSourceDependent {

int age = 28;

private static java.util.Vector _jspx_dependants;

public java.util.List getDependants() {

return _jspx_dependants;

}

public void _jspService(HttpServletRequest request, HttpServletResponse response)

throws java.io.IOException, ServletException {

JspFactory _jspxFactory = null;

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

try {

_jspxFactory = JspFactory.getDefaultFactory();

response.setContentType("text/html;charset=gbk");

pageContext = _jspxFactory.getPageContext(this, request, response,

null, true, 8192, true);

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\r\n\r\n<html>\r\n <head>\r\n \r\n <title>My JSP 'jiaoben.jsp' starting page</title>\r\n\r\n </head>\r\n ");

out.write("\r\n <body>\r\n ");

String mess = "test scriptlet";

out.print(mess + "<br>");

if(age > 18){

out.write("鎴愪汉鎷夛紒<br>\r\n ");

}

out.write("\r\n 璇ヤ汉鐨勫勾榫勪负锛?);

out.print(age );

out.write("\r\n </body>\r\n</html>\r\n");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

out.clearBuffer();

if (pageContext != null) pageContext.handlePageException(t);

}

} finally {

if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);

}

}

}

注意:在 <% %>声明的String mess = "test scriptlet"; 是生成在方法里的。在<%! %>声明的int age = 28; 是生成在属性里的。

执行完后生成的HTML代码如下:

<html>

<head>

<title>My JSP 'jiaoben.jsp' starting page</title>

</head>

<body>

test scriptlet<br>

成人拉! <br>

该人的年龄为:28

</body>

</html>

在浏览器里的运行结果如下图:

注意: 在标准的开发里面,是不允许在页面里面直接写java代码的。在页面里面只能以标签的形式出现。本章后面会介绍通过自定义标签来取代直接写java代码的操作。但是在要求不高的小型项目里面,在页面里面直接使用java代码会提高开发效率。

动作元素

常使用的动作元素为:

<jsp:forward>,<jsp:param>,<jsp:include>,jsp:useBean>,

<jsp:getProperty>,<jsp:setProperty>等等

<jsp:forward>动作

向另一个文件传递一个包含用户请求的request对象 <jsp:forward>标签以后的代码,将不能执行。注意jsp:forwardresponse.sendRedirect的区别在于forward在同一个request作用范围内,而重定向是另一个请求。

下面的例子传递一个参数到forwardTo.jsp

jspone.jsp

<jsp:forward page="forwardTo.jsp">

<jsp:param name="userName" value="riso" />

</jsp:forward>

forwardTo.jsp

<%

String useName=request.getParameter("userName");

out.println(useName + "谢谢光临!");

%>

输出:

riso谢谢光临!

<jsp:include>

包含一个静态或动态文件

<jsp:include><%@ include file="路径"%>区别是前者是动态的,页面的内容可以改变,而后者是静态的,一经载入页面,将不能修改。

请看下例:

static.html

<html>

<body>

<form method=post action="jsp_include.jsp">

<table>

<tr>

<td>

please input your name:

</td>

</tr>

<tr>

<td>

<input type=text name=name>

</td>

</tr>

<tr>

<td>

input you password:

</td>

<td>

<input type=text name=password>

</td>

</tr>

<tr>

<td>

<input type=submit value=login>

</td>

</tr>

</table>

</body>

</html>

two.jsp

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

举例说明include的工作原理:

<br> this is a1=<%=request.getParameter("a1")%><br>

this is a2= <%=request.getParameter("a2")%> <br>

<% out.println("hello from two.jsp");%>

jsp_include.jsp

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

<html>

<body>

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

<%//只是把文件包含进来%>

<a href="two.jsp">goto two--></a><br>

this examples show include works

<jsp:include page="two.jsp" flush="true">

<jsp:param name="a1" value="<%=request.getParameter("name")%>" />

<jsp:param name="a2" value="<%=request.getParameter("password")%>" />

</jsp:include>

</body>

</html>

从浏览器里输入:

http://localhost:818/JspDemo/jsp_include.jsp

看到结果为:

下面介绍JavaBean的使用操作:

PersonBean.java

package com.bean;

public class PersonBean {

private String name;

private String password;

public void setName(String name) {

this.name = name;

}

public void setPass(String pass) {

this.password = pass;

}

public String getName() {

return this.name;

}

public String getPass() {

return this.password;

}

}

login.jsp

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

<%@ page import="com.bean.*" errorPage="error.jsp"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<base href="<%=basePath%>">

<title>My JSP 'login.jsp' starting page</title>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

<meta http-equiv="description" content="This is my page">

<!--

<link rel="stylesheet" type="text/css" href="styles.css">

-->

</head>

<body>

<form action="bean.jsp">

name:

<input type="text" name="name">

<br>

pass:

<input type="password" name="password">

<br>

<input type="submit" value="login">

</form>

</body>

</html>

从浏览器里输入:http://localhost:818/JspDemo/login.jsp

结果为:

当点击login按钮,会跳到bean.jsp

bean.jsp

<jsp:useBean id="myPerson" scope="request" class="com.bean.PersonBean">

<jsp:setProperty name="myPerson" property="name" />

<jsp:setProperty name="myPerson" property="pass" param="password" />

</jsp:useBean>

name:

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

pass:

<jsp:getProperty name="myPerson" property="pass" />

运行的结果为:

注意:

JavaBean的属性存取是通过setXXX(),getXXX()方法来执行,而不是根据属性的名字来存取的。

JavaBeanscope="page | request | session | application"

page在包含“<jsp:useBean>”元素的JSP文件以及该文件中所有静态包含文件中使用这个Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。

request 在任何执行相同请求的Jsp文件中使用这个Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。能够使用Request对象访问这个Bean,比如request.getAttribute(beanInstanceName)

session - 从创建Bean开始,就能在任何使用相同sessionjsp文件中使用这个Bean。这个Bean存在于整个Session生存周期内,任何在分享此SessionJsp文件都能使用同一Bean。注意在创建这个BeanJsp文件中“<% @ page %>”指令中必须指定“session="true"”

application - 从创建Bean开始,就能在任何使用相同applicationJsp文件中使用Bean。这个Bean存在于整个application生存周期内,任何在分享此applicationJsp文件都能使用同一Bean.

JSP内置对象

JSP 有以下九种内置对象,包括:

request,请求对象

response,响应对象

pageContext,页面上下文对象

session,会话对象

application,应用程序对象

out,输出对象

config,配置对象

page,页面对象

exception,例外对象

下表对九个内置对象的解释

对象名 类型 作用域

request javax.servlet.ServletRequest 的子类 request

response javax.servlet.ServletResponse 的子类 page

pageContext javax.servlet.jsp.PageContext page

session javax.servlet.http.HttpSession session

application javax.servlet.ServletContext application

out javax.servlet.jsp.JspWriter page

config java.servlet.ServletConfig page

page java.lang.Object page

exception java.lang.Throwable page

request 对象代表的是来自客户端的请求,例如我们在FORM表单中填写的信息等,是最常用的对象。关于它的方法使用较多的是getParametergetParameterNamesgetParameterValues,通过调用这几个方法来获取请求对象中所包含的参数的值;

response 对象代表的是对客户端的响应,也就是说可以通“response”对象来组织发送到客户端的数据。但是由于组织方式比较底层,所以不建议普通读者使用,需要向客户端发送文字时直接使用“out” 对象即可。

pageContext 对象直译时可以称作页面上下文对象,代表的是当前页面运行的一些属性,常用的方法包括findAttributegetAttributegetAttributesScopegetAttributeNamesInScope,一般情况下“pageContext” 对象用到得也不是很多,只有在项目所面临的情况比较复杂的情况下,才会利用到页面属性来辅助处理。

session 对象代表服务器与客户端所建立的会话,是在服务器端保存客户的信息。主要是跟踪用户的状态,比如是否登陆等。Session对象记载某一特定的客户信息,不同的客户用不同的Session对象来记载。Session对象有效期:默认为20分钟,可设定。常用的方法包括getIdgetValuegetValueNamesputValue等。

application” 对象负责提供应用程序在服务器中运行时的一些全局信息,常用的方法有setAttributegetAttribute等。与Session的区别是:Session对象记载某一特定的客户信息,不同的客户用不同的Session对象来记载而Application对象记载所有访问该应用程序的客户信息 。

out 对象代表了向客户端发送数据的对象,与“response” 对象不同,通过“out” 对象发送的内容将是浏览器需要显示的内容,是文本一级的,可以通过“out” 对象直接向客户端写一个由程序动态生成HTML文件。常用的方法除了pirntprintln之外,还包括clearclearBufferflushgetBufferSizegetRemaining,这是因为“out” 对象内部包含了一个缓冲区,所以需要一些对缓冲区进行操作的方法。

config 对象提供一些配置信息,常用的方法有getInitParametergetInitParameterNames,以获得Servlet初始化时的参数。

page 对象代表了正在运行的由JSP文件产生的类对象,不建议一般读者使用。

exception 对象则代表了JSP文件运行时所产生的例外对象,此对象不能在一般JSP文件中直接使用,而只能在使用了“<%@ page isErrorPage="true "%>”JSP文件中使用。

out对象的使用操作如下代码:

<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>

<%

response.setContentType("text/html");

out.println("学习使用out对象:<br><hr>");

out.println("<br>out.println(boolean):");

out.println(true);

out.println("<br>out.println(char):");

out.println('a');

out.println("<br>out.println(char[]):");

out.println(new char[]{'a','b'});

out.println("<br>out.println(double):");

out.println(2.3d);

out.println("<br>out.println(float):");

out.println(43.2f);

out.println("<br>out.println(int):");

out.println(34);

out.println("<br>out.println(long):");

out.println(2342342343242354L);

out.println("<br>out.println(object):");

out.println(new java.util.Date());

out.println("<br>out.println(string):");

out.println("string");

out.println("<br>out.newLine():");

out.newLine();

out.println("<br>out.getBufferSize():");

out.println(out.getBufferSize());

out.println("<br>out.getRemaining():");

out.println(out.getRemaining());

out.println("<br>out.isAutoFlush():");

out.println(out.isAutoFlush());

out.flush();

%>

运行的结果如下:

config对象的测试

代码如下:

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

import="java.sql.*" errorPage=""%>

<html>

<head>

<title>Untitled Document</title>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312">

</head>

<body>

<% int count=0;

try

{

count=Integer.parseInt(config.getInitParameter("counter"));

}

catch(Exception e)

{

out.println("org:"+e);

}

out.println("此页面设置的初始值为"+count+"");

%>

</body>

</html>

web.xml中的配置信息为:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<description>

This is the description of my J2EE component

</description>

<display-name>

This is the display name of my J2EE component

</display-name>

<servlet-name>SessionServet</servlet-name>

<servlet-class>com.servlet.SessionServet</servlet-class>

</servlet>

<servlet>

<servlet-name>config_counter</servlet-name>

<jsp-file>/config.jsp</jsp-file>

<init-param>

<param-name>counter</param-name>

<param-value>1000</param-value>

</init-param>

</servlet>

<servlet-mapping>

<servlet-name>config_counter</servlet-name>

<url-pattern>/config_counter</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>SessionServet</servlet-name>

<url-pattern>/servlet</url-pattern>

</servlet-mapping>

</web-app>

在浏览器里的运行结果为:

session,application.request对象已经在Servlet里介绍过了。用法完全一样,这里就不多说了。

剩余的内置对象由于不是太常用,在这里也不介绍了。

SessionApplication对象

Session对象记载某一特定的客户信息,不同的客户用不同的Session对象来记载

Application对象记载所有访问该应用程序的客户信息

application对象示例

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

<html>

<head>

<title>application</title>

<head>

<body>

<% out.println("Java Servlet API Version "+application.getMajorVersion()

+"."+application.getMinorVersion()+"<br>");

out.println("peixun2.13.jsp's MIME type is:"+application.getMimeType("peixun2.13.jsp")

+"<br>");

out.println("URL of 'peixun2.13.jsp' is: "+application.getResource(“/peixun2.13.jsp")+"<br>");

out.println("getServerInfo()="+application.getServerInfo()+"<br>");

out.println(application.getRealPath(" "));

application.log("Add a Record to log_file"); %>

</body>

</html>

运行结果

JSP的汉字问题的原理

客户端和服务器用gb2312

HTTP传输编码用ISO8859_1

服务器需要指示客户端文档的编码方式

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

在服务器端接收到客户端数据时需要转换为gb2312后进行处理,方法

String name=new String(name.getBytes("ISO8859_1"), "gb2312");

自定义标签

在目前中大型项目开发里面,对MVC模式的要求是比较高的。在JSP页面只能出现标签和html/xml模板,而不能出现java代码。那么标签的运行原理是通过页面里的标签,而通过xml文件的配置,去调用后台的java对象执行,然后把结果返回给页面。

请看下例:

首先定义标签执行类HelloWorldTag.java

package com.mytag;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.Statement;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.JspWriter;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.Tag;

import com.jdbc.JDBCCon;

public class HelloWorldTag implements Tag {

PageContext pageContext = null;

String mess = null;

public void setMess(String me) {

this.mess = me;

}

public int doEndTag() throws JspException {

// TODO Auto-generated method stub

return this.EVAL_PAGE;

}

public int doStartTag() throws JspException {

// TODO Auto-generated method stub

Connection con = JDBCCon.getCon();

JspWriter out = pageContext.getOut();

String sql = "select * from " + mess;

try {

Statement st = con.createStatement();

ResultSet rs = st.executeQuery(sql);

ResultSetMetaData rsmd = rs.getMetaData();

int lie = rsmd.getColumnCount();

while (rs.next()) {

for (int i = 1; i <= lie; i++) {

out.print(rs.getString(i) + ",");

}

out.println("<br>");

}

out.println("hello " + mess);

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if (con != null) {

con.close();

}

} catch (Exception e) {

e.printStackTrace();

}

}

return this.SKIP_BODY;

}

public Tag getParent() {

// TODO Auto-generated method stub

return null;

}

public void release() {

// TODO Auto-generated method stub

}

public void setPageContext(PageContext arg0) {

// TODO Auto-generated method stub

this.pageContext = arg0;

}

public void setParent(Tag arg0) {

// TODO Auto-generated method stub

}

}

其次定义标签库的描述文件

<?xml version="1.0" encoding="UTF-8"?>

<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd ">

<tlib-version>0.0</tlib-version>

<short-name>NMTOKEN</short-name>

<uri>www.kening.com</uri>

<tag>

<name>shuchu</name>

<tag-class>com.mytag.HelloWorldTag</tag-class>

<body-content>empty</body-content>

<attribute>

<name>mess</name>

<required>true</required>

</attribute>

</tag>

<tag>

<name>xuanzhe</name>

<tag-class>com.mytag.BodyTagTest</tag-class>

<body-content>JSP</body-content>

<attribute>

<name>isOut</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

<tag>

<name>number</name>

<tag-class>com.mytag.CountTag</tag-class>

<body-content>JSP</body-content>

<attribute>

<name>count</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

<tag>

<name>diedai</name>

<tag-class>com.mytag.IteTag</tag-class>

<body-content>JSP</body-content>

<attribute>

<name>name</name>

<required>true</required>

</attribute>

<attribute>

<name>data</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

</taglib>

在页面里的使用如下:HelloTag.jsp

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

<%@ taglib uri="www.kening.com" prefix="hello"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>My JSP 'HelloTag.jsp' starting page</title>

</head>

<body>

<hello:shuchu mess="kebiao" />

</body>

</html>

运行结果如下:

关于标签体,叠代标签的定义请参考JspDemo事例。

标准标签的使用

如下biaozhuntag.jsp

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<base href="<%=basePath%>">

<title>My JSP 'biaozhuntag.jsp' starting page</title>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<!--<link rel="stylesheet" type="text/css" href="styles.css">-->

</head>

<body>

<c:catch var="myexception">

<% int k = 5/0; %>

</c:catch>

<c:out value="${myexception}"></c:out>

<c:if test="false">

hello

</c:if>

<c:forEach var="i" begin="1" end="10" step="3">

<c:out value="${i}"></c:out>

<br>

</c:forEach>

<c:url var="myurl" value="login.jsp" scope="session">

</c:url>

<c:out value="${myurl}" />

<br>

<c:if test="${param.name == 'andy'}">

<c:redirect url="${myurl}"></c:redirect>

</c:if>

<c:out value="${param.name}" />

<c:forTokens var="token"

items="blue,red,green|yellow|pink,black|white" delims="|">

<c:out value="${token}" />

</c:forTokens>

<sql:setDataSource var="ds1"

driver="com.microsoft.jdbc.sqlserver.SQLServerDriver"

url="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=pubs"

user="sa" password="signal" />

<sql:setDataSource var="ds2" dataSource="jdbc/tag" />

<sql:query var="query1" dataSource="${ds2}">

SELECT * FROM student

</sql:query>

<table border="1">

<c:forEach var="row" items="${query1.rows}">

<tr>

<td>

id:

<c:out value="${row.id}" />

</td>

<td>

name:

<c:out value="${row.name}" />

</td>

</tr>

</c:forEach>

</table>

</body>

</html>

运行结果如下图:

关于该例子的源代码请参看附录里:JspDemo工程

内容总结

? JSP是由模板元素(html/xml等),指令元素,java脚本,动作元素组成的。

? 指令元素分为:页面指令(page指令) 包含指令(include指令)标签库指令(taglib指令)这三个包含在“<%@ %>”卷标里

? 脚本元素:声明的格式为<%!变量名=%>转换成Servlet源文件的时候是以属性的形式生成;<% java代码%>嵌入java代码,转换成Servlet源文件的时候是以方法的形式生成;

? 动作元素

<jsp:forward>,<jsp:param>,<jsp:include>,jsp:useBean>,<jsp:getProperty>,<jsp:setProperty>等等

注意jsp:forwardresponse.sendRedirect的区别在于forward在同一个request作用范围内,而重定向是另一个请求。

? javabean:只有get/set方法的java对象

<jsp:useBean id="myPerson" scope="request"

class="bean.PersonBean">

<jsp:setProperty name="myPerson" property="name" />

<jsp:setProperty name="myPerson" property="pass" />

</jsp:useBean>

? JSP 有以下九种内置对象,包括:

request请求对象 response响应对象

pageContext页面上下文对象 session会话对象

application应用程序对象 out输出对象

config配置对象 page页面对象

exception例外对象

? 自定义标签

独立实战

? jsp的组成部分是什么

? jsp的内置对象有哪些,它们的作用分别是什么

? 说不同

<%@ include file><jsp:include>

<%! int age = 28; %><% int age=28;%>

<jsp:forward file><% response.sendRedirect %>

pagerequestsessionapplication

<!-- --> <%-- --%>

? 用<jsp:include>实现一个三栏页面布局,用<%@ include包含公共文件command.jsp

? 自定义一个标签titleTag,输出一个标题条

? 用jsp+jdbc实现用户登录注册和注销功能

第三十章:struts入门

学习目标

? 应用框架介绍

? 理解HTTP, CGI, servlet, JSP

? 使用 Model 2 架构

? 构建简单的应用

Struts简介

StrutsApache软件基金下Jakarta项目的一部分,Struts框架的主要架构设计和开发者是Craig R.McClanahanCraig 也是Tomcat的主要架构师,以及Java Web Services Developer Pack的主要架构师和实现者。他现在是SunJavaServer Faces (JSR-127) 以及J2EE平台的Web层架构的规范领导。

Struts Apache 软件许可 [ASF, License]下对公众是免费的。 使用此软件没有任何获得和再现成本。不象其他一些开源许可,Apache 软件许可对商业用途是友好的。你可以在你的商业项目中使用Struts,并自由分发Struts 库。你也可以将Struts 组件集成到你的框架中,就像他们是你自己编写的一样。

框架之所以叫“Struts”,是为了提醒我们记住那些支撑我们房屋,建筑,桥梁,甚至我们踩高跷时候的基础支撑。 这也是一个解释Struts在开发Web应用程序中所扮演的角色的精彩描述。当建立一个物理建筑时,建筑工程师使用支柱为建筑的每一层提供支持。同样,软件工程师使用Struts为业务应用的每一层提供支持。

什么是应用框架

框架(framework)是可重用的,半完成的应用程序,可以用来产生专门的定制程序[Johnson]

Framework概念并不是很新了,伴随着软件开发的发展,在多层的软件开发项目中,可重用、易扩展的,而且是经过良好测试的软件组件,越来越为人们所青睐。这意味着人们可以将充裕的时间用来分析、构建业务逻辑的应用上,而非繁杂的代码工程。于是人们将相同类型问题的解决途径进行抽象,抽取成一个应用框架。这也就是我们所说的Framework

Framework的体系提供了一套明确机制,从而让开发人员很容易的扩展和控制整个Framework开发上的结构。 通常,Framework的结构中都有一个命令和控制组件("command and control" component——Framework Factory and Manager

1 Framework体系

通过基于请求响应(Request-Response)模式的应用Framework,基本上有如下几个表现逻辑结构组成。

? 控制器(Controller——控制整个Framework中各个组件的协调工作。

? 业务逻辑层(Business Logic——Framwork本身来说,这里仅仅只是概念和几个提够服务的基础组件,真正的实现与客户的业务逻辑接轨,还需要开发人员在Framework上再次扩展。

? 数据逻辑层(Data Logic——绝大应用系统都需要涉及到数据交互,这一层次主要包括了数据逻辑和数据访问接口。对于数据逻辑来说,如果你了解数据建模(Data Modeling)可能就很容易理解。

框架向开发人员提供一系列具有以下特征的骨架组件:

? 已经知道他们在其他程序上工作的很好;

? 它们随时可以在下一个项目中使用;

? 他们可以被组织的其他团队使用;

WEB框架所要解决的问题

涉及的概念和技术

超文本传输协议 (HTTP) :很多服务器应用程序使用HTTP之外的其他协议。他们在计算机之间维护一个持久性的的连接。应用服务器可以清楚的知道是谁连接上来,而且何时中断连接。因为他们知道每一个连接的状态,以及每一个使用它的人。这称之为状态协议。

相反, HTTP 是一个无状态协议。HTTP server 可以接受来自于各种客户的各种请求,并提供各种响应,即使是这个响应仅仅是说No。没有大量的协商和连接持久性,无状态协议可以处理大量的请求。

HTTP请求的第一行包含方法,其后是请求的来源地址和HTTP版本。HTTP请求头跟在首行后面,可以没有也可以有多个。HTTP 头向服务器提供额外的信息。可以包括浏览器的种类和版本,可接受的文档类型,浏览器的 cookies等等。7种请求方法中, GET POST 是用得最多的。

一旦服务器接收到请求,他就要产生一个HTTP响应。响应的第一行称为状态行,包含了HTTP协议的版本,数字型状态,以及状态的简短描述。状态行后,服务器将返回一个HTTP 响应头,类似于HTTP请求头。

Cookies URL 重写是两个在请求间跟踪用户状态的方式。cookie 是一种特殊的信息包,存储于用户的计算机中。URL 重写是在页面地址中存储一个特殊的标记,Java 服务器可以用它来跟踪用户。

标准的HTTP web 服务器并不传输动态内容。它主要是使用请求来定位文件资源,并在响应中返回此资源。通常这里的文件使用Hypertext Markup Language (HTML) [W3C, HTML] 格式化,以使浏览器可以显示它们。

静态内容直接来自于文本或数据文件,比如HTML 或者 JPEG 文件。这些文件可以随时改变,但通过浏览器请求时,却不能自动改变。

动态内容是临时产生的,典型地,它是针对浏览器的个别请求的响应。

建立简单的Struts应用

首先你该做的

下载并安装JDK 下载并安装 Tomcat 校验Tomcat是否工作正常。

用户注册程序

需求描述:用户将看到一个注册屏幕,包含3个字段:用户名,密码和密码确认。成功的注册要求两次密码相符。如果注册成功,控制将转向一个页面,显示注册成功successful!.。如果两次输入密码不同,控制流将转向一个显示失败的页面。

问题: 如何创建HTML 表单; 从HTML表单获取输入; 处理输入(业务逻辑);根据动态输入改变控制流;

解决:为完成这个程序,你需要建立 一个ActionForm 一个Action struts-config.xml文件 和三个页面

步骤1 创建ActionForm

ActionForm是个JavaBean,扩展org.apache.struts.ActionForm类。这个对象捕获通过请求传送的输入。当浏览器提交一个表单,它在请求中为每个表单中的字段创建一个参数。ActionForm针对每个HTML表单中的字段具有一个对应的属性。ActionServlet匹配请求中的参数和ActionForm中的属性。当匹配好后,ActionServlet 为属性调用setter方法,并将请求中的值传入。在我们的练习中,表单中的username字段需要一个setUsername(String)方法。Password1字段需要setPassword1(String) setPassword2(String)方法。

RegisterForm 的源代码显示在清单1中。

package app;

import org.apache.struts.action.*;

public class Registerform extends ActionForm {

protected String userName;

protected String password1;

protected String password2;

public String getPassword1() {

return password1;

}

public void setPassword1(String password1) {

this.password1 = password1;

}

public String getPassword2() {

return password2;

}

public void setPassword2(String password2) {

this.password2 = password2;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

}

存储在

<BaseDirectory>/webapps/register/WEB-INF/classes/app

步骤2 创建 RegisterAction

Action一个Java类,扩展了org.apache.struts.ActionActionServlet组装ActionForm,然后将其传递给ActionAction通常负责输入校验,存取业务信息,以及决定向Servlet返回哪个ActionForward

package app;

import org.apache.struts.action.*;

import javax.servlet.http.*;

import java.io.*;

public class RegisterAction extends Action {

public ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest req, HttpServletResponse res) {

// 转换formRegisterForm

RegisterForm rf = (RegisterForm) form;

String username = rf.getUsername();

String password1 = rf.getPassword1();

String password2 = rf.getPassword2();

// 业务逻辑

if (password1.equals(password2)) {

try {

// 调用服务把用户加到数据库中去

//UserService.getInstance().AddUser(username, password1);

return mapping.findForward("success");

} catch (Exception e) {

return mapping.findForward("failure");

}

}

// Return ActionForward for failure

return mapping.findForward("failure");

}

}

步骤3 创建Struts 配置文件 (struts-config.xml)

struts-config.xml 文件包含了ActionServlet 需要用来处理对应用请求的详细信息。为了练习,我们创建一个空壳的struts-config.xml 文件。

文件存储在<BaseDirectory>/webapps/register/WEB-INF/目录下,需要改变的是:

首先,添加/register <action>元素的 path 属性。ActionServlet 使用Web容器转发给它的URI来选择正确的Action 类。URI ActionMapping path 属性匹配。这里,请求给出的路径必须在去除前缀和后缀后和/register 匹配。前缀或后缀通常是/do/ 或者 .do。 我们的练习中,将后缀设置为.do。当URI 具有一个.do 扩展名,容器就知道将请求转发给ActionServletStruts会自动去除 扩展名,所以我们在配置时不必加上它们。

下一步添加:registerForm <action> 元素的 name 属性。<action> 元素使用name 属性来识别哪个ActionForm 将被创建,并将提交的表单组装给他。

然后,添加 app.RegisterAction <action> 元素的 type 属性。ActionServlet 使用这个属性来识别将用来处理请求的Action 类。

接下来,在<forward> 元素下,添加 success name 属性,并且 /success.html path 属性。最后,再在另一个<forward>下添加 failure name 属性, /failure.html path 属性。 这些元素将创建ActionForward 对象,我们将用它来选择程序的控制流。<forward> 元素定义了在RegisterAction中使用的逻辑名称之间的关联。

Struts-config.xml 源代码:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 1.0//EN"

"http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd">

<struts-config>

<form-beans>

<form-bean name="registerForm" type="app.RegisterForm" />

</form-beans>

<action-mappings>

<action path="/register" type="app.RegisterAction"

name="registerForm">

<forward name="success" path="/success.html" />

<forward name="failure" path="/failure.html" />

</action>

</action-mappings>

</struts-config>

Struts框架将struts-config.xml 文件视为部署描述符使用。它使我们可以创建和改变ActionMapping 和路径的关联而不用重新编译java类。我们也可以改变页面之间的连接,而不改变JSP模板。

步骤4 创建success.html, failure.html, 以及register.jsp 页面存放在<Base Directory>/webapps/register目录下。

success.html

<HTML>

<HEAD>

<TITLE>成功</TITLE>

</HEAD>

<BODY>

恭喜你,注册成功了

<P>

<A href="register.jsp">继续注册吗?</A>

</P>

</BODY>

</HTML>

failure.html

<HTML>

<HEAD>

<TITLE>对不起</TITLE>

</HEAD>

<BODY>

注册失败!

<P>

<A href="register.jsp">还要试一试吗?</A>

</P>

</BODY>

</HTML>

Register.jsp

<%@ taglib uri="/WEB-INF/struts-form.tld" prefix="form"%>

<form:form action="register.do">

用户名:<form:text property="username" />

<br>

输入密码:<form:password property="password1" />

<br>

再输一次:<form:password property="password2" />

<br>

<form:submit value="Register" />

</form:form>

整个程序的流程图如下

现在,试一下运行如何。 如果Tomcat没有运行,启动它。

在浏览器中输入以下地址:

http://localhost:8080/register/register.jsp

回顾一下,我们做了什么

建立Register应用我们实际上完成了以下内容:

? RegisterForm ActionForm

? RegisterAction Action

? 3个页面

它是如何工作的

当你浏览器地址http://localhost:8080/register/register.jspTomcat按通常情况加工这个页面。输入usernamepassword,点击Register 提交页面。浏览器在请求中post表单的内容。容器检查请求将送到哪一个注册的路径去。然后请求被转发到ActionServlet ,并由RegisterAction来处理。在返回成功或失败之前,RegisterAction 校验输入的有效性。最后servlet将控制根据返回的ActionForward转发到响应页面,如图下图

内容总结

? 框架的概念

? 理解HTTP, CGI, servlet, JSP

? 使用 Model 2 架构

? 构建简单的应用

? 理解Struts的执行过程

? 建立简单的Struts应用

独立实践

? 使用struts完成用户注册程序

? 使用struts完成登录(要求显示详细的错误信息)

? 登录成功后在成功页面显示你的姓名等信息

? 简述MODEL 1 MODEL 2的区别

? 简述Struts的工作流程

第三十一章:Struts基础

学习目标

? Struts如何实现Model 2, MVC

? Struts控制流程

? Struts组件介绍

? Struts ActionServlet控制器对象

? Struts Action Classes

? Struts Action Mapping

MVC

MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:

模型、视图、控制器。

它们各自处理自己的任务。

模型(model

模型表示企业数据和业务规则。

MVC的三个部件中,模型拥有最多的处理任务。

模型与数据格式无关,一个模型能为多个视图提供数据。多个视图重用

视图(view

视图是用户看到和交互的界面。

视图由Jsp HTML Flash XHTML ML/XSLWML等标识语言。

视图中没有处理,不管这些数据如何存在,对视图来说,只是一种输出数据并允许用户操纵的方式。

控制器(Controller

控制器接受输入并调用模型和视图去完成用户的需求。

控制器不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后用确定用哪个视图来显示模型处理返回的数据。

控制器一般由servlet承担

MVC的处理过程:首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。

struts框架的优势

? 良好的架构和设计

? 可重用,模块化,扩展性好

? Open source

Struts如何实现Model 2, MVC

下表列出了Struts的核心类,即对应的经典的MVC组件职责。

类 描述

ActionForward 用户指向或者视图选择

ActionForm 状态改变的数据

ActionMapping 状态改变事件

ActionServlet 控制器,接受用户请求和状态改变,以及发出视图选择控制器的一部分,于模型交互,执行状态改变或状态查询,以及告诉Action ActionServlet 下一个选择的视图

除了这些核心类,Struts使用一些配置文件和视图助手(view helpers)来沟通控制器和模型。

下表列出了Struts配置文件和描述了他们在架构中的角色。

文件 目的

ApplicationResources.properties 存储本地化信息和标签,以使应用可以国际化

struts-config.xml 存储控制器对象的缺省配置,包括模型支持的用户指向,状态改变,状态查询

为将Struts配置数据暴露给视图,框架以JSP标签的形式提供了大量的助手类,如表:

标记库描述符 目的

struts-html.tld 扩展HTML FormJSP标记

struts-bean.tld 扩展处理JavaBeanJSP标记

Struts 控制流

下图以UML时序图的方式展示了Struts 请求-响应流程

我们来按这个请求-响应流程走一边

? 客户请求匹配Action URI样式的路径

? 容器将请求传递给ActionServlet.

? 如果这个是模块化应用,ActionServlet选择响应的模块。

? ActionServlet查询路径的映射。

? 如果映射标明了一个form beanActionServlet看是否已经有一个实例,或者创建一个新的实例。如果已经有一个form beanActionServlet重设它(reset),并根据HTTP请求重新组装它。

? 如果mappingvalidate属性设置为true,它将调用form beanvalidate方法,如果验证失败,Servlet将控制转发到input属性标明的路径,控制流终止。

? 如果mapping标明一个Action类型,如果它已经存在或已经实例化,它将被重用。

? 接着Actionperformexecute方法被ActionServlet调用,并传递一个实例化的form bean或者null

? Action可以组装form bean,调用业务对象,以及其他需要做的事情。

? Action返回一个ActionForwardActionServlet

? 如果ActionForward指向另一个Action URI,重新开始;否则,显示页面或者其他资源,流程结束。通常,结果是一个JSP页面,或者Jasper,或其它类似技术 (Struts)

? 如果JSP使用Struts HTML标记,并且在请求中看到正确的ActionForm,他们会从ActionForm中组装HTML控件。否则,<html:form>标记将创建一个。从Struts1.1开始,如果form标记自行创建一个ActionForm,它将调用ActionFormReset方法。如果你只是想创建一个空白的表单,你可以使用标准的ForwardAction来通过Action传递控制,然后离开页面。

Struts framework的工作原理和组件

对于Struts如何控制、处理客户请求,让我们通过对struts的四个核心组件介绍来具体说明。这几个组件就是:ActionServletActionClassesActionMapping(此处包括ActionForward)、ActionFromBean

Struts ActionServlet控制器对象

ActionServlet继承自javax.servlet.http.HttpServlet类,其在Struts framework中扮演的角色是中心控制器。它提供一个中心位置来处理全部的终端请求。控制器ActionServlet主要负责将HTTP的客户请求信息组装后,根据配置文件的指定描述,转发到适当的处理器。

按照Servelt的标准,所有得Servlet必须在web配置文件(web.xml)声明。同样,ActoinServlet必须在Web Application配置文件(web.xml)中描述,有关配置信息如下。

<servlet>

<servlet-name>action</servlet-name>

<servlet-class>

org.apache.struts.action.ActionServlet

</servlet-class>

</servlet>

全部的请求URI*.do的模式存在并映射到这个servlet,其配置如下:

<servlet-mapping>

<servlet-name>action</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

一个该模式的请求URI符合如下格式:

http://www.my_site_name.com/mycontext/actionName.do

中心控制器为所有的表示层请求提供了一个集中的访问点。这个控制器提供的抽象概念减轻了开发者建立公共应用系统服务的困难,如管理视图、会话及表单数据。它也提供一个通用机制如错误及异常处理,导航,国际化,数据验证,数据转换等。

当用户向服务器端提交请求的时候,实际上信息是首先发送到控制器ActionServlet,一旦它获得了请求,它就会把提交的请求都委托给RequestProcessor对象。

RequestProcessor使用struts-config.xml文件检查请求URI找到动作Action标示符。接着将请求信息传交给一些辅助类(help classes)处理。这些辅助类知道如何去处理与请求信息所对应的业务操作。

Struts中,这个辅助类就是org.apache.struts.action.Action。通常开发者需要自己继承Aciton类,从而实现自己的Action实例。

Struts Action Classes

如果Struts配置是一个应用系统的大脑,Action 类则是其强健的四肢。它们是Struts应用中真正干活的组件,开发人员一般都在这里耗费大量的时间处理这个类。

Struts Action的核心职责是:

访问业务

为表现层准备数据对象

处理在其中出现的错误

搞定Action对象

Action最为常用的是execute()方法。(注意,以前的perform方法在struts1.1中已经不再支持),还有一个execute()方法,请参考apidoc

public ActionForward execute(ActionMapping mapping,

ActionForm form,

HttpServletRequest request,

HttpServletResponse response)

throws Exception

Controller收到客户的请求的时候,在将请求转移到一个Action实例时,如果这个实例不存在,控制器会首先创建,然后会调用这个Action实例的execute()方法。Struts Framework为应用系统中的每一个Action类只创建一个实例。因为所有的用户都使用这一个实例,所以你必须确定你的Action 类运行在一个多线程的环境中。

Action做些什么?

取表单数据

MyActionForm myform = (MyActionForm) form;

调用业务逻辑

EmployeeService service = new EmployeeService();

service.insertEmployee( employeeDTO );

检测错误

ActionErrors errors = new ActionErrors();

try {

// * 调用业务对象 *

}

catch (ModelException e) {

errors.add(ActionErrors.GLOBAL_ERROR,

new ActionError("error.detail",e.getMessage()));

}

if (!errors.empty()) {

saveErrors(Request, errors);

return (mapping.findForward("error"));

}

// * 正常继续 *

处理异常

<action path="/employeeAction"

type="app.EmployeeDispatchAction" name="employeeForm"

scope="request" validate="true"

input="/employeeForm.jsp">

<exception key="exception.database.error"

type="app.DatabaseException" path="/error.jsp" />

<forward name="insertSuccess" path="/employeeForm.jsp" />

<forward name="updateSuccess" path="/confirmation.jsp" />

</action>

Action的分类

标准Action

ForwardAction 发出一个RequestDispatcher 转发

IncludeAction 发出一个RequestDispatcher 包含

DispatchAction

LookupDispatchAction

SwitchAction

ForwardAction

创建一个请求分派器,并根据ActionMapping提供的上下文相关的URI转发控制,上下文相关路径在ActionMappingparameter属性中给出:

<action path="/saveSubscription"

type="org.apache.struts.actions.ForwardAction"

name="subscriptionForm" scope="request" input="/subscription.jsp"

name="/path/to/application/resource" />

IncludeAction

通过包含转发,你发出一个include指令。当其servlet完成时,控制要返回。相反,Forward一旦一个响应开始就不能发出一个转发,控制也不返回到发出的servlet

ForwardInclude的区别

Action 响应 控制

Forward 一旦响应开始就不能发出 控制不返回

Include 在响应时也可以发出 控制要回

DispatchAction

Struts开发人员的一个常用策略时使用同一个Action类来处理几个相关的任务。一个很好的例子是对一个数据记录执行基本的CRUD (创建读取修改和删除)操作,它们非常相似,可以放在一个Action类中处理。通过扩展DispatchAction(org.apache.struts.actions.DispatchAction),开发人员可以将多个方法在一个单一的Action内成

DispatchAction可以通过隐藏字段的关键字自动选择正确的方法;他使用反射机制而不是大部分开发人员所使用的逻辑方法。

public ActionForward create(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward read(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward update(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward delete(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

Struts-config.xml中配置如下

<action-mappings >

<action

attribute="employeeForm"

input="/employee.jsp"

name="employeeForm"

parameter="method"

path="/employee"

scope="request"

type="app.EmployeeDispatchAction" />

</action-mappings>

为选择 delete 方法, 你可以调用

http://localhost/struts2/employee?method=delete

实践中method字段的值通常是按钮或者表单中隐藏属性的名来设置,

<html:form action= /employee?method=create>

form中加入一个Hidden字段

<html:hidden name=”method” value=”create”/>

LookupDispatchAction

选择dispatch方法的一个方便的方式是将它们连接到按钮,这对于本地化应用来说是个问题,因为按钮的标签可能根据用户的场所来改变,如英文版本显示Delete按纽而对中文用户要显示删除

LookupDispatchAction(org.apache.struts.actions.LookupDispatchAction)通过将标签映射到资源文件的消息关键字来解决这个问题,但消息关键字不是相应Java方法的名称,开发人员就提供一个hash表来映射消息关键字和dispatch方法名(通过getKeyMethodap方法实现)

protected Map getKeyMethodap(ActionMapping mapping, ActionForm form,

HttpServletRequest Request) {

Map map = new HashMap();

map.put("button.add", "cerate");//button.add映射到create方法

map.put("button.view", "read");

map.put("button.update", "update");

map.put("button.delete", "delete");

return map;

}

public ActionForward create(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward read(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward update(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

public ActionForward delete(ActionMapping mapping,

ActionForm form,

HttpServletRequest Request, HttpServletResponse Response)

throws IOException, ServletException;

JSP中按钮要这样来创建:

<html:form action="/dataRecord">

<html:submit property="method">

<bean:message key="button.add">

</html:submit>

<html:submit property="method">

<bean:message key="button.view">

</html:submit>

<html:submit property="method">

<bean:message key="button.update">

</html:submit>

<html:submit property="method">

<bean:message key="button.delete">

</html:submit>

</html:form>

对应的资源文件

中文ApplicationResources_zh_CN.properties

button.add=增加

button.view=查看

button.update=更新

button.delete=删除

英文ApplicationResources_zh.properties

button.add=Add

button.view=View

button.update=Update

button.delete=Delete

SwitchAction

所有的 Struts应用至少具有一个模块,某些应用可能有多个模块,每个模块使用其自己的一套配置文件和表现页面,并且可以象应用中仅有一个模块的情况一样独立进行开发。当我们想从一个模块跳转到另一个模块时可以使用SwitchAction(org.apache.struts.actions.SwitchAction),它是一个标准Action,可以从一个模块切换到另一个模块,然后转发控制到此模块中的路径。

SwitchAction需要两个请求参数被传递:

page:一个模块相对的URI(/开始)切换后控制应该转发去的地方

prefix:控制应该切换到的应用模块的模块前缀(/开始)缺省使用0长度的字符串

Struts Action Mapping

上面讲到了一个客户请求是如何被控制器转发和处理的,但是,控制器如何知道什么样的信息转发到什么样的Action类呢?这就需要一些与动作和请求信息相对应的映射配置说明。在struts 中,这些配置映射信息是存储在特定的XML文件(比如struts-config.xml)。这些配置信息在系统启动的时候被读入内存,供struts framework在运行期间使用。在内存中,每一个<action>元素都与ActionMapping类的一个实例对应。

下面显示了一个登陆的配置映射。

<action-mappings>

<action path="/logonAction" type="com.test.LogonAction"

name="LogonForm" scope="request" input="logoncheck.jsp"

validate="false">

<forward name="welcome" path="/welcome.jsp" />

<forward name="failure" path="/logon_failure.jsp " />

</action>

</action-mappings>

上面的配置表示:当可以通过/logonAction.do提交请求信息的时候,控制器将信息委托com.test.LogonAction处理。调用LogonAction实例的execute()方法。同时将Mapping实例和所对应的LogonForm Bean信息传入。其中name=LogonForm,使用的form-bean元素所声明的ActionForm Bean

form-bean的声明如下

<form-beans>

<form-bean name="LoginForm" type="com.test.LoginForm" />

</form-beans>

使用ActionForward导航

元素<forward>则表示了当Action实例的execute()方法运行完毕或,控制器根据Mapping可将响应信息转到适当的地方。如上面现实,如果客户登陆成功,则调用welcome forward,将成功信息返回到/welcome.jsp页面。在你的execute()方法的结尾可以使用下面的实例代码而返回welcome forward。当然你的welcome forward必须在action元素属性中定义,正如上面所声明的那样。

return (mapping.findForward("welcome"));

ActionForward对象是配置对象。这些配置对象拥有独一无二的标识以允许它们按照有意义的名称如“success”“failure”等来检索。ActionForward对象封装了向前进的URL路径且被请求处理器用于识别目标视图。ActionForward对象建立自<forward>元素位于struts-config.xml。下面是一个Struts<forward>元素例子,属于<action>元素范围。

<action path="/editCustomerProfile"

type="packageName.EditCustomerProfileAction" name="customerProfileForm"

scope="request">

<forward name="success" path="/MainMenu.jsp" />

<forward name="failure" path="/CustomerService.jsp" />

</action>

基于执行请求处理器的execute(…)方法的结果,当传递一个值匹配指定于<forward>元素中name属性的值的时候,下一个视图可以在execute(…)方法中被开发者用方便的方法org.apache.struts.action.ActionMapping.findForward(…)选择。ActionMapping.findForward(…)方法既从它的本地范围又从全局范围提供一个ActionForward对象,该对象返回至RequestProcessorRequestDispatcher.forward(…)response.sendRedirect(…)调用下一个视图。当<forward>元素有redirect=“false”属性或redirect属性不存在的时候,RequestDispatcher.forward(…)被执行;当redirect=“true”是,将调用sendRedirect(…)方法。下例举例说明了redirect属性的用法:

<forward name="success" path="/Catalog.jsp" redirect="true"/>

如果redirect=true, URL建立如/contextPath/path因为HttpServletResponse.sendRedirect(…)中解释URL采用”/”开头相对于servlet容器根目录。

如果redirect=false, URI建立如/path因为

ServletContext.getRequestDisptacher(…)采用虚拟目录相关URL

global-forwards

在配置文件中描述了整个应用系统可以使用的ActionForward,而不是仅仅是一个特定的Action

<global-forwards>

<forward name="logout" path="/logout.do" />

<forward name="error" path="/error.jsp" />

</global-forwards>

使用时:

Return mapping.findForward(“logout”)或着

Jsp<html:link forward=”error”/>

Struts ActionForm Bean捕获表单数据

使用Web应用的人通常会花费大量的时间通过HTMLform提交数据,HTML

formWeb开发人员提出了两个挑战:一是在数据被提交时获取数据,以及用户要修改的数据预装入一个表单,以供用户修改。

怎么才能把javabean中的数据和HTML form上的数据连接起来呢?Struts通过标签来组装控件,每个HTML标签对应一个标准的struts标记元素(taglib, 每个标签都有一个属性项对应提供bean的属性名称,JavaBean属性的返回值对应HTML控件的value属性。

如:<input name="address"/>Struts标签来代替:

<html:input property="address"/>

ActionForm的作用:

通过Struts的标记库在JavaBeanHtml表单元素间传值

ActionForm 的要求

创建一个ActionForm并不困难,但是你的类必须符合一些要求:

? ActionForm必须扩展自org.apache.struts.ActionForm。基类ActionForm是不能实例化的。

? ActionForm 必须为每个应该从请求中收集的HTML控件定义一个公共属性。

? 如果你要求ActionForm 在传递属性到Action之前校验它们,你就必须实现validate方法;

? 如果想在组装前初始化属性,必须实现reset,它在ActionForm组装前被调用;

下面是一个简单的ActionForm类:

import org.apache.struts.action.*;

public class MyForm extends ActionForm {

protected String name;

protected String address;

public String getName() {

return this.name;

};

public String getAddress() {

return this.address;

};

public void setName(String name) {

this.name = name;

};

public void setAddress(String address) {

this.address = address;

};

};

ActionForm的处理流程

对于每一个客户请求,Struts framework在处理ActionForm的时候,一般需要经历如下几个步骤:

? 检查Action的映射,确定Action中已经配置了对ActionForm的映射

? 根据name属性,查找form bean的配置信息

? 检查Actionformbean的使用范围,确定在此范围下,是否已经有此form bean的实例。

? 假如当前范围下,已经存在了此form bean的实例,而是对当前请求来说,是同一种类型的话,那么就重用。

? 否则,就重新构建一个form bean的实例

? form beanreset()方法备调用

? 调用对应的setter方法,对状态属性赋值

? 如果validatede的属性北设置为true,那么就调用form beanvalidate()方法。

? 如果validate()方法没有返回任何错误,控制器将ActionForm作为参数,传给Action实例的execute()方法并执行。

注意:直接从ActionFrom类继承的reset()validate()方法,并不能实现什么处理功能,所以有必要自己重新覆盖。

使用动态ActionForm

配置动态ActionForm

<form-beans>

<form-bean name="employeeForm"

type="org.apache.struts.action.DynaActionForm">

<form-property name="name" type="java.lang.String" initial=”cjy”/>

<form-property name="password1" type="java.lang.String" />

<form-property name="password2" type="java.lang.String" />

</form-bean>

</form-beans>

动态表单通过子元素来设置动态ActionFrom的属性

name:指定属性名

type指定属性类型,它可以使用以下java类型

java.lang.BigDecimal

java.lang.BigInteger

java.lang.Boolean

java.lang.Byte

java.lang.Character

java.lang.Double

java.lang.Float

java.lang.Integer

...

动态ActionFormreset()的方法

DynaActionForm基类提供了initialize()方法,它把表单所有的属性初始化,初始值有initial属性指定,如果没有设置,初始值由java类型默认,如对象类型为null booleanfalse 整形为为0

访问动态ActionForm的属性

动态ActionForm把所有的属性保存在一个map

public Object get(String name)

public void set(String name,Object value)

get取出指定名称的属性值,用set给指定属性赋值,如

String email=(String)form.get("email");

form.set("email","cjy@21.com");

动态ActionForm的表单验证

DynaActionForm基类的validate()方法没有提供任何默认的行为,必须扩展DynaActionForm类,覆盖validate()方法编程来实现,还可以用Validator验证框架来实现配置验证

ActionForm的继承层次

Struts的其他组件

StrutsFramework本身提供了很多可扩展的组件或subframework,方便的开发人员在其构架上构建web层的应用系统。比如upload,collections,logging等等。让我们来看看两个比较重要的组件:validationg frameworkstruts taglib。有关其他组件请参考Struts用户手册

http://jakarta.apache.org/struts/userGuide

Validation Framework for Struts

struts1.1中,新增了validation framework。增加了对form数据提交的验证。将原本需要在ActionFrom Beanvalidate()进行的验证通过配置文件的描述进行验证。

有关其详细信息,请参考

http://home.earthlink.net/~dwinterfeldt

个人建议对于小型应用系统可以采用这种配置方式,但是对于应用系统中有大量web层表单应用的系统,并且业务需求变动比较大的,使用validation framework可能会加重开发难度、系统维护难度。可以借鉴validation frameworkJavascript Validator Tag

Struts TagLib

struts提供了一组可扩展的自定义标签库(TagLib),可以简化创建用户界面的过程。目前包括:Bean TagsHTML TagsLogic TagsNested TagsTemplate Tags 这几个Taglib。有关Struts Taglib的结构和使用,可以参考后面有关章节。

BeanUtils

这个组件的全称是Bean Introspection Utilites。是属于Jakarta Commons项目组的。主要是帮助构建javabean的属性操作的(getter,setter),已经提供一种动态定义和访问bean的属性。有关详细信息,请参考

http://jakarta.apache.org/commons/beanutils.html

Collections

这个组件主要是提供了一些集合或列表对象,在原有的java collections framework的基础上进行了扩展。详细资料请参考:

http://jakarta.apache.org/commons/collections.html

Digester

这个组件翻译成中文的意思是汇编。其主要功能是根据xml配置文件,初始化系统的一些java类对象。Digester帮助你指定XMLjava对象之间映射模型,而且允许客户话定制映射规则(rules)。详细资料请参考

http://jakarta.apache.org/commons/digester.html

内容总结

? 掌握Struts如何实现Model 2, MVC

? 熟练应用Struts控制流程

? 掌握Struts组件介绍

? 灵活运用Struts ActionServlet控制器对象

? 掌握Struts Action Classes

? 掌握Struts Action Mapping

? 能够对Struts组件熟练使用

? 能够在程序中使用struts框架

独立实践

? 按照struts的流程,做一个雇员注册的功能

? 完成一个雇员登录的功能,如果登录错误,要求显示详细的错误的信息

? 使用表单验证和框架验证完成对注册信息的验证

? 使用DispachAction

? 使用BeanUtils工具,做一个显示雇员表的数据的功能。

第三十二章:配置Struts组件

学习目标

? Web应用部署描述符

? Struts配置文件

? 应用资源文件

? Ant的构建文件

三个 XML文件和一个属性文件

? web.xmlJSP 容器使用这个文件来载入和配这是Java Servlet 要求的web应用部署描述符。Servlet的应用。

? struts-config.xmlStruts框架的部署描述符。它用来载入和配置Struts框架使用的各种组件。

? Build.xmlJakarta Ant构建工具使用它来编译和部署你的应用。使用Ant不是必需的,但它在Struts人员中很流行。

? Application.properties:该文件为你的Struts应用提供资源。像build.xml文件一样, 它不是严格要求的,但是大Struts应用都要用到。

Web应用部署描述符 web.xml

Struts框架的核心是ActionServletStruts 把它当作是一个控制器。将它看成是一个黑盒。他们总是在web应用部署描述符 (web.xml) 中配置它,然后让它自己工作。

Struts框架有两个组件需要从应用部署描述符中配置:ActionServlet 和标签库(可选),例如注册应用的web.xml

<!--前两行将文件标识为一个web应用部署描述符 -->

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"

"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app>

<!--配置 ActionServlet—这一段告诉容器装入action名称下的ActionServlet -->

<servlet>

<servlet-name>action</servlet-name>

<servlet-class>

org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>application</param-name>

<param-value>application</param-value>

</init-param>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/conf/struts-config.xml</param-value>

</init-param>

<init-param>

<param-name>debug</param-name>

<param-value>2</param-value>

</init-param>

<init-param>

<param-name>detail</param-name>

<param-value>2</param-value>

</init-param>

<load-on-startup>2</load-on-startup>

</servlet>

<!-- 标识Struts请求这一段告诉容器将匹配*.do 格式的文件请求转发到action servlet -->

<servlet-mapping>

<servlet-name>action</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

<!-- 创建welcome文件 -->

<welcome-file-list>

<welcome-file>index.jsp</welcome-file>

</welcome-file-list>

<!-- 配置标签库这里我们配置应用中使用的标签库。 3个核心的Struts标签库—bean, html, logic -->

<taglib>

<taglib-uri>/tags/struts-bean</taglib-uri>

<taglib-location>/WEB-INF/lib/struts-bean.tld</taglib-location>

</taglib>

<taglib>

<taglib-uri>/tags/struts-html</taglib-uri>

<taglib-location>/WEB-INF/lib/struts-html.tld</taglib-location>

</taglib>

<taglib>

<taglib-uri>/tags/struts-logic</taglib-uri>

<taglib-location>/WEB-INF/lib/struts-logic.tld</taglib-location>

</taglib>

</web-app>

ActionServlet的参数的配置

Struts配置

Struts配置文件 (struts-config.xml)用来装入多个关键的框架组件。这些对象一起构成了Struts 配置。

<struts-config>

下面配置针对常规ActionFormDynaActionForm<form-bean> 元素配置

<form-bean name="menuForm" type="com.itjob.struts.MenuForm" />

<form-bean name="logonForm"

type="org.apache.struts.action.DynaActionForm">

<form-property name="username" type="java.lang.String" />

<form-property name="password" type="java.lang.String" />

</form-bean>

下面配置全局转发配置这些forward对应用中的每个Action都有效。你也可以部署一个局部 ActionForward<action> 元素中。局部转发仅针对该ActionMapping有效。

<global-forwards>

<forward name="logoff" path="/logoff.do" />

<forward name="logon" path="/logon.do" />

<forward name="welcome" path="/welcome.do" />

</global-forwards>

下面配置ActionMapping,它描述应用要采取的操作、命令。 ActionURI被用作一个ActionMapping的逻辑标识符,或者路径。当web浏览器请求一个ActionURIActionServlet首先查找相应的ActionMappingActionMapping 则告诉 ActionServlet 哪个 Action 对象要用于这个URI

<action-mappings> 元素描述了我们的应用要用来处理请求的ActionMapping对象(org.apache.struts.action.ActionMapping)的集合。请求要到达应用然后到达ActionServlet,它必须匹配上下文和我们在容器中注册的url-pattern 格式。

如果URL是针对http://localhost/myApp/myAction.do 我们只需要引用 /myAction作为 ActionMapping的路径

<action path="/logonSubmit" type="app.LogonAction" name="logonForm"

scope="request" validate="true" input="/pagess/Logon.jsp" />

<action path="/logon" type="app.ContinueAction">

<forward name="continue" path="/pagess/Logon.jsp" />

</action>

<action path="/welcome" type="app.ContinueAction">

<forward name="continue" path="/pagess/Welcome.jsp" />

</action>

请求处理器是ActionServlet处理循环的核心。大多数情况下,你可以编写和装入一个请求处理器,来代替创建你自己的ActionServlet子类。

<contrller nocache="true" null="true"

processorClass="com.myCompany.struts.RequestProcessor" />

每个模块都应该有其自己的缺省消息资源束。

<message-resources parameter="resources.application" />

<message-resources parameter="resources.image" />

plug-inStruts应用中,将验证任务委托给第三方的插件

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">

<set-property property="pathname"

value="/WEB-INF/validator-rules.xml" />

<set-property property="pathname" value="/WEB-INF/validation.xml" />

</plug-in>

data-sources为了帮助开发人员使用连接, Struts 提供了一个数据源管理组件。你可以使用这个组实例化和配置一些实现数据源的对象,并且可以从JavaBean的属性进行整体配置。

下面是一个数据源配置,使用MySQL数据库的Struts 缺省配置:

<data-sources>

<data-source>

<set-property property="maxCount" value="4" />

<set-property property="minCount" value="2" />

<set-property property="description"

value="Artimus:MySQL Data Source Config" />

<set-property property="driverClass"

value="org.gjt.mm.mysql.Driver" />

<set-property property="url"

value="jdbc:mysql://localhost:3306/artimus" />

<set-property property="autoCommit" value="true" />

<set-property property="user" value="root" />

<set-property property="password" value="" />

</data-source>

</data-sources>

应用资源文件

Struts框架提供了好用和灵活的消息系统,JavaJSP代码中,要给定一个消息的关键字;消息文本在运行时丛属性文件中检索。框架文档将消息属性文件引用为 application resources 或者 message resource bundle

如果你想要本地化你的应用,你可以为你想要支持的场所创建一个额外的应用资源文件。这实际上是创建一个资源束 (java.util.ResourceBundle)。框架会为每个用户维护一个标准的Locale对象(java.util.Locale)。针对用户场所的合适的消息会自动从资源束中进行选取。

两个概念:

? Locale(场所) 对象是一个特定的语言和地区的识别符。

? ResourceBundle 对象包含场所特定的对象。当需要一个场所特定的对象时,可以从资源束中取得,它返回匹配用户场所的对象。 Struts 框架为消息文本使用基于字符串的资源束。

属性文件自身是一个平面的文本文件,每一行是一个关键字-值对。你可以使用任何文本编辑器进行编辑,包括Windows Notepad

应用资源文件的缺省名称是通过在web.xmlStruts ActionServlet传递一个初始化参数决定的。在应用中使用Struts应用资源束之前必须首先进行配置。应用资源文件位于应用的CLASSPATH之中,这样Struts可以找到它。最好是放在你的应用的class 文件夹中。这可能是在WEB-INF/classes文件夹中,或者,如果你以二进制部署你的应用时在WEB-INF/lib下的一个JAR文件中。

param-value 应该使你的文件按包命名格式的全限定名称。这意味着如果如果你将资源文件直接放在classes下,你可以直接使用文件名,如前面的代码片断所示。

<init-param>

<param-name>

Application</param-name> <param-value>resources.application</param-value>

</init-param>

物理文件的系统路径应该是:

WEB-INF/classes/resources/application.properties

为了本地化你的应用,为每个支持的场所添加资源文件,并修改基本名称:

WEB-INF/classes/resources/

application.properties

application_es.properties

application_fr_CA.properties

Ant构建文件

构建文件build.xml设置为使用源代码存储在WEB-INF子目录下的项目树。这使得整个应用,包括源代码和编译文件,都集中在一个目录系统之中。这就能使你得应用的工作目录可以位于你的开发服务器中。如果容器可以很好的重装类文件,你就可以重新构建应用来测试最新的改变,而不用重启容器。

<!--project 构建文件总体名称,并且标识一个基础目录和缺省目标-->

<project name="logon basedir=" ." deafult="dist">

<property name="project.title" value="Logon" />

<property name="project.version" value="1.2" />

<property name="dist.name" value="logon" />

<!--构建应用是要使用的classpath 通常是WEB-INF/lib文件夹中的JAR的清单-->

<path id="project.class.path">

<pathelement path="lib/struts.jar" />

<pathelement path="./classes/" />

<pathelement path="${classpath}" />

</path>

<!-- 帮助Ant通过比较类文件和源文件的时间戳来最小化编译工作 -->

<target name="prepare">

<tstamp />

</target>

<!-- 从源代码树中拷贝一些属性文件到classes树。可以保持原始的属性文件和文件源代码中的保持一致 -->

<target name="resources">

<copy todir="classes" includeEmptyDirs="no">

<fileset dir="src/resources">

<patternset>

<include name="**/*.properties" />

</patternset>

</fileset>

</copy>

</target>

<!--首先调用prepare resources 目标,然后开始构建源文件-->

<target name="compile" depends="prepare,resources">

<javac srcdir="src" destdir="classes">

<classpath refid="project.class.path" />

</javac>

</target>

<!--clean目标通过删除和恢复类文件夹来确保所有的东西都重新构建-->

<target name="clean" description="Prepare for clean build">

<delete dir="classes" />

<mkdir dir="classes" />

</target>

<!-- javadoc 目标为应用构建JavaDoc。通常,要标明classpathJAR路径。注意,是冒号分隔的列表-->

<target name="javadoc" description="Generate JavaDoc API docs">

<delete dir="./doc/api" />

<mkdir dir="./doc/api" />

<javadoc sourcepath="./src/java" destdir="./doc/api"

classpath="lib/struts.jar:" packagenames="app.*" author="true"

private="true" version="true" windowtitle=" API Documentation"

doctitle="<h1>${project.title} Documentation (Version ${project.version})</h1>"

bottom="Copyright © 2002" />

</target>

<!-- dist目标为应用创建一个Web归档(WAR)文件。这个文件可以用来在你的生产服务器上部署你的应用。-->

<target name="dist" description="createe binary distribution">

<delete dir="./dist" />

<mkdir dir="./dist" />

<war warfile="./dist/${dist.name}.war"

webxml="../WEB-INF/web.xml" manifest="../META-INF/MANIFEST.MF"

basedir="../"

excludes="WEB-INF/dist,WEB-INF/web.xml,META-INF/MANIFEST.MF" />

</target>

<!-- project 目标将全部构建所有东西,并准备一个二进制的分发包-->

<target name="project" depends="clean,prepare,compile,javadoc,dist" />

</project>

配置Tiles框架

Tiles是一个Struts 框架的可选组件,是一个强大的页面组装工具

步骤1

Struts lib 文件夹拷贝struts-tiles.tld tiles-config.dtd 文件 (如果没有)/WEB-INF 文件夹.

插入下面的语句快到(如果没有)/WEB-INF/web.xml 文件中,并且紧跟其他<taglib> 元素:

<taglib>

<taglib-uri>/tags/tiles</taglib-uri>

<taglib-location>/WEB-INF/tiles.tld</taglib-location>

</taglib>

步骤2 创建一个tiles-defs.xml /WEB-INF文件夹

<!DOCTYPE tiles-definitions PUBLIC

"-//Apache Software Foundation//DTD Tiles Conig//EN"

"http://jakarta.apache.org/struts/dtds/tiles-config.dtd">

<tiles-definitions>

<!-- skeleton definition

<definition name="${name}" path="${path}">

<put name="${name}" value="${value}"/>

</definition>

end blank definition -->

</tiles-definitions>

步骤3 插入这个 <plug-in> 元素到struts-config.xml,位置在关闭的</struts-config> 元素之前:

<plug-in className="org.apache.struts.tiles.TilesPlugin">

<set-property property="definitions-config"

value="/WEB-INF/tiles-defs.xml" />

</plug-in>

内容总结

? Web应用部署描述符

? 应用资源文件

? 掌握Ant的构建文件

? 能够用ant工具对Web应用部署

? 熟练使用Struts配置文件

独立实践

? 按照上一章完成的一个雇员登录的功能,如果登录错误,要求用国际化显示详细的错误的信息

? 假设在地址栏中输入一个不存在的页面,要求能显示已经自定义好的404错误页面

? 如果在地址栏中输入:http://127.0.0.1:8080/login,不输入具体页面,要求显示一个自定义的欢迎页面出来

? 用ant 工具部署雇员管理系统

? 用Tiles框架来优化雇员管理系统

第三十三章:Struts标记库

学习目标

? Bean标记

? 逻辑标记

? HTML标记

? 模板标记

Struts标记库taglib介绍

Struts提供了用来封装逻辑的各种定制JSP标记,因此页面设计者可以将主要精力花在页面的可视特征上,而不必主要考虑Java语法或其它JSP语法

? Struts-bean.tld:使访问和定义bean更容易,为了实现国际化,应使用不同的属性文件

? struts-html.tld:提供显示HTML对象(如表单、按钮和复选框等)的简便方法

? struts-logic.tld:支持逻辑构造,以便可以有条件地显示文本或者作为处理循环的结果来显示文本

? struts-template.tld:支持使用在运行时可以修改的JSP模板

taglib的使用:要在JSP文件顶部的<taglib>伪指令如下所示:

<%@ taglib uri=“struts-html.tld” prefix=“html”%>

<%@ taglib uri=“struts-bean.tld”prefix=“bean”%>

<%@ taglib uri=“struts-logic.tld”prefix=“logic”%>

每个<taglib>伪指令都具有与基于 web.xml的<taglib>标记中的URL相匹配的URL,在jsp中通过以下两种形式使用标记

? 没有嵌套内容的标记可以采用以下格式:

<prefix:tagName attributesAndValues/>

? 嵌套内容是在一对标记之间嵌套的:

<prefix:tagName attributesAndValues />

</prefix:tagName>

Bean标记

这个标记库中包含用于定义新bean、访问bean及其属性的标记。Struts框架提供了多种自定义标记用来在JSP页中处理JavaBean。这些标记被封装在一个普通的标记库中,在文件struts-bean.tld中定义了它的标记库描述器。Bean标记库将标记定义在四个子类别中:

? 创建和复制bean的标记

? 脚本变量定义标记

? bean翻译标记

? 消息国际化标记

创建和复制bean的标记

<bean:define>标记用来:

? 定义新字符串常数

? 将现有的bean复制到新定义的bean对象

? 复制现有bean的属性来创建新的bean

<bean:define>标记属性表:

属性 描述

Id 新定义的bean脚本变量名称,必须设置

Type 定义引入脚本变量的类

Value 为id属性定义的脚本变量分配一个新的对象

Name 目标bean的名称。若value属性没有设置,这个属性就必须设置

Property Name 属性定义的bean的属性名称,用来定义新的bean

Scope 源bean的作用域。若没有设置,搜索范围是从页作用域到应用程序作用域

toScope 目标bean的作用域。若没有设置,默认值是页作用域

例如:定义一个bean:

<bean:define id=”test” value=”this is a test”/>

bean在页作用域中被拷贝大于请求作用域中的另一个bean:

<bean:define id=”targetBean” name=”sourceBean” scope=”page” toScope=”request”/>

定义脚本变量的标记

从多种资源中定义和生成脚本变量,这些资源包括cookie,请求参数,HTTP标头等等。属性如下:

属性 描述

Id 脚本变量和要定义的页作用域属性的名称

Name cookie/标头/参数的名称

multiple 如果这个属性设置了任意一个数值,所有匹配的cookie都会被积累并存储到一个Cookie[](一个数组)类型的bean里。若无设置,指定cookie的第一个值将作为Cookie类型的值

Value 如果没有匹配的cookie或数值,就返回这个属性指定的默认值

例如:<bean:cookie id=”myCookie” name=”userName”/> 脚本变量名称是myCookie,用来创建这个属性的cookie的名称是userName

<bean:header id=”myHeader” name=”Accept-Language”/> 脚本变量名称是myHeader,请求标头的名称是Accept-Language.

<bean:parameter id=”myParameter” name=”myParameter”> 脚本变量名称是myPatameter,它保存的请求参数的名称也是myParameter.

<bean:include>标记将对一个资源的响应进行检索,并引入一个脚本变量和字符串类型的页作用域属性。这个资源可以是一个页,一个ActionForward或一个外部URL。与<jsp:include>的不同是资源的响应被存储到一个页作用域的bean中,而不是写入到输出流。属性如下:

属性 描述

Id 脚本变量和要定义的页作用域属性的名称

Page 一个内部资源

forward 一个ActionForward

Href 要包含的资源的完整URL

例如:<bean:include id=”myInclude” page=”MyJsp?x=1”/>脚本变量的名称是myInclude,要检索的响应来自资源MyJsp?x=1

<bean:resource>标记将检索web应用中的资源,并引入一个脚本变量和InputStream或字符串类型的页作用域属性。如果在检索资源时发生问题,就会产生一个请求时间异常。属性如下:

属性 描述

Id 脚本变量和要定义的页作用域属性的名称

Name 资源的相对路径

Input 如果这个属性不存在,资源的类型就是字符串

例如:<bean:resource id=”myResource” name=”/WEB-INF/images/myResource.xml”/> 脚本变量的名称是myResource,要检索的资源的名称是myResource.xml

显示Bean属性

标记库中定义了<bean:write>标记,用来将bean的属性输送到封装的JSP页写入器。这个标记与<jsp:getProperty>类似,属性如下:

属性 描述

Name 要进行属性显示的bean的名称

property 要显示的属性的名称。如果这个属性类有java.beans.PropertyEditor,getAsText()toString 方法会被调用

Scope Bean的作用域,若没有设置,搜索范围是从页到应用程序作用域

Filter 如果设置true,属性中的所有特殊HTML字符都将被转化为相应的实体引用

Ignore 如果设置false,当发现属性时会产生一个请求时间异常,否则返回null

例如:<bean:write name=”myBean” property=”myProperty” scope=”request” filter=”true”/> myBean的属性myProperty将会被显示,作用域为请求,如果发现任何HTML特殊字符都将被转化为相应的实体引用。

消息标记和国际化

strtus框架支持国际化和本地化,通过使用<bean:message>标记,以及使用java.util数据包中定义的LocaleResourceBundle类来实现Java2平台对这些任务的支持。Java.text.MessageFormat类定义的技术可以支持消息的格式

strtus实现国际化和本地化

定义资源文件的名称,这个文件会包含用默认语言编写的在程序中会出现的所有消息。这些消息以关键字-的形式存储,如下:

error.validation.location = The entered location is invalid

这个文件需要存储在类的路径下,而且它的路径要作为初始化参数传送给ActionServlet作为参数进行传递时,路径的格式要符合完整Java类的标准命名规范。比如,如果资源文件存储在WEB-INF\classes目录中,文件名是ApplicationResources.properties,那么需要传递的参数值是ApplicationResources。如果文件在WEB-INF\classes\com\test中,那么参数值就应该是com.test. ApplicationResources.

例如:资源文件中定义了一个消息:

info.myKey = The numbers entered are {0},{1},{2},{3}

我们可使用下面的消息标记:

<bean:message key=”info.myKey” arg0=”5” arg1=”6” arg2=”7” arg3=”8”/>

这个信息标记输出到JSP页会显示为:

The numbers entered are 5,6,7,8

逻辑标记

逻辑库的标记能够用来处理外观逻辑而不需要使用scriptletStruts逻辑标签库包含的标记能够有条件地产生输出文本,在对象集合中循环从而重复地产生输出文本,以及应用程序流程控制。它也提供了一组在JSP页中处理流程控制的标记。这些标记封装在文件名为struts-logic.tld的标记包中。

逻辑标记库定义的标记能够执行下列三个功能:条件逻辑 重复 转发/重定向响应

条件逻辑:struts有三类条件逻辑。

第一类可以比较下列实体与一个常数的大小: cookie?请求参数?beanbean的参数? 请求标头

以下列出了这一类标记:

标记 功能

<equal> 如果常数与被定义的实体相等,返回true

<notEqual> 如果常数与被定义的实体不相等,返回true

<greaterEqual> 如果常数大于等于被定义的实体,返回true

<lessEqual> 如果常数小于等于被定义的实体,返回true

<lessThan> 如果常数小于被定义的实体,返回true

<greaterThan> 如果常数大于被定义的实体,返回true

这一类的所有标记有相同的属性

属性 描述

Value 要进行比较的常数值

Cookie 要进行比较的HTTP cookie的名称

Header 要进行比较的HTTP请求标头的名称

parameter 要进行比较的HTTP请求参数的名称

Name 如果要进行比较的是beanbean的属性,则这个属性代表bean的名称

property 要进行比较的bean属性的名称

Scope Bean的作用域,如果没有指定作用域,则它的搜索范围是从页到应用程序

例如:

<logic:equal parameter=”name” value=”SomeName”>

The entered name is SomeName

</logic:equal>

判断名为”name”的请求参数的值是否是”SomeName”

<logic:greaterThan name=”bean” property=”prop” scope=”page” value=”7”>

The value of bean.Prop is greater than 7

</logic:greaterThan>

判断在页的作用域中是否有一个名为”bean”bean,它有一个prop属性,这个属性的值是否大于7。如果这个属性能够转化为数值,就进行数值比较,否则就进行字符串比较。

第二类条件标记定义了两个标记:

? <logic:present>

? <logic:notPresent>

它们的功能是在计算标记体之前判断特定的项目是否存在。标记的属性和属性值决定了要进行检查的项目。

属性 描述

Cookie 由这个属性指定的cookie将被检查是否存在

Header 由这个属性指定的请求标头将被检查是否存在

parameter 由这个属性指定的请求参数将被检查是否存在

Name 如果没有设置property属性,那么有这个属性指定的bean将被检查是否存在。如果设置了,那么beanbean属性都将被检查是否存在

property 检查有name属性指定的bean中是否存在指定的属性

Scope 如果指定了bean的名称,这就是bean的作用域。如果没有指定作用域,搜索的范围从页到应用程序作用域

Role 检查当前已经确认的用户是否属于特殊的角色

User 检查当前已经确认的用户是否有特定的名称

例如:<logic:notPresent name=”bean” property=”prop” scope=”page”>

The bean property bean.prop is present

</logic:notPresent>

标记判断在页作用域中是否存在一个名为”bean”bean,这个bean有一个prop属性。

第三类条件标记比较复杂,这些标记根据模板匹配的结果检查标记体的内容。换句话说,这些标记判断一个指定项目的值是否是一个特定常数的子字符串:

? <logic:match>

? <logic:notMatch>

这些标记允许JSP引擎在发现了匹配或是没有发现时计算标记主体。属性如下:

属性 描述

Cookie 要进行比较的HTTP cookie的名称

Header 要进行比较的的HTTP标头 的名称

parameter 要进行比较的的HTTP请求参数的名称

Name 若要对beanbean的属性进行比较,这个属性是用户指定bean的名称

location 如果设置了这个属性的值,将会在这个指定的位置(索引值)进行匹配

scope 如果对bean进行比较,这个属性指定了bean的作用域。如果没有设置这个参数,搜索范围是从页到应用程序作用域

property 要进行比较的bean的属性名称

value 要进行比较的常数值

例如:<logic:match parameter=”name” value=”xyz” location=”1”>

The parameter name is a sub-string of the string xyz from index 1

</logic:match>

标记检查名为”name”的请求参数是否是”xyz”的子字符串,但是子字符串必须从”xyz”的索引位置1开始(也就是说子字符串必须是”y””yz”)。

重复标记

在逻辑标记库中定义了<logic:iterate>标记,它能够根据特定集合中元素的数目对标记体的内容进行重复的检查。集合的类型可以是java.util.Iterator,java.util.Collection,java.util.Map或是一个数组。

有三种方法可以定义这个集合:

? 使用运行时间表达式来返回一个属性集合的集合

? 将集合定义为bean,并且使用name属性指定存储属性的名称。

? 使用name属性定义一个bean,并且使用property属性定义一个返回集合的bean属性。

当前元素的集合会被定义为一个页作用域的bean。属性如下,所有这些属性都能使用运行时表达式。

属性 描述

collection 如果没有设置name属性,它就指定了要进行重复的集合

Id 页作用域bean和脚本变量的名称,它保存着集合中当前元素的句柄

indexed 页作用域JSP bean的名称,它包含着每次重复完成后集合的当前索引

Length 重复的最大次数

Name 作为集合的bean的名称,或是一个bean名称,它由property属性定义的属性,是个集合

Offset 重复开始位置的索引

property 作为集合的Bean属性的名称

Scope 如果指定了bean名称,这个属性设置bean的作用域。若没有设置,搜索范围从页到应用程序作用域

Type 为当前定义的页作用域bean的类型

例如:<logic:iterate id=”currentInt”

collection=”<% =myList %>”

type=”java.lang.Integer”

offset=”1”

length=”2”>

<% =currentint %>

</logic:iterate>

代码将从列表中的第一个元素开始重复两个元素并且能够让当前元素作为页作用域和java.lang.Integer类型的脚本变量来使用。也就是说,如果myList包含元素1234等,代码将会打印12

转发和重定向标记

转发标记

<logic:forward>标记能够将响应转发给重定向到特定的全局ActionForward上。ActionForward的类型决定了是使用PageContext转发响应,还是使用sendRedirect将响应进行重定向。此标记只有一个”name”属性,用来指定全局ActionForward的名称,例如:

<logic:forward name=”myGlobalForward”/>

重定向标记

<logic:redirect>标记是一个能够执行HTTP重定向的强大工具。根据指定的不同属性,它能够通过不同的方式实现重定向。它还允许开发人员指定重定向URL的查询参数。属性如下:

属性 描述

Forward 映射了资源相对路径的ActionForward

Href 资源的完整URL

Page 资源的相对路径

Name Map类型的页名称,请求,会话或程序属性的名称,其中包含要附加大哦重定向URL(如果没有设置 property属性)上的名称-参数。或是具有Map类型属性的bean名称,其中包含相同的信息(没有设置property属性)

Property Map类型的bean属性的名称。Bean的名称由name属性指定。

Scope 如果指定了bean的名称,这个属性指定搜索bean的范围。如果没有设置,搜索范围从页到应用程序作用域

ParamID 定义特定查询参数的名称

ParamName 字符串类型的bean的名称,其中包含查询参数的值(如果没有设置paramProperty属性);或是一个bean的名称,它的属性(paramProperty属性中指定)包含了查询参数值

paramProperty 字符串bean属性的名称,其中包含着查询参数的值

ParamScope ParamName定义的bean的搜索范围. 使用这个标记时至少要指定forward,hrefpage中的一个属性,以便标明将响应重定向到哪个资源

HTML标记

Struts HTML标记可以大致地分为以下几个功能:

? 显示表单元素和输入控件

? 显示错误信息

? 显示其他HTML元素

显示表单元素和输入控件

strutsHTML表单与为表单操作而定义的ActionForm bean紧密联系在一起。表单输入字段的名称与ActionForm bean里定义的属性名称是对应的。当第一次显示表单时,表单的输入字段是从ActionForm bean中移植过来的,当表单被提交时,请求参数将移植到ActionForm bean实例。

所有可以在<form>标记中使用的用来显示HTML输入控件的内嵌标记都使用下列属性来定义JavaScript事件处理器。

属性 描述

Onblur 字段失去了焦点

Onchange 字段失去了焦点并且数值被更改了

Onclick 字段被鼠标点击

Ondblclick 字段被鼠标双击

Onfocus 字段接收到输入焦点

Onkeydown 字段拥有焦点并且有键按下

onkeypress 字段拥有焦点并且有键按下并释放

Onkeyup 字段拥有焦点并且有键被释放

onmousedown 鼠标指针指向字段并且点击

onmousemove 鼠标指针指向字段并且在字段内移动

onmouseout 鼠标指针指向控件,但是指针在元素外围移动

onmouseover 鼠标指针没有指向字段,但是指针在元素内部移动

Onmouseup 鼠标指针指向字段,并且释放了鼠标按键

<form>元素中能够被定义的其他一般属性有:

属性 描述

Accesskey 定义访问输入字段的快捷键

Style 定义输入字段的样式

styleClass 定义输入字段的样式表类

Tabindex 输入字段的tab顺序

表单标记

<html:form>标记用来显示HTML标记,可以指定AcitonForm bean的名称和它的类名。如果没有设置这些属性,就需要有配置文件来指定ActionMapping以表明当前输入的是哪个JSP页,以及从映射中检索的bean名和类。如果在ActionMapping指定的作用域中没有找到指定的名称,就会创建并存储一个新的bean,否则将使用找到的bean<form>标记能够包含与各种HTML输入字段相对应的子标记。

<html:form>标记属性如下:

属性 描述

Action 与表单相关的操作。在配置中,这个操作也用来标识与表单相关的ActionForm bean

Enctype 表单HTTP方法的编码类型

Focus 表单中需要初始化焦点的字段

Method 表单使用的HTTP方法

Name 与表单相关的ActionForm bean的名称。如果没有设置这个属性,bean的名称将会从配置信息中获得

Onreset 表单复位时的JavaScript事件句柄

Onsubmit 表单提交时的JavaScript事件句柄

Scope 搜索ActionForm bean的范围。如果没有设置,将从配置文件中获取

Style 使用的格式

styleClass 这个元素的格式表类

Type ActionForm bean的完整名称。如果没有设置,将从配置文件获得

例如:

<html:form action=”validateEmploee.do” method=”post”>

</html:form>

与表单相关的操作路径是validateEmployee,而表单数据是通过POST传递的。对于这个表单来说,ActionForm bean的其他信息,如bean名称类型,作用域,都是从表单指定操作的ActionMapping中检索得到的:

<form-beans>

<form-bean name=”empForm” type=”com.example.EmployeeForm” />

</form-beans>

<action-mappings>

<action path=”/validateEmployee”

type=”com.example.ValidateExampleAction” name=”empForm” scope=”request”

input=”/employeeInput.jsp”>

<forward name=”success” path=”/employeeOutput.jsp”>

</action>

</action-mapping>

如果配置文件中包含上述信息,并且请求URI*.do被映射到ActionServlet,与表单相关的ActionForm bean的名称,类型和作用域分别是empForm,com.example.EmployeeFormrequest.这些属性也可以使用<html:form>标记属性进行显示的定义。

以下标记必须嵌套在<html:form>标记里

按钮和取消标记

<html:button>标记显示一个按钮控件;<html:cancel>标记显示一个取消按钮。属性如下:

属性 描述

Property 定义在表单被提交时返回到服务器的请求参数的名称

Value 按钮上的标记

复位和提交标记

<html:reset><html:submit>标记分别能够显示HTML复位按钮和提交按钮。

文本和文本区标记

<html:text><html:textarea>标记分别HTML文本框和文本区,属性如下:

属性 描述

Property 定义当表单被提交时送回到服务器的请求参数的名称,或用来确定文本元素当前值的bean的属性名称

Name 属性被查询的bean的名称,它决定了文本框和文本区的值。如果没有设置,将使用与这个内嵌表单相关的ActionForm的名称

<html:text>标记还有以下属性:

属性 描述

Maxlength 能够输入的最大字符数

Size 文本框的大小(字符数)

<html:textarea>标记特有的属性如下:

属性 描述

Rows 文本区的行数

Cols 文本区的列数

检查框和复选框标记

<html:checkbox>标记能够显示检查框控件。<html:multibox>标记能够显示HTML复选框控件,请求对象在传递检查框名称时使用的getParameterValues()调用将返回一个字符串数组。属性如下:

属性 描述

Name Bean的名称,其属性会被用来确定检查是否以选中的状态显示。如果没有设置,将使用与这个内嵌表单相关的ActionFrom bean的名称

Property 检查框的名称,也是决定检查框是否以选中的状态显示的bean属性名称。在复选框的情况下,这个属性必须是一个数组

Value 当检查框被选中时返回到服务器的请求参数的值

例如:<html:checkbox property=”married” value=”Y”/>

一个名为married的检查框,在表单提交时会返回一个”Y”.

文件标记

<html:file>标记可以显示HTML文件控件。属性如下:

属性 描述

Name Bean的名称,它的属性将确定文件控件中显示的内容。如果没设置,将使用与内嵌表单相关的ActionForm bean的名称

property 这个属性定义了当表单被提交时送回到服务器的请求参数的名称,以及用来确定文件控件中显示内容的bean属性名称

Accept 服务器能够处理的内容类型集。它也将对客户浏览器对话框中的可选文件类型进行过滤

Value 按钮上的标记,这个按钮能够在本地文件系统中浏览文件

单选钮标记

<html:radio>标记用来显示HTML单选钮控件,属性如下:

属性 描述

Name Bean的名称,其属性会被用来确定单选钮是否以选中的状态显示。如果没有设置,将使用与这个内嵌表单相关的ActionFrom bean的名称

property 当表单被提交时送回到服务器的请求参数的名称,以及用来确定单选钮是否以被选中状态进行显示的bean属性的名称

Value 当单选钮被选中时返回到服务器的值

隐藏标记

<html:hidden>标记能够显示HTML隐藏输入元素,属性如下:

属性 描述

Name Bean的名称,其属性会被用来确定隐藏元素的当前值。如果没有设置,将使用与这个内嵌表单相关的ActionFrom bean的名称

property 定义了当表单被提交时送回到服务器的请求参数的名称,以及用来确定隐藏元素当前值的bean属性的名称

Value 用来初始化隐藏输入元素的值

密码标记

<html:password>标记能够显示HTML密码控件,属性如下:

属性 描述

maxlength 能够输入的最大字符数

Name Bean的名称,它的属性将用来确定密码元素的当前值。如果没有设置,将使用与这个内嵌表单相关的ActionFrom bean的名称

property 定义了当表单被提交时送回到服务器的请求参数的名称,以及用来确定密码元素当前值的bean属性的名称

redisplay 在显示这个字段时,如果相应的bean属性已经被设置了数据,这个属性决定了是否显示密码的内容

Size 字段的大小

选择标记

<html:select>标记能够显示HTML选择控件,属性如下:

属性 描述

multiple 表明这个选择控件是否允许进行多选

Name Bean的名称,它的属性确定了哪个。如果没有设置,将使用与这个内嵌表单相关的ActionFrom bean的名称

property 定义了当表单被提交时送回到服务器的请求参数的名称,以及用来确定哪个选项需要被选中的bean属性的名称

Size 能够同时显示的选项数目

Value 用来表明需要被选中的选项

选项标记(这个元素需要嵌套在<html:select>标记里)

<html:option>标记用来显示HTML选项元素集合,属性如下:

属性 描述

collection Bean集合的名称,这个集合存储在某个作用域的属性中。选项的数目与集合中元素的数目相同。Property属性能够定义选项值所使用的bean属性,而labelProperty属性定义选项标记所使用的bean的属性

labelName 用来指定存储于某个作用域的bean,这个bean是一个字符串的集合,能够定义<html:option>元素的标记(如果标志与值不相同)

labelProperty 与collection属性共同使用时,用来定义了存储于某个作用域的bean,这个bean将返回一个字符串集合,能够用来写入<html:option>元素的value属性

Name 如果这是唯一被指定的属性,它就定义了存储于某个作用域的bean,这个bean将返回一个字符串集合,能够用来写入<html:option>元素的value属性

property 这个属性在与collection属性共同使用时,定义了每个要显示选项值的独立beanname属性。如果不是与collection属性共同使用,这个属性定义了由name属性指定的bean的属性名称(如果有name属性),或是定义了一个ActionForm bean,这个bean将返回一个集合来写入选项的值

我们看一下这个标记的一些例子:

<html:option collection=”optionCollection” property=”optionValue” labelProperty=”optionLabel”/>

标记假设在某个作用域中有一个名为optionCollection的集合,它包含了一些具有optionValue属性的独立的bean,每个属性将作为一个选项的值。每个选项的标志由beanoptionLabel属性属性进行定义。

<html:option name=”optionValues” labelName=”optionLabels”/>

标记中optionValues代表一个存储在某个作用域中的bean,它是一个字符串集合,能够用来写入选项的值,而optionLabels代表一个存储在某个作用域中的bean,它也是一个字符串集合,能够用来写入选项的标志。

显示错误信息的标记

<html:errors>标记能够与ActionErrors结合在一起来显示错误信息。这个标记首先要从当前区域的资源文件中读取消息关键字errors.header,然后显示消息的文本。接下去它会在ActionErrors对象(通常作为请求参数而存储在Action.ERROR_KEY关键字下)中循环,读取单个ActionError对象的消息关键字,从当前区域的资源文件中读取并格式化相应的消息,并且显示它们。然后它读取与errors.footer关键字相对应的消息并且显示出来。

通过定义property属性能够过滤要显示的消息,这个属性的值应该与ActionErrors对象中存储ActionError对象的关键字对应。属性如下:

属性 描述

Bundle 表示应用程序作用域属性的名称,它包含着消息资源,其默认值Acion.MESSAGE_KEY

Locale 表示会话作用域属性的名称,它存储着用户当前登录的区域信息。其默认值是Action.ERROR_KEY

Name 表示请求属性的名称,它存储着ActionErrors对象。其默认值是Action.ERROR_KEY

property 这个属性指定了ActionErrors对象中存储每个独立ActionError对象的关键字,它可以过滤消息

例子:

<html:errors/>

显示集合中所有的错误。

<html:errors property=”missing.name”/>

显示存储在missing.name关键字的错误。

其他HTML标记

struts HTML标记还定义了下列标记来显示其他HTML元素:

? <html:html> : 显示HTML元素

? <html:img> : 显示图象标记

? <html:link> : 显示HTML链接或锚点

? <html:rewrite> : 创建没有锚点标记的URI

这些标记的详细内容请参照struts文档。

模板标记

动态模板是模块化WEB页布局设计的强大手段。Struts模板标记库定义了自定义标记来实现动态模板。

插入标记

<template:insert>标记能够在应用程序的JSP页中插入动态模板。这个标记只有一个template属性,用来定义模板JSP页。要插入到模板的页是有多个<template:put>标记来指定的,而这些标记被定义为<template:insert>标记的主体内容。

放置标记

<template:put>标记是<template:insert>标记内部使用的,用来指定插入到模板的资源。属性如下:

属性 描述

content 定义要插入的内容,比如一个JSP文件或一个HTML文件

direct 如果这个设置为true,由content属性指定的内容将直接显示在JSP上而不是作为包含文件

Name 要插入的内容的名称

Role 如果设置了这个属性,只有在当前合法用户具有特定角色时才能进行内容的插入

获得标记

在模板JSP页中使用<template:get>标记能够检索由<template:put>标记插入到JSP页的资源。属性如下:

属性 描述

Name 由<template:put>标记插入的内容的名称

Role 如果设置了这个属性,只有在当前合法用户具有特定角色时才能进行内容的检索

使用模板标记

首先编写一个模板JSP页,它将被所有的web页使用:

<html>

<%@ taglib uri=”/template” prefix=”template” %>

<head>

<title></title>

</head>

<body>

<table width=”100%” height=”100%”>

<tr height=”10%”>

<td>

<template:get name=”header” />

</td>

</tr>

<tr height=”80%”>

<td>

<template:get name=”content” />

</td>

</tr>

<tr height=”10%”>

<td>

<template:get name=”footer” />

</td>

</tr>

</table>

</body>

</html>

我们将这个文件命名为template.jsp。这个文件使用<template:get>标记来获得由JSP页使用<template:put>标记提供的内容,并且将内容在一个HTML表格中显示出来。这三个内容是标题,内容和页脚。典型的内容JSP会是这样:

<%@ taglib uri=”/template” prefix=”/template”%>

<template:insert template=”template.jsp”>

<template:put name=”header” content=”header.html”/>

<template:put name=”content” content=”employeeList.jsp”/>

<template:put name=”footer” content=”footer.html”/>

</template:insert>

这个应用程序JSP页使用<template:insert标记来定义模板,然后使用<template:put>标记将特定内容名称指定的资源放到模板JSP页中。如果我们有上百个布局相同的页,但突然想改变这个模板,我们只需要改变template.jsp文件。

内容总结

? 能够熟练使用Bean标记

? 能够熟练使用HTML标记

? 能够熟练使用逻辑标记

? 能够熟练使用模板标记

? 能够把标签运用到项目里

独立实践

? 修改前两章的练习,在页面里统一换成标签的处理

? 做一个struts登录,连接数据库进行验证,数据库中有一张信息表P_InfoP_id,p_password,p_name,p_age,p_sex,p_addr

? 登录成功后能做注册,修改,和显示信息的功能

? 要求用一个Action类进行控制转发

? 用标签完成一个表的分页显示功能

第三十四章:Hibernate基础

学习目标

? 理解ORM机制

? 理解Hibernate的工作原理

? Hibernate的配置和对象-映射文件

? 理解对象持久化

Hibernate简介

HibernateJava应用和关系数据库之间的桥梁,它负责Java 对象关系数据之间的映射。Hibernate 内部封装了通过 JDBC 访问数据库的操作,向上层应用提供了面向对象的数据访问API。在Java 应用中使用Hibernate包含以下步骤。

1) 创建Hibernate的配置文件。

2) 创建持久化类。

3) 创建对象-关系映射文件。

4) 通过Hibernate API编写访问数据库的代码。

建立简单的Hibernate应用

本章通过一个简单的例子 customerApp 应用,演示如何运用 Hibernate 来访问关系数据库。customerApp 应用的功能非常简单:通过 Hibernate 保存、更新、删除、加载以及查询 Customer对象。

创建 Hibernate的配置文件

Hibernate 从其配置文件中读取和数据库连接有关的信息,这个配置文件应该位于应用的 classpath 中。Hibernate 的配置文件有两种形式:一种是 XML 格式的文件;还有一种是Java 属性文件,采用=的形式。 下面介绍如何以Java 属性文件的格式来创建 Hibernate的配置文件。这种配置文件的默认文件名为hibernate.properties

hibernate.properties的内容如下:

hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect

hibernate.connection.driver_class=com.mysql.jdbc.Driver

hibernate.connection.url=jdbc:mysql://localhost:3306/SAMPLEB

hibernate.connection.username=root

hibernate.connection.password=1234

hibernate.show_sql=true

以上hibernate.properties文件包含了一系列属性及其属性值,Hibernate将根据这些属性来连接数据库,本例为连接 MySQL 数据库的配置代码。下表对以上 hibernate.properties文件中的所有属性做了描述。

属性 描述

hibernate.dialect SQL指定数据库使用的方言

hibernate.connection.driver_class 指定数据库的驱动程序

hibernate.connection.url URL指定连接数据库的

hibernate.connection.username 指定连接数据库的用户名

hibernate.connection.password 指定连接数据库的口令

hibernate.show_sql 如果为true,表示在程序运行时,会在控制台输出SQL语句,这有利于跟踪Hibernate的运行状态。默认为false。在应用开发和测试阶段,可以把这个属性设为true,以便跟踪和调试应用程序,在应用发布阶段,应该把这个属性设为false,以便减少应用的输出信息,提高运行性能。

Hibernate能够访问多种关系数据库,如MySQLOracleSybase等。尽管多数关系数据库都支持标准的SQL语言,但是它们往往还有各自的 SQL方言,就象不同地区的人既能说标准的普通话,还能讲各自的方言一样。hibernate.dialect属性用于指定被访问数据库使用的 SQL 方言,当 Hibernate 生成 SQL 查询语句,或者使用 native 对象标识符生成策略时,都会参考本地数据库的SQL方言。

创建持久化类

持久化类是指其实例需要被 Hibernate 持久化到数据库中的类。持久化类通常都是域模型中的实体域类。持久化类符合JavaBean的规范,包含一些属性,以及与之对应的 getXXX()setXXX()方法。以下定义了一个名为 Customer 的持久化类。

package com.itjob.jiaowu.hibernate;

import java.io.Serializable;

import java.sql.Date;

import java.sql.Timestamp;

public class Customer implements Serializable{

private Long id;

private String name;

private String email;

private String password;

private int phone;

private boolean married;

private String address;

private char sex;

private String description;

private byte[] image;

private Date birthday;

private Timestamp registeredTime;

public Customer(){}

public Long getId(){

return id;

}

public void setId(Long id){

this.id = id;

}

public String getName(){

return name;

}

public void setName(String name){

this.name=name;

}

//此处省略emailpasswordphone等属性的getXXX()setXXX()方法

……

}

持久化类符合JavaBean的规范,包含一些属性,以及与之对应的getXXX()setXXX()方法。getXXX()setXXX()方法必须符合特定的命名规则,“get”“set”后面紧跟属性的名字,并且属性名的首字母为大写,例如 name 属性的 get 方法为 getName(),如果把 get

方法写为getname()或者getNAME(),会导致 Hibernate在运行时抛出以下异常:

net.sf.hibernate.PropertyNotFoundException: Could not find a getter for property name in class com.itjob.jiaowu.hibernate.Customer

如果持久化类的属性为boolean类型,那么它的 get方法名既可以用“get”作为前缀,也可以用“is”作为前缀。例如 Customer 类的 married 属性为 boolean 类型,因此以下两种get方法是等价的:

public boolean isMarried(){

return married;

}

或者

public boolean getMarried(){

return married;

}

Hibernate 并不要求持久化类必须实现 java.io.Serializable 接口,但是对于采用分布式结构的 Java 应用,当 Java 对象在不同的进程节点之间传输时,这个对象所属的类必须实现Serializable接口,此外,在Java Web应用中,如果希望对 HttpSession中存放的 Java 对象进行持久化,那么这个Java 对象所属的类也必须实现Serializable接口。

Customer 持久化类有一个id属性,用来惟一标识Customer 类的每个对象。在面向对象术语中,这个 id 属性被称为对象标识符(OIDObject Identifier),通常它都用整数表示,当然也可以设为其他类型。如果 customerA.getId().equals(customerB.getId())的结果是 true,就表示 customerA customerB 对象指的是同一个客户,它们和 CUSTOMERS 表中的同一条记录对应。 Hibernate 要求持久化类必须提供一个不带参数的默认构造方法,在程序运行时,Hibernate运用Java 反射机制,调用java.lang.reflect.Constructor.newInstance()方法来构造持久化类的实例。如果对这个持久化类使用延迟检索策略,为了使 Hibernate 能够在运行时为这个持久化类创建动态代理,要求持久化类的默认构造方法的访问级别必须是 public protected 类型,而不能是 default private 类型。在Customer类中没有引入任何Hibernate APICustomer类不需要继承Hibernate的类,或实现Hibernate的接口,这提高了持久化类的独立性。如果日后要改用其他的ORM产品,比如由Hibernate改为OJB,不需要修改持久化类的代码。

创建数据库 Schema

在本例中,与 Customer 类对应的数据库表名为 CUSTOMERS,它在 MySQL 数据库中的DDL定义如下:

create table CUSTOMERS(

ID bigint not null primary key,

NAME varchar(15) not null,

EMAIL varchar(128) not null,

PASSWORD varchar(8) not null,

PHONE int ,

ADDRESS varchar(255),

SEX char(1) ,

IS_MARRIED bit,

DESCRIPTION text,

IMAGE blob,

BIRTHDAY date,

REGISTERED_TIME timestamp

);

CUSTOMERS 表有一个 ID 字段,它是表的主键,它和 Customer 类的 id 属性对应。CUSTOMERS表中的字段使用了各种各样的 SQL类型,参见下表。

字段名 SQL类型 说明

ID BIGINT 整数,占8字节,取值范围为:-2^63 ~ 2^63-1

NAME VARCHAR 变长字符串,占0 ~ 255个字节

SEX CHAR 定长字符串,占 0 ~ 255个字节

IS_MARRIED BIT 布尔类型

DESCRIPTION TEXT 长文本数据,占 0 ~ 65535 255字节。如果字符串长度小于255 ,可以用VARCHARCHAR类型来表示。如果字符串长度大于255 ,可以定义为TEXT类型。

IMAGE BLOB 二进制长数据,占 0 ~ 65535字节,BLOBBinary Large Object的缩写。IMAGE在本例中, 字段用来存放图片数据

BIRTHDAY DATE 代表日期,格式为“YYYY-MM-DD”

REGISTERED_TIME TIMESTAMP 代表日期和时间,格式为“YYYYMMDDHHMMSS ”

创建对象-关系映射文件

Hibernate采用XML格式的文件来指定对象和关系数据之间的映射。在运行时,Hibernate将根据这个映射文件来生成各种 SQL 语句。在本例中,将创建一个名为 Customer.hbm.xml的文件,它用于把 Customer 类映射到 CUSTOMERS 表,这个文件应该和 Customer.class 文件存放在同一个目录下。以下为Customer.hbm.xml文件的源代码。

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd

">

<hibernate-mapping>

<class name="mypack.Customer" table="CUSTOMERS">

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

<property name="name" column="NAME" type="string" not-null="true"/>

<property name="email" column="EMAIL" type="string" not-null="true" />

<property name="password" column="PASSWORD" type="string" not-null="true"/>

<property name="phone" column="PHONE" type="int" />

<property name="address" column="ADDRESS" type="string" />

<property name="sex" column="SEX" type="character"/>

<property name="married" column="IS_MARRIED" type="boolean"/>

<property name="description" column="DESCRIPTION" type="text"/>

<property name="image" column="IMAGE" type="binary"/>

<property name="birthday" column="BIRTHDAY" type="date"/>

<property name="registeredTime" column="REGISTERED_TIME" type="timestamp"/>

</class>

</hibernate-mapping>

映射文件的文档类型定义(DTD

Customer.hbm.xml文件的开头声明了 DTDDocument Type Definition, 文档类型定义),它对XML文件的语法和格式作了定义。HibernateXML解析器将根据DTD来核对XML文件的语法。 每一种 XML 文件都有独自的 DTD 文件。Hibernate 的对象-关系映射文件使用的 DTD文件的下载网址为:http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd

。此外,在Hibernate软件包的src\net\sf\hibernate目录下也提供了 hibernate-mapping-2.0.dtd文件。在这个文件中,描述顶层元素<hibernate-mapping>的代码如下:

<!ELEMENT hibernate-mapping (meta*, import*, (class|subclass|joined-subclass)*, query*, sql-query*)>

描述顶层元素<hibernate-mapping>的子元素<class>的代码如下:

<!ELEMENT class (

meta*,

(cache|jcs-cache)?,

(id|composite-id),

discriminator?,

(version|timestamp)?,

(property|many-to-one|one-to-one|component|dynamic-component|any|map|set|list|bag|idbag|array

|primitive-array)*,

((subclass*)|(joined-subclass*))

)>

<hibernate-mapping>元素是对象-关系映射文件的根元素,其他元素(即以上DTD 代码中括号以内的元素,如<class>子元素)必须嵌入在<hibernate-mapping>元素以内。在<class>元素中又嵌套了好多子元素。在以上DTD 代码中,还使用了一系列的特殊符号来修饰元素,下表描述了这些符号的作用。在创建自己的对象-关系映射文件时,如果不熟悉某种元素的语法,可以参考DTD 文件。

DTD中特殊符号的作用

符号 含义

无符号 该子元素在父元素内必须存在且只能存在一次。

+ 该子元素在父元素内必须存在,可以存在一次或者多次。

* 该子元素在父元素内可以不存在,或者存在一次或者多次。它是比较常用的符号。

? 该子元素在父元素内可以不存在,或者只存在一次。它是比较常用的符号。

2-3 可以看出,在<hibernate-mapping>元素中,<meta><import><class><query>等子元素可以不存在,或者存在一次或者多次;在<class>元素中,<id>子元素必须存在且只能存在一次,<property>元素可以不存在,或者存在一次或者多次。 此外,在映射文件中,父元素中的各种子元素的定义必须符合特定的顺序。例如根据<class>元素的DTD 可以看出,必须先定义<id>子元素,再定义<property>子元素,以下映射代码颠倒了<id><property>子元素的位置:

<class name="mypack.Customer" table="CUSTOMERS">

<property name="name" column="NAME" type="string" not-null="true" />

<property name="email" column="EMAIL" type="string" not-null="true" />

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

……

</class>

HibernateXML解析器在运行时会抛出 MappingException

[java] 21:27:51,610 ERROR XMLHelper:48 - Error parsing XML:

XML InputStream (24) The content of element type "class" must match "(meta*,(cache|jcs-cache)?,(id|composite-id),discriminator?,(version|timestamp)?,(property|many-to-one|one-to-one|component|dynamic-component|any|map|set|list|bag|idbag|array|primitive-array)*,(subclass*|joined-subclass*))".[java] net.sf.hibernate.MappingException: Error reading resource: mypack/Customer.hbm.xml

at net.sf.hibernate.cfg.Configuration.addClass(Configuration.java:357)

Customer持久化类映射到CUSTOMERS

Customer.hbm.xml文件用于映射 Customer 类。如果需要映射多个持久化类,那么既可以在同一个映射文件中映射所有类,也可以为每个类创建单独的映射文件,映射文件和类同名,扩展名为“hbm.xml”。后一种做法更值得推荐,因为在团队开发中,这有利于管理和维护映射文件。 <class>元素指定类和表的映射,它的name属性设定类名,table属性设定表名。以下代码表明和Customer 类对应的表为CUSTOMERS表:

<class name="mypack.Customer" table="CUSTOMERS">

如果没有设置<class>元素的table属性,Hibernate将直接以类名作为表名,也就是说,默认情况下,与mypack.Customer 类对应的表为Customer 表。 <class>元素包含一个<id>子元素以及多个<property>子元素。<id>子元素设定持久化类的OID 和表的主键的映射。以下代码表明 Customer 类的 id属性和 CUSTOMERS表中的 ID字段对应。

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

<id>元素的<generator>子元素指定对象标识符生成器,它负责为 OID 生成惟一标识符。<property>子元素设定类的属性和表的字段的映射。<property>子元素主要包括 nametypecolumnnot-null属性。

1<property>元素的name属性

<property>元素的name属性指定持久化类的属性的名字。

2<property>元素的type属性

<property>元素的type属性指定 Hibernate映射类型。Hibernate映射类型是Java 类型与SQL 类型的桥梁。

3<property>元素的not-null属性

如果<property>元素的not-null属性为 true,表明不允许为 null,默认为 false。例如以下代码表明不允许Customer 类的name属性为 null

<property name="name" column="NAME" type="string" not-null="true" />

Hibernate在持久化一个 Customer 对象时,会先检查它的 name 属性是否为 null,如果为null,就会抛出以下异常:

net.sf.hibernate.PropertyValueException: not-null property references

a null or transient value: com.itjob.jiaowu.hibernate.Customer.name

如果数据库中CUSTOMERS表的 NAME 字段不允许为 null,但在映射文件中没有设置not-null属性:

<property name="name" column="NAME" type="string" />

那么Hibernate在持久化一个Customer 对象时,不会先检查它的 name属性是否为 null而是直接通过 JDBC API CUSTOMERS 表插入相应的数据,由于 CUSTOMERS 表的NAME 字段设置了not null约束,因此数据库会抛出错误:

708 ERROR JDBCExceptionReporter:58 - General error, message from server:

"Column 'NAME' cannot be null"

4<property>元素的column属性

<property>元素的 column 属性指定与类的属性映射的表的字段名。以下代码表明和address属性对应的字段为ADDRESS字段:

<property name="address" column= "ADDRESS" type="string"/>

如果没有设置< property >元素的 column属性,Hibernate将直接以类的属性名作为字段名,也就是说,默认情况下,与Customer 类的 address属性对应的字段为 address字段。 <property>元素还可以包括<column>子元素,它和<property>元素的 column属性一样,都可以设定与类的属性映射的表的字段名。以下两种设置方式是等价的:

<property name="address" column= "ADDRESS" type="string"/>

或者

<property name="address" type="string">

<column name="ADDRESS" />

</property>

<property>元素的<column>子元素比 column 属性提供更多的功能,它可以更加详细的描述表的字段。例如以下<column>子元素指定 CUSTOMERS 表中的 NAME 字段的 SQL 类型为varchar(15),不允许为null,并且为这个字段建立了索引:

<property name="name" type="string">

<column name="NAME" sql-type="varchar(15)" not-null="true" index="idx_name" />

</property>

通过 Hibernate API 操纵数据库

HibernateJDBC进行了封装,提供了更加面向对象的 API。以下两图对比了直接通过JDBC API以及通过Hibernate API来访问数据库的两种方式。

以下示例的BusinessService类演示了通过 Hibernate APICustomer 对象进行持久化的操作。

package com.itjob.jiaowu.hibernate;

import javax.servlet.*;

import net.sf.hibernate.*;

import net.sf.hibernate.cfg.Configuration;

import java.io.*;

import java.sql.Date;

import java.sql.Timestamp;

import java.util.*;

public class BusinessService{

public static SessionFactory sessionFactory;

/** 初始化Hibernate,创建SessionFactory实例 */

static{

try{

// 根据默认位置的Hibernate配置文件的配置信息,创建一个Configuration实例

Configuration config = new Configuration();

config.addClass(Customer.class);

// 创建SessionFactory实例 */

sessionFactory = config.buildSessionFactory();

}catch(Exception e){e.printStackTrace();}

}

/** 查询所有的Customer对象,然后调用printCustomer()方法打印Customer对象信息 */

public void findAllCustomers(ServletContext context,OutputStream out) throws Exception{…… }

/** 持久化一个Customer对象 *./

public void saveCustomer(Customer customer) throws Exception{…… }

/** 按照OID加载一个Customer对象,然后修改它的属性 */

public void loadAndUpdateCustomer(Long customer_id,String address) throws Exception{……}

/**删除所有的Customer对象 */

public void deleteAllCustomers() throws Exception{

Session session = sessionFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.delete("from Customer as c");

tx.commit();

}catch (Exception e) {

if (tx != null) {

tx.rollback();

}

throw e;

} finally {

session.close();

}

}

/** 选择向控制台还是动态网页输出Customer对象的信息 */

private void printCustomer(ServletContext context,OutputStream out,Customer customer)

throws Exception{

if(out instanceof ServletOutputStream)

printCustomer(context,(ServletOutputStream) out,customer);

else

printCustomer((PrintStream) out,customer);

}

/** Customer对象的信息输出到控制台,如DOS 控制台*/

private void printCustomer(PrintStream out,Customer customer)throws Exception{…… }

/** Customer对象的信息输出到动态网页 */

private void printCustomer(ServletContext context,ServletOutputStream out,Customer customer)

throws Exception{……}

public void test(ServletContext context,OutputStream out) throws Exception{

Customer customer=new Customer();

customer.setName("Tom");

");

customer.setEmail("tom@yahoo.com

customer.setPassword("1234");

customer.setPhone(55556666);

customer.setAddress("Shanghai");

customer.setSex('M');

customer.setDescription("I am very honest.");

//设置Customer对象的image属性,它是字节数组,存放photo.gif文件中的二进

//photo.gif文件和BusinessService.class文件位于同一个目录下

InputStream in=this.getClass().getResourceAsStream("photo.gif");

byte[] buffer = new byte[in.available()];

in.read(buffer);

customer.setImage(buffer);

//设置Customer对象的birthday属性,它是java.sql.Date类型

customer.setBirthday(Date.valueOf("1980-05-06"));

saveCustomer(customer);

findAllCustomers(context,out);

loadAndUpdateCustomer(customer.getId(),"Beijing");

findAllCustomers(context,out);

deleteAllCustomers();

}

public static void main(String args[]) throws Exception {

new BusinessService().test(null,System.out);

sessionFactory.close();

}

}

以上例子演示了通过Hibernate API访问数据库的一般流程。首先应该在应用的启动阶段对Hibernate进行初始化,然后就可以通过 HibernateSession接口来访问数据库。

Hibernate的初始化

BusinessService类的静态代码块负责Hibernate的初始化工作,如读取 Hibernate的配置信息以及对象-关系映射信息,最后创建 SessionFactory 实例。当 JVMJava 虚拟机)加载BusinessService类时,会执行该静态代码块。初始化过程包括如下步骤。

1)创建一个 Configuration类的实例,Configuration 类的构造方法把默认文件路径下的hibernate.properties配置文件中的配置信息读入到内存:

Configuration config = new Configuration();

2)调用Configuration类的addClass(Customer.class)方法:

config.addClass(Customer.class);

该方法把默认文件路径下的Customer.hbm.xml文件中的映射信息读入到内存中。

3)调用Configuration类的buildSessionFactory()方法:

sessionFactory = config.buildSessionFactory();

该方法创建一个SessionFactory实例,并把 Configuration对象包含的所有配置信息拷贝到SessionFactory对象的缓存中。SessionFactory代表一个数据库存储源,如果应用只有一个数据库存储源,那么只需创建一个 SessionFactory 实例。当 SessionFactory 对象创建后,该对象不和 Configuration 对象关联。因此如果再修改 Configuration 对象包含的配置信息,不会对SessionFactory对象有任何影响。

访问HibernateSession接口

初始化过程结束后,就可以调用 SessionFactory实例的openSession()方法来获得 Session实例,然后通过它执行访问数据库的操作。Session接口提供了操纵数据库的各种方法,如:

save()方法:把Java 对象保存数据库中。

update()方法:更新数据库中的Java 对象。

delete()方法:把Java 对象从数据库中删除。

load()方法:从数据库中加载Java 对象。

find()方法:从数据库中查询Java 对象。

Session是一个轻量级对象。通常将每一个Session实例和一个数据库事务绑定,也就是说,每执行一个数据库事务,都应该先创建一个新的Session实例。如果事务执行中出现异常,应该撤销事务。不论事务执行成功与否,最后都应该调用 Sessionclose()方法,从而

释放Session实例占用的资源。以下代码演示了用Session来执行事务的流程,其中Transaction类用来控制事务。

Session session = factory.openSession();

Transaction tx;

try {

//开始一个事务

tx = session.beginTransaction();

//执行事务

...

//提交事务

tx.commit();

}

catch (Exception e) {

//如果出现异常,就撤销事务

if (tx!=null) tx.rollback();

throw e;

}

finally {

//不管事务执行成功与否,最后都关闭Session

session.close();

}

下图为正常执行数据库事务(即没有发生异常)的时序图。

BusinessService 类提供了保存、删除、查询和更新 Customer 对象的各种方法。BusinessService类的main()方法调用test()方法,test()方法又调用以下方法:

1saveCustomer()方法

该方法调用Sessionsave()方法,把 Customer 对象持久化到数据库中。

tx = session.beginTransaction();

session.save(customer);

tx.commit();

当运行session.save()方法时,Hibernate执行以下 SQL语句:

insert into CUSTOMERS (ID, NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX,

IS_MARRIED,DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME)

','1234',55556666,'Shanghai','M',0,'I am very honest.',

values(1,'Tom','tom@yahoo.com

?,'1980-05-06',null)

test()方法中并没有设置Customer 对象的 id属性,Hibernate会根据映射文件的配置,采用increment标识符生成器自动以递增的方式为 OID 赋值。在 Customer.hbm.xml文件中相

关的映射代码如下:

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

test()方法中也没有设置Customer 对象的 registeredTime属性,因此在以上 insert语句中,REGISTERED_TIME 字段的值为 null。但由于 REGISTERED_TIME 字段的 SQL 类型为 TIMESTAMP 类型,如果 insert 语句没有为 TIMESTAMP 类型的字段赋值,底层数据库会自动把当前的系统时间赋值给TIMESTAMP类型的字段。因此,执行完以上 insert语句后,REGISTERED_TIME 字段的值并不为 null,而是插入该记录时的系统时间。

2findAllCustomers()方法

该方法调用Sessionfind()方法,查询所有的 Customer 对象。

tx = session.beginTransaction();

List customers=session.find("from Customer as c order by c.name asc");

for (Iterator it = customers.iterator(); it.hasNext();) {

printCustomer(context,out,(Customer) it.next());

}

tx.commit();

Sessionfind()方法有好几种重载形式,本例中传递的是字符串参数“from Customer as c order by c.name asc”,它使用的是Hibernate查询语言。运行session.find()方法时, Hibernate执行以下SQL语句:

select * from CUSTOMERS order by NAME asc;

3loadAndUpdateCustomer ()方法

该方法调用Sessionload()方法,加载 Customer 对象,然后再修改 Customer 对象的属性。

tx = session.beginTransaction();

Customer c=(Customer)session.load(Customer.class,customer_id);

c.setAddress(address);

tx.commit();

以上代码先调用Sessionload()方法,它按照参数指定的 OID 从数据库中检索出匹配的Customer 对象,Hibernate会执行以下 SQL语句:

select * from CUSTOMERS where ID=1;

loadAndUpdateCustomer()方法接着修改 Customer 对象的 address属性。那么,Hibernate会不会同步更新数据库中相应的CUSTOMERS表的记录呢?答案是肯定的。Hibernate采用脏检查机制,按照内存中的Customer 对象的状态的变化,来同步更新数据库中相关的数据,

Hibernate会执行以下SQL语句:

update CUSTOMERS set NAME="Tom",EMAIL="Tom@yahoo.com

"…ADDRESS="Beijing"…

尽管只有Customer 对象的address属性发生了变化,但是 Hibernate执行的 update语句中会包含所有的字段。 在BusinessService类的test()方法中按如下方式调用 loadAndUpdateCustomer ()方法:

saveCustomer(customer);

loadAndUpdateCustomer(customer.getId(),"Beijing");

以上代码并没有直接给customer 对象的 id属性赋值,当执行 saveCustomer(customer)方法时,Sessionsave()方法把customer 对象持久化到数据库中,并自动为 id属性赋值。

4. printCustomer()方法

该方法打印 Customer 对象的信息,它有三种重载形式。当 helloapp 应用作为独立应用程序运行时,将调用printCustomer(PrintStream out,Customer customer)方法,向控制台输出Customer 信息; 当 customerApp 应用作为Java Web应用运行时,将调用

printCustomer(ServletContext context,ServletOuputStream out,Customer customer)方法向Web客户输出Customer信息:

private void printCustomer(ServletContext context,ServletOutputStream out,

Customer customer)throws Exception{

//Customer对象的image属性包含的二进制图片数据保存到photo_copy.gif文件中

byte[] buffer=customer.getImage();

String path=context.getRealPath("/");

FileOutputStream fout=new FileOutputStream(path+"photo_copy.gif");

fout.write(buffer);

fout.close();

out.println("------以下是"+customer.getName()+"的个人信息------"+"<br>");

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

out.println("口令: "+customer.getPassword()+"<br>");

out.println("E-Mail: "+customer.getEmail()+"<br>");

out.println("电话: "+customer.getPhone()+"<br>");

out.println("地址: "+customer.getAddress()+"<br>");

String sex=customer.getSex()=='M'? "":"";

out.println("性别: "+sex+"<br>");

String marriedStatus=customer.isMarried()? "已婚":"未婚";

out.println("婚姻状况: "+marriedStatus+"<br>");

out.println("生日: "+customer.getBirthday()+"<br>");

out.println("注册时间: "+customer.getRegisteredTime()+"<br>");

out.println("自我介绍: "+customer.getDescription().substring(0,25)+"<br>");

out.println("<img src='photo_copy.gif' border=0><p>"); //显示photo_copy.gif图片

}

5deleteAllCustomers()方法

该方法调用Sessiondelete()方法,删除所有的 Customer 对象:

tx = session.beginTransaction();

session.delete("from Customer as c");

tx.commit();

Sessiondelete()方法有好几种重载形式,本例向delete()方法提供了字符串参数“from Customer as c”,它使用的是Hibernate查询语言(HQLHibernate Query Language)。HQL是一种面向对象的语言,“from Customer as c”字符串指定的是 Customer 类的名字,而非

CUSTOMERS表的名字,其中“as c”表示为 Customer 类赋予别名“c”

运行session.delete()方法时,Hibernate先执行 select语句,查询 CUSTOMERS表的所有Customer 对象:

select * from CUSTOMERS;

接下来Hibernate根据Customer 对象的 OID,依次删除每个对象:

delete from CUSTOMERS where ID=1;

Hibernate工作原理图

内容总结

? 创建Hibernate的配置文件

? 创建Hibernate的对象-关系映射文件

? 在应用程序中通过Hibernate API来访问数据库

? 掌握存储二进制大数据以及长文本数据的技巧

? 创建持久化类

独立实践

? 使用Hibernate完成用户注册的功能

? 使用Hibernate完成用户登陆的功能

? 使用Hibernate完成用户信息修改的功能

? 使用Hibernate完成用户信息删除的功能

? 结合Struts,Hibernate完成用户注册,登陆,修改和删除等功能

第三十五章: 映射继承关系

学习目标

? 进一步理解Hibernate的运行原理

? 在Hibernate实现域模型中的各种关系

? 在Hibernate中实现复杂的多对一关联

域模型关系

在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,在下图的域模型中,Company类和Employee类之间为一对多的双向关联关系(假定不允许雇员同时在多个公司兼职),Employee类为抽象类,因此它不能被实例化,它有两个具体的子类HourlyEmployee类和SalariedEmployee类。由于Java只允许一个类最多有一个直接的父类因此Employee类、HourlyEmployee类和SalariedEmployee类构成了一颗继承关系树。

包含继承关系的域模型

在面向对象的范畴中,还存在多态的概念,多态建立在继承关系的基础上。简单的理解,多态是指当一个 Java 应用变量被声明为 Employee 类时,这个变量实际上既可以引用HourlyEmployee类的实例,也可以引用 SalariedEmployee类的实例。以下这段程序代码就体

现了多态:

List employees= businessService.findAllEmployees();

Iterator it=employees.iterator();

while(it.hasNext()){

Employee e=(Employee)it.next();

if(e instanceof HourlyEmployee){

System.out.println(e.getName()+" "+((HourlyEmployee)e).getRate());

}else

System.out.println(e.getName()+" "+((SalariedEmployee)e).getSalary());

}

BusinessService类的findAllEmployees()方法通过Hibernate API从数据库中检索出所有Employee对象。findAllEmployees()方法返回的集和既包含 HourlyEmployee类的实例,也包含 SalariedEmployee 类的实例,这种查询被称为多态查询。以上程序中变量 e 被声明为Employee 类型,它实际上既可能引用 HourlyEmployee 类的实例,也可能引用SalariedEmployee类的实例。

此外,从 Company 类到 Employee 类为多态关联,因为 Company 类的 employees 集合中可以包含 HourlyEmployee 类和 SalariedEmployee 类的实例。从 Employee 类到 Company类不是多态关联,因为Employee类的company属性只会引用 Company类本身的实例。

数据库表之间并不存在继承关系,那么如何把域模型的继承关系映射到关系数据模型中呢?本章将介绍以下三种映射方式:

? 继承关系树的每个具体类对应一个表:关系数据模型完全不支持域模型中的继承关系和多态。

? 继承关系树的根类对应一个表:对关系数据模型进行非常规设计,在数据库表中加入额外的区分子类型的字段。通过这种方式,可以使关系数据模型支持继承关系和多态。

? 继承关系树的每个类对应一个表:在关系数据模型中用外键参照关系来表示继承关系。

继承关系树的每个具体类对应一个表

把每个具体类映射到一张表是最简单的映射方式。如上图所示,在关系数据模型中只需定义 COMPANIESHOURLY_EMPLOYEES SALARIED_EMPLOYEES 表。为了叙述的方便,下文把 HOURLY_EMPLOYEES 表简称为 HE 表,把 SALARIED_EMPLOYEES表简称为SE 表。HourlyEmployee类和 HE 表对应,HourlyEmployee类本身的 rate属性,以及从 Employee 类中继承的 id 属性和 name 属性,在 HE 表中都有对应的字段。此外,HourlyEmployee类继承了Employee类与Company类的关联关系,与此对应,在HE 表中定义了参照COMPANIES表的COMPANY_ID 外键。SalariedEmployee 类和 SE 表对应,SalariedEmployee 类本身的 salary 属性,以及从Employee 类中继承的 id 属性和 name 属性,在 SE 表中都有对应的字段。此外,SalariedEmployee 类继承了 Employee 类与 Company 类的关联关系,与此对应,在 SE 表中定义了参照COMPANIES表的COMPANY_ID 外键。

为每个具体类对应一个表

Company 类、HourlyEmployee 类和 SalariedEmployee 类都有相应的映射文件,而Employee类没有相应的映射文件。图14-3显示了持久化类、映射文件和数据库表之间的对应关系。

持久化类、映射文件和数据库表之间的对应关系

如果 Employee 类不是抽象类,即 Employee 类本身也能被实例化,那么还需要为Employee类创建对应的EMPLOYEES表,此时HE表和 SE 表的结构仍然和上图中的一样。这意味着在 EMPLOYEES 表、HE 表和 SE 表中都定义了相同的 NAME 字段以及参照COMPANIES 表的外键 COMPANY_ID。另外,还需为 Employee 类创建单独的Employee.hbm.xml文件。

创建映射文件

Company 类到 Employee 类是多态关联,但是由于关系数据模型没有描述 Employee类和它的两个子类的继承关系,因此无法映射 Company类的 employees集合。下面的示例是Company.hbm.xml文件的代码,该文件仅仅映射了 Company类的 idname属性。

<hibernate-mapping >

<class name="com.itjob.jiaowu.hibernate.HourlyEmployee"

table="HOURLY_EMPLOYEES">

<id name="id" type="long" column="ID">

<generator class="increment"/>

</id>

<property name="name" type="string" column="NAME" />

<property name="rate" column="RATE" type="double" />

<many-to-one

name="company"

column="COMPANY_ID"

class="mypack.Company"

/>

</class>

</hibernate-mapping>

SalariedEmployee.hbm.xml文件用于把 SalariedEmployee类映射到 SE 表,在这个映射文件中,除了需要映射 SalariedEmployee 类本身的 salary 属性,还需要映射从 Employee 类中继承的name属性,此外还要映射从 Employee类中继承的与Company类的关联关系。下面的示例是SalariedEmployee.hbm.xml文件的代码。

<hibernate-mapping >

<class name="mypack.SalariedEmployee

<id name="id" type="long" column="I

<generator class="increment"/>

</id>

<property name="name" type="string"

<property name="salary" column="SA

<many-to-one

name="company"

column="COMPANY_ID"

class="mypack.Company"

/>

</class>

</hibernate-mapping>

由于Employee类没有相应的映射文件,因此在初始化Hibernate时,只需向Configuration对象中加入Company类、HourlyEmployee类和 SalariedEmployee类:

Configuration config = new Configuration();

config.addClass(Company.class)

.addClass(HourlyEmployee.class)

.addClass(SalariedEmployee.class);

操纵持久化对象

这种映射方式支持多态查询,对于以下查询语句:

List employees=session.find("from Employee");

Hibernate会检索出所有HourlyEmployee对象和SalariedEmployee 对象。此外,也可以单独查询Employee类的两个子类的实例,例如:

List hourlyEmployees=session.find("from HourlyEmployee");

对于本例程序,在运行该程序前,需要在 SAMPLEDB 数据库中手工创建 COMPANIES 表、EMPLOYEES 表、HE 表和 SE 表,然后加入测试数据,相关的SQL脚本文件为/schema/sampledb.sql

DOS命令行下进入chapter14根目录,然后输入命令:

ant -file build3.xml run

就会运行 BusinessService 类。BusinessService main()方法调用 test()方法,test()方法依次调用以下方法:

? findAllHourlyEmployees():检索数据库中所有的 HourlyEmployee对象。

? findAllEmployees():检索数据库中所有的 Employee对象。

? loadCompany():加载一个Company对象。

? saveEmployee():保存一个Employee对象。

1.运行findAllHourlyEmployees()方法,它的代码如下:

tx = session.beginTransaction();

List results=session.find("from HourlyEmployee");

tx.commit();

return results;

在运行Sessionfind()方法时,Hibernate执行以下 select语句:

select * from HOURLY_EMPLOYEES he inner join EMPLOYEES e

on he.EMPLOYEE_ID=e.ID;

select * from COMPANIES where ID=1;

Hibernate通过HE表与EMPLOYEES表的内连接获得HourlyEmployee对象的所有属性值,此外,在加载HourlyEmployee对象时,还会同时加载与它关联的Company对象。

2.运行findAllEmployees()方法,它的代码如下:

tx = session.beginTransaction();

List results=session.find("from Employee");

tx.commit();

return results;

在运行Sessionfind()方法时,Hibernate执行以下 select语句:

select * from EMPLOYEES e

left outer join HOURLY_EMPLOYEES he on e.ID=he.EMPLOYEE_ID

left outer join SALARIED_EMPLOYEES se on e.ID=se.EMPLOYEE_ID;

select * from COMPANIES where ID=1;

Hibernate EMPLOYEES 表与 HE 表以及 SE 表进行左外连接,从而获得HourlyEmployee对象和SalariedEmployee对象的所有属性值。在这种映射方式下,Hibernate支持多态查询,对于以上查询语句获得的查询结果,如果 HE 表的 EMPLOYEE_ID 字段不为null,就创建HoulyEmployee实例,如果 SE 表的EMPLOYEE_ID 字段不为null,就创建SalariedEmployee实例,这些实例所关联的 Company对象也被加载。

3.运行loadCompany()方法,它的代码如下:

tx = session.beginTransaction();

Company company=(Company)session.load(Company.class,new Long(id));

Hibernate.initialize(company.getEmployees());

tx.commit();

这种映射方式支持多态关联。如果在 Company.hbm.xml文件中对employees集合设置了立即检索策略,那么 Sessionload()方法加载的 Company对象的 employees 集合中包含所有关联的Employee对象。由于本书提供的 Company.hbm.xml文件对 employees集合设置了

延迟检索策略,因此以上程序代码还通过 Hibernate 类的静态 initialize()方法来显式初始化employees集合。

4.运行saveEmployee(Employee employee)方法,它的代码如下:

tx = session.beginTransaction();

session.save(employee);

tx.commit();

test()方法中,创建了一个 HourlyEmployee 实例,然后调用 saveEmployee()方法保存这个实例:

Employee employee=new HourlyEmployee("Mary",300,company);

saveEmployee(employee);

Sessionsave()方法能判断employee变量实际引用的实例的类型,如果 employee变量引用HourlyEmployee实例,就执行如下 insert语句:

insert into EMPLOYEES (ID,NAME, COMPANY_ID) values (5, 'Mary', 1);

insert into HOURLY_EMPLOYEES (EMPLOYEE_ID ,RATE) values (5, 300);

可见,每保存一个HourlyEmployee对象,需要分别向 EMPLOYEES表和HE 表插入一条记录,EMPLOYEES表的记录和HE 表的记录共享同一个主键。

选择继承关系的映射方式

本章介绍的三种映射方式各有优缺点,下表对这三种映射方式作了比较。

比较三种映射关系

如果不需要支持多态查询和多态关联,可以采用每个具体类对应一个表的映射方式,如果需要支持多态查询和多态关联,并且子类包含的属性不多,可以采用根类对应一个表的映射方式,如果需要支持多态查询和多态关联,并且子类包含的属性很多,可以采用每个类对应一个表的映射方式。如果继承关系树中包含接口,可以把它当作抽象类来处理。

下显示了一颗复杂的继承关系树,其中 DOClass类为抽象类,其他均为具体类。

复杂的继承关系树

可以将上图的继承关系树分解为3颗子树:

? DOClass 类、ClassA 类和 ClassB 类为一颗子树:DOClass 类为抽象类,位于整个继承关系树的顶层,通常不会对它进行多态查询,因此可以采用每个具体类对应一个表的映射方式,ClassA类对应 TABLE_A表,ClassB类对应 TABLE_B表。

? ClassA类、ClassC类、ClassD 类、ClassG类和 ClassH 类为一颗子树:ClassA类的所有子类都只包含少量属性,因此可以采用根类对应一个表的映射方式,ClassA类对应TABLE_A表。

? ClassB类、ClassE 类和ClassF为一颗子树:ClassB类的两个子类都包含很多属性,因此采用每个类对应一个表的映射方式, ClassB类对应 TABLE_B 表,ClassE 类对应TABLE_E 表,ClassF类对应TABLE_F表。

如下图所示,在关系数据模型中,只需创建 TABLE_ATABLE_BTABLE_E TABLE_F 表,其中 TABLE_A 中包含了与 DOClassClassAClassCClassDClassG ClassH 的属性对应的字段。TABLE_B 中包含了与 DOClass ClassB 的属性对应的字段,TABLE_E TABLE_FB_ID 字段既是主键,又是参照 TABLE_B表的外键。

复杂继承关系树对应的关系数据模型

只需创建两个映射文件,ClassA.hbm.xml ClassB.hbm.xml,下面两个示例分别为它们的源代码。

示例ClassA.hbm.xml

<hibernate-mapping >

<class name="mypack.ClassA" table="TABLE_A" discriminator-value="A" >

<id name="id" type="long" column="ID">

<generator class="increment"/>

</id>

<discriminator column="A_TYPE" type="string" />

<property name="a1" type="string" column="A1" />

<subclass name="mypack.ClassC" discriminator-value="C" >

<property name="c1" column="C1" type="string" />

</subclass>

<subclass name="mypack.ClassD" discriminator-value="D" >

<property name="d1" column="D1" type="string" />

<subclass name="mypack.ClassG" discriminator-value="G" >

<property name="g1" column="G1" type="string" />

</subclass>

<subclass name="mypack.ClassH" discriminator-value="H" >

<property name="h1" column="H1" type="string" />

</subclass>

</subclass>

</class>

</hibernate-mapping>

示例ClassB.hbm.xml

<hibernate-mapping >

<class name="mypack.ClassB" table="TABLE_B">

<id name="id" type="long" column="ID">

<generator class="increment"/>

</id>

<property name="b1" type="string" column="B1" />

<joined-subclass name="mypack.ClassE" table="TABLE_E">

<key column="B_ID" />

<property name="e1" column="E1" type="string" />

<property name="e2" column="E2" type="string" />

<property name="e3" column="E3" type="string" />

<property name="e4" column="E4" type="string" />

<property name="e5" column="E5" type="string" />

<property name="e6" column="E6" type="string" />

</joined-subclass >

<joined-subclass name="mypack.ClassF" table="TABLE_F">

<key column="B_ID" />

<property name="f1" column="F1" type="string" />

<property name="f2" column="F2" type="string" />

<property name="f3" column="F3" type="string" />

<property name="f4" column="F4" type="string" />

<property name="f5" column="F5" type="string" />

<property name="f6" column="F6" type="string" />

</joined-subclass >

</class>

</hibernate-mapping>

ClassA.hbm.xml 文件中,在用于映射 ClassD <subclass>元素中还嵌入了两个<subclass>元素,它们分别映射ClassG ClassH 类。在<class>以及所有的<subclass>元素中都设置了discriminator-value属性,Hibernate根据 discriminator-value属性来判断 TABLE_A表中的记录对应哪个类的实例,如果 TABLE_A表的一条记录的 A_TYPE 字段取值为“A”,表明它是ClassA类的实例,如果A_TYPE 字段取值为“G”,表明它是 ClassG 类的实例,

以此类推。

值得注意的是,在<subclass>元素中只能嵌入<subclass>子元素,但不能嵌入<joined-subclass>子元素,而在<joined-subclass>元素中只能嵌入<joined-subclass>子元素,但不能嵌入< subclass>子元素。

映射多对一多态关联

CompanyEmployee类之间为一对多多态关联关系,如果继承关系树的根类对应一个表,或者每个类对应一个表,那么就能映射 Company 类的 employees 集合。本节介绍如何映射多对一多态关联。如下图所示,ClassD ClassA为多对一多态关联关系。

ClassDClassA为多对一多态关联关系

ClassAClassBClassC构成了一颗继承关系树,如果继承关系树的根类对应一个表,或者每个类对应一个表,那么可以按以下方式映射ClassD a 属性:

<many-to-one name="a"

class="ClassA"

column="A_ID"

cascade="save-update" />

假定与ClassD 对应的表为TABLE_D,与ClassA对应的表为 TABLE_A,在 TABLE_D中定义了外键A_ID,它参照TABLE_A表的主键。 ClassD 对象的a 属性既可以引用 ClassB对象,也可以引用 ClassC对象,例如:

tx = session.beginTransaction();

ClassD d=(ClassD)session.get("ClassD",id);

ClassA a=d.getA();

if(a instanceof ClassB)

System.out.println(((ClassB)a).getB1());

if(a instanceof ClassC)

System.out.println(((ClassC)a).getC1());

tx.commit();

以下代码在映射ClassD 类的a 属性时使用了延迟检索策略:

<many-to-one name="a"

class="ClassA"

column="A_ID"

lazy="true"

cascade="save-update" />

Hibernate 加载 ClassD 对象时,它的属性 a 引用 ClassA 的代理类实例,在这种情况下,如果对ClassA的代理类实例进行类型转换,会抛出 ClassCastException;

ClassA a=d.getA();

ClassB b=(ClassB)a; //抛出ClassCastException

解决以上问题的一种办法是使用 Session.load()方法:

ClassA a=d.getA();

ClassB b=(ClassB)session.load(ClassB.class,a.getId());

System.out.println(b.getB1());

当执行Sessionload()方法时,Hibernate并不会访问数据库,而是仅仅返回 ClassB的代理类实例。这种解决办法的前提条件是必须事先知道ClassD 对象实际上和 ClassA的哪个子类的对象关联。

解决以上问题的另一种办法是显式使用迫切左外连结检索策略,避免 Hibernate 创建ClassA的代理类实例,而是直接创建 ClassA的子类的实例:

tx = session.beginTransaction();

ClassD d=(ClassD)session.createCriteria(ClassD.class)

.add(Expression.eq("id",id))

.setFetchMode("a",FetchMode.EAGER)

.uniqueResult();

ClassA a=d.getA();

if(a instanceof ClassB)

System.out.println(((ClassB)a).getB1());

if(a instanceof ClassC)

System.out.println(((ClassC)a).getC1());

tx.commit();

如果继承关系树的具体类对应一个表,为了表达 ClassD ClassA的多态关联,需要在TABLE_D 中定义两个字段:A_ID A_TYPEA_TYPE 字段表示子类的类型,A_ID 参照在子类对应的表中的主键。下图显示了表TABLE_DTABLE_BTABLE_C的结构。

TABLE_DTABLE_BTABLE_C的结构

由于关系数据模型不允许一个表的外键同时参照两个表的主键,因此无法对 TABLE_D表的A_ID 字段定义外键参照约束,而应该通过其他方式,如触发器,来保证 A_ID 字段的参照完整性。由于 TABLE_D 表的A_ID 字段既可能参照 TABLE_B表的 ID 主键,也可能参照TABLE_C表的ID 主键,要求TABLE_B表和 TALBE_C表的 ID 主键具有相同的 SQL类型。 在ClassD.hbm.xml文件中,用<any>元素来映射 ClassD a 属性:

<any name="a"

meta-type="string"

id-type="long"

cascade="save-update">

<meta-value value="B" class="ClassB" />

<meta-value value="C" class="ClassC" />

<column name="A_TYPE" />

<column name="A_ID" />

</any>

<any>元素的 meta-type 属性指定 TABLE_D A_TYPE 字段的类型,id-type 属性指定TABLE_D A_ID 字段的类型,<meta-value>子元素设定 A_TYPE 字段的可选值。在本例中,如果A_TYPE 字段取值为“B”,表示为 ClassB的对象,A_ID 字段参照 TABLE_B表中的ID主键;如果A_TYPE字段取值为“C”,表示为ClassC的对象,A_ID字段参照TABLE_C表中的ID 主键。<column>子元素指定TABLE_D 表中的 A_TYPE 字段和 A_ID 字段,必须先指定A_TYPE 字段,再指定A_ID 字段。

内容总结

? 继承关系树的每个具体类对应一个表:在具体类对应的表中,不仅包含和具体类的属性对应的字段,还包含和具体类的父类的属性对应的字段。这种映射方式不支持多态关联和多态查询。

? 继承关系树的根类对应一个表:在根类对应的表中,不仅包含和根类的属性对应的字段,还包含和所有子类的属性对应的字段。这种映射方式支持多态关联和多态查询,并且能获得最佳查询性能,缺点是需要对关系数据模型进行非常规设计,在数据库表中加入额外的区分各个子类的字段,此外,不能为所有子类的属性对应的字段定义not null约束。

? 继承关系树的每个类对应一个表:在每个类对应的表中只需包含和这个类本身的属性对应的字段,子类对应的表参照父类对应的表。这种映射方式支持多态关联和多态查询,而且符合关系数据模型的常规设计规则,缺点是它的查询性能不如第二种映射方式。在这种映射方式下,必须通过表的内连接或左外连接来实现多态查询和多态关联。

独立实践

? 用 Hibernate编写一个员工管理系统的小Java应用程序,实现的功能包括:总经理和普通员工信息的添加,显示,删除和修改等功能。

? 结合StrutsHibernate编写一个Web应用实现上面第一道题目的基本要求。

? 用Hibernate实现员工工资管理的小Java应用程序,实现的功能包括:总经理和普通员工信息的添加,显示,删除和修改等功能。

? 结合StrutsHibernate编写一个Web应用实现上面第三道的题目的基本要求。

? 结合StrutsHibernate编写一个Web综合应用实现的要求如下:总经理和普通员工的个人信息,考勤信息以及工资信息的添加,显示,删除和修改等功能。

第三十六章:HQL介绍

学习目标

? 了解和掌握HQL

? 掌握HQLSQL的共同点和不同点

? 掌握HQL的四种函数

? 在Java中灵活运用HQL

HQL的出现

传统的SQL语言采用的是结构化的查询方法,而这种方法对于查询以对象形式存在的数据却无能为力。幸运的是, Hibernate为我们提供了一种语法类似于SQL的语言,Hibernate查询语言(HQL),和SQL不同的是,HQL是一种面向对象的查询语言,它可以查询以对象形式存在的数据。因此,本章就HQL如何工作以及如何使用HQL展开了深入的讨论。

SQL本身是非常强大的。当SQL的这种强大和处理面向对象数据的能力相结合时,就产生了HQL。和SQL一样,HQL提供了丰富的查询功能,如投影查询、聚合函数、分组和约束。任何复杂的SQL都可以映射成HQL

进入HQL世界

一个ORM框架是建立在面向对象的基础上的。最好的例子是Hibernate如何提供类SQL查询。虽然HQL的语法类似于SQL,但实际上它的查询目标是对象。HQL拥有面向对象语言的所有的特性,这其中包括多态、继承和组合。这就相当于一个面向对象的SQL,为了提供更强大的功能,HQL还提供了很多的查询函数。这些函数可以被分为四类:

  1. 投影函数

  2. 约束函数

  3. 聚合函数

  4. 分组函数

使用HQL可以建立简单的查询,也可以建立更复杂的查询。在本章中并不讨论那些非常复杂的查询,如含有子查询和很多连接的查询。本文只讨论连接两个表的查询。现在让我们开始接近HQL吧!

投影

所谓投影,就是一个可以访问的对象或对象的属性。在HQL中,可以使用fromselect子句来完成这个工作。from子句返回指定的类的所有实例。如from Order将返回Order类的所有实例。换句话说,以上的查询相当于以下的SQL语句:

select * from order

from 是最简单的查询子句。from后面可以跟一个或多个类名(类名也可以带有别名)。为了得到OrderProduct的所有实例,可以使用如下的查询:

from Order, Product

和类名一样,别名也可以在from后使用,如下代码如示:

from Order as o, Product p

当查询很复杂时,加入别名可以减少语句的长度。我们可以看看如下的SQL语句:

select o.*, p.* from order o, product p where o.order_id = p.order_id

我们可以很容易看出,上面的查询是一对多的关系。在HQL中相当于一个类中包含多个其它类的实例。因此,以上的SQL写成HQL就是:

from Order as o inner join o.products as product

现在让我们考虑另外一个从表中得到指定属性的情况。这就是最常用的select子句。这在HQL中的工作方式和SQL中一样。而在HQL中,如果只是想得到类的属性的话,select语句是最后的选择。以上的SQL可以使用select子句改成如下的HQL语句:

select product from Order as o inner join o.products as product

以上的HQL语句将返回Order中的所有Products实例。如果要得到对象的某一个属性,可以将HQL语句写成如下的形式:

select product.name from Order as o inner join o.products as product

如果要得到多个对象的属性,可以将HQL语句写成如下形式:

select o.id, product.name from Order as o inner join o.products as product

接下来,我们将进入下一个议题。假设我们需要根据某些条件得到数据。那么以上所述的HQL语句将无法满足需求。为了达到这一目的,我们就要用到下面将要讨论的约束子句。

约束

从以上可知,投影返回的是所有的数据。但在大多数时候我们并不需要这么多数据。这就需要对数据进行过滤。在HQL中过滤数据的子句和SQL一样,也是where。它的语法类似于SQL,通过where子句,可以对行进行过滤。我们可以看看下面的SQL语句:

select * from orders where id = ‘1234’

这条查询语句返回了id等于1234的所有的字段。和这条SQL对等的是下面的HQL语句:

select o from Order o where o.id=’1234’

从以上两条语句可以看出,它们的where子句非常相似。而它们唯一的不同是SQL操作的是记录,而HQL操作的是对象。在HQL中,除了where子句可以过滤数据外,having子句也可以做到这一点(关于having子句的详细内容我将在分组部分讨论)。投影和约束是两个基本的操作,这两个操作再加上聚合函数的话,那HQL将变得更加强大。下面我们就来讨论什么是聚合。

聚合

上述的查询都是将每一个记录(对象)当做一个单位,而如果使用聚合,可以将一类记录(对象)当做一个单位。然后再对每一类的记录(对象)进行一系列地操作,如对某一列取平均值、求和、统计行数等等。HQL支持以下的聚合函数:

? avg(…), sum(…)

? min(…), max(…)

? count(*), count(…), count(distinct…), count(all…)

以上的聚合函数都返回数值类型。这些操作都可以在select子句中使用,如下所示:

select max(o.priceTotal) + max(p.price) from Order o join o.products p group by o.id

以上的HQL语句返回了两个值的和:orders表中的priceTotal的最大值和products表中的price的最大值之和。我们还可以使用 having子句对分组进行过滤。如我们想按id统计priceTotal小于1000的数量可按如下的HQL语句去实现:

select count(o) from Order o having o.priceTotal < 1000 group by o.id

我们还可以将聚合函数和having子句一起使用。如我们要按products表的id统计price小于amount的平均数的产品数量,HQL语句如下:

select count(p) from Product p having p.price < avg(amount) group by p.id

从上面的一系列的HQL语句可以看出,所有通过SQL实现的,都可以通过HQL来实现。

分组

在上一部分,已经涉及到了分组的概念。分组操作的是行的集合。它根据某一列(属性)对记录集进行分组。这一切是通过group子句实现的。如下的例子描述了group子句的一般用法。

select count(o) from Order o having o.priceTotal >= 1200 and o.priceTotal <= 3200 group by o.id

HQL中的分组和SQL中的分组类似。总之,除了一些对SQL的特殊扩展外,其它所有的SQL功能都可以使用HQL描述。在接下来的部分,让我们举例说明如何在java中使用HQL

Java中使用HQL

到现在为止,我们已经学习了HQL的基本用法。接下来我们举一个例子来说明如何在Java中使用HQL。下面的例子只给出了主要的部分,由于本文只是讨论HQL的用法,因此,关于Hibernate的一些设置和在main()函数中调用Hibernate的部分并未给出,读者可以参考相关的文当。现在让我们看看下面的例子。

下面是必须引用的包

 

import java.util.List;

import org.hibernate.*;

import org.hibernate.cfg.*

import com.Order;

下面是类的声明

public class MyOrder

{

… …

}

下面让我们来实现MyOrder类的构造函数

public class MyOrder

{

SessionFactory sf;

public MyOrder()

{

Configuration cfg = new Configuration().addClass(Order.class);

sf = cfg.buildSessionFactory();

}

… …

}

下面的getOrder函数根据priceTotal的区间值返回Order对象。

public class MyOrder

{

…. ….

public Order getOrder(String lower, String upper)

{

// 打开一个会话

Session sess = sf.openSession();

// HQL语句

String query = "select o from o "

+ "Order as o join o.products as p "

+ "where o.priceTotal > :priceTotalLower"

+ "and o.priceTotal< :priceTotalUpper";

Query q = sess.createQuery(query);

// 将两个参数传入HQL

q.setDouble("priceTotalLower", Double.parseDouble(lower));

q.setDouble("priceTotalUpper", Double.parseDouble(upper));

List list = q.list();

Order o=(Order)list.iterator.next();

return o;

}

… …

}

下面的main函数将测试MyOrder

public class MyOrder

{

… …

public static void main(String args[])

{

Order o=MyOrder().getOrder(“100”, “300”);

System.out.println(“id=”+ o.id);

… …

}

}

内容总结

? HQL并不区分字母的大小写,但在HQL中的Java类和属性名必须和实际的类和属性名一致。如SELECTselect之间可以互换,但Orderorder却代表不同的含义。

? 如果HQL中引用的类未被导入,在HQL中必须引用具体的包。如本例中,如果com.Order未被导入,在HQL中必须将Order写成com.Order

独立实践

? 利用Hibernate HQL编写一个用户信息的显示功能

? 利用Hibernate HQL实现多表格查询功能

? 利用Hibernate HQL实现求用户的总人数

? 利用Hibernate HQL实现用平均数

? 结合StrutsHibernate实现用户信息的显示功能

第三十七章 Spring介绍

学习目标

? 了解Spring框架

? 理解并使用IOC原理

? 理解并使用AOP

? 理解并使用Spring的事务管理

? 整合SpringStruts框架

? 整合SpringHibernate框架

? 整合Spring,StrutsHibernate框架

Spring简介

Spring是一个非常优秀的轻量级框架,通过SpringIoC容器,我们的关注点便放到了需要实现的业务逻辑上。对AOP的支持则能让我们动态增强业务方法。编写普通的业务逻辑Bean是非常容易而且易于测试的,因为它能脱离J2EE容器(如ServletJSP环境)单独进行单元测试。最后的一步便是在Spring框架中将这些业务BeanXML配置文件的方式组织起来,它们就按照我们预定的目标正常工作了!非常容易!

IOC控制反转

IOC: Inversion of Control (控制反转) 的意思呢: 就是把要在程序中实例化的对象配置到文件中,在程序中不用new来产生,而是让容器通过配置文件返回一个给你,好处就是当需要更改业务逻辑时,方便替换对接口的不同实现类,也就是Dependency Injection(依赖注入)

以下示例演示了IOC的原理。

注: 使用的MyEclipse的版本是5.5Spring的版本是2.0

首先打开eclipse新建个工程(app/web),然后倒入Spring框架,会自动在src目录下新建个applicationContext.xml文件。文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation=

"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

</beans>

Spring简单的运行步骤

第一步 自定义一个接口。

package com.itjob.spring;

public interface Fly {

public abstract void fly(String thing);

}

第二步 定义好实现Fly接口的类:

package com.itjob.spring;

public class Plane implements Fly {

public void fly(String thing) {

// TODO Auto-generated method stub

System.out.println("plane fly......" + thing);

}

}

第三步 在applicationContext.xml配置文件中加上下面的标记:

<bean id="myfly" class="com.itjob.spring.Plane">

</bean>

第四步 在单元测试中测试,测试代码在备注里,运行结果如下图:

对上例的分析:

上面的例子真的很简单,但是缺表达了Spring框架的一个思想依赖注入! 从上面的例子可以看出,BirdPlane两个类相当于两个组件,注入到代码中。当是Bird组件时,运行结果是

bird de weight is 40.8 and fly......higher and higher

而当是Plane组件时,运行结果是:

plane fly......higher and higher

上述代码充分体现出了Spring框架IOC的核心思想。

Spring的容器

Spring有两种容器:

<1>Bean工厂(org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,提供了基础的依赖注入支持。

<2>应用上下文(org.springframework.context.ApplicationContext接口定义)建立在Bean工厂基础之上,提供更多的系统构架服务,:资源,消息,事件。

AOP面向切面编程

AOP: Aspect Oriented Programming (面向切面编程) 用来在系统中提升业务的分离,把日志,安全,事务等东西和核心业务分离开,甚至核心业务都不知道它们的存在. 基本实现就是对相关的方法进行拦截,添加所需的处理动作。

AOP的专业术语

<I> Aspect(切面):切面是你要实现的交叉功能,如日志记录,安全控制,事务管理等. (个人感觉是个大概念,下面几个小概念组合在一起即是它)

<II> Joinpoint(连接点): 连接点是应用程序执行过程中插入Aspect的地点.这个地点可以是方法调用,异常抛出等时刻.(概念因素多一些)

<III> Advice(通知): 通知为Aspect的具体实现.(有具体实物类,即继承了org.aopalliance.aop.Advice的接口类)

<IV> PointCut(切入点): 切入点定义了Advice应该应用在哪些Joinpoint.(有具体实物类,即继承了org.springframework.aop.Pointcut的接口类)

<V> Introduction(引入): 引入允许你为已存在的类添加新的方法和属性. (有具体实物类,即继承了org.aopalliance.intercept.Interceptor的接口类,其实Interceptor继承了Advice的接口,也可看作是一种Advice)

<VI> Target(目标对象): 实施Advice的目标.可以为你编写的类,也可为你要添加定制行为的第三方类.(有具体实物类)

<VII> Proxy(代理): 代理是将Advice应用到Target后所创建的对象. 表面上和Target对象用法一样,只是被容器包装过.

(spring将自动实现,分两种代理创建方式:<1>如果Target实现了一个(或多个)接口暴露的方法,Spring使用JDKjava.lang.reflect.Proxy类创建代理,这个类让Spring动态产生一个新的类,它实现了所需的接口,织入了Advice,并代理对Target的所有请求. <2>如果目标对象没有实现任何接口,Spring使用CGLIB库生成Target的子类(注意final方法不能被通知),在创建这个子类的时候,SpringAdvice织入,并将对目标对象的调用委托给这个子类.现实应用程序中应该得到和使用由容器产生的这个proxy对象,:ProxyFactoryBean)

<VIII> Weaving(织入): 织入是将Aspect应用到Target从而创建一个新的Proxy对象的过程.织入可以发生在目标对象的生命周期的多个点上,分为: 编译期织入;类装载期织入;运行期织入(Spring使用)

也许这么多概念都把人搞迷糊了. Advice包括需要应用的交叉行为,JoinpointAdvice要在应用系统需要应用的所有PointCut,PointCut定义了Advice要在哪些JoinPoint应用.在此最关键的概念是什么呢? PointCut定义了Advice要在哪些JoinPoint应用。

定义PointCut切入点的原因: 如果我们不能表达在应用系统的什么地方应用这些Advice的话,编写的Advice毫无用处,这就是切入点的用处。

下面是AOP的示例:

下面来看看Pointcut的接口:

public interface Pointcut{

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();

}

Pointcut是根据方法和类决定在什么地方织入(weaving)通知(Advice). ClassFilter接口决定了一个类是否符合通知(Advice)的要求。

ClassFilter接口:

public interface ClassFilter{

boolean matches(Class class);

}

虽然ClassFilter让你通过类过滤切面,如果你同时需要方法的过滤,则通过MethodMatcher接口可以实现这个功能:

public interface MethodMatcher{

public boolean matches(Method m,Class targetClass);

public boolean isRuntime();

public boolean matches(Method m,Class target,Object[] args);

}

现在知道了定义切点(PointCut)的内部结构,但是在实际应用中,会经常使用spring预定义的切入点实现。

大多数切面(Aspect)是由定义切面行为的通知(Advice)和定义切面在什么地方执行的切入点(Pointcut)组合而成,所以spring提供了Advisor,它把通知(Advice)和切入点(Pointcut)组合在一个对象中.如下:

public interface PointcutAdvisor{

Pointcut getPointCut();

Advice getAdvice();

}

应用中我们大多数都会使用Spring自带的切入点,大多数Spring自带的切入点都会有一个对应的PointcutAdvisor.方便你在一个地方定义切入点(Pointcut)和通知(Advice)

Spring提供的切入点分静态和动态两种.主要介绍静态的.

静态切入点:NameMatchMethodPointcut,这个类你只需关注public void setMappedName(String)public void setMappedNames(String[])两个方法即可.

简单配置文件如下:

(目标Target)

<bean id="mailTarget" class="xxx.xxx.mailTargetImpl" />

(实现的通知Advice)

<bean id="customAdvice" class="xxx.xxx.cusetAdvice" />

(配置PointcutAdvisor)

<bean id="customerPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor>

<property name="mappedNames">

<list>

<value>aMethodName</value>

<value>endMatch*</vaule>

<list>

</property>

<property name="advice"> <ref bean="customAdvice"/> </property>

</bean>

(配置代理Proxy)

<bean id="mailService" class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="proxyInterfaces"> <value> xxx.xxx.interface.mailService </value> </property>

<property name="interceptorNames"> <list> <value> customerPointcutAdvisor </value> </list> </property>

<property name="target"> <value ref="mailTarget" /> </property>

</bean>

SpringRegexpMethodPointcut可以利用正则表达式来定义切入点.动态切入点的类是ControlFlowPointcut

以下是Spring配置文件的一些常用项,一一列出来.(property中的name其实就是class类中的一个field,通过反射set方法向里面赋值)

<1>配置DataSource

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="url"> <value> jdbc:mysql://192.168.0.1:3306/someDB?characterEncoding=utf-8 </value> </property>

<property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property>

<property name="username"><value>${db.dbuser}</value></property>

<property name="password"><value>${db.password}</value></property>

</bean>

${variable}符号可以从装载的property文件中取值.

(2) 装载Property配置文件

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="location">

<value> jdbc.properties </value>

</property>

</bean>

(3)文本国际化,使用方法是applicationContext.getMessage("key");

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basename"><value>messageFileName</value></property>

</bean>

文件结尾按地区分类:

messageFileName_en_US.properies,messageFileName_zh_CN.properies,默认不加区位:messageFileName.properies.

Spring事务管理

Spring提供程序控制式和声明式两种事务管理方法。

spring没有直接管理事务,而提供很多供选择的事务管理器:单一的JDBC: org.springframework.jdbc.datasource.DataSourceTransactionManager

Hibernate:org.springframework.orm.hibernate.HibernateTransactionManager加入到xml应用上下文:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource"> <ref bean="dataSource"> </property>

</bean>

<>手动程序控制事务:

一个方法是使用SpringTransactionTemple,用程序添加事务边界,利用一套回调机制.

public void enrollStudentInCourse()

{

transactionTemple.execute(

new TransactionCallback(){

public Object doInTransaction()

{

try{

//do stuff

}catch(Exception e)

{

ts.setRollbackOnly();

}

return null; //如果成功,事务被提交

}//try

}//public

);

}

使用的同时要配置相关文件,transactionTemple注入到应用类中,如下

<bean id="transactionTemple" class="org.springframework.transaction.support.TransactionTemplate">

<property name="transactionManager"> <ref bean="transactionManager" /> </property>

</bean>

<bean id="courseService" class="xxx.xxx.CourseServiceImpl">

<property name="transactionTemplate"> <ref bean="transactionTemple" /> </property>

</bean>

<>声明式事务

要使用声明式事务,必须得使用TransactionProxyFactoryBean.配置如下:

<bean id="courseService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<property name="proxyInterfaces"> <list> <value> xxx.xxx.CourseService </value> </list> </property>

<property name="target"> <ref bean="courseServiceTarget" /> </property>

<property name="transactionManager"> <ref bean="transactionManager" /> </property>

<property name="transactionAttributeSource"> <ref bean="attributeSource" /> </property>

</bean>

SpringStruts整合

首先看一下在struts-config.xml中需要做的改动,最开始是加入插件声明:

<struts-config>

<plug-in

className="org.springframework.web.struts.ContextLoaderPlugIn">

<set-property property="contextConfigLocation"

value="/WEB-INF/applicationContext.xml" />

</plug-in>

</struts-config>

然后通过SpringDelegatingActionProxy类代理Struts的动作,即在Struts配置文件中,定义<action-mappings>type属性全部改为DelegatingActionProxy,而不是具体的类名,并在Spring配置文件中定义与Struts动作映射对应的bean,从而将StrutsActionSpring分开,并把Struts的动作置于Spring的控制之下。

下面的示例演示了整合StrutsSpring的登录功能,开发的步骤如下:

1.首先建立web工程 itjob,加入spring框架,加入struts框架。

2.增加strutsaction,form,jsp组合,命名为login

配置文件struts-config.xml中的action

<action

attribute="loginForm"

input="/login.jsp"

name="loginForm"

path="/login"

scope="request"

type="org.springframework.web.struts.DelegatingActionProxy" />

注意 type类型由spring来代理,当我们访问"/login"的时候,struts会跟据type类型来查找Spring 环境中的动作,为些,我们还要加载spring环境,可以struts中加入一个plugin,同样在struts-config.xml中加入

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">

<set-property property="contextConfigLocation" value="/WEB-INF/beans.xml" />

</plug-in>

3.下面就是要把struts 动作注册为springbean。在beans.xml中加入:

<bean name="/login"

class="com.itjob.struts.action.LoginAction">

</bean>

这样,我们就能访问"/login"这个动作了。只不过它由spring代理了。

4. 下面写LoginActionJava代码如下:

public ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response) {

LoginForm loginForm = (LoginForm) form;// 生成ActionForm对象

String username=loginForm.getUsername();

String password=loginForm.getPassword();

if(username.equals(“itjob”)&&password.equals(“itjob”))

return mapping.findForward(“success”);

else{

ActionErrors errors=new ActionErrors();

errors.add(“login.fail”,new ActionError(“login.fail”));

saveErrors(errors);

return mapping.findForward(“fail”);

}

}

事实上Struts中加入Spring并不是很复杂,最重要的是我们需要清楚系统是不是一定要Spring,即Spring的所谓依赖注入等特性对开发的意义是不是要大于使用Spring后增加的开发成本。

下面用图来说明StrutsSpring结合后的程序运行基本流程:

如上所示,StrutsSpring集成最重要一点就是改变struts-config.xml中的Action-Mapping设置,在进入StrutsAction之前,Spring就获得了控制权。

SpringHibernate整合

有关StrutsHibernate的集成,内容不是很复杂,我归纳了以下几个要点:

? 配置hibernate.cfg.xml

这个文件的<bean>项中需要设定SessionFactory属性,Spring已经整合了SessionFactory设置,所以无需hibernate.cfg.xml再设了。同时,这个项中也可以设定映射文件.hbm.xml的列表。

? DAO继承HibernateDaoSupport,以调用其函数

HibnernateDaoSupport实现了HibernateTemplateSessionFactory的关联。HibernateTemplate(用HibnernateDaoSupport里的getHibernateTemplate()可以获得)对Hibernate Session进行了封装,可省去获得Session实例、事务启动与提交/回滚以及异常处理等一系列步骤,所以非常简单。

下面的示例演示了整合SpringHibernate两大框架。开发步骤如下:

1.首先我们把hibernate里的所有持久化类和配置文件拷贝过来,并且导入hibernate的开发包。

2. 自定义一个接口来完成对持久化对象的操作:

package com.itjob.dao;

import java.util.List;

import com.itjob.model.Teacher;

public interface InterfaceTeacherDao {

public void save(Teacher t);

public Teacher find(Long id);

public List query(String name);

}

3.定义实现上面接口的Dao, TeacherDao代码如下

package com.itjob.dao;

import java.util.List;

import org.hibernate.Query;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.itjob.model.Teacher;

public class TeacherDao extends HibernateDaoSupport implements

InterfaceTeacherDao {

public Teacher find(Long id) {

// TODO Auto-generated method stub

Teacher t =(Teacher)this.getHibernateTemplate().get(Teacher.class, id);

return t;

}

public void save(Teacher t) {

// TODO Auto-generated method stub

this.getHibernateTemplate().save(t);

}

public List query(String name){

SessionFactory sf = getHibernateTemplate().getSessionFactory();

Session session = sf.openSession();

Query query = session.createQuery("from Teacher where name = '" + name +"'");

return query.list();

}

}

4.在配置文件applicationContext.xml中加下面的标记:

<bean id="teacherbusi" class="com.itjob.dao.BusiTeacher">

<property name="itd">

<ref local="teacher"/>

</property>

</bean>

5.最后进行单元测试,测试代码在下面的备注里。

独立实践

? 利用Spring的控制反转原理来实现在两个类之间的相互调用。

? 利用SpringAOP原理来实现在函数调用时同时写入日志。

? 利用Spring来进行事务的管理。

? 整合SpringStruts编写一个用户登录功能。

? 整合SpringHibernate编写一个用户信息录入的功能。


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值