MicroFish

Open & Open hits
随笔 - 33, 文章 - 2, 评论 - 4, 引用 - 0
数据加载中……

2006年9月8日

HSQL 学习笔记1(zz)

http://hi.baidu.com/wannachan/blog/item/8e82bf86fc5d663f67096ef1.html

HSQL 学习笔记

1.     hsql 学习
1.1.     学习目的
本文档是针对hSQL 数据库方面的基础学习,为了使项目组成员能够达到使用hSQL 数据库的目的。
1.2.     培训对象
开发人员
1.3.     常用词及符号说明
常用词:
hsql:一种免费的跨平台的数据库系统
E:\hsqldb:表示是在dos 命令窗口下面
1.4.     参考信息
doc\guide\guide.pdf

2.     HSQL
2.1.     HSQL 运行工具
java -cp ../lib/hsqldb.jar org.hsqldb.util.DatabaseManager
注意hsqldb.jar 文件的文件路径,最好能放到classpath 里面,或者放到当前路径下.
java -cp hsqldb.jar org.hsqldb.util.DatabaseManager

2.2.     运行数据库
启动方式: Server Modes and
In-Process Mode (also called Standalone Mode).

一个test 数据库会包含如下文件:
• test.properties
• test.script
• test.log
• test.data
• test.backup
test.properties 文件包含关于数据库的一般设置.
test.script   文件包含表和其它数据库,插入没有缓存表的数据.
test.log 文件包含当前数据库的变更.
test.data 文件包含缓存表的数据
test.backup 文件是最近持久化状态的表的数据文件的压缩备份文件
所有以上这个文件都是必要的,不能被删除.如果数据库没有缓存表,test.data 和test.backup 文件将不会存在.另外,除了以上文件HSQLDB 数据库可以链接到任何文本文件,比如cvs 文件.

当操作test 数据库的时候, test.log 用于保存数据的变更. 当正常SHUTDOWN,这个文件将被删除. 否则(不是正常shutdown),这个文件将用于再次启动的时候,重做这些变更.test.lck 文件也用于记录打开的数据库的事实, 正常SHUTDOWN,文件也被删除.在一些情况下,test.data.old 文件会被创建,并删除以前的.






2.3.     Server Mode
java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb

命令行方式:


启动数据,数据库文件mydb,数据库名称xdb

也可以在 server.properties 文件中定义启动的数据库,最多10个
例如: server.properties:
server.database.0=file:E:/hsqldb/data/mydb
server.dbname.0=xdb

server.database.1=file:E:/hsqldb/data/testdb
server.dbname.1=testdb

server.database.2=mem:adatabase
server.dbname.2=quickdb
启动命令: java -cp ../lib/hsqldb.jar org.hsqldb.Server
运行结果如下



java 测试程序:
package test;
import junit.framework.TestCase;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestConnect extends TestCase {
     Connection connection;
     protected void setUp()
     {        
         try {
             Class.forName("org.hsqldb.jdbcDriver" );
             connection = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost/xdb","sa","");
            
            
         } catch (Exception e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
     }
     public void testselect()
     {
         Statement stmt=null;
         ResultSet rs=null;
         try {
             stmt = connection.createStatement();
             String sql ="select * from test";
             rs=stmt.executeQuery( sql);
             while(rs.next() )
             {
                 System.out.println("id="+rs.getString("id"));
                 System.out.println("name="+rs.getString("name"));
             }
            
         } catch (SQLException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
         finally
         {
             try {
                 rs.close() ;
                 stmt.close();
             } catch (SQLException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }            
         }    
        
     }
     protected void tearDown()
     {
         try {
             connection.close();
         } catch (Exception e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
     }

}
以上在eclipse 中测试通过.

2.4.     In-Process (Standalone) Mode
不需要启动server
connection = DriverManager.getConnection("jdbc:hsqldb:file:E:/hsqldb/data/mydb","sa","");
这样就可以连接数据库。
只能在一个jvm 中使用,不能在多个jvm 中使用。
这种模式是在相同的jvm 下作为你的应用程序的一部分,运行数据库引擎。对大多数应用程序,这种模式运行会相当快,作为数据,不需要转换和网络传输。

主要的缺点就是不可能从外面的应用程序访问到默认数据库,因此当你的应用运行时候,你不能通过别的工具检查数据库内容。在1.8.0 版本中,你可以在相同jvm 中的线程中运行数据库初始化,并提供外面访问你的进程内数据库。
     推荐在开发应用中使用这种方式。
连接串:
Windows: DriverManager.getConnection("jdbc:hsqldb:file:E:/hsqldb/data/mydb","sa","");
Unix: DriverManager.getConnection("jdbc:hsqldb:file:/opt/db/testdb","sa","");

2.5.     Memory-Only Databases
当随即访问内存,数据库不固定时,可以采用内存的方式运行数据库,由于没有数据写到硬盘上,这种方式使用在应用数据和applets 和特殊应用的内部进程中使用,URL:

Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:aname", "sa", "");
2.6.     Using Multiple Databases in One JVM
2.7.     Different Types of Tables
HSQLDB 支持 TEMP 表和三种类型的持久表(MEMORY 表, CACHED 表,TEXT表)

当使用 CREATE TABLE   命令时,Memory 表时默认类型,它们的数据整体保存在内存当中,但是任何改变它们的结构或者内容,它们会被写到<dbname>.script 文件中。这个脚本文件在数据库下一次打开的时候被对出,内存表重新被创建内容,根temp 表不同,内存表时持久化的。

CACHED 表通过CREATE CACHED TABLE 命令建立. 只有部分的它们的数据或者索引被保存在内存中,允许大表占用几百兆的内存空间。例外一个优点,在数据库引擎中,启动大量数据的缓存表需要花费少量的时间,缺点是减慢了运行和使用Hsqldb 的速度。表相对小的时候,不要使用cache 表,在小表中使用内存数据库。

从版本 1.7.0 以后,支持text 表,使用 CSV (Comma Separated Value)   或者其它分隔符文本文件作为它们的数据源。你可以特殊指定一个存在的CSV 文件,例如从其它的数据或者程序中导出文件,作为TXT 表的数据源。 同时,你可以指定一个空文件,通过数据库引擎填充数据。TEXT 表将比cache 表更加效率高。Text 表可以指向不同的数据文件。

* memory-only databases 数据库只支持memory 表和cache 表,不支持text 表。
2.8.     约束和索引
HSQLDB 支持 PRIMARY KEY, NOT NULL, UNIQUE, CHECK and FOREIGN KEY 约束.





3.     sql 命令
3.1.     sql 支持
select top 1 * from test;
select limit 0 2 * from test;
DROP TABLE test IF EXISTS;
3.2.     Constraints and Indexes
主健约束:PRIMARY KEY
唯一约束:
唯一索引:
外健:
CREATE TABLE child(c1 INTEGER, c2 VARCHAR, FOREIGN KEY (c1, c2) REFERENCES parent(p1, p2));

3.3.     索引和查询速度
索引提高查询速度,比提高排序速度。
主健和唯一所列自动创建索引,否则需要自己创建CREATE INDEX command。
索引: 唯一索引和非唯一索引
多列的索引,如果只是使用后面的,不使用第一个,将不会条查询速度。

(TB is a very large table with only a few rows where TB.COL3 = 4)
SELECT * FROM TA JOIN TB ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;
SELECT * FROM TB JOIN TA ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;(faster)

原因是 TB.COL3 可以被快速的估计,如果TB 表放到前面(index on TB.COL3):
一般规则是把缩小条件的列的表放在前面

3.4.     使用where 还是join
使用 WHERE   条件链接表可能会降低运行速度.
下面的例子将会比较慢,即使使用了索引:
     SELECT ... FROM TA, TB, TC WHERE TC.COL3 = TA.COL1 AND TC.COL3=TB.COL2 AND TC.COL4 = 1
这个查询隐含TA.COL1 = TB.COL2 ,但是没有直接设定这个条件.如果 TA 和 TB 每个表都包含100 条记录,10000 组合将和 TC 关联,用于TC这个列的条件,尽管有索引在这个列上.使用JOIN 关键字, 在组合TC 之前,TA.COL1 = TB.COL2 条件直接并缩小组合 TA 和 TB 的行数, 在运行大数据量的表的结果是,将会很快:
     SELECT ... FROM TA JOIN TB ON TA.COL1 = TB.COL2 JOIN TC ON TB.COL2 = TC.COL3 WHERE TC.COL4 = 1
这个查询可以提高一大步,如果改变表的顺序, 所以 TC.COL1 = 1 将最先使用,这样更小的集合将组合在一起:
     SELECT ... FROM TC JOIN TB ON TC.COL3 = TB.COL2 JOIN TA ON TC.COL3 = TA.COL1 WHERE TC.COL4 = 1
以上例子,数据引擎自动应用于TC.COL4 = 1 组合小的集合于其它表关联. Indexes TC.COL4, TB.COL2   TA.COL1 都将使用索引,提高查询速度.
3.5.     Subqueries and Joins
使用join 和调整表的顺序提高效率.
例如:, 第二个查询的速度将更快一些(TA.COL1 和TB.COL3都有索引):
Example 2.2. Query comparison
     SELECT ... FROM TA WHERE TA.COL1 = (SELECT MAX(TB.COL2) FROM TB WHERE TB.COL3 = 4)

     SELECT ... FROM (SELECT MAX(TB.COL2) C1 FROM TB WHERE TB.COL3 = 4) T2 JOIN TA ON TA.COL1 = T2.C1
第二个查询将 MAX(TB.COL2) 与一个单记录表相关联. 并使用TA.COL1索引,这将变得非常快. 第一个查询是将 TA 表中的每一条记录不断地与MAX(TB.COL2)匹配.
3.6.     数据类型
TINYINT, SMALLINT, INTEGER, BIGINT, NUMERIC and DECIMAL (without a decimal point) are supported integral types and map to byte, short, int, long and BigDecimal in Java.

Integral Types:
TINYINT, SMALLINT, INTEGER, BIGINT, NUMERIC and DECIMAL
Other Numeric Types:
REAL, FLOAT or DOUBLE
Bit and Boolean Types:
     BOOLEAN: UNDEFINED,TRUE,FALSE  
NULL values are treated as undefined.
Storage and Handling of Java Objects
Sequences and Identity

Identity Auto-Increment Columns:
The next IDENTITY value to be used can be set with the
ALTER TABLE ALTER COLUMN <column name> RESTART WITH <new value>;
Sequences:
SELECT NEXT VALUE FOR mysequence, col1, col2 FROM mytable WHERE ...
    
3.7.     事务问题:
SET PROPERTY "sql.tx_no_multi_rewrite" TRUE

4.     Connections
通用驱动jdbc:hsqldb:   下列协议标识(mem: file: res: hsql: http: hsqls: https:)
Table 4.1. Hsqldb URL Components
Driver and Protocol     Host and Port     Database
jdbc:hsqldb:mem:
     not available     accounts

jdbc:hsqldb:mem:.
jdbc:hsqldb:file:
     not available     mydb
/opt/db/accounts
C:/data/mydb

数据库路径.
jdbc:hsqldb:res:
     not available     /adirectory/dbname

jars files are accessed in Java programs. The /adirectory above stands for a directory in one of the jars.
jdbc:hsqldb:hsql:
jdbc:hsqldb:hsqls:
jdbc:hsqldb:http:
jdbc:hsqldb:https:
     //localhost
//192.0.0.10:9500
//dbserver.somedomain.com
     /an_alias
/enrollments
/quickdb

别名在server.properties or webserver.properties文件中指定
     database.0=file:/opt/db/accounts
     dbname.0=an_alias

     database.1=file:/opt/db/mydb
     dbname.1=enrollments

     database.2=mem:adatabase
     dbname.2=quickdb
In the example below, the database files lists.* in the /home/dbmaster/ directory are associated with the empty alias:
     database.3=/home/dbmaster/lists
     dbname.3=
4.1.     Connection properties
Connection properties are specified either by establishing the connection via the:
     DriverManager.getConnection (String url, Properties info);
method call, or the property can be appended to the full Connection URL.
Table 4.2. Connection Properties
get_column_name     true     column name in ResultSet
This property is used for compatibility with other JDBC driver implementations. When true (the default), ResultSet.getColumnName(int c) returns the underlying column name
When false, the above method returns the same value as ResultSet.getColumnLabel(int column) Example below:
     jdbc:hsqldb:hsql://localhost/enrollments;get_column_name=false
                    
When a ResultSet is used inside a user-defined stored procedure, the default, true, is always used for this property.
ifexists     false     connect only if database already exists
Has an effect only with mem: and file: database. When true, will not create a new database if one does not already exist for the URL.
When false (the default), a new mem: or file: database will be created if it does not exist.
Setting the property to true is useful when troubleshooting as no database is created if the URL is malformed. Example below:
     jdbc:hsqldb:file:enrollments;ifexists=true
shutdown     false     shut down the database when the last connection is closed
This mimics the behaviour of 1.7.1 and older versions. When the last connection to a database is closed, the database is automatically shut down. The property takes effect only when the first connection is made to the database. This means the connection that opens the database. It has no effect if used with subsequent, simultaneous connections.
This command has two uses. One is for test suites, where connections to the database are made from one JVM context, immediately followed by another context. The other use is for applications where it is not easy to configure the environment to shutdown the database. Examples reported by users include web application servers, where the closing of the last connection conisides with the web app being shut down.


4.2.     Properties Files
大小写敏感 (e.g. server.silent=FALSE will have no effect, but server.silent=false will work).
属性文件和设定存储如下 :
Table 4.3. Hsqldb Server Properties Files
File Name     Location     Function
server.properties     the directory where the command to run the Server class is issued     settings for running HSQLDB as a database server communicating with the HSQL protocol
webserver.properties     the directory where the command to run the WebServer class is issued     settings for running HSQLDB as a database server communicating with the HTTP protocol
<dbname>.properties     the directory where all the files for a database are located     settings for each particular database
Properties files for running the servers are not created automatically. You should create your own files that contain server.property=value pairs for each property.
4.2.1.     Server and Web Server Properties
server.properties and webserver.properties 文件支持如下设定:
Table 4.4. Property File Properties
Value     Default     Description
server.database.0     test     the path and file name of the first database file to use
server.dbname.0     ""     lowercase server alias for the first database file
server.urlid.0     NONE     SqlTool urlid used by UNIX init script. (This property is not used if your are running Server/Webserver on a platform other than UNIX, or of you are not using our UNIX init script).
server.silent     true     no extensive messages displayed on console
server.trace     false     JDBC trace messages displayed on console
In 1.8.0, 每个服务器支持同时启动10个不同的数据库. The server.database.0 property defines the filename / path whereas the server.dbname.0 defines the lowercase alias used by clients to connect to that database. The digit 0 is incremented for the second database and so on. Values for the server.database.{0-9} property can use the mem:, file: or res: prefixes and properties as discussed above under CONNECTIONS. For example,
     database.0=mem:temp;sql.enforce_strict_size=true;
Values specific to server.properties are:
Table 4.5. Server Property File Properties
Value     Default     Description
server.port     9001     TCP/IP port used for talking to clients. All databases are served on the same port.
server.no_system_exit     true     no System.exit() call when the database is closed
Values specific to webserver.properties are:
Table 4.6. WebServer Property File Properties
Value     Default     Description
server.port     80     TCP/IP port used for talking to clients
server.default_page     index.html     the default web page for server
server.root     ./     the location of served pages
.<extension>     ?     multiple entries such as .html=text/html define the mime types of the static files served by the web server. See the source for WebServer.java for a list.
All the above values can be specified on the command line to start the server by omitting the server. prefix.
5.     SqlTool
Mem 数据库:
E:\hsqldb>java -jar ./lib/hsqldb.jar mem
Hsql Server:
(前提是xdb server 已经启动):
(java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb)
java -jar ./hsqldb.jar xdb

posted @ 2007-03-26 17:18 刘璐 阅读(684) | 评论 (0)编辑 收藏

(转)用DbUnit进行SqlMap单元测试- -

http://starrynight.blogdriver.com/starrynight/621943.html
DbUnit简介

为依赖于其他外部系统(如数据库或其他接口)的代码编写单元测试是一件很困难的工作。在这种情况下,有效的单元必须隔离测试对象和外部依赖,以便管理测试对象的状态和行为。

使用mock object对象,是隔离外部依赖的一个有效方法。如果我们的测试对象是依赖于DAO的代码,mock object技术很方便。但如果测试对象变成了DAO本身,又如何进行单元测试呢?

开源的DbUnit项目,为以上的问题提供了一个相当优雅的解决方案。使用DbUnit,开发人员可以控制测试数据库的状态。进行一个DAO单元测试之前,DbUnit为数据库准备好初始化数据;而在测试结束时,DbUnit会把数据库状态恢复到测试前的状态。

下面的例子使用DbUnit为iBATIS SqlMap的DAO编写单元测试。

准备测试数据
首先,要为单元测试准备数据。使用DbUnit,我们可以用XML文件来准备测试数据集。下面的XML文件称为目标数据库的Seed File,代表目标数据库的表名和数据,它为测试准备了两个Employee的数据。employee对应数据库的表名,employee_uid、start_date、first_name和last_name都是表employee的列名。

<?xml version="1.0" encoding="GB2312"?>
<dataset>
    <employee employee_uid="0001"
        start_date="2001-01-01"
        first_name="liutao"
        last_name="liutao" />
   
    <employee employee_uid="0002"
        start_date="2001-04-01"
        first_name="wangchuang"
        last_name="wangchuang" />
</dataset>

缺省情况下,DbUnit在单元测试开始之前删除Seed File中所有表的数据,然后导入Seed File的测试数据。在Seed File中不存在的表,DbUnit则不处理。
Seed File可以手工编写,也可以用程序导出现有的数据库数据并生成。

SqlMap代码
我们要测试的SqlMap映射文件如下所示:
<select id="queryEmployeeById" parameterClass="java.lang.String"
    resultClass="domain.Employee">
    select employee_uid as userId,
        start_date as startDate,
        first_name as firstName,
        last_name as lastName
    from EMPLOYEE where employee_uid=#value#
</select>
<delete id="removeEmployeeById" parameterClass="java.lang.String">
    delete from EMPLOYEE where employee_uid=#value#
</delete>
<update id="updateEmpoyee" parameterClass="domain.Employee">
    update EMPLOYEE
    set start_date=#startDate#,
    first_name=#firstName#,
    last_name=#lastName#
    where employee_uid=#userId#
</update>
<insert id="insertEmployee" parameterClass="domain.Employee">
    insert into employee (employee_uid,
        start_date, first_name, last_name)
        values (#userId#, #startDate#, #firstName#, #lastName#)
</insert>

编写DbUnit TestCase
为了方便测试,首先为SqlMap的单元测试编写一个抽象的测试基类,代码如下。

public abstract class BaseSqlMapTest extends DatabaseTestCase {
    protected static SqlMapClient sqlMap;

    protected IDatabaseConnection getConnection() throws Exception {
        return new DatabaseConnection(getJdbcConnection());
    }
    protected void setUp() throws Exception {
        super.setUp();
        init();
    }
    protected void tearDown() throws Exception {
        super.tearDown();
        getConnection().close();
        if (sqlMap != null) {
            DataSource ds = sqlMap.getDataSource();
            Connection conn = ds.getConnection();
            conn.close();
        }
    }
    protected void init() throws Exception {
        initSqlMap("sqlmap/SqlMapConfig.xml", null);
    }
    protected SqlMapClient getSqlMapClient() {
        return sqlMap;
    }
    protected void initSqlMap(String configFile, Properties props)
            throws Exception {
        Reader reader = Resources.getResourceAsReader(configFile);
        sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader, props);
        reader.close();
    }
    protected void initScript(String script) throws Exception {
        DataSource ds = sqlMap.getDataSource();
        Connection conn = ds.getConnection();
       
        Reader reader = Resources.getResourceAsReader(script);
        ScriptRunner runner = new ScriptRunner();
        runner.setStopOnError(false);
        runner.setLogWriter(null);
        runner.setErrorLogWriter(null);

        runner.runScript(conn, reader);
        conn.commit();
        conn.close();
        reader.close();
    }
    private Connection getJdbcConnection() throws Exception {
        Properties props = new Properties();
        props.load(Resources.getResourceAsStream("sqlmap/SqlMapConfig.properties"));
        Class driver = Class.forName(props.getProperty("driver"));
        Connection conn = DriverManager.getConnection(props.getProperty("url"),
                props.getProperty("username"), props.getProperty("password"));
        return conn;
    }
}

然后为每个SqlMap映射文件编写一个测试用例,extends上面的抽象类。如编写Employ.xml的测试用例如下,它覆盖了DbUnit的DatabaseTestCase类的getDataSet方法。

public class EmployeeDaoTest extends BaseSqlMapTest {
   
    protected IDataSet getDataSet() throws Exception {
        Reader reader = Resources.getResourceAsReader("config/employee_seed.xml");
        return new FlatXmlDataSet(reader);
    }
    public void testQueryEmpoyeeById() throws Exception {
        String id = "0001";
        Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
        assertNotNull(emp);
        assertEquals("0001", emp.getUserId());
        assertEquals("liutao", emp.getFirstName());
    }
    public void testRemoveEmployeeById() throws Exception {
        String id = "0001";
        int num = sqlMap.delete("removeEmployeeById", id);
        assertEquals(1, num);
       
        // 注意这里, 确认删除不能使用SqlMap的查询, 很奇怪!
        ITable table = getConnection().createQueryTable("removed",
                "select * from employee where employee_uid='0001'");
        assertEquals(0, table.getRowCount());
    }
    public void testUpdateEmployee() throws Exception {
        String id = "0002";
        Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
        emp.setLastName("wch");
        sqlMap.update("updateEmpoyee", emp);
       
        Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
        assertEquals("wch", emp1.getLastName());
    }
    public void testInsertEmployee() throws Exception {
        Employee emp = new Employee();
        emp.setUserId("0005");
        emp.setStartDate("2003-09-09");
        emp.setFirstName("macy");
        emp.setLastName("macy");
        sqlMap.insert("insertEmployee", emp);
       
        Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", "0005");
        assertEquals(emp.getFirstName(), emp1.getFirstName());
        assertEquals(emp.getStartDate(), emp1.getStartDate());
    }
}

以上例子中的绿色代码部分使用ITable接口来查询已删除的数据。因为使用SqlMapClient.queryForObject方法查询,已删除的数据还存在,真奇怪(有时间再研究)。

DbUnit的断言
我们可以使用DbUnit的Assertion类的方法来比较数据是否相同。

public class Assertion {
    public static void assertEquals(ITable expected, ITable actual)
    public static void assertEquals(IDataSet expected, IDataSet actual)
}

DatabaseTestCase的getSetUpOperation和getTearDownOperation方法
缺省情况下,DbUnit执行每个测试前,都会执行CLEAN_INSERT操作,删除Seed File中所有表的数据,并插入文件的测试数据。你可以通过覆盖getSetUpOperation和getTearDownOperation方法改变setUp和tearDown的行为。

protected DatabaseOperation getSetUpOperation() throws Exception {
    return DatabaseOperation.REFRESH;
}
protected DatabaseOperation getTearDownOperation() throws Exception {
   
return DatabaseOperation.NONE;
}

REFRESH操作执行测试前并不执行CLEAN操作,只是导入文件中的数据,如果目标数据库数据已存在,DbUnit使用文件的数据来更新数据库。

使用Ant
上面的方法通过extends DbUnit的DatabaseTestCase来控制数据库的状态。而
使用DbUnit的Ant Task,完全可以通过Ant脚本的方式来实现。

<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>
<!-- 执行set up 操作 -->
<dbunit driver="org.hsqldb.jdbcDriver"
        url="jdbc:hsqldb:hsql://localhost/xdb"
        userid="sa" password="">
    <operation type="INSERT" src="employee_seed.xml"/>
</dbunit>
<!-- run all tests in the source tree -->
<junit printsummary="yes" haltonfailure="yes">
  <formatter type="xml"/>
  <batchtest fork="yes" todir="${reports.tests}">
    <fileset dir="${src.tests}">
      <include name="**/*Test*.java"/>
    </fileset>
  </batchtest>
</junit>
<!-- 执行tear down 操作 -->
<dbunit driver="org.hsqldb.jdbcDriver"
        url="jdbc:hsqldb:hsql://localhost/xdb"
        userid="sa" password="">
    <operation type="DELETE" src="employee_seed.xml"/>
</dbunit>

以上的Ant脚本把junit task放在DbUnit的Task中间,可以达到控制数据库状态的目标。

由此可知,DbUnit可以灵活控制目标数据库的测试状态,从而使编写SqlMap单元测试变得更加轻松。

本文抄袭了资源列表的“Effective Unit Test with DbUnit”,但重新编写了代码示例。

网上资源

1、DbUnit Framework

2、Effective Unit Testing with DbUnit

3、Control your test-environement with DbUnit and Anthill

posted @ 2007-03-26 17:16 刘璐 阅读(709) | 评论 (0)编辑 收藏

抽象类和接口的区别

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。  

理解抽象类  

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?  

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。  

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。  


从语法定义层面看abstract class和interface  

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。  

使用abstract class的方式定义Demo抽象类的方式如下:  

abstract class Demo {  
 abstract void method1();  
 abstract void method2();  
 …  
}  

使用interface的方式定义Demo抽象类的方式如下:  

interface Demo {  
 void method1();  
 void method2();  
 …  
}  

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。  

      从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。  

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。  

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。  

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。  

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。  


从设计理念层面看abstract class和interface  

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。  

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。  

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:  

使用abstract class方式定义Door:  

abstract class Door {  
 abstract void open();  
 abstract void close();  
}  

   
使用interface方式定义Door:  


interface Door {  
 void open();  
 void close();  
}  

   
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。  

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。  

解决方案一:  

简单的在Door的定义中增加一个alarm方法,如下:  

abstract class Door {  
 abstract void open();  
 abstract void close();  
 abstract void alarm();  
}  

   
或者  

interface Door {  
 void open();  
 void close();  
 void alarm();  
}  

   
那么具有报警功能的AlarmDoor的定义方式如下:  

class AlarmDoor extends Door {  
 void open() { … }  
 void close() { … }  
 void alarm() { … }  
}  

   
或者  

class AlarmDoor implements Door {  
 void open() { … }  
 void close() { … }  
 void alarm() { … }  
}  

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。  

解决方案二:  

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。  

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。  

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。  

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:  

abstract class Door {  
 abstract void open();  
 abstract void close();  
}  
interface Alarm {  
 void alarm();  
}  
class AlarmDoor extends Door implements Alarm {  
 void open() { … }  
 void close() { … }  
    void alarm() { … }  
}  

   
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

posted @ 2007-03-08 13:27 刘璐 阅读(292) | 评论 (0)编辑 收藏

关于session的详细解释

一、术语session

  在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

  session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

  然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者“一个POP3 session”③。

  而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

  鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。

  在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

  二、HTTP协议与状态保持

  HTTP协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

  然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

  让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:

  1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。

  2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。

  3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

  由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

  三、理解cookie机制

  cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

  正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。

  而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

  cookie的内容主要包括:名字,值,过期时间,路径和域。

  其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。

  路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。

  路径与域合在一起就构成了cookie的作用范围。

  如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

  存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

  下面就是一个goolge设置cookie的响应头的例子

HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html

  这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分


  浏览器在再次访问goolge的资源时自动向外发送cookie

  使用Firefox可以很容易的观察现有的cookie的值

  使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。


  IE也可以设置在接受cookie前询问

  这是一个询问接受cookie的对话框。

  四、理解session机制

 session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

  当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

  保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

  由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。

  为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

  另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单



  在被传递给客户端之前将被改写成




  这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。实际上这种技术可以简单的用对action应用URL重写来代替。

  在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

  恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

  五、理解javax.servlet.http.HttpSession

  HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

  首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,cookie的生存时间等。

  一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

  复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

  cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

  cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

  关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

  六、HttpSession常见问题

  (在本小节中session的含义为⑤和⑥的混合)

  1、session在何时被创建

  一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <%@page session="false"%>关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。

  由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

  2、session何时被删除

  综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

  3、如何做到在浏览器关闭时删除session

  严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

  4、有个HttpSessionListener是怎么回事

  你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。

        5、存放在session中的对象必须是可序列化的吗

  不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

  6、如何才能正确的应付客户端禁止cookie的可能性

  对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

  7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session

  参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

  8、如何防止用户打开两个浏览器窗口操作导致的session混乱

  这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

  9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

  10、为什么session不见了

  排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。

  出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

  七、跨应用程序的session共享

  常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

  然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

  首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。

  根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。

  笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

  iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。

/NASApp

  需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。


  在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

  我们再看一下Weblogic Server是如何处理session的。

  从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下

  对于这样一种结构,在session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

  应用程序A

context.setAttribute("appA", session);

  应用程序B

contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");

  值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

  那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

  八、总结

  session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。

posted @ 2007-03-07 10:50 刘璐 阅读(261) | 评论 (0)编辑 收藏

Java Web 开发

13. Tomcat与其他HTTP服务器集成
13.1 Tomcat与HTTP服务器集成的原理
 <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
    <Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />
 
<!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009"
               enableLookups="false" redirectPort="8443" protocol="AJP/1.3" />
 
第一个连接监听8080端口,负责建立HTTP连接,在通过浏览器访问TOMCAT服务器的WEB应用时,使用的就是这个连接。
第二个连接器监听8009端口,负责和其他的HTTP服务器建立连接,在把TOMCAT与其他HTTP服务器集成时,就需要用到这个连接器。
 
13.2 在Windows下Tomcat与Apache服务器集成
1.install apache http server

2.copy jk into <APACHE_HOME>/modules
 
3.create workers.properties and copy it into <APACHE_HOME>/conf
workers.tomcat_home=C:\jakarta-tomcat  #让mod_jk模块知道Tomcat
workers.java_home=C:\j2sdk1.4.2  #让mod_jk模块知道j2sdk
ps=\  #指定文件路径分割符
worker.list=worker1
worker.worker1.port=8009  #工作端口,若没占用则不用修改
worker.worker1.host=localhost  #Tomcat服务器的地址
worker.worker1.type=ajp13  #类型
worker.worker1.lbfactor=1  #负载平衡因数
 
4.modify <APACHE_HOME>/conf/httpd.conf
# Using mod_jk2.dll to redirect dynamic calls to Tomcat
LoadModule jk_module modules\mod_jk_2.0.46.dll
JkWorkersFile "conf\workers.properties"
JkLogFile "logs\mod_jk2.log"
JkLogLevel debug
JkMount /*.jsp worker1
JkMount /helloapp/* worker1

5.test

6.banlance
(1)copy jk into <APACHE_HOME>/lib
 
(2)create workers.properties in <APACHE_HOME>/conf
ps=\  #指定文件路径分割符
worker.list=worker1,worker2,loadbalancer
worker.worker1.port=8009  #工作端口,若没占用则不用修改
worker.worker1.host=localhost  #Tomcat服务器的地址
worker.worker1.type=ajp13  #类型
worker.worker1.lbfactor=100  #负载平衡因数
worker.worker2.port=8009  #工作端口,若没占用则不用修改
worker.worker2.host=anotherhost  #Tomcat服务器的地址
worker.worker2.type=ajp13  #类型
worker.worker2.lbfactor=100  #负载平衡因数
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=worker1, worker2
 
(3)modify <APACHE_HOME>/conf/httpd.conf
# Using mod_jk2.dll to redirect dynamic calls to Tomcat
LoadModule jk_module modules\mod_jk_2.0.46.dll
JkWorkersFile "conf\workers.properties"
JkLogFile "logs\mod_jk2.log"
JkLogLevel debug
JkMount /*.jsp loadbalancer
JkMount /helloapp/* loadbalancer

(4)keep worker's name the same as jvmRoute property of <Engine> element in server.xml
eg. <Engine name="Catalina" defaultHost="localhost" debug="0" jmvRoute="worker1">

(5)test
 
if both of tomcat servers are running in the same server, at least the port of one of tomact servers should be modified.
shut down HTTP connection of tomcat server, cancel HTTP connector in server.xml
 
13.3 Tomcat与IIS服务器集成
1.prepare
(1)JK isapi_redirect.dll add it into <CATALINA_HOME>/bin

(2)create workers.properties in <CATALINA_HOME>/conf
workers.tomcat_home=C:\jakarta-tomcat  #让mod_jk模块知道Tomcat
workers.java_home=C:\j2sdk1.4.2  #让mod_jk模块知道j2sdk
ps=\  #指定文件路径分割符
worker.list=worker1
worker.worker1.port=8009  #工作端口,若没占用则不用修改
worker.worker1.host=localhost  #Tomcat服务器的地址
worker.worker1.type=ajp13  #类型
worker.worker1.lbfactor=1  #负载平衡因数
 
(3)create uriworkermap.properties in <CATALINA_HOME>/conf
/index.jsp=worker1
/helloapp/*=worker1
2.edit register
equals configurate attribuates of JK
 
3.add 'jakarta' vitural directory in IIS for JK
choose 脚本和可执行程序
 
4.把JK插件作为ISAPI筛选器加入IIS
 
14. MVC
14.1 用RequestDispatcher 实现MVC
1. 定义用以表示数据的bean
2. 使用一个servlet处理请求
– servlet读取请求参数,检查数据的缺失或异常等。
3. 填充bean
– 该servlet调用业务逻辑(与具体应用相关的代码)或数据访问代码得到最终的结果。得出的结果被放在第一步中定义的bean中。
4. 将bean存储在请求、会话或servlet的上下文中
– 该servlet调用请求、会话或servlet上下文对象的setAttribute存储表达请求结果的bean的引用。
5. 将请求转发到JSP页面
– 该servlet确定哪个JSP页面适合于处理当前的情形,并使用RequestDispatcher的forward方法将控制转移到那个页面。
6. 从bean中提取数据
– JSP页面使用jsp:useBean和与第4步匹配的位置访问之前存储的bean,然后使用jsp:getProperty输出bean的属性。
– JSP页面并不创建或修改bean;它只是提取并显示由servlet创建的数据。
 
RequestDispatcher dispatcher =
request.getRequestDispatcher(address);
dispatcher.forward(request, response);
 
14.2 jsp:useBean在MVC中的使用与在独立JSP页面中有什么不同
• JSP页面不应该创建对象
– 应该由servlet,而非JSP页面,创建所有的数据对象。因此,为了保证JSP页面不会创建对象,我们应该使用<jsp:useBean ... type="package.Class" />而不是<jsp:useBean ... class="package.Class" />
• JSP页面也不应该修改已有的对象
– 因此,我们应该只使用jsp:getProperty,不使用jsp:setProperty。
 
14.3 基于请求的数据共享
• Servlet
ValueObject value = new ValueObject(...);
request.setAttribute("key", value);
RequestDispatcher dispatcher =request.getRequestDispatcher("/WEB-INF/SomePage.jsp");
dispatcher.forward(request, response);
• JSP
<jsp:useBean id="key" type="somePackage.ValueObject" scope="request" />
<jsp:getProperty name="key" property="someProperty" />
 
14.4 基于会话的数据共享
• Servlet
ValueObject value = new ValueObject(...);
HttpSession session = request.getSession();
session.setAttribute("key", value);
RequestDispatcher dispatcher =request.getRequestDispatcher("/WEB-INF/SomePage.jsp");
dispatcher.forward(request, response);
• JSP
<jsp:useBean id="key" type="somePackage.ValueObject" scope="session" />
<jsp:getProperty name="key" property="someProperty" />
 
14.5 基于ServletContext的数据共享
• Servlet
synchronized(this) {
ValueObject value = new ValueObject(...);
getServletContext().setAttribute("key", value);
RequestDispatcher dispatcher =request.getRequestDispatcher("/WEB-INF/SomePage.jsp");
dispatcher.forward(request, response);
}
• JSP
<jsp:useBean id="key" type="somePackage.ValueObject" scope="application" />
<jsp:getProperty name="key" property="someProperty" />
 
15. Expression Language
15.1 启用表达式语言
• 仅能够用于支持JSP 2.0 (servlets 2.4) 的服务器中
–例如:Tomcat 5,而非Tomcat 4
• 必须在web.xml文件中使用JSP 2.0
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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 web-app_2_4.xsd"
version="2.4">

</web-app>
 
15.2 访问作用域变量
• ${varName}
– 表示在PageContext,HttpServletRequest,HttpSession,和ServletContext中,依照所列的顺序进行查找,输出这个属性名所对应的对象。
– PageContext不适用于MVC。
• 等价的形式
– ${name}
– <%= pageContext.findAttribute("name") %>
– <jsp:useBean id="name" type="somePackage.SomeClass" scope="...">
<%= name %>
 
15.3 访问bean的属性
• ${varName.propertyName}
– 表示查找给定名称的作用域变量,并输出指定的bean属性
• 等价的形式
– ${customer.firstName}
– <%@ page import="coreservlets.NameBean" %>
<%NameBean person =(NameBean)pageContext.findAttribute("customer");%>
<%= person.getFirstName() %>
 
15.4 访问集合
• ${attributeName[entryName]}
• Works for
– Array. Equivalent to
• theArray[index]
– List. Equivalent to
• theList.get(index)
– Map. Equivalent to
• theMap.get(keyName)
• Equivalent forms (for HashMap)
– ${stateCapitals["maryland"]}
– ${stateCapitals.maryland}
– But the following is illegal since 2 is not a legal var name
• ${listVar.2}
 
15.5 引用隐式对象(预定义变量名)
• pageContext. The PageContext object.
– E.g. ${pageContext.session.id}
• param and paramValues. Request params.
– E.g. ${param.custID}
• header and headerValues. Request headers.
– E.g. ${header.Accept} or ${header["Accept"]}
– ${header["Accept-Encoding"]}
• cookie. Cookie object (not cookie value).
– E.g. ${cookie.userCookie.value} or ${cookie["userCookie"].value}
• initParam. Context initialization param.
• pageScope, requestScope, sessionScope, appliationScope.
– Instead of searching scopes.
• Problem
– Using implicit objects usually works poorly with MVC model

posted @ 2007-03-06 17:19 刘璐 阅读(324) | 评论 (0)编辑 收藏

几种所见所得的在线编辑器

 1.FCKeditor 编辑器

FCKeditor is compatible with most internet browsers which include: IE 5.5+ (Windows), Firefox 1.0+, Mozilla 1.3+ and Netscape 7+.
最新版本:FCKeditor 2.0
语言环境:多国语言
特性功能:所见所得,支持平台众多,支持XHTML 1.0,文本格式设置,常见的编辑,复制,粘贴,撤销,文件上传等功能
授权方式:Open Source & Free
官方地址:http://www.fckeditor.net/
下载地址:http://www.fckeditor.net/download/default.html
演示:http://www.fckeditor.net/demo/default.html
小节:FCKeditor的2.0版比1.6版有了许多的改进。首先是FCKeditor的文件结构更加清晰,
可以更方便地将其部署在自己的系统中。另外2.0版开始支持了Firefox浏览器

2.WebEditor
WebEditor系列文档控件,采用了ESS专业电子签名和电子盖章的技术,除继续支持手写签名,电子印章,痕迹保留外,
该版本采用了我们强大的B/S通讯技术,能够自由的将服务器数据库

最新版本:WebEditor[V4.3.0.0 OCX版]
语言环境:简体中文
技术特性
采用IE5.0以上版本内置的传输协议,通讯性能稳定,高效。
采用标准HTTP通讯端口80,使用方便,无需特别配置、安全性好、更无需特别设置防火墙。
采用插件技术,可以完全融入IE浏览器中,方便系统维护、方便用户操作。
采用自定义协议包进行数据的组装和分析,系统开放性好,便于二次开发。
采用了Com组件和JavaBean组件技术,很好的实现了跨平台系统,便于开发。
官方地址:http://www.dragongod.com/
演示:http://www.dragongod.com/WebEditor/Demo.asp
下载:http://www.dragongod.com/download/index.asp#item1
小节:这个编辑器很具有中国特色的,但必须支持安装WebEditor插件,才能正常运行
对于很多用户来说,,这个是无法接收的.

3.InnovaStudio WYSIWYG Editor在线编辑器WYSIWYG 英文:What You See Is What You Get(所见所得)
一款基于web的WYSIWYG(所见即所得)的HTML编辑器,支持ASP, PHP, ASP.NET
最新版本:InnovaStudio WYSIWYG Editor version 2.9
功能特性:快速加载,融合所有的HTML标签,全屏编辑模式,所见所得预览,拼写检查.
特有的嵌入式WEB编辑.
官方:http://www.innovastudio.com/
演示:http://www.innovastudio.com/editor_tutorial.asp
下载:官方好像都是收费的,,自己去网上搜索找找,好像有人已经做过有2.0的汉化版


小节:采用DHTML, JavaScript 构件的WYSIWYG也有很多不足之处,由于代码放在远程服务器,
一些控件事先没有下载到客户端,使得一些操作速度变慢,那些加入了ActiveX 控件的更慢,
另外它无法在网页里直接调用CSS,所以在操作框里的界面效果和尺寸和实际上的界面有差距。
有很多WYSIWYG对各种浏览器还不兼容,而且有的也要在IE的高版本下才能正常运行(此段出自老鬼的blog).


4.eWebEditor在线编辑器

eWebEditor是基于网页的、所见即所得的在线HTML编辑器。她能够在网页上实现许多桌面编辑软件
(如:Word)所具有的强大可视编辑功能;她是一个真正的绿色软件,不需要在计算机上安装
任何的客户端软件;她的易用使得WEB开发人员只要一行代码即可完成调用。
最新版本:Version 3.6 (ASP版、PHP版、JSP版、ASP.NET版)
官方:http://www.ewebsoft.com/
演示:http://ewebeditor.webasp.net/demo.asp
下载:http://ewebeditor.webasp.net/download.asp
功能特性:
纯静态:大大提高了效率,最佳的融入您的产品;
多样式支持:您只需要在您的整个网站中使用一个eWebEditor;
自动提交:无需在提交按钮中加入脚本进行处理;
自动获取远程文件:把别人服务器上的图片等文件自动传到自己的服务器;
相对或绝对路径:完全摆脱了站点或系统或目录迁移而带来的路径问题;
图形热点链接:始创能够在线编辑实现图形的热点链接功能;
多种编辑模式:HTML代码模式、可视设计模式、纯文本模式、预览模式;
大小自适应:可以按你调用的宽度和高度自适应编辑器的大小;
内附文件上传功能:自带有从客户端直接上传到服务器端的功能;
完善的接口:您可以通过接口实时控制或者获取编辑器的内容或者状态;


小节:我个人比较推荐这个编辑器,为什么呢?除了他是国产的外,比较符合国人的习惯,另外也可能是众多编辑器,
唯一还带后台管理模式,可以直接在后台管理编辑模式,
另外接口定制非常容易,具体的自己可以去官方下载回来看看.


5.htmlarea
htmlarea.com is a directory of browser based wysiwyg editor components for use
in CMS (content management software) and other web software.
htmlArea是一款非常优秀的所见即所得编辑器,被使用在CMS系统或其他的WEB系统中..
语言环境:多国语言版
版本:version 3.0
官方:http://www.htmlarea.com/
演示:没找到,
下载:官方没找到下载地址,我自己找了个3.0的版本 http://www.92key.com/mp3/chinahtml@_HTMLArea3.rar
小节:这个编辑器调用也非常方便,
已经广泛用在各种CMS和blog系统里,记得bo-blog,phpArticle里整合了这个编辑器.


6.XStandard XHTML (Strict or 1.1) WYSIWYG Editor 也是所见所得,比起前几个,,它已经完全支持XHTML 1.1的严格验证,
这个非常厉害 @@
版本:XStandard Version 1.6.2
官方:http://xstandard.com/
下载:http://xstandard.com/download.asp
演示:
小节:这个编辑器,我也很少在国内的CMS,blog上见到有人使用,当然一个原因就是它是一个共享软件.
XStandard Lite是免费的
XStandard Pro是30天的试用期,而且价格还不菲$179


7.Cross-Browser Rich Text Editor
The cross-browser rich-text editor (RTE) is based on the designMode() functionality introduced in Internet Explorer 5, and implemented in Mozilla 1.3+ using the Mozilla Rich Text Editing API. The cross-browser rich-text editor now includes table support (as of 2/10/2005) as well as an option to generate xhtml-compliant code

官方:http://www.kevinroth.com
演示:http://www.kevinroth.com/rte/demo.htm
下载:http://www.kevinroth.com/rte/demo.htm

posted @ 2007-03-06 17:18 刘璐 阅读(269) | 评论 (0)编辑 收藏

转 J2EE开发之常用开源项目介绍

主要就我所了解的J2EE开发的框架或开源项目做个介绍,可以根据需求选用适当的开源组件进行开发.主要还是以Spring为核心,也总结了一些以前web开发常用的开源工具和开源类库
 
1持久层:
1)Hibernate
这个不用介绍了,用的很频繁,用的比较多的是映射,包括继承映射和父子表映射
对于DAO在这里介绍个在它基础上开发的包bba96,目前最新版本是bba96 2.0它对Hibernate进行了封装, 查询功能包括执行hsql或者sql查询/更新的方法,如果你要多层次逻辑的条件查询可以自己组装QueryObject.可以参考它做HibernateDAO.也可以直接利用它
2) iBATIS
另一个ORM工具,Apache的,没有Hibernate那么集成,自由度比较大
2:SpringMVC
       原理说明和快速入门:
       配置文件为:
Spring的配置文件默认为WEB-INF/xxxx-servelet.xm其中xxx为web.xml中org.springframework.web.servlet.DispatcherServlet的servlet-name。
       Action分发:
Spring将按照配置文件定义的URL,Mapping到具体Controller类,再根据URL里的action= xxx或其他参数,利用反射调用Controller里对应的Action方法。
输入数据绑定:
Spring提供Binder 通过名字的一一对应反射绑定Pojo,也可以直接从request.getParameter()取数据。
输入数据验证
Sping 提供了Validator接口当然还可以使用开源的Commons-Validaor支持最好
Interceptor(拦截器)
Spring的拦截器提供接口需要自己编写,在这点不如WebWork做的好.全面
       (这里提一下WebWork和Struts的区别最主要的区别在于WebWork在建立一个Action时是新New一个对象而Struts是SingleMoule所有的都继承它的一个Action,所以根据项目需要合适的选择.)
3:View层
1) 标签库:JSP2.0/JSTL
由于Webwork或Spring的标签确实很有限,一般view层用JSTL标签,而且据说JSTL设计很好速度是所有标签中最快的使用起来也很简单
 
2) 富客户端:DOJO Widgets, YUI(YahooUI),FCKEditor, Coolest日历控件
Dojo主要提供Tree, Tab等富客户端控件,可以用其进行辅助客户端开发
YahooUI和DOJO一样它有自己的一套javascript调试控制台,主要支持ajax开发也有很多Tree,Table,Menu等富客户端控件
FCKEditor 最流行的文本编辑器
Coolest日历控件 目前很多日历控件可用,集成在项目中也比较简单,这个只是其中的一个,界面不错的说..
 
3) JavaScript:Prototype.js
Prototype.js作为javascript的成功的开源框架,封装了很多好用的功能,通过它很容易编写AJAX应用,现在AJAX技术逐渐成熟,框架资源比较丰富,比如YUI,DWR等等,也是因为JavaScript没有合适的调试工具,所以没有必要从零开始编写AJAX应用,个人认为多用一些成熟的Ajax框架实现无刷新更新页面是不错的选择.
 
4)表格控件:Display Tag ,Extreme Table
这两个的功能差不多,都是View层表格的生成,界面也比较相向,可以导出Excel,Pdf,对Spring支持很容易.
相比较而言比较推荐ExtremeTable,它的设计很好功能上比DisplayTag多一些,支持Ajax,封装了一些拦截器,而且最方面的是在主页wiki中有详细的中文使用文档.
 
5):OSCache
OSCache是OpenSymphony组织提供的一个J2EE架构中Web应用层的缓存技术实现组件,Cache是一种用于提高系统响应速度、改善系统运行性能的技术。尤其是在Web应用中,通过缓存页面的输出结果,可以很显著的改善系统的稳定性和运行性能。
它主要用在处理短时间或一定时间内一些数据或页面不会发生变化,或将一些不变的统计报表,缓冲在内存,可以充分的减轻服务器的压力,防治负载平衡,快速重启服务器(通过硬盘缓存).
 
6)SiteMesh
sitemesh应用Decorator模式主要用于提高页面的可维护性和复用性,其原理是用Filter截取request和response,把页面组件head,content,banner结合为一个完整的视图。通常我们都是用include标签在每个jsp页面中来不断的包含各种header, stylesheet, scripts and footer,现在,在sitemesh的帮助下,我们删掉他们轻松达到复合视图模式.
Sitemesh也是 OpenSymphony的一个项目现在最近的版本是2.2,目前OpenSymphony自从04年就没有更新的版本了..感觉它还是比较有创新的一种页面组装方式, OpenSymphony开源组织的代码一般写的比较漂亮,可以改其源代码对自己的项目进行适配.
测试发现Sitemesh还存在一些问题,比如中文问题,它的默认编码是iso-8859-1在使用时候需要做一些改动.
 
7)CSS,XHTML
这个不用说了,遵循W3C标准的web页面开发.
 
8)分页标签: pager-taglib组件
Pager-taglib 是一套分页标签库,可以灵活地实现多种不同风格的分页导航页面,并且可以很好的与服务器分页逻辑分离.使用起来也比较简单.
 
9)Form: Jodd Form taglib
Jodd Form taglib使用比较简单,只要把<form>的头尾以<jodd:form bean= "mybean">包住
就会自动绑定mybean, 自动绑定mybean的所有同名属性到普通html标记input, selectbox, checkbox,radiobox.....在这些input框里不用再写任何代码…
      
10)Ajax:DWR
       J2EE应用最常用的ajax框架
      
       11)报表 图表
Eclipse BIRT功能比较强大,也很庞大..好几十M,一般没有特别需求或别的图表设计软件可以解决的不用它
JasperReports+ iReport是一个基于Java的开源报表工具,它可以在Java环境下像其它IDE报表工具一样来制作报表。JasperReports支持PDF、HTML、XLS、CSV和XML文件输出格式。JasperReports是当前Java开发者最常用的报表工具。
JFreeChart主要是用来制作各种各样的图表,这些图表包括:饼图、柱状图(普通柱状图以及堆栈柱状图)、线图、区域图、分布图、混合图、甘特图以及一些仪表盘等等。
      琴棋报表,国产的..重点推荐,适合中国的情况,开放源代码,使用完全免费。纯JAVA开发,适用多种系统平台。特别适合B/S结构的系统。官方网站有其优点介绍,看来用它还是不错的选择,最重要的是支持国产呵呵
 
4:权限控制: Acegi
Acegi是Spring Framework 下最成熟的安全系统,它提供了强大灵活的企业级安全服务,如完善的认证和授权机制,Http资源访问控制,Method 调用访问控制等等,支持CAS
(耶鲁大学的单点登陆技术,这个单点登陆方案比较出名.我也进行过配置使用,可以根据项目需要,如果用户分布在不同的地方不同的系统通用一套登陆口令可以用它进行解决,一般注册机登陆机就是这样解决的)
       Acegi只是于Spring结合最好的安全框架,功能比较强大,当然还有一些其他的安全框架,这里列举一些比较流行的是我从网上找到的,使用方法看其官方文档把…
JAAS, Seraph, jSai - Servlet Security, Gabriel, JOSSO, Kasai, jPAM, OpenSAML都是些安全控制的框架..真够多的呵呵
 
5:全文检索
       1) Lucene
       Lucene是一套全文索引接口,可以通过它将数据进行倒排文件处理加入索引文件,它的索引速度和查询速度是相当快的,查询百万级数据毫秒级出结果,现在最火的Apache开源项目,版本更新速度很快现在已经到了2.0,每个版本更新的都比较大,目前用的最多的版本应该是1.4.3,但它有个不太方面的地方单个索引文件有2G文件限制,现在2.0版本没有这个限制,我研究的比较多,它的扩展性比较好,可以很方面的扩充其分词接口和查询接口.
       基于它的开发的系统很多,比如最常用的Eclipse的搜索功能,还有一些开源的软件比如Compass,Nutch,Lius,还有我最近做的InSearch(企业级FTP文件网页搜索)
6:公共Util类
       主要是Jakarta-Commons类库,其中最常用得是以下几个类库
1) Jakarta-Commons-Language
       最常用得类是StringUtils类,提供了使用的字符串处理的常用方法效率比较高
2) Jakarta-Commons-Beantuils
       主要用Beantuils能够获得反射函数封装及对嵌套属性,map,array型属性的读取。
3) Jakarta-Commons-Collections
       里面有很多Utils方法
 
7 日志管理
       Log4J
       任务是日志记录,分为Info,Warn,error几个层次可以更好的调试程序
 
8 开源的J2EE框架
       1) Appfuse
              Appfuse是Matt Raible 开发的一个指导性的入门级J2EE框架, 它对如何集成流行的Spring、Hibernate、iBatis、Struts、Xdcolet、JUnit等基础框架给出了示范. 在持久层,AppFuse采用了Hibernate O/R映射工具;在容器方面,它采用了Spring,用户可以自由选择Struts、Spring/MVC,Webwork,JSF这几个Web框架。
      
       2) SpringSide
       .SpringSide较完整的演示了企业应用的各个方面,是一个电子商务网站的应用 SpringSide也大量参考了Appfuse中的优秀经验。最重要的是它是国内的一个开源项目,可以了解到国内现在的一些实际技术动态和方向很有指导意义…
 
9:模版 Template
主要有Veloctiy和Freemarker
模板用Servlet提供的数据动态地生成 HTML。编译器速度快,输出接近静态HTML             页面的速度。
 
10:工作流
       我所知道比较出名的主要有JBpm Shark Osworkflow,由于对它没有过多的研究所以还不是很清楚之间有什么区别.
 
项目管理软件
dotProject:是一个基于LAMP的开源项目管理软件。最出名的项目管理软件
JIRA: 项目计划,任务安排,错误管理
Bugzilla:提交和管理bug,和eclipse集成,可以通过安装MyEclipse配置一下即可使用
BugFree借鉴微软公司软件研发理念、免费开放源代码、基于Web的精简版Bug管理
CVS:这个就不介绍了都在用.
SVN: SubVersion已逐渐超越CVS,更适应于JavaEE的项目。Apache用了它很久后,Sourceforge刚刚推出SVN的支持。
测试用例:主要JUnit单元测试,编写TestCase,Spring也对Junit做了很好的支持
 
后记:
       以Spring为主的应用开发可选用的组件中间件真是眼花缭乱,所以针对不同的项目需求可以利用不同的开源产品解决,比如用Spring+Hibernate/ iBATIS或Spring+WebWork+Hibernate/ iBATIS或Spring+Struts+Hibernate/ iBATIS,合理的框架设计和代码复用设计对项目开发效率和程序性能有很大的提高,也有利于后期的维护.

posted @ 2006-12-28 17:36 刘璐 阅读(322) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(9)--基础进阶(4)

 

      到目前为止,我们都是使用BeanFactory接口以及它的子接口来使用Spring,尽管使用BeanFactory也是一种不错的方式,但正如前面看到的,有些时候,我们还是不得不自己编写代码来调用诸如preInstantiateSingletons,postProcessBeanFactory等方法。
      为了使用的方便,Spring提供了BeanFactory的一个扩展:ApplicationContext。使用ApplicationContext,我们可以减少需要编写的代码的数量,并且ApplicationContext也增加了一些新的功能。如果在Web工程中使用Spring,我们甚至可以让Spring自动加载ApplicationContext,而无需自己编写代码来创建它。
      ApplicationContext具备一些BeanFactory不具备的功能:国际化(Internationalization),事件发布(Event publication),资源管理和访问(Resource management and access),更多的生命周期控制接口(Additional lifecycle interfaces)以及底层组件的自动配置(Improved automatic configuration of infrastructure components)。
      Spring为ApplicationContext接口提供了三种实现:FileSystemXmlApplicationContext,ClasspathXmlApplicationContext和XmlWebApplicationContext。其中XmlWebApplicationContext是专为Web工程定制的,并且我们可以使用ContextLoaderListener或ContextLoaderServlet来自动加载ApplicationContext配置。
      通常国际化是通过使用MessageSource接口来实现的,为了在ApplicationContext中使用MessageSource,我们需要配置一个类型为MessageSource,名称为messageSource的bean。
<beans>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>buttons</value>
                <value>labels</value>
            </list>
        </property>
     </bean>
</beans>
      在配置了messageSource后,我们就可以使用ApplicationContext的getMessage方法来获取资源内容了。
      ApplicationContext的另一个重要特性就是对发布和接收事件的支持,ApplicationContext会在其被配置的时候自动注册所有的侦听器(listener),事件的发布是通过ApplicationContext的publishEvent方法来实现的。
//Event class
public class MessageEvent extends ApplicationEvent {
    private String msg;
    public MessageEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }
    public String getMessage() {
        return msg;
    }
}
//Listener class
public class MessageEventListener implements ApplicationListener {
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof MessageEvent) {
            MessageEvent msgEvt = (MessageEvent)event;
            System.out.println("Received: " + msgEvt.getMessage());
        }
    }
}
//Publish class
public class Publisher implements ApplicationContextAware {
    private ApplicationContext ctx;
    public static void main(String[] args) {
        ApplicationContext ctx =
                new FileSystemXmlApplicationContext("./ch5/src/conf/events/events.xml");
        Publisher pub = (Publisher) ctx.getBean("publisher");
        pub.publish("Hello World!");
        pub.publish("The quick brown fox jumped over the lazy dog");
    }
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.ctx = applicationContext;
    }
    public void publish(String message) {
        ctx.publishEvent(new MessageEvent(this, message));
    }
}
<beans>
    <bean id="publisher" class="com.apress.prospring.ch5.event.Publisher"/>
    <bean id="messageEventListener" class="com.apress.prospring.ch5.event.MessageEventListener"/>
</beans>
      资源访问是我们经常碰到的,这些资源有可能存在于一个文件中,类路径中的一个jar包中,抑或是在远程服务器上。Spring为我们提供了统一的,协议无关的方式来访问各种各样的资源,这给我们访问各种资源提供了极大的方便。
public class ResourceDemo {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx =
                new FileSystemXmlApplicationContext("./ch5/src/conf/events/events.xml");
        Resource res1 = ctx.getResource("
file:///d:/tmp/test.txt ");
        displayInfo(res1);
        Resource res2 = ctx.getResource("classpath:lib/commons-logging.jar");
        displayInfo(res2);
        Resource res3 = ctx.getResource("
http://www.google.co.uk ");
        displayInfo(res3);
    }
    private static void displayInfo(Resource res) throws Exception {
        System.out.println(res.getClass());
        System.out.println(res.getURL().getContent());
        System.out.println("");
    }
}

posted @ 2006-12-21 10:25 刘璐 阅读(284) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(8)--基础进阶(3)

 

      如果bean无法简单地通过new关键字来创建怎么办,通常碰到这样的情况时,我们都会使用工厂模式来处理,Spring也提供了对FactoryBean的支持,当我们配置了一个bean为FactoryBean时,随后我们获取的则并不是该FactoryBean,Spring会通过调用FactoryBean.getObject()方法,返回真正的目标bean。FactoryBean在Spring中的最著名应用就是对声明式事务的处理。
      在Spring中使用FactoryBean,我们需要编写一个实现了FactoryBean接口的类,以一个信息摘要FactoryBean为例,它主要实现根据不同的参数,创建不同的MessageDigest的实例。
public class MessageDigestFactoryBean implements FactoryBean, InitializingBean {
    private String algorithmName = "MD5";
    private MessageDigest messageDigest = null;
    public Object getObject() throws Exception {
        return messageDigest.clone();
    }
    public Class getObjectType() {
        return MessageDigest.class;
    }
    public boolean isSingleton() {
        return true;
    }
    public void afterPropertiesSet() throws Exception {
        messageDigest = MessageDigest.getInstance(algorithmName);
    }
    public void setAlgorithmName(String algorithmName) {
        this.algorithmName = algorithmName;
    }
}
      getObject方法是供Spring调用,用来返回真正的bean给其它bean的(而不是FactoryBean本身),getObjectType方法可以返回null,但如果指定了类型,就可以使用Spring的自动装载功能了。isSingleton方法是指定bean是否是单例的,注意不能通过FactoryBean的配置文件来指定bean是否为单例,因为那样指定的是FactoryBean本身,而不是真正的目标bean。
      FactoryBean的配置和普通bean并没有什么区别。
      由于Bean配置文件中,各个属性都是以String的形式配置的(除了使用ref引用其它bean外),因此,Spring在组装bean的时候,需要把String类型的值转化成合适的类型,这就需要用到JavaBean中的概念:PropertyEditor
      Spring内置了7种预先注册的PropertyEditor:ByteArrayPropertyEditor,ClassEditor,FileEditor,LocaleEditor,PropertiesEditor,StringArrayPropertyEditor,URLEditor。通过名字,我们就能清楚地知道它们对应的类型了。
      尽管内置的PropertyEditor可以处理大部分常见的类型,我们仍然会碰到需要创建自己的PropertyEditor的情况。为了简化自定义PropertyEditor的创建,Spring提供了PropertyEditorSupport类,我们只需要扩展该类,并实现其中的setAsText方法即可。
public class PatternPropertyEditor extends PropertyEditorSupport {
    public void setAsText(String text) throws IllegalArgumentException {
        Pattern pattern = Pattern.compile(text);
        setValue(pattern);
    }
}
      可以看到,实现一个自定义的PropertyEditor是很容易的,但怎么才能让它起作用呢,也就是通常所说的注册PropertyEditor。Spring提供了两种注册的方式:1.通过ConfigurableBeanFactory的registerCustomEditor方法;2.在BeanFactory的配置文件中定义CustomEditorConfigurer。
<bean name="customEditorConfigurer"
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="java.util.regex.Pattern">
                <bean class="com.apress.prospring.ch5.pe.PatternPropertyEditor"/>
            </entry>
        </map>
    </property>
</bean>
      自定义的PropertyEditor是通过CustomEditorConfigurer的一个类型为Map的属性添加的,key值是自定义PropertyEditor对应的类型的全类名。
      在使用时需要先调用:
CustomEditorConfigurer config =
    (CustomEditorConfigurer) factory.getBean("customEditorConfigurer");
config.postProcessBeanFactory(factory);
      来将所有自定义的ProperyEditor注册到BeanFactory中。

posted @ 2006-12-21 10:24 刘璐 阅读(335) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(7)--基础进阶(2)

 

      Spring1.1中引入了一个很有用的IOC新特性--方法注入(Method Injection)。这能让我们在组装bean时获得极大的灵活性。Spring的方法注入可以分为两种形式:查询方法注入(Lookup Method Injection)和方法替换(Method Replacement)。查询方法注入提供了另一种机制来让bean获取它依赖的其它bean,方法替换则可以在不修改源代码的基础上,修改任意bean的任何方法的实现。为了提供这两个功能,Spring借助了CGLIB包。
      先来看看查询方法注入的使用。想象这样的情形:我们有一个singleton类型的bean A,它需要使用另一个非singleton类型的bean B来执行一些操作。由于两个bean的生命周期是不同的,因此我们不能简单的在bean A的配置中使用ref标签来要求Spring注入bean B,因为那样会让我们每次取得bean A时都使用同一个bean B的实例。回想前面介绍过的BeanFactoryAware接口,我们可以让bean A实现该接口,这样就可以在bean A内部使用beanFactory的genBean方法来获取bean B了。只要我们将bean B配置为非singleton类型,每次使用getBean方法就会得到一个新的bean B的实例。
      使用查询方法注入,我可以不必实现任何的Spring的接口,也不需要在bean A中显示的使用getBean方法来获得bean B。我们只需要在bean A中申明一个查询方法,然后在bean A的配置文件中指明该查询方法,那么Spring就会自动的将bean B注入到bean A中去了。由于查询方法注入的概念相对比较复杂,因此我们还是通过具体的例子来直观的感受它是如何工作的。
      在这个例子中,我们创建一个非singleton的bean和两个实现了同一个接口的singleton的bean,一个通过传统的设值方法注入获得非singleton的bean,另一个则通过查询方法注入。
//The MyHelper Bean
public class MyHelper {
    public void doSomethingHelpful() {
        // do something!
    }
}
//The DemoBean Interface
public interface DemoBean {
    public MyHelper getMyHelper();
    public void someOperation();
}
//The StandardLookupDemoBean
public class StandardLookupDemoBean implements DemoBean {
    private MyHelper helper;
    public void setMyHelper(MyHelper helper) {
        this.helper = helper;
    }
    public MyHelper getMyHelper() {
        return this.helper;
    }
    public void someOperation() {
        helper.doSomethingHelpful();
    }
}
//The AbstractLookupDemoBean
public abstract class AbstractLookupDemoBean implements DemoBean {
    public abstract MyHelper getMyHelper();
    public void someOperation() {
        getMyHelper().doSomethingHelpful();
    }
}
<beans>
    <bean id="helper" class="com.apress.prospring.ch5.mi.MyHelper" singleton="false"/>
    <bean id="abstractLookupBean" 
class="com.apress.prospring.ch5.mi.AbstractLookupDemoBean">
        <lookup-method name="getMyHelper" bean="helper"/>
    </bean>
    <bean id="standardLookupBean"
    class="com.apress.prospring.ch5.mi.StandardLookupDemoBean">
        <property name="myHelper">
            <ref local="helper"/>
        </property>
    </bean>
</beans>
      可以看到,我们使用了lookup-method来对查询方法注入进行配置,name属性告诉Spring需要覆写bean中的哪个方法,该方法必须是没有参数的,并且返回类型是我们需要获得的bean的类型,bean属性告诉Spring查询方法需要返回哪个bean。
      通过DemoBean standardBean = (DemoBean)factory.getBean("standardLookupBean");获得standardBean,然后比较standardBean.getMyHelper() == standardBean.getMyHelper(),可以发现,直接使用设置方法注入,每次获得的非singleton的bean实际上是同一个实例。而使用查询方法注入,则会每次得到一个新的实例,这实际上是因为我们使用factory.getBean("abstractLookupBean")获得abstractBean时,Spring根据lookup-method的配置,覆写了查询方法,从而返回一个新的bean实例,正如前面提到的,这是通过CGLIB包来实现的。
      值得注意的是,我们将查询方法定义为抽象的,这不是必须的,但这么做能防止我们忘记了配置lookup-method,从而使用了空的(当然不是我们期望的)查询方法。
      好了,让我们再来看看方法替换,虽然它被划分为方法注入的一种,但它和我们以前接触到的注入方式有着很大的差异,以前我们都是注入一个bean,而方法替换则是替换bean中的方法实现。同查询方法注入一样,我们还是通过一个例子来理解方法替换的使用。
//The ReplacementTarget Class
public class ReplacementTarget {
    public String formatMessage(String msg) {
        return "<h1>" + msg + "</h1>";
    }
    public String formatMessage(Object msg) {
        return "<h1>" + msg + "</h1>";
    }
}
     现在由于某些原因,我们需要修改以String为参数的方法的实现方式,并且不能修改ReplacementTarget类的源代码,借助于Spring的方法替换,我们可以轻松地实现。
     首先,创建一个实现了MethodReplacer接口的类。
public class FormatMessageReplacer implements MethodReplacer {
    public Object reimplement(Object target, Method method, Object[] args)
throws Throwable {
        ...
        String msg = (String) args[0];
        return "<h2>" + msg + "</h2>";
        ...
    }
}
<beans>
    <bean id="methodReplacer"
    class="com.apress.prospring.ch5.mi.FormatMessageReplacer"/>
    <bean id="replacementTarget"
    class="com.apress.prospring.ch5.mi.ReplacementTarget">
        <replaced-method name="formatMessage" replacer="methodReplacer">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>
    <bean id="standardTarget"
    class="com.apress.prospring.ch5.mi.ReplacementTarget"/>
</beans>
      通过replaced-method标签来配置需要替换的方法,name属性指明了需要替换的方法名,replacer属性指明了用哪个bean来实现替换操作。arg-type是用来指明需要替换的方法的参数类型的,在bean中有重载方法时(像ReplacementTarget类一样),指明参数类型可以让Spring明确知道需要替换的是哪个方法。值得注意的是,arg-type是模式匹配的,因此,String将匹配String和StringBuffer。
      方法替换在很多场合都是有用的,比如我们只想为一个特殊的bean改变它所依赖的另一个bean的实现方法,而不影响其它bean的引用。最好是为每个需要替换的方法,或者是一组重载的方法定义一个替换类,而不要为众多毫不相关的方法使用同一个替换类。这可以减少很多没必要的比较,从而提高代码的执行效率。

posted @ 2006-12-21 10:23 刘璐 阅读(420) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(6)--基础进阶(1)

 

      Spring不仅提供了基本的IOC功能,借助于BeanFactory以及其它组件,我们还可以使用其它一些更高级的功能。但是需要注意的是,使用这些高级功能,将不可避免的使得我们的工程依赖于Spring。这些高级功能大致包括:管理bean的生命周期,让bean感知Spring(Spring aware),使用方法注入,使用FactoryBean,使用PropertyEditor以及了解Spring的ApplicationContext
      有时候,我们需要在bean的生命周期中的某些阶段做一些相应的操作(类似于Servlet和EJB),像其它IOC容器一样,Spring也提供了管理bean生命周期的功能。两个主要的阶段是:
post-initialization(bean初始化之后)和pre-destruction(bean销毁之前).
      post-initialization是指在Spring完成了bean的所有依赖注入以及相应的检测之后,
      pre-destruction是指在Spring销毁bean之前。
      post-initialization和pre-destruction只能应用于sigleton模式的bean,因为非sigleton的bean的生命周期并不由Spring控制(它们是由JVM控制的)。
      Spring提供了两种机制来实现bean生命周期的管理:基于接口的和基于指定方法的。
      使用基于接口的机制,我们需要实现Spring的相关接口,然后,Spring会使用回调机制在指定的阶段调用接口中定义的方法,从而实现bean生命周期的管理。而使用基于指定方法的机制,我们需要在BeanFactory的配置中指定相应的方法名称,Spring会使用Java的反射机制在指定的阶段调用我们指定的方法,从而实现bean生命周期的管理。
      选择哪种机制取决于我们的工程的需要。基于接口的机制要求我们实现Spring的接口,这会带来对Spring的依赖,但是当我们定义了很多相同类型的bean时,使用基于接口的机制可以免去我们为每个bean定义指定相应方法的麻烦。基于指定方法的机制则可以让我们避免对Spring的依赖,这在当我们使用第三方的类库(因此,我们无法修改其代码来实现Spring的接口)时是非常有用的。
      以post-initialization为例:
      使用基于接口的机制,我们需要实现Spring的InitializingBean接口的afterPropertiesSet方法
      public class SimpleBeanWithInterface implements InitializingBean {
          ...
          public void afterPropertiesSet() throws Exception {
              ...
          }
          ...
      }
      <beans>
         <bean id="simpleBean1"
         class="com.apress.prospring.ch5.lifecycle.SimpleBeanWithInterface">
             ...
         </bean>
      </beans>
      使用基于指定方法的机制,我们只需在BeanFactory的配置中指定相应的方法名称
      <beans>
          <bean id="simpleBean1"
          class="com.apress.prospring.ch5.lifecycle.SimpleBean"
          init-method="init">
              ...
          </bean>
      </beans>

      SimpleBean中包含对应的init方法
      public class SimpleBean {
          ...
          public void init() {
              ...
          }
          ...
      }

      需要注意的是,init方法必须不包含任何参数,尽管我们可以为init方法指定任意的返回值,但Spring会忽略它,因此最好的做法是在init方法中抛出异常来表明初始化失败了。
      我们也可以同时使用这两种机制,这时,Spring会先调用InitializingBean接口的afterPropertiesSet方法,再调用我们自己指定的init方法。
      相对的,pre-destruction使用DisposableBean接口的destroy方法和bean标签的destroy-method属性
      相对于依赖查找(Dependency Lookup),依赖注入(Dependency Injection)最大的卖点就是容器的无关性,但是有时候,我们确实需要和容器进行一些交互,例如访问BeanFactory或者是获得bean的名称等。
      为了获得bean的名称,我们需要实现Spring的BeanNameAware接口,BeanNameAware只有一个方法,void setBeanName(String name),只要我们的bean实现了BeanNameAware接口,Spring就会在bean配置结束,其它生命周期相关的回调函数(initialization或者 destroy)调用之前,回调该方法置入bean的名称。
      类似的,为了访问beanFactory,我们需要实现Spring的BeanFactoryAware接口的
void setBeanFactory(BeanFactory factory)方法。

posted @ 2006-12-21 10:23 刘璐 阅读(345) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(5)--配置BeanFactory(2)

 

      BeanFactory中的每个bean都必须至少有一个唯一的名字,Spring为了灵活的支持各种情形,使用了相对复杂的命名体系。如果我们为bean指定了id属性,那么id属性的值将成为bean的名称,如果没有指定id属性,Spring将使用name属性的第一个值(多个值用逗号或者分号分隔)作为bean的名称,如果name属性也没有指定,Spring将使用bean的类名(包括package的完整类名)来作为bean的名称,对于多个bean使用相同类名的情况,Spring会在类名后面添加#2,#3等序号来加以区别。不管怎么说,使用id属性来指定bean的名称总是一个不错的选择。
     在Spring中,所有bean默认都是以单例(singleton)形态存在的,也就是说我们通过BeanFactory的getBean方法获取的同一名称的bean是完全相同的,即:beanFactory.getBean("name")==beanFactory.getBean("name")。更改bean的形态为非singleton(原型的,prototype)是很容易的,只需在bean标签中,指定singleton属性为false就可以了。
<bean id="nonSingleton" class="java.lang.String" singleton="false">
    ...
</bean>
      为了简化配置,Spring还提供了自动组装(Auto-Wiring)bean的功能,有四种自动组装方式:根据名称(byName),根据类型(byType),构造子(Constructor)以及自动侦测(autodetect)。
      使用根据名称自动组装时,Spring根据bean的属性的名称去寻找同名的bean配置。
      使用根据类型自动组装时,Spring根据bean的属性的类型去寻找同类型的bean配置,如果有多个同类型的bean配置,那么Spring将会抛出异常,以表示无法确定使用哪个。
      使用构造子自动组装和使用根据类型自动组装比较类似,需要注意的是,使用构造子自动组装时,Spring会匹配尽可能多的参数。例如,我们有一个bean拥有两个构造函数,第一个接收一个String类型的参数,第二个接收一个String类型和一个Integer类型的参数,这时,如果在beanFactory的配置文件中有一个String类型的bean配置和一个Integer类型的bean配置,那么Spring将会使用第二个拥有两个参数的构造函数。
       使用自动侦测组装时,Spring会在根据类型自动组装和根据构造子自动组装之间自动作出选择,依据是:如果bean拥有默认的(无参数的)构造函数,那么Spring使用根据类型自动组装的方式,否则,使用根据构造子自动组装的方式。
       有时候,我们可能会有多个bean拥有共同的类型,或是实现一些共同的接口,因此它们会有一些相同的配置,如果能将这些相同的配置提取出来,各个bean只要继承这些相同的配置,然后添加自己特有的配置,将会大大减少重复的配置,降低发生错误的可能。Spring的bean继承机制就是出于这样的目的而提供的。在Spring中,我们可以将这些相同的配置提取出来,配置成一个parent bean,然后各个bean(child bean)可以继承parent bean,在child bean中,可以覆写parent bean中的配置,也可以添加自己的配置。parent bean是通过child bean的bean标签的parent属性来指定的。
<bean id="inheritParent" class="com.apress.prospring.ch4.inheritance.SimpleBean">
    <property name="name">
        <value>Rob Harrop</value>
    </property>
    <property name="age">
        <value>22</value>
    </property>
</bean>
<bean id="inheritChild" class="com.apress.prospring.ch4.inheritance.SimpleBean"
parent="inheritParent">
    <property name="age">
        <value>35</value>
    </property>
</bean>
      需要注意的是,bean的继承体系并不完全等同于java的类继承体系,我们完全可以在数个拥有相同类型的bean上使用继承,这不会带来任何问题。因此,相对于java的继承体系来说,Spring 的bean继承体系更类似于模板类型。

posted @ 2006-12-21 10:22 刘璐 阅读(444) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(4)--配置BeanFactory(1)

 

      Spring使用spring-beans.dtd文件来定义BeanFactory的XML配置规范。可以在http://www.springframework.org/dtd/spring-beans.dtd找到该dtd文件,当然,Spring的下载文件中也已经包含了该dtd文件。它被放在dist文件夹中。
      配置文件的根元素是beans,每个组件使用bean元素来定义,bean元素可以有许多属性,其中有两个是必须的:idclass(这里说的id是必须的并不意味着在配置文件中必须指定id,后面会详细说明)。id表示组件的默认名称,class表示组件的类型。
      如果使用设值注入,则需要使用property子标签,来指定组件的属性。
<bean id="renderer" class="com.apress.prospring.ch2.StandardOutMessageRenderer">
    <property name="messageProvider">
        <ref local="provider"/>
    </property>
</bean>
      使用构造子注入时,则使用constructor-arg子标签,来指定构造函数的参数。
<bean id="provider" class="com.apress.prospring.ch4.ConfigurableMessageProvider">
    <constructor-arg>
        <value>This is a configurable message</value>
    </constructor-arg>
</bean>
      当构造函数有多个参数时,可以使用constructor-arg标签的index属性,index属性的值从0开始。
<bean id="provider" class="com.apress.prospring.ch4.ConfigurableMessageProvider">
    <constructor-arg index="0">
        <value>first parameter</value>
    </constructor-arg>
    <constructor-arg index="1">
        <value>second parameter</value>
    </constructor-arg>

</bean>
     在使用构造子注入时,需要注意的问题是要避免构造子冲突的情况发生。考虑下面的情况:
public class ConstructorConfusion {
    public ConstructorConfusion(String someValue) {
        System.out.println("ConstructorConfusion(String) called");
    }
    public ConstructorConfusion(int someValue) {
        System.out.println("ConstructorConfusion(int) called");
    }
}
     使用如下配置文件
<bean id="constructorConfusion" class="com.apress.prospring.ch4.ConstructorConfusion">
    <constructor-arg>
        <value>90</value>
    </constructor-arg>
</bean>
     那么,当实例化组件constructorConfusion时,将输出ConstructorConfusion(String) called,也就是说参数类型为String的构造函数被调用了,这显然不符合我们的要求。为了让Spring调用参数为int的构造函数来实例化组件constructorConfusion,我们需要在配置文件中明确的告诉Spring,需要使用哪个构造函数,这需要使用constructor-argtype属性。
<bean id="constructorConfusion" class="com.apress.prospring.ch4.ConstructorConfusion">
    <constructor-arg type="int">
        <value>90</value>
    </constructor-arg>
</bean>
     我们不仅可以构造单个BeanFactory,而且可以建立有继承关系的多个BeanFactory。只需要将父BeanFactory作为参数传给子BeanFactory的构造函数即可。
BeanFactory parent =
    new XmlBeanFactory(new FileSystemResource("./ch4/src/conf/parent.xml"));
BeanFactory child =
    new XmlBeanFactory(new FileSystemResource("./ch4/src/conf/beans.xml"), parent);
     如果子BeanFactory和父BeanFactory中含有名称相同的Bean,那么在子BeanFactory中使用
<ref bean="sameNameBean"/>引用的将是子BeanFactory中的bean,为了引用父BeanFactory中的bean,我们需要使用ref标签的parent属性,<ref parent="sameNameBean"/>
     为了注入集合属性,Spring提供了list,map,setprops标签,分别对应List,Map,Set和Properties,我们甚至可以嵌套的使用它们(List of Maps of Sets of Lists)。
<bean id="injectCollection" class="com.apress.prospring.ch4.CollectionInjection">
    <property name="map">
        <map>
            <entry key="someValue">
                <value>Hello World!</value>
            </entry>
            <entry key="someBean">
                <ref local="oracle"/>
            </entry>
        </map>
    </property>
    <property name="props">
        <props>
            <prop key="firstName">
                Rob
            </prop>
            <prop key="secondName">
                Harrop
            </prop>
        </props>
    </property>
    <property name="set">
        <set>
            <value>Hello World!</value>
            <ref local="oracle"/>
        </set>
    </property>
    <property name="list">
        <list>
            <value>Hello World!</value>
            <ref local="oracle"/>
        </list>
    </property>
</bean>

posted @ 2006-12-21 10:22 刘璐 阅读(577) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(3)--Spring中的IOC

 

      控制反转包括两种类型:依赖查找(Dependency Lookup)和依赖注入(Dependency Injection),我们主要讨论的是依赖注入。
      依赖注入可以分为两种类型:构造子注入(Constructor Dependency Injection)和设值注入(Setter Dependency Injection)
      构造子注入,组件的依赖关系是通过构造函数来提供的,每个依赖关系对应构造函数的各个参数,IOC容器在实例化组件时,通过构造函数注入依赖关系。
public class ConstructorInjection {
    private Dependency dep;
    public ConstructorInjection(Dependency dep) {
        this.dep = dep;
    }
}
构造子注入适用于在使用某个组件之前,确保所有的依赖关系都已注入。
      设值注入,组件的依赖关系是通过JavaBean形式的setter方法注入的,IOC容器通过调用相应的set方法注入依赖关系。
public class SetterInjection {
    private Dependency dep;
    public void setMyDependency(Dependency dep) {
        this.dep = dep;
    }
}
设值注入适用于需要给某些依赖关系提供默认值的情况。
      Spring IOC容器的核心是BeanFactory,BeanFactory负责管理组件以及组件间的依赖关系。术语bean在Spring中代表由容器管理的任何组件。Spring提供了两种常用的BeanFactory实现:DefaultListableBeanFactory和XmlBeanFactory。
      在BeanFactory中,每个bean都至少有一个名称,作为唯一标识,如果bean拥有多个名称的话,那么除第一个名称外的其它名称都将被认为是别名。
      DefaultListableBeanFactory可以使用PropertiesBeanDefinitionReader或者XmlBeanDefinitionReader(当然还可以是其它任何实现了BeanDefinitionReader接口的Bean定义读取器)来读取配置文件。
//使用PropertiesBeanDefinitionReader
private static BeanFactory getBeanFactory() throws Exception {
    // get the bean factory
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // create a definition reader
    PropertiesBeanDefinitionReader rdr = new PropertiesBeanDefinitionReader(factory);
    // load the configuration options
   
Properties props = new Properties();
    props.load(new FileInputStream("./ch2/src/conf/beans.properties"));
    rdr.registerBeanDefinitions(props);
    return factory;
}
//使用XmlBeanDefinitionReader
private static BeanFactory getBeanFactory() throws Exception {
    // get the bean factory
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // create a definition reader
    XmlBeanDefinitionReader rdr = new XmlBeanDefinitionReader(factory);
    // load the configuration options
    rdr.loadBeanDefinitions(new FileSystemResource("ch4/src/conf/beans.xml"));
    return factory;
}
      XmlBeanFactory继承了DefaultListableBeanFactory,在使用XML配置文件时可以简化代码的编写,这也是我们最常用的BeanFactory。
private static BeanFactory getBeanFactory() throws Exception {
    XmlBeanFactory factory =
        new XmlBeanFactory(new FileSystemResource("ch4/src/conf/beans.xml"));
    return factory;
}
      获得了BeanFactory对象后,我们就可以使用bean的名称来获取我们需要的组件对象了。
Oracle oracle = (Oracle)factoyr.get("oracle");

posted @ 2006-12-21 10:20 刘璐 阅读(300) | 评论 (0)编辑 收藏

《Pro Spring》学习笔记(2)--何为Spring

 

      Spring通常被称为是构造Java工程的轻量级框架。这其中包含了两方面的内容:首先,你可以使用Spring构造各种Java工程,而不是像Apache的Struts那样,仅仅局限于Web工程。其次,所谓轻量级框架,并不是指类的数量或是工程的规模是轻量级的,用Spring的话说,轻量级就意味着更少的影响,你可以很容易的修改你的程序来获得Spring提供的各种好处,也可以很容易的从你的工程中去除Spring。当然,需要注意的是,这仅仅是指Spring的核心组件。Spring还提供了很多其它的组件,像数据访问等,使用这些组件,将使得你的工程与Spring框架形成耦合,但是,相比于你获得的好处而言,这些耦合根本算不得什么。
      Spring框架的核心是基于控制反转(Invdersion Of Control, IOC)理论的,控制反转使得我们可以在外部来创建和管理组件之间的依赖关系。例如:类Foo依赖于一个Bar类型的实例来执行某些操作,传统的方式是,Foo使用new关键字或者从某个工厂类来创建一个Bar类的实例。然而,使用控制反转,一个Bar类型的实例将在运行时由外部程序提供。这种方式可以很形象的看作是在运行时,将依赖的组件实例注入到当前组件中。因此,在某些时候,控制反转也被称为依赖注入(Dependency Injection, DI)。在Spring中,你可以认为控制反转就等同于依赖注入。
      依赖注入给我们带来的好处包括:
      1.减少了组件关联的代码:你不再需要在组件内部自己创建需要使用的其它组件,它们会在运行时自动被注入进来,特别是当某些组件需要通过JNDI来获得,或是根本无法直接获得,例如远程资源时,DI带来的好处就更加明显了。
      2.将依赖关系移到代码外:这会带来两点好处,首先,你可以在外部方便的管理组件间的依赖关系,而不需要重新编译代码。其次,你可以方便的切换某些依赖的实现类,例如将PostgreSQL的DAO实现切换为Oracle的DAO实现。
      3.统一的依赖管理:传统的散布在工程各处的管理依赖关系的代码将不复存在,所有的依赖信息都集中在一个统一的地方,这使得维护变得容易,同时降低了发生错误的可能。
      4.提高了可测试性:你可以很容易的替换依赖类的实现方式,如使用mock实现来代替真正的实现方式。假设我们需要测试业务对象(Business Object, BO),我们就可以使用mock实现来代替真正的DAO实现,从而避免真正地访问数据库,这大大提高了测试的效率。
      5.遵循良好的设计方式:使用DI通常意味着遵循面向接口的设计方式。毫无疑问,面向接口是一种良好的设计方式。
      Spring框架还包括了以下这些方面的内容:
      1.面向方面的编程(Aspect Oriented Programming, AOP)
      2.数据访问(Accessing Data in Spring)
      3.事务管理(Managing Transactions)
      4.简化J2EE的整合(Simplifying and Integrating with J2EE)
      5.Web层的MVC框架(MVC in the Web Tier)
      6.对远程访问的支持(Remoting Support)
      7.邮件支持(Mail Support)
      8.任务安排的支持(Job Scheduling Support)
      9.简化的异常处理(Simplified Exception Handling)
      10.源代码级的元数据(Source Level Metadata)

posted @ 2006-12-21 10:20 刘璐 阅读(338) | 评论 (0)编辑 收藏

(转)《Pro Spring》学习笔记(1)--写在前面的话

不是刚开始接触Spring了,也用Spring做过一些东西。我的感觉是用Spring会有一种很爽的感觉,一切都是那么的简单高效,顺理成章。
      在网上也看到过很多牛人对Spring的评论文章,其中不乏一些颇有微词的评论。当然,任何事务都不可能十全十美的,我只是一个普通的Java程序员,还无法站在一定的高度,看到Spring这样那样的不足。我感觉到的只是Spring给我带来的好处和便利,因此我选择了Spring。
      接下来的文字,记录的只是在学习《Pro Spring》这本书的过程中,我觉得需要着重了解的部分,大部分文字属于抄书,外加一点点个人的理解。
      感谢Rob Harrop和Jan Machacek给我们带来了这本通俗易懂的Spring入门书籍。

posted @ 2006-12-21 10:19 刘璐 阅读(270) | 评论 (0)编辑 收藏

springIDE

首先尝试link方式安装,无法使用。上网查询后,网友都认为应该用updata方式安装

1.打开eclipse,菜单"help"->"software updates"->"find and install"
2.在弹出的界面中选择“Search for new features to install”
3.这一步可分成两种情况:
a.如果本地已经下载SpringIDE的压缩包,请解压到一个目录(比如SpringIDE),里面有两个目录(features、plugins)和一个文件site.xml,在弹出的界面中选择"New Local Site",指到前面的解压后目录(SpringIDE),点“OK”,在"Name"中输入“SpringIDE Update”
b.如果没有下载SpringIDE的压缩包,在弹出的界面中选择“New Remote Site”,在"Name"中输入“SpringIDE Update”,在“URL”中输入“http://springide.org/updatesite/”,点“OK”
4.选中"SpringIDE Update",点“finish”,开始搜索站点的更新信息
5.在搜索界面的界面中选中要更新的,继续
因为我的网络有防火墙隔离,所以直接下载了springide_updatesite_1.2.1.zip在本地安装。

安装过程中,出现error retrieving “feature.xml“的错误,这时候删除了根目录的site.xml文件,就可以updata了

另外,springIDE的使用方法可以参考 http://www.volitation.net/Dynamic/print.asp?SelectID=101

posted @ 2006-11-18 16:13 刘璐 阅读(535) | 评论 (0)编辑 收藏

Eclipse 插件集

Eclipse SQLExplorer plugin
一个数据库管理插件
http://sourceforge.net/projects/eclipsesql

Poperties Editor
 
http://propedit.sourceforge.jp/eclipse/updates/

eclipseME
 
http://eclipseme.org/updates/

Eclipse加速插件KeepResident
http://suif.stanford.edu/pub/keepresident/

 
MyEclipse  J2EE开发插件,支持SERVLET/JSP/EJB/数据库操纵等
www.myeclipseide.com
 
Properties Editor  编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html
http://propedit.sourceforge.jp/eclipse/updates/
 
Colorer Take  为上百种类型的文件按语法着色
http://colorer.sourceforge.net/
 
XMLBuddy 编辑xml文件
www.xmlbuddy.com
 
Code Folding  加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport
 
Easy Explorer  从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/
 
Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/
 
RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php
 
JasperAssistant 报表插件(强,要钱的)
http://www.jasperassistant.com/
 
Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/
 
Profiler 性能跟踪、测量工具,能跟踪、测量BS程序
http://sourceforge.net/projects/eclipsecolorer/
 
AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html
 
Log4E     Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page
 
VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin
 
Implementors   提供跳转到一个方法的实现类,而不是接中的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/
 
Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html
 
EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/
 
Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm
 
Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/
 
spring updatesite 插件
http://springide.org/updatesite/

VeloEclipse  Velocity插件
http://propsorter.sourceforge.net/
 
EditorList   方便的列出所有打开的Editor
http://editorlist.sourceforge.net/
 
MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/

Eclipse的游戏插件
http://eclipse-games.sourceforge.net/

JBoss-IDE
http://jboss.sourceforge.net/jbosside/updates/

自动反编译class,安装后要设定class文件缺省关联到jode
http://www.technoetic.com/eclipse/update


jigloo swing/sw设计工具,里面自带的form/anchor布局很好用!
http://cloudgarden.soft-gems.net/update-site/

jinto的资源文件编辑工具,同时编辑多种语言,而且自动转换成iso8859-1编码。很好用!
http://www.guh-software.de/eclipse/

posted @ 2006-10-29 13:54 刘璐 阅读(596) | 评论 (0)编辑 收藏

JAVA断言使用

相信学过c,c++的朋友一定不会对断言感到陌生。下面介绍一下断言在JAVA中的使用,JAVA是从JDK1.4才开始支持断言的(添加了关键字assert),请注意老版的JRE不支持。

断言概述


  编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设
  可以将断言看作是异常处理的一种高级形式
  断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真
  可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。
  使用断言可以创建更稳定,品质更好且易于除错的代码
  当需要在一个值为FALSE时中断当前操作的话,可以使用断言
  单元测试必须使用断言(Junit/JunitX)
  除了类型检查和单元测试外,断言还提供了一种确定个种特性是否在程序中得到维护的极好的方法
  使用断言使我们向按契约式设计更近了一部

常见的断言特性


  前置条件断言:代码执行之前必须具备的特性
  后置条件断言:代码执行之后必须具备的特性
  前后不变断言:代码执行前后不能变化的特性

断言使用方式


  断言可以有两种形式
  1.assert Expression1
  2.assert Expression1:Expression2
  其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。如果Expression1为假,则抛出一个 AssertionError,这是一个错误,而不是一个异常,也就是说是一个不可控制异常(unchecked Exception),AssertionError由于是错误,所以可以不捕获,但不推荐这样做,因为那样会使你的系统进入不稳定状态。

起用断言


  断言在默认情况下是关闭的,要在编译时启用断言,需要使用source1.4标记 既javac source1.4 Test.java ,在运行时启用断言需要使用 -ea参数 。要在系统类中启用和禁用断言可以使用 -esa 和 -dsa参数。
  
例如:
public class AssertExampleOne{
   public AssertExampleOne(){}
   public static void main(String args[]){
      int x=10;
      System.out.println("Testing Assertion that x==100");
      assert x==100:"Out assertion failed!";
      System.out.println("Test passed!");
   }
}

如果编译时未加 -source1.4,则编译通不过
在执行时未加 -ea 时输出为
Testing Assertion that x==100
Test passed
jre忽略了断言的就代码,而使用了该参数就会输出为
Testing Assertion that x==100
Exception in thread "main" java.lang.AssertionError: Out assertion failed!
        at AssertExampleOne.main(AssertExampleOne.java:6)

断言的副作用


  由于程序员的问题,断言的使用可能会带来副作用 ,例如:
  boolean isEnable=false;
  //...
  assert isEnable=true;
  这个断言的副作用是因为它修改了程序中变量的值并且未抛出错误,这样的错误如果不细心的检查是很难发现的。但是同时我们可以根据以上的副作用得到一个有用的特性,根据它来测试断言是否打开。

 public class AssertExampleTwo{

   public static void main(String args[]){
     boolean isEnable=false;
     //...
     assert isEnable=true;
     if(isEnable==false){
       throw new RuntimeException("Assertion shoule be enable!");
     }
   }
}

何时需要使用断言


  1.可以在预计正常情况下程序不会到达的地方放置断言 :assert false
  2.断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)
  3.使用断言测试方法执行的前置条件和后置条件
  4.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)

什么地方不要使用断言


  断言语句不是永远会执行,可以屏蔽也可以启用
因此:
  1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行
  2.断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值

参考:
http://jcp.org/en/jsr/detail?id=41
http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html
http://www-128.ibm.com/developworkers/cn/java/jdiag0723/
http://www-128.ibm.com/developworkers/cn/java/jmerlin/part9/

posted @ 2006-10-24 16:17 刘璐 阅读(21014) | 评论 (2)编辑 收藏

static方法不应该被重写

如果你认为重写只是在子类中简单的替换了一个方法,你就很容易认为static 方法也能
被重写。事实上,我有很多包含人们举例指明static 方法能被重写的代码的邮件。然而,这
些并没有考虑方法重写在运行时决定哪个版本的方法被调用的细节问题。下面的代码似乎表
明static 方法是怎样被重写的。
class Base{
   static void amethod(){
   System.out.println("Base.amethod");
   }
}
public class Cravengib extends Base{
   public static void main(String arg[]){
      Cravengib cg = new Cravengib();
      cg.amethod();
   }
   static void amethod(){
      System.out.println("Cravengib.amethod");
   }
}
如果你编译并运行这段代码,你会发现输出文本Cravengib.amethod,这似乎很好的指
明了重写。然而,对于重写,还有相对于在子类中使用一个方法简单替换另一个方法更多的
东西。还有运行时决定的方法基于引用的类的类型的问题,这可以通过制造正在被实例化的
类的引用类型(实例初始化语句的左半部分)来说明。
在上面的例子中,因为名字叫amethod 的方法与类发生了关联,而不是与特定的类的实
例相关联,它不在乎什么类型的类正在创建它,而仅仅在意引用的类型。因此,如果你在调
用amethod 前改变一下这一行,
Base cg= new Cravengib()
你就会发现当你运行程序时,你会得到输出:Base.amethod
cg 是一个类Cravengib 在内存中的一个Base 类型的实例的引用(或者指针)。如果一个static
方法被调用了,JVM 不会检查什么类型正在指向它,它只会调用跟Base 类相关联的方法的
实例。
与上面的情况相对比:当一个方法被重写时,JVM 检查通过引用正在指向的类的类型,
并调用此类型相关的方法。可以结束这个例子了,如果你将两个版本的amethod 方法改变为
非static,并依然创建类:
Base cg= new Cravengib()
编译并运行上述代码,你会发现amethod 已经被重写了,并且输出Cravengib.amethod。

posted @ 2006-10-24 11:47 刘璐 阅读(812) | 评论 (0)编辑 收藏

Struts HTML 标签库详解

<html:html></html:html>:用于生成<html>元素,标签中有一个lang属性,用于显示用户使用的语言
<html:html lang="true">
如果客户浏览器使用中文语言,那么运行时将被解析为<html lang="zh-CN">

<html:base/>:用于生成当前网页的绝对URL路径

<html:link>、<html:rewrite>:前者用于生成<a>元素,创建超链接时允许在URL中包含请求参数,当用户浏览器关闭Cookie时,会自动重写URL,把SessionID作为请求参数包含在URL中,用于跟踪用户的Session状态。后者用于输出超链接中的URI部分。URI用于具体的请求资源。

posted @ 2006-10-09 15:54 刘璐 阅读(436) | 评论 (0)编辑 收藏

新手必须掌握8个DOS命令

  一,ping

  它是用来检查网络是否通畅或者网络连接速度的命令。作为一个生活在网络上的管理员或者黑客来说,ping命令是第一个必须掌握的DOS命令,它所利用的原理是这样的:网络上的机器都有唯一确定的IP地址,我们给目标IP地址发送一个数据包,对方就要返回一个同样大小的数据包,根据返回的数据包我们可以确定目标主机的存在,可以初步判断目标主机的操作系统等。下面就来看看它的一些常用的操作。先看看帮助吧,在DOS窗口中键入:ping /? 回车,。所示的帮助画面。在此,我们只掌握一些基本的很有用的参数就可以了(下同)。

  -t 表示将不间断向目标IP发送数据包,直到我们强迫其停止。试想,如果你使用100M的宽带接入,而目标IP是56K的小猫,那么要不了多久,目标IP就因为承受不了这么多的数据而掉线,呵呵,一次攻击就这么简单的实现了。

  -l 定义发送数据包的大小,默认为32字节,我们利用它可以最大定义到65500字节。结合上面介绍的-t参数一起使用,会有更好的效果哦。

  -n 定义向目标IP发送数据包的次数,默认为3次。如果网络速度比较慢,3次对我们来说也浪费了不少时间,因为现在我们的目的仅仅是判断目标IP是否存在,那么就定义为一次吧。

  说明一下,如果-t 参数和 -n参数一起使用,ping命令就以放在后面的参数为标准,比如“ping IP -t -n 3”,虽然使用了-t参数,但并不是一直ping下去,而是只ping 3次。另外,ping命令不一定非得ping IP,也可以直接ping主机域名,这样就可以得到主机的IP。

  下面我们举个例子来说明一下具体用法。

  这里time=2表示从发出数据包到接受到返回数据包所用的时间是2秒,从这里可以判断网络连接速度的大小 。从TTL的返回值可以初步判断被ping主机的操作系统,之所以说“初步判断”是因为这个值是可以修改的。这里TTL=32表示操作系统可能是win98。

  (小知识:如果TTL=128,则表示目标主机可能是Win2000;如果TTL=250,则目标主机可能是Unix)

  至于利用ping命令可以快速查找局域网故障,可以快速搜索最快的QQ服务器,可以对别人进行ping攻击……这些就靠大家自己发挥了。

  二,nbtstat

  该命令使用TCP/IP上的NetBIOS显示协议统计和当前TCP/IP连接,使用这个命令你可以得到远程主机的NETBIOS信息,比如用户名、所属的工作组、网卡的MAC地址等。在此我们就有必要了解几个基本的参数。

  -a 使用这个参数,只要你知道了远程主机的机器名称,就可以得到它的NETBIOS信息(下同)。

  -A 这个参数也可以得到远程主机的NETBIOS信息,但需要你知道它的IP。

  -n 列出本地机器的NETBIOS信息。

  当得到了对方的IP或者机器名的时候,就可以使用nbtstat命令来进一步得到对方的信息了,这又增加了我们入侵的保险系数。

  三,netstat

  这是一个用来查看网络状态的命令,操作简便功能强大。

  -a 查看本地机器的所有开放端口,可以有效发现和预防木马,可以知道机器所开的服务等信息,如图4。

  这里可以看出本地机器开放有FTP服务、Telnet服务、邮件服务、WEB服务等。用法:netstat -a IP。

  -r 列出当前的路由信息,告诉我们本地机器的网关、子网掩码等信息。用法:netstat -r IP。

  四,tracert

  跟踪路由信息,使用此命令可以查出数据从本地机器传输到目标主机所经过的所有途径,这对我们了解网络布局和结构很有帮助。如图5。

  这里说明数据从本地机器传输到192.168.0.1的机器上,中间没有经过任何中转,说明这两台机器是在同一段局域网内。用法:tracert IP。

  五,net

  这个命令是网络命令中最重要的一个,必须透彻掌握它的每一个子命令的用法,因为它的功能实在是太强大了,这简直就是微软为我们提供的最好的入侵工具。首先让我们来看一看它都有那些子命令,键入net /?回车如图6。

  在这里,我们重点掌握几个入侵常用的子命令。

  net view

  使用此命令查看远程主机的所以共享资源。命令格式为net view \\IP。

  net use

  把远程主机的某个共享资源影射为本地盘符,图形界面方便使用,呵呵。命令格式为net use x: \\IP\sharename。上面一个表示把192.168.0.5IP的共享名为magic的目录影射为本地的Z盘。下面表示和192.168.0.7建立IPC$连接(net use $">\\IP\IPC$ "password" /user:"name"),

  建立了IPC$连接后,呵呵,就可以上传文件了:copy nc.exe $">\\192.168.0.7\admin$,表示把本地目录下的nc.exe传到远程主机,结合后面要介绍到的其他DOS命令就可以实现入侵了。

  net start

  使用它来启动远程主机上的服务。当你和远程主机建立连接后,如果发现它的什么服务没有启动,而你又想利用此服务怎么办?就使用这个命令来启动吧。用法:net start servername,如图9,成功启动了telnet服务。

  net stop

  入侵后发现远程主机的某个服务碍手碍脚,怎么办?利用这个命令停掉就ok了,用法和net start同。 net user

  查看和帐户有关的情况,包括新建帐户、删除帐户、查看特定帐户、激活帐户、帐户禁用等。这对我们入侵是很有利的,最重要的,它为我们克隆帐户提供了前提。键入不带参数的net user,可以查看所有用户,包括已经禁用的。下面分别讲解。

  1,net user abcd 1234 /add,新建一个用户名为abcd,密码为1234的帐户,默认为user组成员。

  2,net user abcd /del,将用户名为abcd的用户删除。

  3,net user abcd /active:no,将用户名为abcd的用户禁用。

  4,net user abcd /active:yes,激活用户名为abcd的用户。

  5,net user abcd,查看用户名为abcd的用户的情况

  net localgroup

  查看所有和用户组有关的信息和进行相关操作。键入不带参数的net localgroup即列出当前所有的用户组。在入侵过程中,我们一般利用它来把某个帐户提升为administrator组帐户,这样我们利用这个帐户就可以控制整个远程主机了。用法:net localgroup groupname username /add。

  现在我们把刚才新建的用户abcd加到administrator组里去了,这时候abcd用户已经是超级管理员了,呵呵,你可以再使用net user abcd来查看他的状态,和图10进行比较就可以看出来。但这样太明显了,网管一看用户情况就能漏出破绽,所以这种方法只能对付菜鸟网管,但我们还得知道。现在的手段都是利用其他工具和手段克隆一个让网管看不出来的超级管理员,这是后话。

  net time

  这个命令可以查看远程主机当前的时间。如果你的目标只是进入到远程主机里面,那么也许就用不到这个命令了。但简单的入侵成功了,难道只是看看吗?我们需要进一步渗透。这就连远程主机当前的时间都需要知道,因为利用时间和其他手段(后面会讲到)可以实现某个命令和程序的定时启动,为我们进一步入侵打好基础。用法:net time \\IP。

  六,at

  这个命令的作用是安排在特定日期或时间执行某个特定的命令和程序(知道net time的重要了吧?)。当我们知道了远程主机的当前时间,就可以利用此命令让其在以后的某个时间(比如2分钟后)执行某个程序和命令。用法:at time command \\computer。

  表示在6点55分时,让名称为a-01的计算机开启telnet服务(这里net start telnet即为开启telnet服务的命令)。

  七,ftp

  大家对这个命令应该比较熟悉了吧?网络上开放的ftp的主机很多,其中很大一部分是匿名的,也就是说任何人都可以登陆上去。现在如果你扫到了一台开放ftp服务的主机(一般都是开了21端口的机器),如果你还不会使用ftp的命令怎么办?下面就给出基本的ftp命令使用方法。

  首先在命令行键入ftp回车,出现ftp的提示符,这时候可以键入“help”来查看帮助(任何DOS命令都可以使用此方法查看其帮助)。

  大家可能看到了,这么多命令该怎么用?其实也用不到那么多,掌握几个基本的就够了。

  首先是登陆过程,这就要用到open了,直接在ftp的提示符下输入“open 主机IP ftp端口”回车即可,一般端口默认都是21,可以不写。接着就是输入合法的用户名和密码进行登陆了,这里以匿名ftp为例介绍。

  用户名和密码都是ftp,密码是不显示的。当提示kafeiba.com* logged in时,就说明登陆成功。这里因为是匿名登陆,所以用户显示为Anonymous。

  接下来就要介绍具体命令的使用方法了。

  dir 跟DOS命令一样,用于查看服务器的文件,直接敲上dir回车,就可以看到此ftp服务器上的文件。

  cd 进入某个文件夹。

  get 下载文件到本地机器。

  put 上传文件到远程服务器。这就要看远程ftp服务器是否给了你可写的权限了,如果可以,呵呵,该怎么 利用就不多说了,大家就自由发挥去吧。

  delete 删除远程ftp服务器上的文件。这也必须保证你有可写的权限。

  bye 退出当前连接。

  quit 同上。

  八,telnet

  功能强大的远程登陆命令,几乎所有的入侵者都喜欢用它,屡试不爽。为什么?它操作简单,如同使用自己的机器一样,只要你熟悉DOS命令,在成功以administrator身份连接了远程机器后,就可以用它来干想干的一切了。下面介绍一下使用方法,首先键入telnet回车,再键入help查看其帮助信息。 然后在提示符下键入open IP回车,这时就出现了登陆窗口,让你输入合法的用户名和密码,这里输入任何密码都是不显示的。

  当输入用户名和密码都正确后就成功建立了telnet连接,这时候你就在远程主机上具有了和此用户一样的权限,利用DOS命令就可以实现你想干的事情了。这里我使用的超级管理员权限登陆的。

  到这里为止,网络DOS命令的介绍就告一段落了,这里介绍的目的只是给菜鸟网管一个印象,让其知道熟悉和掌握网络DOS命令的重要性。其实和网络有关的DOS命令还远不止这些,这里只是抛砖引玉,希望能对广大菜鸟网管有所帮助。学好DOS对当好网管有很大的帮助,特别的熟练掌握了一些网络的DOS命令。

  另外大家应该清楚,任何人要想进入系统,必须得有一个合法的用户名和密码(输入法漏洞差不多绝迹了吧),哪怕你拿到帐户的只有一个很小的权限,你也可以利用它来达到最后的目的。所以坚决消灭空口令,给自己的帐户加上一个强壮的密码,是最好的防御弱口令入侵的方法。

  培养良好的安全意识才是最重要的。

posted @ 2006-10-01 16:07 刘璐 阅读(338) | 评论 (0)编辑 收藏

持久化框架、ORM框架、DAO设计模式之间的关系

ORM框架是一种持久化框架,DAO是用于实现持久化框架的一种设计模式

posted @ 2006-09-20 15:21 刘璐 阅读(843) | 评论 (0)编辑 收藏

Oracle Portal 及其门户网站开发概述

摘要: Portal是IT领域的新技术,是企业信息化工作的发展方向之一。本文首先介绍了Oracle Portal的定义、特点,接着阐述了portal的体系结构。随后本文介绍了Oracle9iAS Portal的功能特点及基于它的企业门户网站的开发。最后本文简要探讨了基于portal的门户网站开发的问题。

关键词: oracle portal,门户,网站,信息入口,企业入口

引言

  Oracle Portal为企业提供了一个单一的访问企业各种信息资源的入口,利用它,企业可以为员工、供应商、零售商、合作伙伴等提供个性化的信息服务,使他们能通过单一的入口,迅速找到所需的信息。Portal具有众多的核心功能特性,将企业的应用、人员、信息与流程有机地结合起来,为信息化工作的开展提供了可行的思路和解决方案。

Oracle Portal概述

1、Oracle Portal是什么

  Portal一词是从Internet所衍生出来的,最初的“门户”一词主要是指信息门户,诸如雅虎、新浪等网站都是一种信息门户。随后,门户的应用领域又得到扩展,出现了企业信息门户(Enterprise Information Portal,EIP)。Oracle Portal是一个建立企业信息门户的集成环境。通过Oracle Portal,企业员工可以很方便地将自己所需要的,来源于各种渠道的信息集成在一个统一的视图之内。例如,在传统企业信息系统环境下,一个财务部门可能要接触这样一些信息源:企业财务软件,企业内部网站的政策、新闻、公告,各种图表、报表,互联网上的财经新闻,股票行情等等;通过Oracle Portal提供的“自助式”的服务,财务部门可以为自己部门量身定制一套财务人员的信息门户,将上述信息有效地组织在Web应用程序之中,并根据不同级别人员的职能设定相应的访问权限。在以前,这可能需要向IT部门提交详细的需求分析,并等待好几个月才能投入使用;通过Oracle Portal提供的快速、易用的开发工具和内建功能模块,非IT人员也可以根据自己的实际业务需求,创建这样的集成化Web应用了。

2、Oracle Portal的体系结构

1) Oracle Portal节点

  每台安装Oracle Portal示例的服务器称为一个Oracle Portal节点。每个OraclePortal节点包含有超过200个表及视图,以及超过1000个PL/SQL包。Oracle Portal节点之间可以相互注册,形成一个联合Portal结构。节点的注册和管理可以很容易地由企业的中心Portal管理。Portal容器是存储Oracle Portal对象的地方。它保存着Provider注册表,页面的样式定义,用户对Portlet的定制,访问权限,内容区域及页面的定义,数据驱动的组件等等。数据库Provider管理基于数据库的PL/SQL或Java存储过程形式的Portlet,它存储在Portal节点之外。Portal框架通过数据库Provider调用这些Portlet,并将返回的HTML/XML形式的结果显示在页面的某个区域中。与数据库Provider不同的是,Web Provider面向的是来自Web的数据,它们通常是通过HTTP协议而不是数据库存储过程来调用的,并且返回的结果已经是格式化的HTML/XML内容。

2) Oracle Logon/SSO Server

  SSO是Single Sign-On的缩写,即单一登录模式。SSO服务器集中管理用户的身份验证和权限管理,这使得其它应用程序可以将这些任务交给SSO服务器统一完成,也使得用户只需要在SSO服务器上登陆一次就可以使用所有相关联的应用程序,而无需记忆多个帐户密码。

3)Oralcle 9i Application Server

  Oracle 9iAS是Oracle Portal的中间层应用程序服务器。在Oracle9iAS中,Oracle HTTP Server提供主要的HTTP服务,Oracle Jserv提供JSP和servlet服务,modPL/SQL以Apache模块的模式提供从Web访问Oracle数据库的服务。

4)Oracle Portal Cache

  Oracle Portal Cache使用基于文件系统的存储系统,为页面定义和portlet生成的内容提供缓存,使用缓存系统,Oracle Portal无需每次生成页面的时候都访问对象存储系统或调用所有portlet,这样大大提高了页面的响应时间。

Oracle9iAS Portal概述

1、Oracle9ias Portal的功能

  Oracle9iAS Portal是1999年初作为Oracle WebDB一部分引入的技术的下一代。Oracle9iAS Portal是Oracle9i的关键组件,作为一项服务集成到Oracle9i应用服务器中。有了Oracle9iAS Portal,企业就拥有了一种强有力的手段,可以向员工、合作伙伴和供应商提供一致、高效的访问途径,使他们能够访问更有效地完成日常业务任务所需的企业信息和工具。Oracle9iAS Portal通过提供如下功能实现了上述目标。

1) 单点交互功能

  Oracle9iAS Portal的可扩展的门户框架提供了集成能力和对企业信息的标准访问途径,同时,灵活的工作环境使各机构、部门和个人用户能够对他们的门户风格进行个性化定制。Oracle9iAS Portal作为单一交互点,通过把信息集中在一起来解决这些问题:把用户与他们的信息源更好地连接在一起,同时提供了适应不同个人开展工作与处理信息的方式所需的灵活性。Oracle9iAS Portal作为真正企业级的电子商务门户,用称为portlet的可重复使用的信息组件,在彼此互不相连的动态数据、文档和Web站点的世界中架起了沟通的桥梁。

2) 集成的成套门户服务

  Oracle9iAS Portal的自助式门户服务使用户和开发人员能够组织与发布信息并创建应用。除了能访问企业数据并对其进行个性化处理外,Oracle9iAS Portal还并入了若干自助式功能,允许门户用户和管理员直接地管理他们的信息。这些服务能够让最终用户控制并负责他们的信息,也向IT专业人员提供了更好地满足最终用户的需求工具。Oracle9iAS Portal还为文档发布、文件上传、页面格式化以及访问控制提供了一套集成功能,可以让用户共享和管理信息。除了文件类型内容外,来自Oracle数据库的信息也能够很容易地集成到Oracle9iAS Portal之中。Oracle9iAS Portal包含有一个数据驱动的组件库,可以用于与Oracle数据库地交互,并把信息无缝地嵌入门户页面内的portlet之中。这些组件包括Javascript增强型地数据输入表单、以HTML、纯文本或MS Excel格式输出的分栏报告、可定制HTML条形图以及其他几个用于发布Oracle数据库中数据的组件。

3)完整的部署和管理环境

  Oracle9iAS Portal借助简单性而赋予用户力量,因为它基于互联网的开放标准。它不要求用户安装和学习大型复杂的程序,不让用户接触这些不必要的复杂性,而是通过管理基础性的Oracle8i数据库中的内容,而对Oracle9iAS Portal进行集中维护。Oracle9iAS Portal的成熟的三层体系结构支持多种多样的配置,并可扩展以满足复杂的要求。Oracle9iAS具备灵活的部署模式,支持在类型广泛的硬件和操作系统上的多种配置形式,从而产生了高度可伸缩的性能。Oracle9iAS Portal支持向全球社会部署。包含在Oracle9iAS Portal的向导、对话框、消息和帮助内容中的文本已经翻译成20多种语言,以满足不同用户的需求。

2、基于Oracle9iAS的门户网站的开发

  为满足一般企业的业务需求和技术需求,可以构建它的集成信息咨询门户。该解决方案分为4层结构:客户层、中间层、基础架构层,数据层。

1)客户层:我们采用浏览器作为客户端的显示工具,使最终用户无需安装任何客户端工具,即可在任何时间任何地点在任何系统平台上访企业的门户平台。

2)中间层:中间层是标准的基于Apache的Oracle9iAS安装;Oracle Portal直接操纵mod_PL/SQL和mod_OC4J,中间层包括三个部分,Oracle门户,网络提供者(Web Provider)和商务智能。Oracle门户包括HTTP服务器,用以处理Oracle前端的页面请求并负责应答;并行页面引擎,是一个servlet引擎,与Web和数据库提供者通讯生成相应的portlet内容并负责页面装配,也与缓存页面定义和portlet内容的“本地”文件系统交互;可配置多个线程来实现与提供者之间的并行通讯;mod_PL/SQL用于处理PL/SQL的应用模块。 Web-Cache在HTTP服务器上的应用能提高整个Oracle Portal的效率。运用9iAS强大的商务智能分析数据,可提供根据客户需求所定制的报表。Web内容提供者可工作在各种Web环境,通过HTTP通讯并返回HTML/XML格式的结果;通过SOAP XML进行数据交换;适用于现有的和远程的Web服务,并支持订阅模式。

3)基础构架层:提供单一登陆服务,对整个系统进行单一的安全管理;提供访问后台数据库的统一连接;还包括Oracle的集成和工作流服务,用以连接各种异构平台和系统。

4)数据层:包括所有为Portal提供数据的客户服务器,内部网站系统和外部网站。

4、门户网站开发存在的问题

  以Oracle9iAS 为基础,为企业提供快速建立企业入口网站的方案,让企业无需购买、安装软件,无需担心额外增加MIS人员等负担,只要嵌入Oracle网站,即可享受Oracle提供的基础构架,并在网站上勾选所需的功能以组建自己的网站。Web界面容易上手的特性,使得Intranet成为企业沟通、管理的重要工具。这也使得企业门户网站的建设逐渐变成一种必然。然而纵观国内多数企业开设的入口网站,目前仍处于低级阶段,尚未发挥应有的功效,而软件商提出的各种解决方案又令人目不暇接,难以抉择。总的来说,企业门户网站建设存在以下几个问题:1)企业的门户网站有建成大杂烩的趋势;2)企业的文化障碍凌驾于技术之上,阻止了企业门户网站的继续开发和投入使用;3)门户网站内容的完成仅仅是个开始,还有很多其它技术任务有待完成。如何解决上述三个问题无疑是当今企业门户网站建设成功与否的关键。

结束语

  Oracle9iAS可以用来快速部署和开发个性化门户网站,而它包括的Oracle9iAS Portal具有易用的Web界面,使用户能够有条理的对商业信息、网页内容和应用进行个性化的浏览。使用Oracle的门户解决方案,简单实现门户单点信息访问,将使您的企业门户网站成为一种全新的电子商务桌面,去迎接更为激烈的挑战!

posted @ 2006-09-16 08:46 刘璐 阅读(213) | 评论 (0)编辑 收藏

spring 编程入门十大问题解答

1、如何学习Spring?
你可以通过下列途径学习spring:
(1) spring下载包中doc目录下的MVC-step-by-step和sample目录下的例子都是比较好的spring开发的例子。

(2) AppFuse集成了目前最流行的几个开源轻量级框架或者工具Ant,XDoclet,Spring,Hibernate(iBATIS),JUnit,Cactus,StrutsTestCase,Canoo's WebTest,Struts Menu,Display Tag Library,OSCache,JSTL,Struts 。
你可以通过AppFuse源代码来学习spring。
AppFuse网站:http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse

(3)Spring 开发指南(夏昕)(http://www.xiaxin.net/Spring_Dev_Guide.rar
一本spring的入门书籍,里面介绍了反转控制和依赖注射的概念,以及spring的bean管理,spring的MVC,spring和hibernte,iBatis的结合。

(4) spring学习的中文论坛
SpringFramework中文论坛(http://spring.jactiongroup.net)
Java视线论坛(http://forum.javaeye.com)的spring栏目

2、利用Spring框架编程,console打印出log4j:WARN Please initialize the log4j system properly?
说明你的log4j.properties没有配置。请把log4j.properties放到工程的classpath中,eclipse的classpath为bin目录,由于编译后src目录下的文件会拷贝到bin目录下,所以你可以把log4j.properties放到src目录下。
这里给出一个log4j.properties的例子:

log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p (%F:%L) - %m%n


3、出现 java.lang.NoClassDefFoundError?
一般情况下是由于你没有把必要的jar包放到lib中。

比如你要采用spring和hibernate(带事务支持的话),你除了spring.jar外还需要hibernat.jar、aopalliance.jar、cglig.jar、jakarta-commons下的几个jar包。

http://www.springframework.org/download.html下载spring开发包,提供两种zip包
spring-framework-1.1.3-with-dependencies.zip和spring-framework-1.1.3.zip,我建议你下载spring-framework-1.1.3-with-dependencies.zip。这个zip解压缩后比后者多一个lib目录,其中有hibernate、j2ee、dom4j、aopalliance、jakarta-commons等常用包。

4、java.io.FileNotFoundException: Could not open class path resource [....hbm.xml],提示找不到xml文件?
原因一般有两个:
(1)该xml文件没有在classpath中。
(2)applicationContext-hibernate.xml中的xml名字没有带包名。比如:
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="dataSource"><ref bean="dataSource"/></property>
        <property name="mappingResources">
            <list>
                <value>User.hbm.xml</value>         错,改为: <value>com/yz/spring/domain/User.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
         <props>
         <prop key="hibernate.dialect"> net.sf.hibernate.dialect.MySQLDialect </prop>
         <prop key="hibernate.show_sql">true</prop>
        </props>
        </property>
</bean>


5、org.springframework.beans.NotWritablePropertyException: Invalid property 'postDao' of bean class?
出现异常的原因是在application-xxx.xml中property name的错误。
<property name="...."> 中name的名字是与bean的set方法相关的,而且要注意大小写。
比如
public class PostManageImpl extends BaseManage implements PostManage {
    private PostDAO dao = null;
    public void setPostDAO(PostDAO postDAO){
        this.dao = postDAO;
    }
}
那么xml的定义应该是:
<bean id="postManage" parent="txProxyTemplate">
        <property name="target">
            <bean class="com.yz.spring.service.implement.PostManageImpl">
                <property name="postDAO"><ref bean="postDAO"/></property>    对
                <property name="dao"><ref bean="postDAO"/></property>             
            </bean>
        </property>
</bean>

6、Spring中如何实现事务管理?
        首先,如果使用mysql,确定mysql为InnoDB类型。
        事务管理的控制应该放到商业逻辑层。你可以写个处理商业逻辑的JavaBean,在该JavaBean中调用DAO,然后把该Bean的方法纳入spring的事务管理。

比如:xml文件定义如下:
<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref bean="transactionManager"/></property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
</bean>

<bean id="userManage" parent="txProxyTemplate">
      <property name="target">
          <bean class="com.yz.spring.service.implement.UserManageImpl">
              <property name="userDAO"><ref bean="userDAO"/></property>
          </bean>
      </property>
</bean>

com.yz.spring.service.implement.UserManageImpl就是我们的实现商业逻辑的JavaBean。我们通过parent元素声明其事务支持。

7、如何管理Spring框架下更多的JavaBean?
        JavaBean越多,spring配置文件就越大,这样不易维护。为了使配置清晰,我们可以将JavaBean分类管理,放在不同的配置文件中。 应用启动时将所有的xml同时加载。
比如:
        DAO层的JavaBean放到applicationContext-hibernate.xml中,商业逻辑层的JavaBean放到applicationContext-service.xml中。然后启动类中调用以下代码载入所有的ApplicationContext。

String[] paths = {"com/yz/spring/dao/hibernate/applicationContext-hibernate.xml",
                          "com/yz/spring/service/applicationContext-service.xml"};
ctx = new ClassPathXmlApplicationContext(paths);


8、web应用中如何加载ApplicationContext?
可以通过定义web.xml,由web容器自动加载。

 <servlet>
  <servlet-name>context</servlet-name>
  <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext-hibernate.xml</param-value>
  <param-value>/WEB-INF/applicationContext-service.xml</param-value>
 </context-param>

9、在spring中如何配置的log4j?
在web.xml中加入以下代码即可。
<context-param>
  <param-name>log4jConfigLocation</param-name>
  <param-value>/WEB-INF/log4j.properties</param-value>
</context-param>

10、Spring框架入门的编程问题解决了,我该如何更深地领会Spring框架呢?
这两本书你该去看看。这两本书是由Spring的作者Rod Johnson编写的。
Expert One on one J2EE Design and Development
Expert One on one J2EE Development Without EJB
你也该看看martinfowler的Inversion of Control Containers and the Dependency Injection pattern。
http://www.martinfowler.com/articles/injection.html

再好好研读一下spring的文档。
http://www.jactiongroup.net/reference/html/index.html(中文版,未全部翻译)

还有就是多实践吧。

posted @ 2006-09-14 08:25 刘璐 阅读(255) | 评论 (0)编辑 收藏

java反射机制

Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。

Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。

JavaBean 是 reflection 的实际应用之一,它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件(类) 的属性。



1. 一个简单的例子

考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。

import java.lang.reflect.*;
public class DumpMethods {
   public static void main(String args[]) {
       try {
           Class c = Class.forName(args[0]);
           Method m[] = c.getDeclaredMethods();
           for (int i = 0; i < m.length; i++)
               System.out.println(m[i].toString());
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

按如下语句执行:

java DumpMethods java.util.Stack

它的结果输出为:

public java.lang.Object java.util.Stack.push(java.lang.Object)

public synchronized java.lang.Object java.util.Stack.pop()

public synchronized java.lang.Object java.util.Stack.peek()

public boolean java.util.Stack.empty()

public synchronized int java.util.Stack.search(java.lang.Object)

这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。

这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。

2.开始使用 Reflection

用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。

下面就是获得一个 Class 对象的方法之一:

Class c = Class.forName("java.lang.String");

这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:

Class c = int.class;

或者

Class c = Integer.TYPE;

它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。

第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。

一旦取得这个信息,就可以进行第三步了——使用 reflection API 来操作这些信息,如下面这段代码:

Class c = Class.forName("java.lang.String");

Method m[] = c.getDeclaredMethods();

System.out.println(m[0].toString());

它将以文本方式打印出 String 中定义的第一个方法的原型。

在下面的例子中,这三个步骤将为使用 reflection 处理特殊应用程序提供例证。

模拟 instanceof 操作符

得到类信息之后,通常下一个步骤就是解决关于 Class 对象的一些基本的问题。例如,Class.isInstance 方法可以用于模拟 instanceof 操作符:

class A {
}

public class instance1 {
   public static void main(String args[]) {
       try {
           Class cls = Class.forName("A");
           boolean b1 = cls.isInstance(new Integer(37));
           System.out.println(b1);
           boolean b2 = cls.isInstance(new A());
           System.out.println(b2);
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

在这个例子中创建了一个 A 类的 Class 对象,然后检查一些对象是否是 A 的实例。Integer(37) 不是,但 new A() 是。

3.找出类的方法

找出一个类中定义了些什么方法,这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法:

import java.lang.reflect.*;

public class method1 {
   private int f1(Object p, int x) throws NullPointerException {
       if (p == null)
           throw new NullPointerException();
       return x;
   }

   public static void main(String args[]) {
       try {
           Class cls = Class.forName("method1");
           Method methlist[] = cls.getDeclaredMethods();
           for (int i = 0; i < methlist.length; i++) {
               Method m = methlist[i];
               System.out.println("name = " + m.getName());
               System.out.println("decl class = " + m.getDeclaringClass());
               Class pvec[] = m.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                   System.out.println("param #" + j + " " + pvec[j]);
               Class evec[] = m.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                   System.out.println("exc #" + j + " " + evec[j]);
               System.out.println("return type = " + m.getReturnType());
               System.out.println("-----");
           }
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

这个程序首先取得 method1 类的描述,然后调用 getDeclaredMethods 来获取一系列的 Method 对象,它们分别描述了定义在类中的每一个方法,包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods,你还能获得继承来的各个方法的信息。

取得了 Method 对象列表之后,要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型,都可以由描述类的对象按顺序给出。

输出的结果如下:

name = f1

decl class = class method1

param #0 class java.lang.Object

param #1 int

exc #0 class java.lang.NullPointerException

return type = int

-----

name = main

decl class = class method1

param #0 class [Ljava.lang.String;

return type = void

-----


4.获取构造器信息

获取类构造器的用法与上述获取方法的用法类似,如:

import java.lang.reflect.*;

public class constructor1 {
   public constructor1() {
   }

   protected constructor1(int i, double d) {
   }

   public static void main(String args[]) {
       try {
           Class cls = Class.forName("constructor1");
           Constructor ctorlist[] = cls.getDeclaredConstructors();
           for (int i = 0; i < ctorlist.length; i++) {
               Constructor ct = ctorlist[i];
               System.out.println("name = " + ct.getName());
               System.out.println("decl class = " + ct.getDeclaringClass());
               Class pvec[] = ct.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                   System.out.println("param #" + j + " " + pvec[j]);
               Class evec[] = ct.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                   System.out.println("exc #" + j + " " + evec[j]);
               System.out.println("-----");
           }
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

这个例子中没能获得返回类型的相关信息,那是因为构造器没有返回类型。

这个程序运行的结果是:

name = constructor1

decl class = class constructor1

-----

name = constructor1

decl class = class constructor1

param #0 int

param #1 double

-----

5.获取类的字段(域)

找出一个类中定义了哪些数据字段也是可能的,下面的代码就在干这个事情:


import java.lang.reflect.*;

public class field1 {
   private double d;
   public static final int i = 37;
   String s = "testing";

   public static void main(String args[]) {
       try {
           Class cls = Class.forName("field1");
           Field fieldlist[] = cls.getDeclaredFields();
           for (int i = 0; i < fieldlist.length; i++) {
               Field fld = fieldlist[i];
               System.out.println("name = " + fld.getName());
               System.out.println("decl class = " + fld.getDeclaringClass());
               System.out.println("type = " + fld.getType());
               int mod = fld.getModifiers();
               System.out.println("modifiers = " + Modifier.toString(mod));
               System.out.println("-----");
           }
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier,它也是一个 reflection 类,用来描述字段成员的修饰语,如“private int”。这些修饰语自身由整数描述,而且使用 Modifier.toString 来返回以“官方”顺序排列的字符串描述 (如“static”在“final”之前)。这个程序的输出是:

name = d

decl class = class field1

type = double

modifiers = private

-----

name = i

decl class = class field1

type = int

modifiers = public static final

-----

name = s

decl class = class field1

type = class java.lang.String

modifiers =

-----

和获取方法的情况一下,获取字段的时候也可以只取得在当前类中申明了的字段信息 (getDeclaredFields),或者也可以取得父类中定义的字段 (getFields) 。


6.根据方法的名称来执行方法

文本到这里,所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情,比如执行一个指定了名称的方法。下面的示例演示了这一操作:

import java.lang.reflect.*;
public class method2 {
   public int add(int a, int b) {
       return a + b;
   }
   public static void main(String args[]) {
       try {
           Class cls = Class.forName("method2");
           Class partypes[] = new Class[2];
           partypes[0] = Integer.TYPE;
           partypes[1] = Integer.TYPE;
           Method meth = cls.getMethod("add", partypes);
           method2 methobj = new method2();
           Object arglist[] = new Object[2];
           arglist[0] = new Integer(37);
           arglist[1] = new Integer(47);
           Object retobj = meth.invoke(methobj, arglist);
           Integer retval = (Integer) retobj;
           System.out.println(retval.intValue());
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

假如一个程序在执行的某处的时候才知道需要执行某个方法,这个方法的名称是在程序的运行过程中指定的 (例如,JavaBean 开发环境中就会做这样的事),那么上面的程序演示了如何做到。

上例中,getMethod 用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的 Method 对象之后,在正确的对象实例中执行它。执行该方法的时候,需要提供一个参数列表,这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象,它封装了返回值 84。

7.创建新的对象

对于构造器,则不能像执行方法那样进行,因为执行一个构造器就意味着创建了一个新的对象 (准确的说,创建一个对象的过程包括分配内存和构造对象)。所以,与上例最相似的例子如下:

import java.lang.reflect.*;

public class constructor2 {
   public constructor2() {
   }

   public constructor2(int a, int b) {
       System.out.println("a = " + a + " b = " + b);
   }

   public static void main(String args[]) {
       try {
           Class cls = Class.forName("constructor2");
           Class partypes[] = new Class[2];
           partypes[0] = Integer.TYPE;
           partypes[1] = Integer.TYPE;
           Constructor ct = cls.getConstructor(partypes);
           Object arglist[] = new Object[2];
           arglist[0] = new Integer(37);
           arglist[1] = new Integer(47);
           Object retobj = ct.newInstance(arglist);
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

根据指定的参数类型找到相应的构造函数并执行它,以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象,而不是在编译的时候创建对象,这一点非常有价值。

8.改变字段(域)的值

reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它,下面的例子可以说明这一点:

import java.lang.reflect.*;

public class field2 {
   public double d;

   public static void main(String args[]) {
       try {
           Class cls = Class.forName("field2");
           Field fld = cls.getField("d");
           field2 f2obj = new field2();
           System.out.println("d = " + f2obj.d);
           fld.setDouble(f2obj, 12.34);
           System.out.println("d = " + f2obj.d);
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

这个例子中,字段 d 的值被变为了 12.34。

9.使用数组

本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型,一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的:

import java.lang.reflect.*;

public class array1 {
   public static void main(String args[]) {
       try {
           Class cls = Class.forName("java.lang.String");
           Object arr = Array.newInstance(cls, 10);
           Array.set(arr, 5, "this is a test");
           String s = (String) Array.get(arr, 5);
           System.out.println(s);
       } catch (Throwable e) {
           System.err.println(e);
       }
   }
}

例中创建了 10 个单位长度的 String 数组,为第 5 个位置的字符串赋了值,最后将这个字符串从数组中取得并打印了出来。

下面这段代码提供了一个更复杂的例子:

import java.lang.reflect.*;

public class array2 {
   public static void main(String args[]) {
       int dims[] = new int[]{5, 10, 15};
       Object arr = Array.newInstance(Integer.TYPE, dims);
       Object arrobj = Array.get(arr, 3);
       Class cls = arrobj.getClass().getComponentType();
       System.out.println(cls);
       arrobj = Array.get(arrobj, 5);
       Array.setInt(arrobj, 10, 37);
       int arrcast[][][] = (int[][][]) arr;
       System.out.println(arrcast[3][5][10]);
   }
}
例中创建了一个 5 x 10 x 15 的整型数组,并为处于 [3][5][10] 的元素赋了值为 37。注意,数组实际上就是数组的数组,例如,第一个 Array.get 之后,arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素,即长度为 15 的数组,并使用 Array.setInt 为它的第 10 个元素赋值。

注意创建数组时的类型是动态的,在编译时并不知道其类型

posted @ 2006-09-08 17:56 刘璐 阅读(562) | 评论 (0)编辑 收藏