ï»??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲福利视频二区,福利小视频在线,国产精品久久二区二区http://www.aygfsteel.com/liulu/Open & Open <a title="Free Web Counter"><font color="red"><script src="http://fastwebcounter.com/secure.php?s=www.aygfsteel.com/liulu"></script>hits</font></a>zh-cnWed, 18 Jun 2025 14:53:57 GMTWed, 18 Jun 2025 14:53:57 GMT60HSQL 学习½W”è®°1(zz)http://www.aygfsteel.com/liulu/archive/2007/03/26/106463.html刘璐刘璐Mon, 26 Mar 2007 09:18:00 GMThttp://www.aygfsteel.com/liulu/archive/2007/03/26/106463.htmlhttp://www.aygfsteel.com/liulu/comments/106463.htmlhttp://www.aygfsteel.com/liulu/archive/2007/03/26/106463.html#Feedback0http://www.aygfsteel.com/liulu/comments/commentRss/106463.htmlhttp://www.aygfsteel.com/liulu/services/trackbacks/106463.html http://hi.baidu.com/wannachan/blog/item/8e82bf86fc5d663f67096ef1.html

HSQL 学习½W”è®°

1.     hsql 学习
1.1.     学习目的
本文档是针对hSQL 数据库方面的基础学习åQŒäؓ了ä‹É™å¹ç›®¾l„成员能够达åˆîC‹É用hSQL 数据库的目的ã€?br />1.2.     培训对象
开发ähå‘?br />1.3.     常用词及½W¦å·è¯´æ˜Ž
常用词:
hsqlåQšä¸€¿Uå…è´¹çš„è·¨åã^台的数据库系¾l?br />E:\hsqldbåQšè¡¨½Cºæ˜¯åœ¨dos 命ä×o½H—口下面
1.4.     参考信�br />doc\guide\guide.pdf

2.     HSQL
2.1.     HSQL ˜qè¡Œå·¥å…·
java -cp ../lib/hsqldb.jar org.hsqldb.util.DatabaseManager
注意hsqldb.jar æ–‡äšg的文件èµ\å¾?最好能攑ֈ°classpath 里面,或者放到当前èµ\径下.
java -cp hsqldb.jar org.hsqldb.util.DatabaseManager

2.2.     ˜qè¡Œæ•°æ®åº?br />启动方式: Server Modes and
In-Process Mode (also called Standalone Mode).

一个test 数据库会包含如下文äšg:
�test.properties
�test.script
�test.log
�test.data
�test.backup
test.properties æ–‡äšg包含关于数据库的一般设¾|?
test.script   æ–‡äšg包含表和其它数据åº?插入没有¾~“存表的数据.
test.log æ–‡äšg包含当前数据库的变更.
test.data æ–‡äšg包含¾~“存表的数据
test.backup æ–‡äšg是最˜q‘持久化状态的表的数据文äšg的压¾~©å¤‡ä»½æ–‡ä»?br />所有以上这个文仉™ƒ½æ˜¯å¿…要的,不能被删é™?如果数据库没有缓存表,test.data å’Œtest.backup æ–‡äšgž®†ä¸ä¼šå­˜åœ?另外,除了以上文äšgHSQLDB 数据库可以链接到ä»ÖM½•文本文äšg,比如cvs æ–‡äšg.

当操作test 数据库的时å€? test.log 用于保存数据的变æ›? 当正常SHUTDOWN,˜q™ä¸ªæ–‡äšgž®†è¢«åˆ é™¤. 否则(不是正常shutdown),˜q™ä¸ªæ–‡äšgž®†ç”¨äºŽå†‹Æ¡å¯åŠ¨çš„æ—¶å€?重做˜q™äº›å˜æ›´.test.lck æ–‡äšg也用于记录打开的数据库的事å®? 正常SHUTDOWN,æ–‡äšg也被删除.在一些情况下,test.data.old æ–‡äšg会被创徏,òq¶åˆ é™¤ä»¥å‰çš„.






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

命ä×o行方å¼?


启动数据,数据库文件mydb,数据库名¿U°xdb

也可以在 server.properties æ–‡äšg中定义启动的数据åº?最å¤?0ä¸?br />例如: 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
启动命ä×o: java -cp ../lib/hsqldb.jar org.hsqldb.Server
˜qè¡Œ¾l“果如下



java ‹¹‹è¯•½E‹åº:
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","");
˜q™æ ·ž®±å¯ä»¥è¿žæŽ¥æ•°æ®åº“ã€?br />只能在一个jvm 中ä‹É用,不能在多个jvm 中ä‹É用ã€?br />˜q™ç§æ¨¡å¼æ˜¯åœ¨ç›¸åŒçš„jvm 下作ä¸ÞZ½ çš„应用程序的一部分åQŒè¿è¡Œæ•°æ®åº“引擎。对大多数应用程序,˜q™ç§æ¨¡å¼˜qè¡Œä¼šç›¸å½“å¿«åQŒä½œä¸ºæ•°æ®ï¼Œä¸éœ€è¦è{换和¾|‘络传输ã€?br />
主要的缺点就是不可能从外面的应用½E‹åºè®‰K—®åˆ°é»˜è®¤æ•°æ®åº“åQŒå› æ­¤å½“你的应用˜qè¡Œæ—¶å€™ï¼Œä½ ä¸èƒ½é€šè¿‡åˆ«çš„工具‹‚€æŸ¥æ•°æ®åº“内容。在1.8.0 版本ä¸?你可以在相同jvm 中的¾U¿ç¨‹ä¸­è¿è¡Œæ•°æ®åº“初始化,òq¶æä¾›å¤–面访问你的进½E‹å†…数据库ã€?br />     推荐在开发应用中使用˜q™ç§æ–¹å¼ã€?br />˜qžæŽ¥ä¸ÔŒ¼š
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
当随卌™®¿é—®å†…存,数据库不固定æ—Óž¼Œå¯ä»¥é‡‡ç”¨å†…存的方式运行数据库åQŒç”±äºŽæ²¡æœ‰æ•°æ®å†™åˆ°ç¡¬ç›˜ä¸ŠåQŒè¿™¿Uæ–¹å¼ä‹É用在应用数据和applets 和特ŒDŠåº”用的内部˜q›ç¨‹ä¸­ä‹É用,URLåQ?br />
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 表和三种¾cÕdž‹çš„æŒä¹…表åQˆMEMORY è¡? CACHED 表,TEXT表)

当ä‹Éç”?CREATE TABLE   命ä×oæ—Óž¼ŒMemory 表时默认¾cÕdž‹åQŒå®ƒä»¬çš„æ•°æ®æ•´ä½“保存在内存当中,但是ä»ÖM½•改变它们的结构或者内容,它们会被写到<dbname>.script æ–‡äšg中。这个脚本文件在数据库下一‹Æ¡æ‰“开的时候被对出åQŒå†…存表重新被创建内容,根temp 表不同,内存表时持久化的ã€?br />
CACHED 表通过CREATE CACHED TABLE 命ä×o建立. 只有部分的它们的数据或者烦引被保存在内存中åQŒå…è®¸å¤§è¡¨å ç”¨å‡ ç™‘Ö…†çš„内存空间。例外一个优点,在数据库引擎中,启动大量数据的缓存表需要花费少量的旉™—´åQŒç¼ºç‚ÒŽ˜¯å‡æ…¢äº†è¿è¡Œå’Œä½¿ç”¨Hsqldb 的速度。表相对ž®çš„æ—¶å€™ï¼Œä¸è¦ä½¿ç”¨cache 表,在小表中使用内存数据库ã€?br />
从版æœ?1.7.0 以后åQŒæ”¯æŒtext 表,使用 CSV (Comma Separated Value)   或者其它分隔符文本文äšgä½œäØ“å®ƒä»¬çš„æ•°æ®æºã€‚ä½ å¯ä»¥ç‰ÒޮпŒ‡å®šä¸€ä¸ªå­˜åœ¨çš„CSV æ–‡äšgåQŒä¾‹å¦‚从其它的数据或者程序中导出文äšgåQŒä½œä¸ºTXT 表的数据源ã€?同时,你可以指定一个空文äšgåQŒé€šè¿‡æ•°æ®åº“引擎填充数据。TEXT 表将比cache 表更加效率高。Text 表可以指向不同的数据文äšgã€?br />
* memory-only databases 数据库只支持memory 表和cache 表,不支持text 表ã€?br />2.8.     ¾U¦æŸå’Œçƒ¦å¼?br />HSQLDB 支持 PRIMARY KEY, NOT NULL, UNIQUE, CHECK and FOREIGN KEY ¾U¦æŸ.





3.     sql 命ä×o
3.1.     sql 支持
select top 1 * from test;
select limit 0 2 * from test;
DROP TABLE test IF EXISTS;
3.2.     Constraints and Indexes
ä¸Õd¥¾U¦æŸåQšPRIMARY KEY
唯一¾U¦æŸåQ?br />唯一索引åQ?br />外健åQ?br />CREATE TABLE child(c1 INTEGER, c2 VARCHAR, FOREIGN KEY (c1, c2) REFERENCES parent(p1, p2));

3.3.     索引和查询速度
索引提高查询速度åQŒæ¯”提高排序速度ã€?br />ä¸Õd¥å’Œå”¯ä¸€æ‰€åˆ—自动创建烦引,否则需要自己创建CREATE INDEX commandã€?br />索引åQ?唯一索引和非唯一索引
多列的烦引,如果只是使用后面的,不ä‹É用第一个,ž®†ä¸ä¼šæ¡æŸ¥è¯¢é€Ÿåº¦ã€?br />
(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):
一般规则是把羃ž®æ¡ä»¶çš„列的表放在前é?br />
3.4.     使用where ˜q˜æ˜¯join
使用 WHERE   条äšg链接表可能会降低˜qè¡Œé€Ÿåº¦.
下面的例子将会比较慢,即ä‹É使用了烦å¼?
     SELECT ... FROM TA, TB, TC WHERE TC.COL3 = TA.COL1 AND TC.COL3=TB.COL2 AND TC.COL4 = 1
˜q™ä¸ªæŸ¥è¯¢éšå«TA.COL1 = TB.COL2 ,但是没有直接讑֮š˜q™ä¸ªæ¡äšg.如果 TA å’?TB 每个表都包含100 条记å½?10000 ¾l„合ž®†å’Œ TC å…Œ™”,用于TC˜q™ä¸ªåˆ—的条äšg,ž®½ç®¡æœ‰çƒ¦å¼•在˜q™ä¸ªåˆ—上.使用JOIN 关键å­? 在组合TC 之前,TA.COL1 = TB.COL2 条äšg直接òq¶ç¾ƒž®ç»„å?TA å’?TB 的行æ•? 在运行大数据量的表的¾l“æžœæ˜?ž®†ä¼šå¾ˆå¿«:
     SELECT ... FROM TA JOIN TB ON TA.COL1 = TB.COL2 JOIN TC ON TB.COL2 = TC.COL3 WHERE TC.COL4 = 1
˜q™ä¸ªæŸ¥è¯¢å¯ä»¥æé«˜ä¸€å¤§æ­¥,如果改变表的™åºåº, 所ä»?TC.COL1 = 1 ž®†æœ€å…ˆä‹Éç”?˜q™æ ·æ›´å°çš„集合将¾l„合在一èµ?
     SELECT ... FROM TC JOIN TB ON TC.COL3 = TB.COL2 JOIN TA ON TC.COL3 = TA.COL1 WHERE TC.COL4 = 1
以上例子,数据引擎自动应用于TC.COL4 = 1 ¾l„合ž®çš„集合于其它表兌™”. Indexes TC.COL4, TB.COL2   TA.COL1 都将使用索引,提高查询速度.
3.5.     Subqueries and Joins
使用join 和调整表的顺序提高效�
例如:, ½W¬äºŒä¸ªæŸ¥è¯¢çš„速度ž®†æ›´å¿«ä¸€äº?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
½W¬äºŒä¸ªæŸ¥è¯¢å°† MAX(TB.COL2) 与一个单记录表相兌™”. òq¶ä‹É用TA.COL1索引,˜q™å°†å˜å¾—非常å¿? ½W¬ä¸€ä¸ªæŸ¥è¯¢æ˜¯ž®?TA 表中的每一条记录不断地与MAX(TB.COL2)匚w….
3.6.     数据¾cÕdž‹
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æ–‡äšg中指å®?br />     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 æ–‡äšg支持如下讑֮š:
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, 每个服务器支持同时启�0个不同的数据� 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


刘璐 2007-03-26 17:18 发表评论
]]>
(è½?用DbUnit˜q›è¡ŒSqlMap单元‹¹‹è¯•- -http://www.aygfsteel.com/liulu/archive/2007/03/26/106460.html刘璐刘璐Mon, 26 Mar 2007 09:16:00 GMThttp://www.aygfsteel.com/liulu/archive/2007/03/26/106460.htmlhttp://www.aygfsteel.com/liulu/comments/106460.htmlhttp://www.aygfsteel.com/liulu/archive/2007/03/26/106460.html#Feedback0http://www.aygfsteel.com/liulu/comments/commentRss/106460.htmlhttp://www.aygfsteel.com/liulu/services/trackbacks/106460.html http://starrynight.blogdriver.com/starrynight/621943.html
DbUnit½Ž€ä»?/strong>

ä¸ÞZ¾èµ–于其他外部¾pȝ»ŸåQˆå¦‚数据库或其他接口åQ‰çš„代码¾~–写单元‹¹‹è¯•是一件很困难的工作。在˜q™ç§æƒ…况下,有效的单元必™å»é𔼛ÀLµ‹è¯•对象和外部依赖åQŒä»¥ä¾¿ç®¡ç†æµ‹è¯•å¯¹è±¡çš„çŠ¶æ€å’Œè¡ŒäØ“ã€?/p>

使用mock object对象åQŒæ˜¯éš”离外部依赖的一个有效方法。如果我们的‹¹‹è¯•对象是依赖于DAO的代码,mock object技术很方便。但如果‹¹‹è¯•对象变成了DAO本èínåQŒåˆå¦‚何˜q›è¡Œå•å…ƒ‹¹‹è¯•呢?

开源的DbUnit™å¹ç›®åQŒäؓ以上的问题提供了一个相当优雅的解决æ–ÒŽ¡ˆã€‚ä‹É用DbUnitåQŒå¼€å‘äh员可以控制测试数据库的状态。进行一个DAO单元‹¹‹è¯•之前åQŒDbUnit为数据库准备好初始化数据åQ›è€Œåœ¨‹¹‹è¯•¾l“束æ—Óž¼ŒDbUnit会把数据库状态恢复到‹¹‹è¯•前的状态ã€?/p>

下面的例子ä‹É用DbUnit为iBATIS SqlMapçš„DAO¾~–写单元‹¹‹è¯•ã€?/p>

准备‹¹‹è¯•数据
首先åQŒè¦ä¸ºå•元测试准备数据。ä‹É用DbUnitåQŒæˆ‘们可以用XMLæ–‡äšg来准备测试数据集。下面的XMLæ–‡äšg¿UîCؓ目标数据库的Seed FileåQŒä»£è¡¨ç›®æ ‡æ•°æ®åº“的表名和数据åQŒå®ƒä¸ºæµ‹è¯•准备了两个Employee的数据。employee对应数据库的表名åQŒemployee_uid、start_date、first_nameå’Œlast_name都是表employee的列名ã€?/span>

<?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中不存在的表åQŒDbUnit则不处理ã€?br />Seed File可以手工¾~–写åQŒä¹Ÿå¯ä»¥ç”¨ç¨‹åºå¯¼å‡ºçŽ°æœ‰çš„æ•°æ®åº“æ•°æ®åÆˆç”Ÿæˆã€?br />

SqlMap代码
我们要测试的SqlMap映射文äšg如下所½Cºï¼š
<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
ä¸ÞZº†æ–¹ä¾¿‹¹‹è¯•åQŒé¦–å…ˆäØ“SqlMap的单元测试编写一个抽象的‹¹‹è¯•基类åQŒä»£ç å¦‚下ã€?/span>

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映射文äšg¾~–写一个测试用例,extends上面的抽象类。如¾~–写Employ.xml的测试用例如下,它覆盖了DbUnitçš„DatabaseTestCase¾cȝš„getDataSetæ–ÒŽ³•ã€?br />

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);
       
        // 注意˜q™é‡Œ, ¼‹®è®¤åˆ é™¤ä¸èƒ½ä½¿ç”¨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());
    }
}

以上例子中的¾l¿è‰²ä»£ç éƒ¨åˆ†ä½¿ç”¨ITable接口来查询已删除的数据。因ä¸ÞZ‹É用SqlMapClient.queryForObjectæ–ÒŽ³•查询åQŒå·²åˆ é™¤çš„æ•°æ®è¿˜å­˜åœ¨åQŒçœŸå¥‡æ€ªï¼ˆæœ‰æ—¶é—´å†ç ”ç©¶åQ‰ã€?br />
DbUnit的断­a€
我们可以使用DbUnitçš„Assertion¾cÈš„æ–ÒŽ³•来比较数据是否相同ã€?br />

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操作åQŒåˆ é™¤Seed File中所有表的数据,òq¶æ’入文件的‹¹‹è¯•数据。你可以通过覆盖getSetUpOperationå’ŒgetTearDownOperationæ–ÒŽ³•改变setUpå’ŒtearDown的行为ã€?br />

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

REFRESH操作执行‹¹‹è¯•å‰åÆˆä¸æ‰§è¡ŒCLEAN操作åQŒåªæ˜¯å¯¼å…¥æ–‡ä»¶ä¸­çš„æ•°æ®ï¼Œå¦‚果目标数据库数据已存在åQŒDbUnit使用文äšg的数据来更新数据库ã€?/span>

使用Ant
上面的方法通过extends DbUnit的DatabaseTestCase来控制数据库的状态。�/span>
使用DbUnitçš„Ant TaskåQŒå®Œå…¨å¯ä»¥é€šè¿‡Ant脚本的方式来实现ã€?/span>

<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中间åQŒå¯ä»¥è¾¾åˆ°æŽ§åˆ¶æ•°æ®åº“状态的目标ã€?br />

由此可知åQŒDbUnit可以灉|´»æŽ§åˆ¶ç›®æ ‡æ•°æ®åº“çš„‹¹‹è¯•状态,从而ä‹É¾~–写SqlMap单元‹¹‹è¯•变得更加è½ÀL¾ã€?br />

本文抄袭了资源列表的“Effective Unit Test with DbUnit”,但重新编写了代码½CÞZ¾‹ã€?br />

¾|‘上资源

1�a >DbUnit Framework

2�a >Effective Unit Testing with DbUnit

3�a >Control your test-environement with DbUnit and Anthill



]]>
抽象¾cÕd’ŒæŽ¥å£çš„区åˆ?/title><link>http://www.aygfsteel.com/liulu/archive/2007/03/08/102584.html</link><dc:creator>刘璐</dc:creator><author>刘璐</author><pubDate>Thu, 08 Mar 2007 05:27:00 GMT</pubDate><guid>http://www.aygfsteel.com/liulu/archive/2007/03/08/102584.html</guid><wfw:comment>http://www.aygfsteel.com/liulu/comments/102584.html</wfw:comment><comments>http://www.aygfsteel.com/liulu/archive/2007/03/08/102584.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/liulu/comments/commentRss/102584.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/liulu/services/trackbacks/102584.html</trackback:ping><description><![CDATA[ <span style="FONT-WEIGHT: 400"> <span id="wmqeeuq" class="unnamed4"> <font style="FONT-SIZE: 9pt">abstract classå’Œinterface是Java语言中对于抽象类定义˜q›è¡Œæ”¯æŒçš„两¿UæœºåˆÓž¼Œæ­£æ˜¯ç”׃ºŽ˜q™ä¸¤¿Uæœºåˆ¶çš„存在åQŒæ‰èµ‹äºˆäº†Java强大的面向对象能力。abstract classå’Œinterfaceä¹‹é—´åœ¨å¯¹äºŽæŠ½è±¡ç±»å®šä¹‰çš„æ”¯æŒæ–¹é¢å…·æœ‰å¾ˆå¤§çš„ç›æ€¼¼æ€§ï¼Œç”šè‡³å¯ä»¥ç›æ€º’替换åQŒå› æ­¤å¾ˆå¤šå¼€å‘者在˜q›è¡ŒæŠ½è±¡¾cÕd®šä¹‰æ—¶å¯¹äºŽabstract classå’Œinterface的选择昑־—比较随意。其实,两者之间还是有很大的区别的åQŒå¯¹äºŽå®ƒä»¬çš„选择甚至反映出对于问题领域本质的理解、对于设计意囄¡š„理解是否正确、合理。本文将对它们之间的区别˜q›è¡Œä¸€ç•ªå‰–析,试图¾l™å¼€å‘者提供一个在二者之间进行选择的依据。 Â?br /><br />理解抽象¾c»Â Â?br /><br />abstract classå’Œinterface在Java语言中都是用来进行抽象类åQˆæœ¬æ–‡ä¸­çš„æŠ½è±¡ç±»òq‰™žä»Žabstract class¾˜»è¯‘而来åQŒå®ƒè¡¨ç¤ºçš„æ˜¯ä¸€ä¸ªæŠ½è±¡ä½“åQŒè€Œabstract class为Java语言中用于定义抽象类的一¿Uæ–¹æ³•,误‚¯»è€…注意区分)定义的,那么什么是抽象¾c»ï¼Œä½¿ç”¨æŠ½è±¡¾c»èƒ½ä¸ºæˆ‘们带来什么好处呢åQŸÂ Â?br /><br />在面向对象的概念中,我们知道所有的对象都是通过¾cÀL¥æç»˜çš„,但是反过来却不是˜q™æ ·ã€‚åÆˆä¸æ˜¯æ‰€æœ‰çš„¾c»éƒ½æ˜¯ç”¨æ¥æ¾l˜å¯¹è±¡çš„åQŒå¦‚果一个类中没有包含èƒö够的信息来描¾l˜ä¸€ä¸ªå…·ä½“的对象åQŒè¿™æ ïLš„¾cÕd°±æ˜¯æŠ½è±¡ç±»ã€‚抽象类往往用来表征我们在对问题领域˜q›è¡Œåˆ†æžã€è®¾è®¡ä¸­å¾—出的抽象概念,是对一¾pÕdˆ—看上åŽÖM¸åŒï¼Œä½†æ˜¯æœ¬è´¨ä¸Šç›¸åŒçš„具体概念的抽象。比如:如果我们˜q›è¡Œä¸€ä¸ªå›¾å½¢ç¼–辑èÊY件的开发,ž®×ƒ¼šå‘现问题领域存在着圆、三角åÅž˜q™æ ·ä¸€äº›å…·ä½“概念,它们是不同的åQŒä½†æ˜¯å®ƒä»¬åˆéƒ½å±žäºŽåŞ状这样一个概念,形状˜q™ä¸ªæ¦‚念在问题领域是不存在的åQŒå®ƒž®±æ˜¯ä¸€ä¸ªæŠ½è±¡æ¦‚å¿üc€‚正是因为抽象的概念在问题领域没有对应的具体概念åQŒæ‰€ä»¥ç”¨ä»¥è¡¨å¾æŠ½è±¡æ¦‚å¿ëŠš„抽象¾cÀL˜¯ä¸èƒ½å¤Ÿå®žä¾‹åŒ–的。 Â?br /><br />在面向对象领域,抽象¾cÖM¸»è¦ç”¨æ¥è¿›è¡Œç±»åž‹éšè—ã€‚我们可以构造出一个固定的一¾l„行为的抽象描述åQŒä½†æ˜¯è¿™¾l„行为却能够有ä“Q意个可能的具体实现方式。这个抽象描˜q°å°±æ˜¯æŠ½è±¡ç±»åQŒè€Œè¿™ä¸€¾l„ä“Q意个可能的具体实现则表现为所有可能的‹z„¡”Ÿ¾c…R€‚æ¨¡å—å¯ä»¥æ“ä½œä¸€ä¸ªæŠ½è±¡ä½“ã€‚ç”±äºŽæ¨¡å—ä¾èµ–äºŽä¸€ä¸ªå›ºå®šçš„æŠ½è±¡ä½“ï¼Œå› æ­¤å®ƒå¯ä»¥æ˜¯ä¸å…è®æ€¿®æ”¹çš„åQ›åŒæ—Óž¼Œé€šè¿‡ä»Žè¿™ä¸ªæŠ½è±¡ä½“‹z„¡”ŸåQŒä¹Ÿå¯æ‰©å±•此模块的行为功能。熟悉OCP的读者一定知道,ä¸ÞZº†èƒ½å¤Ÿå®žçŽ°é¢å‘å¯¹è±¡è®¾è®¡çš„ä¸€ä¸ªæœ€æ ¸å¿ƒçš„åŽŸåˆ™OCP(Open-Closed Principle)åQŒæŠ½è±¡ç±»æ˜¯å…¶ä¸­çš„关键所在。 Â?br /><br /><br />从语法定义层面看abstract classå’Œinterface  <br /><br />在语法层面,Java语言对于abstract classå’Œinterface¾l™å‡ºäº†ä¸åŒçš„定义方式åQŒä¸‹é¢ä»¥å®šä¹‰ä¸€ä¸ªåä¸ºDemo的抽象类ä¸ÞZ¾‹æ¥è¯´æ˜Žè¿™¿Uä¸åŒã€‚ Â?br /><br />使用abstract class的方式定义Demo抽象¾cÈš„æ–¹å¼å¦‚下åQšÂ Â?br /><br />abstract class Demo { Â?br /> abstract void method1();  <br /> abstract void method2();  <br /> … Â?br />} Â?br /><br />使用interface的方式定义Demo抽象¾cÈš„æ–¹å¼å¦‚下åQšÂ Â?br /><br />interface Demo {  <br /> void method1();  <br /> void method2();  <br /> … Â?br />}  <br /><br />在abstract class方式中,Demo可以有自å·Þqš„æ•°æ®æˆå‘˜åQŒä¹Ÿå¯ä»¥æœ‰éžabstarct的成员方法,而在interface方式的实çŽîC¸­åQŒDemo只能够有静态的不能被修改的数据成员åQˆä¹Ÿž®±æ˜¯å¿…须是static final的,不过在interface中一般不定义数据成员åQ‰ï¼Œæ‰€æœ‰çš„æˆå‘˜æ–ÒŽ³•都是abstract的。从某种意义上说åQŒinterface是一¿Uç‰¹ŒDŠåŞ式的abstract class。 Â?br /><br />      从编½E‹çš„角度来看åQŒabstract classå’Œinterface都可以用来实çŽ?design by contract"的思想。但是在具体的ä‹É用上面还是有一些区别的。 Â?br /><br />首先åQŒabstract class在Java语言中表½Cºçš„æ˜¯ä¸€¿Uç‘ô承关¾p»ï¼Œä¸€ä¸ªç±»åªèƒ½ä½¿ç”¨ä¸€‹Æ¡ç‘ô承关¾p…R€‚但是,一个类却可以实现多个interface。也许,˜q™æ˜¯Java语言的设计者在考虑Java对于多重¾l§æ‰¿çš„æ”¯æŒæ–¹é¢çš„一¿UæŠ˜ä¸­è€ƒè™‘吧。 Â?br /><br />其次åQŒåœ¨abstract class的定义中åQŒæˆ‘ä»¬å¯ä»¥èµ‹äºˆæ–¹æ³•çš„é»˜è®¤è¡ŒäØ“ã€‚ä½†æ˜¯åœ¨interface的定义中åQŒæ–¹æ³•å´ä¸èƒ½æ‹¥æœ‰é»˜è®¤è¡ŒäØ“åQŒäؓ了绕˜q‡è¿™ä¸ªé™åˆÓž¼Œå¿…须使用委托åQŒä½†æ˜¯è¿™ä¼šÂ å¢žåŠ ä¸€äº›å¤æ‚æ€§ï¼Œæœ‰æ—¶ä¼šé€ æˆå¾ˆå¤§çš„éº»çƒ¦ã€‚Â Â?br /><br />åœ¨æŠ½è±¡ç±»ä¸­ä¸èƒ½å®šä¹‰é»˜è®¤è¡Œä¸ø™¿˜å­˜åœ¨å¦ä¸€ä¸ªæ¯”较严重的问题åQŒé‚£ž®±æ˜¯å¯èƒ½ä¼šé€ æˆ¾l´æŠ¤ä¸Šçš„éºÈƒ¦ã€‚因为如果后来想修改¾cȝš„界面åQˆä¸€èˆ¬é€šè¿‡abstract class或者interface来表½Cºï¼‰ä»¥é€‚应新的情况åQˆæ¯”如,æ·ÕdŠ æ–°çš„æ–ÒŽ³•或者给已用的方法中æ·ÕdŠ æ–°çš„å‚æ•°åQ‰æ—¶åQŒå°±ä¼šéžå¸¸çš„éºÈƒ¦åQŒå¯èƒ½è¦èŠÞp´¹å¾ˆå¤šçš„æ—¶é—ß_¼ˆå¯¹äºŽ‹z„¡”Ÿ¾cÕd¾ˆå¤šçš„æƒ…况åQŒå°¤ä¸ºå¦‚此)。但是如果界面是通过abstract class来实现的åQŒé‚£ä¹ˆå¯èƒ½å°±åªéœ€è¦ä¿®æ”¹å®šä¹‰åœ¨abstract classä¸­çš„é»˜è®¤è¡ŒäØ“ž®±å¯ä»¥äº†ã€‚ Â?br /><br />同样åQŒå¦‚果不能在抽象¾cÖM¸­å®šä¹‰é»˜è®¤è¡ŒäØ“åQŒå°±ä¼šå¯¼è‡´åŒæ ïLš„æ–ÒŽ³•实现出现在该抽象¾cÈš„æ¯ä¸€ä¸ªæ´¾ç”Ÿç±»ä¸­ï¼Œ˜qåäº?one ruleåQŒone place"原则åQŒé€ æˆä»£ç é‡å¤åQŒåŒæ ·ä¸åˆ©äºŽä»¥åŽçš„维护。因此,在abstract classå’Œinterface间进行选择时要非常的小心。 Â?br /><br /><br />从设计理念层面看abstract classå’Œinterface  <br /><br />上面主要从语法定义和¾~–程的角度论˜qîCº†abstract classå’Œinterface的区别,˜q™äº›å±‚面的区别是比较低层‹Æ¡çš„、非本质的。本ž®èŠ‚ž®†ä»Žå¦ä¸€ä¸ªå±‚面:abstract classå’Œinterface所反映出的设计理念åQŒæ¥åˆ†æžä¸€ä¸‹äºŒè€…的区别。作者认为,从这个层面进行分析才能理解二者概å¿ëŠš„本质所在。 Â?br /><br />前面已经提到˜q‡ï¼Œabstarct class在Java语言中体çŽîCº†ä¸€¿Uç‘ô承关¾p»ï¼Œè¦æƒ³ä½¿å¾—¾l§æ‰¿å…³ç³»åˆç†åQŒçˆ¶¾cÕd’Œ‹z„¡”Ÿ¾cÖM¹‹é—´å¿…™åÕd­˜åœ?is a"关系åQŒå³çˆ¶ç±»å’Œæ´¾ç”Ÿç±»åœ¨æ¦‚忉|œ¬è´¨ä¸Šåº”该是相同的åQˆå‚考文献ã€?〕中有关äº?is a"关系的大½‹‡å¹…深入的论˜qŽÍ¼Œæœ‰å…´­‘£çš„读者可以参考)。对于interface 来说则不ç„Óž¼Œòq¶ä¸è¦æ±‚interface的实现者和interface定义在概忉|œ¬è´¨ä¸Šæ˜¯ä¸€è‡´çš„åQŒä»…仅是实现了interface定义的契¾U¦è€Œå·²ã€‚äØ“äº†ä‹Éè®ø™¿°ä¾¿äºŽç†è§£åQŒä¸‹é¢å°†é€šè¿‡ä¸€ä¸ªç®€å•的实例˜q›è¡Œè¯´æ˜Žã€‚ Â?br /><br />考虑˜q™æ ·ä¸€ä¸ªä¾‹å­ï¼Œå‡è®¾åœ¨æˆ‘们的问题领域中有一个关于Door的抽象概念,该Doorå…ähœ‰æ‰§è¡Œä¸¤ä¸ªåŠ¨ä½œopenå’ŒcloseåQŒæ­¤æ—¶æˆ‘们可以通过abstract class或者interface来定义一个表½Cø™¯¥æŠ½è±¡æ¦‚念的类型,定义方式分别如下所½Cºï¼šÂ Â <br /><br />使用abstract class方式定义DooråQšÂ Â?br /><br />abstract class Door {  <br /> abstract void open();  <br /> abstract void close()åQ›Â Â?br />}  <br /><br />   <br />使用interface方式定义DooråQšÂ Â?br /><br /><br />interface Door {  <br /> void open();  <br /> void close();  <br />}  <br /><br />   <br />其他具体的Door¾cÕdž‹å¯ä»¥extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看èµäh¥å¥½åƒä½¿ç”¨abstract classå’Œinterface没有大的区别。 Â?br /><br />如果现在要求Door˜q˜è¦å…ähœ‰æŠ¥è­¦çš„功能。我们该如何设计针对该例子的¾cȝ»“构呢åQˆåœ¨æœ¬ä¾‹ä¸­ï¼Œä¸»è¦æ˜¯äؓ了展½Cºabstract classå’Œinterface反映在设计理念上的区别,其他斚w¢æ— å…³çš„问题都做了½Ž€åŒ–或者忽略)åQŸä¸‹é¢å°†¾|—列出可能的解决æ–ÒŽ¡ˆåQŒåƈ从设计理念层面对˜q™äº›ä¸åŒçš„æ–¹æ¡ˆè¿›è¡Œåˆ†æžã€‚ Â?br /><br />解决æ–ÒŽ¡ˆä¸€åQšÂ Â?br /><br />½Ž€å•的在Door的定义中增加一个alarmæ–ÒŽ³•åQŒå¦‚下:  <br /><br />abstract class Door {  <br /> abstract void open();  <br /> abstract void close()åQ›Â Â?br /> abstract void alarm();  <br />}  <br /><br />   <br />或者 Â?br /><br />interface Door {  <br /> void open();  <br /> void close();  <br /> void alarm();  <br />}  <br /><br />   <br />那么å…ähœ‰æŠ¥è­¦åŠŸèƒ½çš„AlarmDoor的定义方式如下:  <br /><br />class AlarmDoor extends Door {  <br /> void open() { … }  <br /> void close() { … }  <br /> void alarm() { … }  <br />}  <br /><br />   <br />或者 Â?br /><br />class AlarmDoor implements Door { Â?br /> void open() { … }  <br /> void close() { … }  <br /> void alarm() { … }  <br />} Â?br /><br />˜q™ç§æ–ÒŽ³•˜qåäº†é¢å‘对象设计中的一个核心原则ISPåQˆInterface Segregation PricipleåQ‰ï¼Œåœ¨Door的定义中把Door概念本èín固有的行为方法和另外一个概å¿?报警å™?çš„è¡Œä¸ºæ–¹æ³•æØœåœ¨äº†ä¸€èµ—÷€‚这样引èµïLš„一个问题是那些仅仅依赖于Door˜q™ä¸ªæ¦‚å¿µçš„æ¨¡å—ä¼šå› äØ“"报警å™?˜q™ä¸ªæ¦‚念的改变(比如åQšä¿®æ”¹alarmæ–ÒŽ³•的参敎ͼ‰è€Œæ”¹å˜ï¼Œåä¹‹ä¾ç„¶ã€‚ Â?br /><br />解决æ–ÒŽ¡ˆäºŒï¼šÂ Â <br /><br />既然open、closeå’Œalarm属于两个不同的概念,æ ÒŽ®ISP原则应该把它们分别定义在代表˜q™ä¸¤ä¸ªæ¦‚å¿ëŠš„抽象¾cÖM¸­ã€‚定义方式有åQšè¿™ä¸¤ä¸ªæ¦‚念都ä‹É用abstract class方式定义åQ›ä¸¤ä¸ªæ¦‚念都使用interface方式定义åQ›ä¸€ä¸ªæ¦‚念ä‹É用abstract class方式定义åQŒå¦ä¸€ä¸ªæ¦‚念ä‹É用interface方式定义。 Â?br /><br />昄¡„¶åQŒç”±äºŽJava语言不支持多重ç‘ô承,所以两个概念都使用abstract class方式定义是不可行的。后面两¿Uæ–¹å¼éƒ½æ˜¯å¯è¡Œçš„åQŒä½†æ˜¯å¯¹äºŽå®ƒä»¬çš„选择却反映出对于问题领域中的概念本质的理解、对于设计意囄¡š„反映是否正确、合理。我们一一来分析、说明。 Â?br /><br />如果两个概念都ä‹É用interface方式来定义,那么ž®±åæ˜ å‡ºä¸¤ä¸ªé—®é¢˜åQ?、我们可能没有理解清楚问题领域,AlarmDoor在概忉|œ¬è´¨ä¸Šåˆ°åº•是Door˜q˜æ˜¯æŠ¥è­¦å™¨ï¼Ÿ2、如果我们对于问题领域的理解没有问题åQŒæ¯”如:我们通过对于问题领域的分析发现AlarmDoor在概忉|œ¬è´¨ä¸Šå’ŒDoor是一致的åQŒé‚£ä¹ˆæˆ‘们在实现时就没有能够正确的揭½Cºæˆ‘们的设计意图åQŒå› ä¸ºåœ¨˜q™ä¸¤ä¸ªæ¦‚å¿ëŠš„定义上(均ä‹É用interface方式定义åQ‰åæ˜ ä¸å‡ÞZ¸Š˜q°å«ä¹‰ã€‚ Â?br /><br />如果我们对于问题领域的理解是åQšAlarmDoor在概忉|œ¬è´¨ä¸Šæ˜¯DooråQŒåŒæ—¶å®ƒæœ‰å…·æœ‰æŠ¥è­¦çš„功能。我们该如何来设计、实现来明确的反映出我们的意思呢åQŸå‰é¢å·²¾lè¯´˜q‡ï¼Œabstract class在Java语言中表½CÞZ¸€¿Uç‘ô承关¾p»ï¼Œè€Œç‘ô承关¾pÕdœ¨æœ¬è´¨ä¸Šæ˜¯"is a"关系。所以对于Door˜q™ä¸ªæ¦‚念åQŒæˆ‘们应该ä‹É用abstarct class方式来定义。另外,AlarmDooråˆå…·æœ‰æŠ¥è­¦åŠŸèƒ½ï¼Œè¯´æ˜Žå®ƒåˆèƒ½å¤Ÿå®ŒæˆæŠ¥è­¦æ¦‚å¿µä¸­å®šä¹‰çš„è¡ŒäØ“åQŒæ‰€ä»¥æŠ¥è­¦æ¦‚念可以通过interface方式定义。如下所½Cºï¼šÂ Â <br /><br />abstract class Door {  <br /> abstract void open();  <br /> abstract void close()åQ›Â Â?br />}  <br />interface Alarm {  <br /> void alarm();  <br />}  <br />class AlarmDoor extends Door implements Alarm {  <br /> void open() { … }  <br /> void close() { … }  <br />    void alarm() { … }  <br />}  <br /><br />   <br />˜q™ç§å®žçŽ°æ–¹å¼åŸºæœ¬ä¸Šèƒ½å¤Ÿæ˜Ž¼‹®çš„反映出我们对于问题领域的理解åQŒæ­£¼‹®çš„æ­ç¤ºæˆ‘们的设计意图。其实abstract class表示的是"is a"关系åQŒinterface表示的是"like a"关系åQŒå¤§å®¶åœ¨é€‰æ‹©æ—¶å¯ä»¥ä½œä¸ÞZ¸€ä¸ªä¾æ®ï¼Œå½“ç„¶˜q™æ˜¯å»ºç«‹åœ¨å¯¹é—®é¢˜é¢†åŸŸçš„理解上的,比如åQšå¦‚果我们认为AlarmDoor在概忉|œ¬è´¨ä¸Šæ˜¯æŠ¥è­¦å™¨åQŒåŒæ—¶åˆå…ähœ‰Door的功能,那么上述的定义方式就要反˜q‡æ¥äº†ã€?/font> </span> </span> <img src ="http://www.aygfsteel.com/liulu/aggbug/102584.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/liulu/" target="_blank">刘璐</a> 2007-03-08 13:27 <a href="http://www.aygfsteel.com/liulu/archive/2007/03/08/102584.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于session的详¾l†è§£é‡?/title><link>http://www.aygfsteel.com/liulu/archive/2007/03/07/102334.html</link><dc:creator>刘璐</dc:creator><author>刘璐</author><pubDate>Wed, 07 Mar 2007 02:50:00 GMT</pubDate><guid>http://www.aygfsteel.com/liulu/archive/2007/03/07/102334.html</guid><wfw:comment>http://www.aygfsteel.com/liulu/comments/102334.html</wfw:comment><comments>http://www.aygfsteel.com/liulu/archive/2007/03/07/102334.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/liulu/comments/commentRss/102334.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/liulu/services/trackbacks/102334.html</trackback:ping><description><![CDATA[ <p style="TEXT-INDENT: 2em"> <span id="ArticleContent1_ArticleContent1_lblContent">一、术语session <p>  在我的经验里åQŒsession˜q™ä¸ªè¯è¢«æ»¥ç”¨çš„程度大概仅‹Æ¡äºŽtransactionåQŒæ›´åŠ æœ‰­‘£çš„æ˜¯transaction与session在某些语境下的含义是相同的ã€?/p><p>  sessionåQŒä¸­æ–‡ç»å¸¸ç¿»è¯‘äØ“ä¼šè¯åQŒå…¶æœ¬æ¥çš„含义是指有始有¾lˆçš„一¾pÕdˆ—动作/消息åQŒæ¯”如打电话时从拿è“v电话拨号到挂断电话这中间的一¾pÕdˆ—˜q‡ç¨‹å¯ä»¥¿UîC¹‹ä¸ÞZ¸€ä¸ªsession。有时候我们可以看到这æ ïLš„话“在一个浏览器会话期间åQ?..”,˜q™é‡Œçš„会话一词用的就是其本义åQŒæ˜¯æŒ‡ä»Žä¸€ä¸ªæµè§ˆå™¨½H—å£æ‰“å¼€åˆ°å…³é—­è¿™ä¸ªæœŸé—´â‘ ã€‚æœ€æ··äØ•çš„æ˜¯â€œç”¨æˆøP¼ˆå®¢æˆ·ç«¯ï¼‰åœ¨ä¸€‹Æ¡ä¼šè¯æœŸé—´â€è¿™æ ·ä¸€å¥è¯åQŒå®ƒå¯èƒ½æŒ‡ç”¨æˆïLš„一¾pÕdˆ—动作åQˆä¸€èˆ¬æƒ…况下是同某个具体目的相关的一¾pÕdˆ—动作åQŒæ¯”如从ç™Õd½•åˆ°é€‰è´­å•†å“åˆ°ç»“è´¦ç™»å‡ø™¿™æ ·ä¸€ä¸ªç½‘上购物的˜q‡ç¨‹åQŒæœ‰æ—¶å€™ä¹Ÿè¢«ç§°ä¸ÞZ¸€ä¸ªtransactionåQ‰ï¼Œç„¶è€Œæœ‰æ—¶å€™ä¹Ÿå¯èƒ½ä»…仅是指一‹Æ¡è¿žæŽ¥ï¼Œä¹Ÿæœ‰å¯èƒ½æ˜¯æŒ‡å«ä¹‰â‘ ï¼Œå…¶ä¸­çš„差别只能靠上下文来推断②ã€?/p><p>  然而当session一词与¾|‘络协议相关联时åQŒå®ƒåˆå¾€å¾€éšå«äº†â€œé¢å‘连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到å¯ÒŽ–¹æŽ¥äº†ç”µè¯é€šä¿¡æ‰èƒ½å¼€å§‹ï¼Œä¸Žæ­¤ç›¸å¯¹çš„æ˜¯å†™ä¿¡åQŒåœ¨ä½ æŠŠä¿¡å‘出去的时候你òq¶ä¸èƒ½ç¡®è®¤å¯¹æ–¹çš„地址是否正确åQŒé€šä¿¡æ¸ é“不一定能建立åQŒä½†å¯¹å‘ä¿¡äh来说åQŒé€šä¿¡å·²ç»å¼€å§‹äº†ã€‚“保持状态”则是指通信的一方能够把一¾pÕdˆ—的消息关联è“v来,使得消息之间可以互相依赖åQŒæ¯”如一个服务员能够认出再次光äÍçš„è€é¡¾å®¢åÆˆä¸”è®°å¾—ä¸Š‹Æ¡è¿™ä¸ªé¡¾å®¢è¿˜‹Æ åº—里一块钱。这一¾cȝš„例子有“一个TCP session”或者“一个POP3 session”③ã€?/p><p>  而到了web服务器蓬勃发展的时代åQŒsession在web开发语境下的语义又有了新的扩展åQŒå®ƒçš„含义是指一¾cȝ”¨æ¥åœ¨å®¢æˆ·ç«¯ä¸ŽæœåŠ¡å™¨ä¹‹é—´ä¿æŒçŠ¶æ€çš„è§£å†³æ–ÒŽ¡ˆâ‘£ã€‚有时候session也用来指˜q™ç§è§£å†³æ–ÒŽ¡ˆçš„存储结构,如“把xxx保存在session里”⑤。由于各¿Uç”¨äºŽweb开发的语言在一定程度上都提供了对这¿Uè§£å†Ïx–¹æ¡ˆçš„æ”¯æŒåQŒæ‰€ä»¥åœ¨æŸç§ç‰¹å®šè¯­è¨€çš„语境下åQŒsession也被用来指代该语­a€çš„è§£å†Ïx–¹æ¡ˆï¼Œæ¯”如¾lå¸¸æŠŠJava里提供的javax.servlet.http.HttpSession½Ž€¿UîCØ“sessionâ‘¥ã€?/p><p>  鉴于˜q™ç§æ··äؕ已不可改变,本文中session一词的˜qç”¨ä¹Ÿä¼šæ ÒŽ®ä¸Šä¸‹æ–‡æœ‰ä¸åŒçš„含义,请大家注意分辨ã€?/p><p>  在本文中åQŒä‹É用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表辑֐«ä¹‰â‘¤åQŒä‹É用具体的“HttpSession”来表达含义â‘?/p><p>  二、HTTP协议与状态保æŒ?/p><p>  HTTP协议本èín是无状态的åQŒè¿™ä¸ŽHTTP协议本来的目的是相符的,客户端只需要简单的向服务器è¯äh±‚下蝲某些文äšgåQŒæ— è®ºæ˜¯å®¢æˆ·ç«¯è¿˜æ˜¯æœåŠ¡å™¨éƒ½æ²¡æœ‰å¿…è¦çºªå½•å½¼æ­¤è¿‡åŽÈš„è¡ŒäØ“åQŒæ¯ä¸€‹Æ¡è¯·æ±‚之间都是独立的åQŒå¥½æ¯”一个顾客和一个自动售货机或者一个普通的åQˆéžä¼šå‘˜åˆÓž¼‰å¤§å–åœÞZ¹‹é—´çš„关系一栗÷€?/p><p>  然而聪明(或者贪心?åQ‰çš„äºÞZ»¬å¾ˆå¿«å‘现如果能够提供一些按需生成的动态信息会使web变得更加有用åQŒå°±åƒç»™æœ‰çº¿ç”µè§†åŠ ä¸Šç‚ÒŽ’­åŠŸèƒ½ä¸€æ —÷€‚è¿™¿Uéœ€æ±‚一斚w¢˜q«ä‹ÉHTML逐步æ·ÕdŠ äº†è¡¨å•ã€è„šæœ¬ã€DOM½{‰å®¢æˆïL«¯è¡ŒäØ“åQŒå¦ä¸€æ–šw¢åœ¨æœåŠ¡å™¨ç«¯åˆ™å‡ºçŽ°äº†CGI规范以响应客æˆïL«¯çš„åŠ¨æ€è¯·æ±‚ï¼Œä½œäØ“ä¼ è¾“è½½ä½“çš„HTTP协议也添加了文äšg上蝲、cookie˜q™äº›ç‰ÒŽ€§ã€‚其中cookieçš„ä½œç”¨å°±æ˜¯äØ“äº†è§£å†³HTTP协议无状态的¾~ºé™·æ‰€ä½œå‡ºçš„努力。至于后来出现的session机制则是又一¿Uåœ¨å®¢æˆ·ç«¯ä¸ŽæœåŠ¡å™¨ä¹‹é—´ä¿æŒçŠ¶æ€çš„è§£å†³æ–ÒŽ¡ˆã€?/p><p>  让我们用几个例子来描˜qîC¸€ä¸‹cookieå’Œsession机制之间的区别与联系。笔者曾¾lå¸¸åŽÈš„一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠åQŒç„¶è€Œä¸€‹Æ¡æ€§æ¶ˆè´?杯咖啡的æœÞZ¼šå¾®ä¹Žå…¶å¾®åQŒè¿™æ—¶å°±éœ€è¦æŸ¿Uæ–¹å¼æ¥¾Uªå½•某位™å‘Ö®¢çš„æ¶ˆè´ÒŽ•°é‡ã€‚想象一下其实也无外乎下面的几种æ–ÒŽ¡ˆåQ?/p><p>  1、该店的店员很厉宻I¼Œèƒ½è®°ä½æ¯ä½é¡¾å®¢çš„æ¶ˆè´¹æ•°é‡åQŒåªè¦é¡¾å®¢ä¸€èµ°è¿›å’–啡店,店员ž®ÞqŸ¥é“该怎么对待了。这¿Uåšæ³•就是协议本íw«æ”¯æŒçŠ¶æ€ã€?/p><p>  2、发¾l™é¡¾å®¢ä¸€å¼ å¡ç‰‡ï¼Œä¸Šé¢è®°å½•着消费的数量,一般还有个有效期限。每‹Æ¡æ¶ˆè´ÒŽ—¶åQŒå¦‚果顾客出½Cø™¿™å¼ å¡ç‰‡ï¼Œåˆ™æ­¤‹Æ¡æ¶ˆè´¹å°±ä¼šä¸Žä»¥å‰æˆ–以后的消费相联¾p»è“v来。这¿Uåšæ³•就是在客户端保持状态ã€?/p><p>  3、发¾l™é¡¾å®¢ä¸€å¼ ä¼šå‘˜å¡åQŒé™¤äº†å¡å·ä¹‹å¤–什么信息也不纪录,每次消费æ—Óž¼Œå¦‚æžœ™å‘Ö®¢å‡ºç¤ºè¯¥å¡ç‰‡ï¼Œåˆ™åº—员在店里的纪录本上找到这个卡号对应的¾Uªå½•æ·ÕdŠ ä¸€äº›æ¶ˆè´¹ä¿¡æ¯ã€‚è¿™¿Uåšæ³•就是在服务器端保持状态ã€?/p><p>  ç”׃ºŽHTTP协议是无状态的åQŒè€Œå‡ºäºŽç§¿Uè€ƒè™‘ä¹Ÿä¸å¸Œæœ›ä½¿ä¹‹æˆäØ“æœ‰çŠ¶æ€çš„åQŒå› æ­¤ï¼ŒåŽé¢ä¸¤ç§æ–ÒŽ¡ˆž®±æˆä¸ºçŽ°å®žçš„é€‰æ‹©ã€‚å…·ä½“æ¥è¯´cookie机制采用的是在客æˆïL«¯ä¿æŒçŠ¶æ€çš„æ–ÒŽ¡ˆåQŒè€Œsession机制采用的是在服务器端保持状态的æ–ÒŽ¡ˆã€‚同时我们也看到åQŒç”±äºŽé‡‡ç”¨æœåŠ¡å™¨ç«¯ä¿æŒçŠ¶æ€çš„æ–ÒŽ¡ˆåœ¨å®¢æˆïL«¯ä¹Ÿéœ€è¦ä¿å­˜ä¸€ä¸ªæ ‡è¯†ï¼Œæ‰€ä»¥session机制可能需要借助于cookie机制来达åˆîC¿å­˜æ ‡è¯†çš„目的åQŒä½†å®žé™…上它˜q˜æœ‰å…¶ä»–选择ã€?/p><p>  三、理解cookie机制 </p><p>  cookie机制的基本原理就如上面的例子一æ ïL®€å•,但是˜q˜æœ‰å‡ ä¸ªé—®é¢˜éœ€è¦è§£å†»I¼šâ€œä¼šå‘˜å¡â€å¦‚何分发;“会员卡”的内容åQ›ä»¥åŠå®¢æˆ·å¦‚何ä‹É用“会员卡”ã€?/p><p>  正统的cookie分发是通过扩展HTTP协议来实现的åQŒæœåŠ¡å™¨é€šè¿‡åœ¨HTTP的响应头中加上一行特ŒDŠçš„æŒ‡ç¤ºä»¥æ½Cºæµè§ˆå™¨æŒ‰ç…§æŒ‡ç¤ºç”Ÿæˆç›¸åº”çš„cookie。然而纯¾_¹çš„客户端脚本如JavaScript或者VBScript也可以生成cookieã€?/p><p>  而cookieçš„ä‹É用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器‹‚€æŸ¥æ‰€æœ‰å­˜å‚¨çš„cookieåQŒå¦‚果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置åQŒåˆ™æŠŠè¯¥cookie附在è¯äh±‚资源的HTTPè¯äh±‚头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示åQŒå¦‚果某家分店还发行了自å·Þqš„会员卡,那么˜q›è¿™å®¶åº—的时候除了要出示麦当劳的会员卡,˜q˜è¦å‡ºç¤º˜q™å®¶åº—的会员卡ã€?/p><p>  cookie的内容主要包括:名字åQŒå€û|¼Œ˜q‡æœŸæ—‰™—´åQŒèµ\径和域ã€?/p><p>  其中域可以指定某一个域比如.google.comåQŒç›¸å½“于æ€Õdº—招牌åQŒæ¯”如宝‹zå…¬å¸ï¼Œä¹Ÿå¯ä»¥æŒ‡å®šä¸€ä¸ªåŸŸä¸‹çš„具体某台机器比如www.google.com或者froogle.google.comåQŒå¯ä»¥ç”¨é£˜æŸ”来做比ã€?/p><p>  路径ž®±æ˜¯è·Ÿåœ¨åŸŸååŽé¢çš„URL路径åQŒæ¯”å¦?或è€?foo½{‰ç­‰åQŒå¯ä»¥ç”¨æŸé£˜æŸ”专柜做比ã€?/p><p>  路径与域合在一起就构成了cookie的作用范围ã€?/p><p>  如果不设¾|®è¿‡æœŸæ—¶é—ß_¼Œåˆ™è¡¨½Cø™¿™ä¸ªcookie的生命期为浏览器会话期间åQŒåªè¦å…³é—­æµè§ˆå™¨½H—口åQŒcookiež®±æ¶ˆå¤×ƒº†ã€‚è¿™¿Uç”Ÿå‘½æœŸä¸ºæµè§ˆå™¨ä¼šè¯æœŸçš„cookie被称ä¸ÞZ¼šè¯cookie。会话cookie一般不存储在硬盘上而是保存在内存里åQŒå½“ç„¶è¿™¿Uè¡Œä¸ºåƈ不是规范规定的。如果设¾|®äº†˜q‡æœŸæ—‰™—´åQŒæµè§ˆå™¨ž®×ƒ¼šæŠŠcookie保存到硬盘上åQŒå…³é—­åŽå†æ¬¡æ‰“å¼€‹¹è§ˆå™¨ï¼Œ˜q™äº›cookie仍然有效直到­‘…过讑֮šçš„过期时间ã€?/p><p>  存储在硬盘上的cookie可以在不同的‹¹è§ˆå™¨è¿›½E‹é—´å…׃ínåQŒæ¯”如两个IE½H—口。而对于保存在内存里的cookieåQŒä¸åŒçš„‹¹è§ˆå™¨æœ‰ä¸åŒçš„处理方式。对于IEåQŒåœ¨ä¸€ä¸ªæ‰“开的窗口上按Ctrl-NåQˆæˆ–者从文äšg菜单åQ‰æ‰“开的窗口可以与原窗口共享,而ä‹É用其他方式新开的IE˜q›ç¨‹åˆ™ä¸èƒ½å…±äº«å·²¾læ‰“开的窗口的内存cookieåQ›å¯¹äºŽMozilla Firefox0.8åQŒæ‰€æœ‰çš„˜q›ç¨‹å’Œæ ‡½{ùN¡µéƒ½å¯ä»¥å…±äº«åŒæ ïLš„cookie。一般来说是用javascriptçš„window.open打开的窗口会与原½H—口å…׃ín内存cookie。浏览器对于会话cookie的这¿Uåªè®¤cookie不认人的处理方式¾lå¸¸¾l™é‡‡ç”¨session机制的web应用½E‹åºå¼€å‘者造成很大的困扰ã€?/p><p>  下面ž®±æ˜¯ä¸€ä¸ªgoolge讄¡½®cookie的响应头的例å­?/p><p>HTTP/1.1 302 Found<br />Location: http://www.google.com/intl/zh-CN/<br />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<br />Content-Type: text/html</p><p align="center"></p><p>  ˜q™æ˜¯ä½¿ç”¨HTTPLook˜q™ä¸ªHTTP Sniffer软äšg来俘èŽïLš„HTTP通讯¾Uªå½•的一部分</p><p align="center"></p><p><br />  ‹¹è§ˆå™¨åœ¨å†æ¬¡è®‰K—®goolge的资源时自动向外发送cookie</p><p align="center"></p><p>  使用Firefox可以很容易的观察现有的cookieçš„å€?/p><p>  使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理ã€?/p><p align="center"></p><p><br />  IE也可以设¾|®åœ¨æŽ¥å—cookie前询é—?/p><p align="center"></p><p>  ˜q™æ˜¯ä¸€ä¸ªè¯¢é—®æŽ¥å—cookie的对话框ã€?/p><p>  四、理解session机制</p><p> session机制是一¿UæœåŠ¡å™¨ç«¯çš„æœºåˆ¶åQŒæœåŠ¡å™¨ä½¿ç”¨ä¸€¿Uç±»ä¼égºŽæ•£åˆ—表的¾l“æž„åQˆä¹Ÿå¯èƒ½ž®±æ˜¯ä½¿ç”¨æ•£åˆ—表)来保存信息ã€?/p><p>ã€€ã€€å½“ç¨‹åºéœ€è¦äØ“æŸä¸ªå®¢æˆ·ç«¯çš„è¯äh±‚创徏一个session的时候,服务器首先检查这个客æˆïL«¯çš„请求里是否已包含了一个session标识 - ¿UîCØ“session idåQŒå¦‚果已包含一个session id则说明以前已¾läؓ此客æˆïL«¯åˆ›å¾˜q‡sessionåQŒæœåС噍ž®±æŒ‰ç…§session id把这个session‹‚€ç´¢å‡ºæ¥ä‹É用(如果‹‚€ç´¢ä¸åˆŽÍ¼Œå¯èƒ½ä¼šæ–°å»ÞZ¸€ä¸ªï¼‰åQŒå¦‚果客æˆïL«¯è¯äh±‚不包含session idåQŒåˆ™ä¸ºæ­¤å®¢æˆ·ç«¯åˆ›å»ÞZ¸€ä¸ªsessionòq¶ä¸”生成一个与此session相关联的session idåQŒsession id的值应该是一个既不会重复åQŒåˆä¸å®¹æ˜“被扑ֈ°è§„律以仿造的字符ä¸ÔŒ¼Œ˜q™ä¸ªsession idž®†è¢«åœ¨æœ¬‹Æ¡å“åº”中˜q”回¾l™å®¢æˆïL«¯ä¿å­˜ã€?/p><p>  保存˜q™ä¸ªsession id的方式可以采用cookieåQŒè¿™æ ·åœ¨äº¤äº’˜q‡ç¨‹ä¸­æµè§ˆå™¨å¯ä»¥è‡ªåŠ¨çš„æŒ‰ç…§è§„åˆ™æŠŠ˜q™ä¸ªæ ‡è¯†å‘挥¾l™æœåŠ¡å™¨ã€‚ä¸€èˆ¬è¿™ä¸ªcookie的名字都是类ä¼égºŽSEEESIONIDåQŒè€Œã€‚比如weblogic对于web应用½E‹åºç”Ÿæˆçš„cookieåQŒJSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764åQŒå®ƒçš„名字就是JSESSIONIDã€?/p><p>  ç”׃ºŽcookie可以被äh为的¼›æ­¢åQŒå¿…™åÀLœ‰å…¶ä»–机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一¿UæŠ€æœ¯å«åšURL重写åQŒå°±æ˜¯æŠŠsession id直接附加在URL路径的后面,附加方式也有两种åQŒä¸€¿Uæ˜¯ä½œäØ“URL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764另一¿Uæ˜¯ä½œäؓ查询字符串附加在URL后面åQŒè¡¨çްåÅžå¼äØ“http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764<br />˜q™ä¸¤¿Uæ–¹å¼å¯¹äºŽç”¨æˆäh¥è¯´æ˜¯æ²¡æœ‰åŒºåˆ«çš„,只是服务器在解析的时候处理的方式不同åQŒé‡‡ç”¨ç¬¬ä¸€¿Uæ–¹å¼ä¹Ÿæœ‰åˆ©äºŽæŠŠsession id的信息和正常½E‹åºå‚数区分开来ã€?/p><p>  ä¸ÞZº†åœ¨æ•´ä¸ªäº¤äº’过½E‹ä¸­å§‹ç»ˆä¿æŒçŠ¶æ€ï¼Œž®±å¿…™åÕdœ¨æ¯ä¸ªå®¢æˆ·ç«¯å¯èƒ½è¯·æ±‚的路径后面都包含这个session idã€?/p><p>  另一¿UæŠ€æœ¯å«åšè¡¨å•隐藏字ŒDüc€‚就是服务器会自动修改表单,æ·ÕdŠ ä¸€ä¸ªéšè—å­—ŒDµï¼Œä»¥ä¾¿åœ¨è¡¨å•提交时能够把session id传递回服务器。比如下面的表单</p><p></p><form name="testform" action="/xxx"><br /><input /><br /></form></span> </p> <p>  在被传递给客户端之前将被改写成</p> <p> </p> <form name="testform" action="/xxx"> <br /> <input type="hidden" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764" name="jsessionid" /> <br /> <input /> <br /> </form> <p>  ˜q™ç§æŠ€æœ¯çŽ°åœ¨å·²è¾ƒå°‘åº”ç”¨åQŒç¬”者接触过的很古老的iPlanet6(SunONE应用服务器的前èín)ž®×ƒ‹É用了˜q™ç§æŠ€æœ¯ã€‚实际上˜q™ç§æŠ€æœ¯å¯ä»¥ç®€å•的用对action应用URL重写来代æ›Ñ€?/p> <p>  在谈论session机制的时候,常常听到˜q™æ ·ä¸€¿Uè¯¯è§£â€œåªè¦å…³é—­æµè§ˆå™¨åQŒsessionž®±æ¶ˆå¤×ƒº†â€ã€‚其实可以想象一下会员卡的例子,除非™å‘Ö®¢ä¸ÕdŠ¨å¯¹åº—å®¶æå‡ºé”€å¡ï¼Œå¦åˆ™åº—å®¶¾lå¯¹ä¸ä¼šè½ÀL˜“删除™å‘Ö®¢çš„资料。对session来说也是一æ ïLš„åQŒé™¤éžç¨‹åºé€šçŸ¥æœåŠ¡å™¨åˆ é™¤ä¸€ä¸ªsessionåQŒå¦åˆ™æœåŠ¡å™¨ä¼šä¸€ç›´ä¿ç•™ï¼Œ½E‹åºä¸€èˆ¬éƒ½æ˜¯åœ¨ç”¨æˆ·åšlog off的时候发个指令去删除session。然而浏览器从来不会ä¸ÕdŠ¨åœ¨å…³é—­ä¹‹å‰é€šçŸ¥æœåŠ¡å™¨å®ƒž®†è¦å…³é—­åQŒå› æ­¤æœåС噍æ ÒŽœ¬ä¸ä¼šæœ‰æœºä¼šçŸ¥é“浏览器已经关闭åQŒä¹‹æ‰€ä»¥ä¼šæœ‰è¿™¿Ué”™è§‰ï¼Œæ˜¯å¤§éƒ¨åˆ†session机制都ä‹É用会话cookie来保存session idåQŒè€Œå…³é—­æµè§ˆå™¨åŽè¿™ä¸ªsession idž®±æ¶ˆå¤×ƒº†åQŒå†‹Æ¡è¿žæŽ¥æœåŠ¡å™¨æ—¶ä¹Ÿž®±æ— æ³•找到原来的session。如果服务器讄¡½®çš„cookie被保存到¼‹¬ç›˜ä¸Šï¼Œæˆ–者ä‹É用某¿Uæ‰‹ŒD‰|”¹å†™æµè§ˆå™¨å‘出的HTTPè¯äh±‚å¤ß_¼ŒæŠŠåŽŸæ¥çš„session id发送给服务器,则再‹Æ¡æ‰“å¼€‹¹è§ˆå™¨ä»ç„¶èƒ½å¤Ÿæ‰¾åˆ°åŽŸæ¥çš„sessionã€?/p> <p>ã€€ã€€æ°æ°æ˜¯ç”±äºŽå…³é—­æµè§ˆå™¨ä¸ä¼šå¯ÆD‡´session被删除,˜q«ä‹ÉæœåŠ¡å™¨äØ“seesion讄¡½®äº†ä¸€ä¸ªå¤±æ•ˆæ—¶é—ß_¼Œå½“距¼›Õd®¢æˆïL«¯ä¸Šä¸€‹Æ¡ä‹É用session的时间超˜q‡è¿™ä¸ªå¤±æ•ˆæ—¶é—´æ—¶åQŒæœåС噍ž®±å¯ä»¥è®¤ä¸ºå®¢æˆïL«¯å·²ç»åœæ­¢äº†æ´»åŠ¨ï¼Œæ‰ä¼šæŠŠsession删除以节省存储空间ã€?/p> <p>  五、理解javax.servlet.http.HttpSession</p> <p>  HttpSession是Javaòq›_°å¯¹sessionæœºåˆ¶çš„å®žçŽ°è§„èŒƒï¼Œå› äØ“å®ƒä»…ä»…æ˜¯ä¸ªæŽ¥å£ï¼Œå…·ä½“åˆ°æ¯ä¸ªweb应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEAçš„Weblogic Server8.1ä½œäØ“ä¾‹å­æ¥æ¼”½Cºã€?/p> <p>  首先åQŒWeblogic Server提供了一¾pÕdˆ—的参数来控制它的HttpSession的实玎ͼŒåŒ…括使用cookie的开关选项åQŒä‹É用URL重写的开关选项åQŒsession持久化的讄¡½®åQŒsession失效旉™—´çš„设¾|®ï¼Œä»¥åŠé’ˆå¯¹cookie的各¿Uè®¾¾|®ï¼Œæ¯”如讄¡½®cookie的名字、èµ\径、域åQŒcookie的生存时间等ã€?/p> <p>  一般情况下åQŒsession都是存储在内存里åQŒå½“服务器进½E‹è¢«åœæ­¢æˆ–者重启的时候,内存里的session也会被清½Iºï¼Œå¦‚果讄¡½®äº†session的持久化ç‰ÒŽ€§ï¼ŒæœåŠ¡å™¨å°±ä¼šæŠŠsession保存到硬盘上åQŒå½“服务器进½E‹é‡æ–°å¯åŠ¨æˆ–˜q™äº›ä¿¡æ¯ž®†èƒ½å¤Ÿè¢«å†æ¬¡ä½¿ç”¨åQŒWeblogic Server支持的持久性方式包括文件、数据库、客æˆïL«¯cookie保存和复制ã€?/p> <p>ã€€ã€€å¤åˆ¶ä¸¥æ ¼è¯´æ¥ä¸ç®—æŒä¹…åŒ–ä¿å­˜ï¼Œå› äØ“session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进½E‹ä¸­åQŒè¿™æ ·å³ä½¿æŸä¸ªæœåС噍˜q›ç¨‹åœæ­¢å·¥ä½œä¹Ÿä»ç„¶å¯ä»¥ä»Žå…¶ä»–˜q›ç¨‹ä¸­å–å¾—sessionã€?/p> <p>  cookie生存旉™—´çš„设¾|®åˆ™ä¼šåª„响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解ã€?/p> <p>  cookieçš„èµ\径对于web应用½E‹åºæ¥è¯´æ˜¯ä¸€ä¸ªéžå¸”R‡è¦çš„选项åQŒWeblogic Server对这个选项的默认处理方式ä‹É得它与其他服务器有明昄¡š„区别。后面我们会专题讨论ã€?/p> <p>  关于session的设¾|®å‚考[5] <a >http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869</a></p> <p>  六、HttpSession常见问题</p> <p>  åQˆåœ¨æœ¬å°èЂ䏭sessionçš„å«ä¹‰äØ“â‘¤å’Œâ‘¥çš„æ··åˆåQ?/p> <p>  1、session在何时被创徏</p> <p>  一个常见的误解是以为session在有客户端访问时ž®Þp¢«åˆ›å¾åQŒç„¶è€Œäº‹å®žæ˜¯ç›´åˆ°æŸserver端程序调用HttpServletRequest.getSession(true)˜q™æ ·çš„语句时才被创徏åQŒæ³¨æ„å¦‚æžœJSP没有昄¡¤ºçš„ä‹Éç”?<%@page session="false"%>关闭sessionåQŒåˆ™JSPæ–‡äšg在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);˜q™ä¹Ÿæ˜¯JSP中隐含的session对象的来历ã€?/p> <p>  ç”׃ºŽsession会消耗内存资源,因此åQŒå¦‚果不打算使用sessionåQŒåº”该在所有的JSP中关闭它ã€?/p> <p>  2、session何时被删é™?/p> <p>  ¾l¼åˆå‰é¢çš„讨论,session在下列情况下被删除a.½E‹åºè°ƒç”¨HttpSession.invalidate();或b.距离上一‹Æ¡æ”¶åˆ°å®¢æˆïL«¯å‘送的session idæ—‰™—´é—´éš”­‘…过了session的超时设¾|?或c.服务器进½E‹è¢«åœæ­¢åQˆéžæŒä¹…sessionåQ?/p> <p>  3、如何做到在‹¹è§ˆå™¨å…³é—­æ—¶åˆ é™¤session</p> <p>  严格的讲åQŒåšä¸åˆ°˜q™ä¸€ç‚V€‚可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进½E‹è¿™äº›éžå¸¸è§„手段仍然无能为力ã€?/p> <p>  4、有个HttpSessionListener是怎么回事</p> <p>ã€€ã€€ä½ å¯ä»¥åˆ›å»ø™¿™æ ïLš„listeneråŽÈ›‘控session的创建和销毁事ä»Óž¼Œä½¿å¾—在发生这æ ïLš„事äšg时你可以做一些相应的工作。注意是session的创建和销毁动作触发listeneråQŒè€Œä¸æ˜¯ç›¸åã€‚类似的与HttpSession有关的listener˜q˜æœ‰HttpSessionBindingListeneråQŒHttpSessionActivationListenerå’ŒHttpSessionAttributeListenerã€?br /><br />        5、存攑֜¨session中的对象必须是可序列化的å?/p> <p>ã€€ã€€ä¸æ˜¯å¿…éœ€çš„ã€‚è¦æ±‚å¯¹è±¡å¯åºåˆ—åŒ–åªæ˜¯äØ“äº†session能够在集¾Ÿ¤ä¸­è¢«å¤åˆ¶æˆ–者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Serverçš„session中放¾|®ä¸€ä¸ªä¸å¯åºåˆ—化的对象在控制åîC¸Šä¼šæ”¶åˆîC¸€ä¸ªè­¦å‘Šã€‚我所用过的某个iPlanet版本如果session中有不可序列化的对象åQŒåœ¨session销毁时会有一个ExceptionåQŒå¾ˆå¥‡æ€ªã€?/p> <p>  6、如何才能正¼‹®çš„应付客户端禁止cookie的可能æ€?/p> <p>  å¯Òމ€æœ‰çš„URL使用URL重写åQŒåŒ…括超链接åQŒformçš„actionåQŒå’Œé‡å®šå‘çš„URLåQŒå…·ä½“做法参见[6]<br /><a >http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</a></p> <p>  7、开两个‹¹è§ˆå™¨çª—口访问应用程序会使用同一个session˜q˜æ˜¯ä¸åŒçš„session</p> <p>  参见½W¬ä¸‰ž®èЂ坹cookie的讨论,对session来说是只认id不认人,因此不同的浏览器åQŒä¸åŒçš„½H—口打开方式以及不同的cookie存储方式都会对这个问题的½{”案有媄响ã€?/p> <p>  8、如何防止用æˆäh‰“开两个‹¹è§ˆå™¨çª—口操作导致的sessionæ··äØ•</p> <p>  ˜q™ä¸ªé—®é¢˜ä¸Žé˜²æ­¢è¡¨å•多‹Æ¡æäº¤æ˜¯¾cÖM¼¼çš„,可以通过讄¡½®å®¢æˆ·ç«¯çš„令牌来解冟뀂就是在服务器每‹Æ¡ç”Ÿæˆä¸€ä¸ªä¸åŒçš„id˜q”回¾l™å®¢æˆïL«¯åQŒåŒæ—¶ä¿å­˜åœ¨session里,客户端提交表单时必须把这个id也返回服务器åQŒç¨‹åºé¦–先比较返回的id与保存在session里的值是否一è‡ß_¼Œå¦‚果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表½Cºå±‚模式的部分。需要注意的是对于ä‹É用javascript window.open打开的窗口,一般不讄¡½®˜q™ä¸ªidåQŒæˆ–者ä‹É用单独的idåQŒä»¥é˜²ä¸»½H—口无法操作åQŒå¾è®®ä¸è¦å†window.open打开的窗口里做修æ”ÒŽ“ä½œï¼Œ˜q™æ ·ž®±å¯ä»¥ä¸ç”¨è®¾¾|®ã€?/p> <p>  9ã€äØ“ä»€ä¹ˆåœ¨Weblogic Server中改变session的值后要重新调用一‹Æ¡session.setValue<br />做这个动作主要是ä¸ÞZº†åœ¨é›†¾Ÿ¤çŽ¯å¢ƒä¸­æç¤ºWeblogic Server session中的值发生了改变åQŒéœ€è¦å‘其他服务器进½E‹å¤åˆ¶æ–°çš„session倹{€?/p> <p>  10ã€äØ“ä»€ä¹ˆsession不见äº?/p> <p>  排除session正常失效的因素之外,服务器本íw«çš„可能性应该是微乎其微的,虽然½W”者在iPlanet6SP1加若òq²è¡¥ä¸çš„Solaris版本上倒也遇到˜q‡ï¼›‹¹è§ˆå™¨æ’件的可能性次之,½W”者也遇到˜q?721插äšg造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题ã€?/p> <p>  出现˜q™ä¸€é—®é¢˜çš„大部分原因都是½E‹åºçš„é”™è¯¯ï¼Œæœ€å¸¸è§çš„å°±æ˜¯åœ¨ä¸€ä¸ªåº”ç”¨ç¨‹åºä¸­åŽ»è®¿é—®å¦å¤–ä¸€ä¸ªåº”ç”¨ç¨‹åºã€‚æˆ‘ä»¬åœ¨ä¸‹ä¸€èŠ‚è®¨è®ø™¿™ä¸ªé—®é¢˜ã€?/p> <p id="#7">  七、跨应用½E‹åºçš„sessionå…׃ín<br /><br />  常常有这æ ïLš„æƒ…况åQŒä¸€ä¸ªå¤§™å¹ç›®è¢«åˆ†å‰²æˆè‹¥å¹²ž®é¡¹ç›®å¼€å‘,ä¸ÞZº†èƒ½å¤Ÿäº’不òq²æ‰°åQŒè¦æ±‚每个小™å¹ç›®ä½œäؓ一个单独的web应用½E‹åºå¼€å‘,可是åˆîCº†æœ€åŽçªç„¶å‘现某几个ž®é¡¹ç›®ä¹‹é—´éœ€è¦å…±äº«ä¸€äº›ä¿¡æ¯ï¼Œæˆ–者想使用session来实现SSO(single sign on)åQŒåœ¨session中保存login的用户信息,最自然的要求是应用½E‹åºé—´èƒ½å¤Ÿè®¿é—®å½¼æ­¤çš„sessionã€?/p> <p>  然而按照Servlet规范åQŒsession的作用范围应该仅仅限于当前应用程序下åQŒä¸åŒçš„应用½E‹åºä¹‹é—´æ˜¯ä¸èƒ½å¤Ÿäº’相讉K—®å¯ÒŽ–¹çš„session的。各个应用服务器从实际效果上都遵守了˜q™ä¸€è§„范åQŒä½†æ˜¯å®žçŽ°çš„¾l†èŠ‚å´å¯èƒ½å„æœ‰ä¸åŒï¼Œå› æ­¤è§£å†³è·¨åº”ç”¨ç¨‹åºsessionå…׃ín的方法也各不相同ã€?/p> <p>  首先来看一下Tomcat是如何实现web应用½E‹åºä¹‹é—´session的隔¼›Èš„åQŒä»ŽTomcat讄¡½®çš„cookie路径来看åQŒå®ƒå¯¹ä¸åŒçš„应用½E‹åºè®„¡½®çš„cookie路径是不同的åQŒè¿™æ ·ä¸åŒçš„应用½E‹åºæ‰€ç”¨çš„session id是不同的åQŒå› æ­¤å³ä½¿åœ¨åŒä¸€ä¸ªæµè§ˆå™¨½H—口里访问不同的应用½E‹åºåQŒå‘送给服务器的session id也可以是不同的ã€?br /></p> <p align="center"> </p> <p>  æ ÒŽ®˜q™ä¸ªç‰ÒŽ€§ï¼Œæˆ‘们可以推测Tomcat中session的内存结构大致如下ã€?br /></p> <p align="center"> </p> <p>  ½W”者以前用˜q‡çš„iPlanet也采用的是同æ ïLš„æ–¹å¼åQŒä¼°è®¡SunONE与iPlanet之间不会有太大的差别。对于这¿Uæ–¹å¼çš„æœåŠ¡å™¨ï¼Œè§£å†³çš„æ€èµ\很简单,实际实行èµäh¥ä¹Ÿä¸éš¾ã€‚要么让所有的应用½E‹åºå…׃ín一个session idåQŒè¦ä¹ˆè®©åº”用½E‹åºèƒ½å¤ŸèŽ·å¾—å…¶ä»–åº”ç”¨½E‹åºçš„session idã€?/p> <p>  iPlanet中有一¿Uå¾ˆ½Ž€å•çš„æ–ÒŽ³•来实现共享一个session idåQŒé‚£ž®±æ˜¯æŠŠå„个应用程序的cookie路径都设ä¸?åQˆå®žé™…上应该æ˜?NASAppåQŒå¯¹äºŽåº”用程序来讲它的作用相当于根)ã€?/p> <p> <session-info> <path>/NASApp</path> <br /> </session-info> </p> <p>  需要注意的是,操作å…׃ínçš„session应该遵åó@一些编½E‹çº¦å®šï¼Œæ¯”如在session attribute名字的前面加上应用程序的前缀åQŒä‹Éå¾—setAttribute("name", "neo")变成setAttribute("app1.name", "neo")åQŒä»¥é˜²æ­¢å‘½å½Iºé—´å†²çªåQŒå¯¼è‡´äº’相覆盖ã€?/p> <p> <br />  在Tomcat中则没有˜q™ä¹ˆæ–¹ä¾¿çš„选择。在Tomcat版本3上,我们˜q˜å¯ä»¥æœ‰ä¸€äº›æ‰‹ŒD‰|¥å…׃ínsession。对于版æœ?以上的TomcatåQŒç›®å‰ç¬”者尚未发现简单的办法。只能借助于第三方的力量,比如使用文äšg、数据库、JMS或者客æˆïL«¯cookieåQŒURL参数或者隐藏字ŒD늭‰æ‰‹æ®µã€?/p> <p>  我们再看一下Weblogic Server是如何处理sessionçš„ã€?br /></p> <p align="center"> </p> <p align="center"> </p> <p>  从截屏画面上可以看到Weblogic Serverå¯Òމ€æœ‰çš„应用½E‹åºè®„¡½®çš„cookieçš„èµ\径都æ˜?åQŒè¿™æ˜¯ä¸æ˜¯æ„å‘³ç€åœ¨Weblogic Server中默认的ž®±å¯ä»¥å…±äº«session了呢åQŸç„¶è€Œä¸€ä¸ªå°å®žéªŒå›_¯è¯æ˜Žå³ä‹É不同的应用程序ä‹É用的是同一个sessionåQŒå„个应用程序仍然只能访问自己所讄¡½®çš„那些属性。这说明Weblogic Server中的session的内存结构可能如ä¸?br /></p> <p align="center"> </p> <p>  对于˜q™æ ·ä¸€¿Uç»“构,在session机制本èín上来解决sessionå…׃ín的问题应该是不可能的了。除了借助于第三方的力量,比如使用文äšg、数据库、JMS或者客æˆïL«¯cookieåQŒURL参数或者隐藏字ŒD늭‰æ‰‹æ®µåQŒè¿˜æœ‰ä¸€¿Uè¾ƒä¸ºæ–¹ä¾¿çš„做法åQŒå°±æ˜¯æŠŠä¸€ä¸ªåº”用程序的session攑ֈ°ServletContext中,˜q™æ ·å¦å¤–一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,</p> <p>  应用½E‹åºA</p> <p>context.setAttribute("appA", session); </p> <p>  应用½E‹åºB</p> <p>contextA = context.getContext("/appA");<br />HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); </p> <p>  值得注意的是˜q™ç§ç”¨æ³•不可¿UÀL¤åQŒå› ä¸ºæ ¹æ®ServletContextçš„JavaDocåQŒåº”用服务器可以处于安全的原因对于context.getContext("/appA");˜q”回½Iºå€û|¼Œä»¥ä¸Šåšæ³•在Weblogic Server 8.1中通过ã€?/p> <p>  那么Weblogic Serverä¸ÞZ»€ä¹ˆè¦æŠŠæ‰€æœ‰çš„应用½E‹åºçš„cookie路径都设ä¸?å‘¢ï¼ŸåŽŸæ¥æ˜¯äØ“äº†SSOåQŒå‡¡æ˜¯å…±äº«è¿™ä¸ªsession的应用程序都可以å…׃ín认证的信息。一个简单的实验ž®±å¯ä»¥è¯æ˜Žè¿™ä¸€ç‚¹ï¼Œä¿®æ”¹é¦–å…ˆç™Õd½•的那个应用程序的描述½W¦weblogic.xmlåQŒæŠŠcookie路径修改ä¸?appA讉K—®å¦å¤–一个应用程序会重新要求ç™Õd½•åQŒå³ä½¿æ˜¯åè¿‡æ¥ï¼Œå…ˆè®¿é—®cookie路径ä¸?的应用程序,再访问修改过路径的这个,虽然不再提示ç™Õd½•åQŒä½†æ˜¯ç™»å½•的用户信息也会丢失。注意做˜q™ä¸ªå®žéªŒæ—¶è®¤è¯æ–¹å¼åº”该ä‹É用FORMåQŒå› ä¸ºæµè§ˆå™¨å’Œweb服务器对basic认证方式有其他的处理方式åQŒç¬¬äºŒæ¬¡è¯äh±‚的认证不是通过session来实现的。具体请参看[7] secion 14.8 AuthorizationåQŒä½ å¯ä»¥ä¿®æ”¹æ‰€é™„çš„½CÞZ¾‹½E‹åºæ¥åš˜q™äº›è¯•验ã€?/p> <p>  八、æ€È»“</p> <p>  session机制本èínòq¶ä¸å¤æ‚åQŒç„¶è€Œå…¶å®žçŽ°å’Œé…¾|®ä¸Šçš„灵‹zÀL€§å´ä½¿å¾—具体情况复杂多变。这也要求我们不能把仅仅某一‹Æ¡çš„¾léªŒæˆ–者某一个浏览器åQŒæœåŠ¡å™¨çš„ç»éªŒå½“ä½œæ™®éé€‚ç”¨çš„ç»éªŒï¼Œè€Œæ˜¯å§‹ç»ˆéœ€è¦å…·ä½“æƒ…å†µå…·ä½“åˆ†æžã€?/p> <img src ="http://www.aygfsteel.com/liulu/aggbug/102334.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/liulu/" target="_blank">刘璐</a> 2007-03-07 10:50 <a href="http://www.aygfsteel.com/liulu/archive/2007/03/07/102334.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>è½?J2EE开发之常用开源项目介¾l?/title><link>http://www.aygfsteel.com/liulu/archive/2006/12/28/90547.html</link><dc:creator>刘璐</dc:creator><author>刘璐</author><pubDate>Thu, 28 Dec 2006 09:36:00 GMT</pubDate><guid>http://www.aygfsteel.com/liulu/archive/2006/12/28/90547.html</guid><wfw:comment>http://www.aygfsteel.com/liulu/comments/90547.html</wfw:comment><comments>http://www.aygfsteel.com/liulu/archive/2006/12/28/90547.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/liulu/comments/commentRss/90547.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/liulu/services/trackbacks/90547.html</trackback:ping><description><![CDATA[ <div twffan="done">主要ž®±æˆ‘所了解的J2EE开发的框架或开源项目做个介¾l?可以æ ÒŽ®éœ€æ±‚选用适当的开源组件进行开å?主要˜q˜æ˜¯ä»¥Spring为核å¿?也æ€È»“了一些以前web开发常用的开源工具和开源类åº?/div> <div twffan="done"> </div> <div twffan="done">1持久å±?</div> <div style="TEXT-INDENT: 21pt" twffan="done">1)Hibernate</div> <div style="TEXT-INDENT: 21pt" twffan="done">˜q™ä¸ªä¸ç”¨ä»‹ç»äº?用的很频¾J?用的比较多的是映ž®?包括¾l§æ‰¿æ˜ å°„和父子表映射</div> <div style="TEXT-INDENT: 21pt" twffan="done">对于DAO在这里介¾lä¸ªåœ¨å®ƒåŸºç¡€ä¸Šå¼€å‘的包bba96,目前最新版本是bba96 2.0它对Hibernate˜q›è¡Œäº†å°è£? 查询功能包括执行hsql或者sql查询/更新的方法,如果你要多层‹Æ¡é€»è¾‘的条件查询可以自å·Þq»„装QueryObject.可以参考它做HibernateDAO.也可以直接利用它</div> <div style="TEXT-INDENT: 21pt" twffan="done">2) iBATIS</div> <div style="TEXT-INDENT: 21pt" twffan="done">另一个ORM工具,Apacheçš?没有Hibernate那么集成,自由度比较大</div> <div twffan="done">2:SpringMVC</div> <div twffan="done"> <span twffan="done">       </span>原理说明和快速入é—?</div> <div twffan="done"> <span twffan="done">       </span>配置文äšgä¸?</div> <div style="TEXT-INDENT: 21pt" twffan="done">Spring的配¾|®æ–‡ä»‰™»˜è®¤äØ“WEB-INF/xxxx-servelet.xm其中xxx为web.xml中org.springframework.web.servlet.DispatcherServletçš„servlet-nameã€?/div> <div twffan="done"> <span twffan="done">       Action</span>分发:</div> <div style="TEXT-INDENT: 21pt" twffan="done">Springž®†æŒ‰ç…§é…¾|®æ–‡ä»¶å®šä¹‰çš„URLåQŒMapping到具体Controller¾c»ï¼Œå†æ ¹æ®URL里的action= xxx或其他参敎ͼŒåˆ©ç”¨åå°„调用Controller里对应的Actionæ–ÒŽ³•ã€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">输入数据¾l‘定:</div> <div style="TEXT-INDENT: 21pt" twffan="done">Spring提供Binder 通过名字的一一对应反射¾l‘定PojoåQŒä¹Ÿå¯ä»¥ç›´æŽ¥ä»Žrequest.getParameter()取数据ã€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">输入数据验证</div> <div style="TEXT-INDENT: 21pt" twffan="done">Sping 提供了Validator接口当然˜q˜å¯ä»¥ä‹É用开源的Commons-Validaor支持最å¥?/div> <div style="TEXT-INDENT: 21pt" twffan="done">Interceptor(拦截å™?</div> <div style="TEXT-INDENT: 21pt" twffan="done">Spring的拦截器提供接口需要自å·Þq¼–å†?在这点不如WebWork做的å¥?全面 </div> <div twffan="done"> <span twffan="done">       (</span>˜q™é‡Œæä¸€ä¸‹WebWorkå’ŒStruts的区别最主要的区别在于WebWork在徏立一个Action时是新New一个对象而Struts是SingleMoule所有的都ç‘ô承它的一个Action,所以根据项目需要合适的选择.)</div> <div twffan="done">3:Viewå±?/div> <div style="TEXT-INDENT: 21pt" twffan="done">1) 标签åº?JSP2.0/JSTL</div> <div style="TEXT-INDENT: 21pt" twffan="done">ç”׃ºŽWebwork或Spring的标½{„¡¡®å®žå¾ˆæœ‰é™,一般view层用JSTL标签,而且据说JSTL设计很好速度是所有标½{¾ä¸­æœ€å¿«çš„使用èµäh¥ä¹Ÿå¾ˆ½Ž€å?/div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">2) 富客æˆïL«¯:DOJO Widgets, YUI(YahooUI),FCKEditor, Coolest日历控äšg</div> <div style="TEXT-INDENT: 21pt" twffan="done">Dojo主要提供Tree, Tab½{‰å¯Œå®¢æˆ·ç«¯æŽ§ä»?可以用其˜q›è¡Œè¾…助客户端开å?/div> <div style="TEXT-INDENT: 21pt" twffan="done">YahooUIå’ŒDOJO一样它有自å·Þqš„一套javascript调试控制å?主要支持ajax开发也有很多Tree,Table,Menu½{‰å¯Œå®¢æˆ·ç«¯æŽ§ä»?/div> <div style="TEXT-INDENT: 21pt" twffan="done">FCKEditor 最‹¹è¡Œçš„æ–‡æœ¬ç¼–辑器</div> <div style="TEXT-INDENT: 21pt" twffan="done">Coolest日历控äšg 目前很多日历控äšg可用,集成在项目中也比较简å?˜q™ä¸ªåªæ˜¯å…¶ä¸­çš„一ä¸?界面不错的说..</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">3) JavaScript:Prototype.js</div> <div style="TEXT-INDENT: 21pt" twffan="done">Prototype.jsä½œäØ“javascript的成功的开源框æžÓž¼Œž®è£…了很多好用的功能,通过它很å®ÒŽ˜“¾~–写AJAX应用,现在AJAX技术逐渐成熟,框架资源比较丰富,比如YUI,DWR½{‰ç­‰,ä¹Ÿæ˜¯å› äØ“JavaScript没有合适的调试工具,所以没有必要从零开始编写AJAX应用,个ähè®¤äØ“å¤šç”¨ä¸€äº›æˆç†Ÿçš„Ajax框架实现无刷新更新页面是不错的选择.</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">4)表格控äšg:Display Tag ,Extreme Table</div> <div style="TEXT-INDENT: 21pt" twffan="done">˜q™ä¸¤ä¸ªçš„功能差不å¤?都是View层表格的生成,界面也比较相å?可以导出Excel,Pdf,对Spring支持很容æ˜?</div> <div style="TEXT-INDENT: 21pt" twffan="done">相比较而言比较推荐ExtremeTable,它的设计很好功能上比DisplayTag多一äº?支持Ajax,ž®è£…了一些拦截器,而且最斚w¢çš„æ˜¯åœ¨ä¸»™åµwiki中有详细的中文ä‹É用文æ¡?</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">5):OSCache</div> <div style="TEXT-INDENT: 21pt" twffan="done">OSCache是OpenSymphony¾l„织提供的一个J2EE架构中Web应用层的¾~“存技术实现组ä»?Cache是一¿Uç”¨äºŽæé«˜ç³»¾lŸå“åº”速度、改善系¾lŸè¿è¡Œæ€§èƒ½çš„æŠ€æœ¯ã€‚尤其是在Web应用中,通过¾~“å­˜™åµé¢çš„输出结果,可以很显著的改善¾pȝ»Ÿçš„稳定性和˜qè¡Œæ€§èƒ½ã€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">它主要用在处理短旉™—´æˆ–一定时间内一些数据或™åµé¢ä¸ä¼šå‘生变化,或将一些不变的¾lŸè®¡æŠ¥è¡¨,¾~“冲在内å­?可以充分的减è½ÀLœåŠ¡å™¨çš„åŽ‹åŠ?防治负蝲òqŒ™¡¡,快速重启服务器(通过¼‹¬ç›˜¾~“å­˜).</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">6)SiteMesh</div> <div style="TEXT-INDENT: 21pt" twffan="done">sitemesh应用Decorator模式主要用于提高™åµé¢çš„可¾l´æŠ¤æ€§å’Œå¤ç”¨æ€§ï¼Œå…¶åŽŸç†æ˜¯ç”¨Filter截取requestå’Œresponse,把页面组件head,content,banner¾l“合ä¸ÞZ¸€ä¸ªå®Œæ•´çš„视图。通常我们都是用include标签在每个jsp™åµé¢ä¸­æ¥ä¸æ–­çš„包含各¿Uheader, stylesheet, scripts and footeråQŒçŽ°åœ¨ï¼Œåœ¨sitemesh的帮助下åQŒæˆ‘们删掉他们轻松达到复合视图模å¼?</div> <div style="TEXT-INDENT: 21pt" twffan="done">Sitemesh也是<span twffan="done"> OpenSymphony</span>的一个项目现在最˜q‘的版本æ˜?.2,目前OpenSymphony自从04òq´å°±æ²¡æœ‰æ›´æ–°çš„版本了..感觉它还是比较有创新的一¿Ué¡µé¢ç»„装方å¼? OpenSymphony开源组¾l‡çš„代码一般写的比较漂äº?可以改其源代码对自己的项目进行适配.</div> <div style="TEXT-INDENT: 21pt" twffan="done">‹¹‹è¯•发现Sitemesh˜q˜å­˜åœ¨ä¸€äº›é—®é¢?比如中文问题,它的默认¾~–码是iso-8859-1在ä‹É用时候需要做一些改åŠ?</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">7)CSS,XHTML</div> <div style="TEXT-INDENT: 21pt" twffan="done">˜q™ä¸ªä¸ç”¨è¯´äº†,遵åó@W3C标准的web™åµé¢å¼€å?</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">8)分页标签: pager-taglib¾l„äšg</div> <div style="TEXT-INDENT: 21pt" twffan="done">Pager-taglib 是一套分™å‰| ‡½{‘Öº“åQŒå¯ä»¥çµ‹zÕdœ°å®žçŽ°å¤šç§ä¸åŒé£Žæ ¼çš„åˆ†™åµå¯¼èˆªé¡µé¢ï¼Œòq¶ä¸”可以很好的与服务器分™åµé€»è¾‘分离.使用èµäh¥ä¹Ÿæ¯”较简å?</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div style="TEXT-INDENT: 21pt" twffan="done">9)Form: Jodd Form taglib</div> <div style="TEXT-INDENT: 21pt" twffan="done">Jodd Form taglib使用比较½Ž€å?只要æŠ?lt;form>的头ž®¾ä»¥<jodd:form bean= "mybean">包住</div> <div twffan="done">ž®×ƒ¼šè‡ªåЍ¾l‘定mybean, 自动¾l‘定mybean的所有同名属性到普通html标记input, selectbox, checkbox,radiobox.....在这些input框里不用再写ä»ÖM½•代码â€?/div> <div twffan="done"> <span twffan="done">       </span> </div> <div style="TEXT-INDENT: 21pt" twffan="done">10)Ajax:DWR</div> <div twffan="done"> <span twffan="done">       J2EE</span>应用最常用的ajax框架</div> <div twffan="done"> <span twffan="done">       </span> </div> <div twffan="done"> <span twffan="done">       11)</span>报表 图表</div> <div style="MARGIN-LEFT: 21pt" twffan="done">Eclipse BIRT功能比较强大,也很庞大..好几å?span twffan="done">M,一般没有特别需求或别的图表设计软äšg可以解决的不用它</span></div> <div style="TEXT-INDENT: 21pt" twffan="done">JasperReports+ iReport是一个基于Javaçš„å¼€æºæŠ¥è¡¨å·¥å…øP¼Œå®ƒå¯ä»¥åœ¨Java环境下像其它IDE报表工具一æ äh¥åˆ¶ä½œæŠ¥è¡¨ã€‚JasperReports支持PDF、HTML、XLS、CSVå’ŒXMLæ–‡äšg输出格式。JasperReports是当前Java开发者最常用的报表工兗÷€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">JFreeChart主要是用来制作各¿Uå„æ ïLš„图表åQŒè¿™äº›å›¾è¡¨åŒ…括:饼图、柱状图(普通柱状图以及堆栈æŸÞqжå›?ã€çº¿å›¾ã€åŒºåŸŸå›¾ã€åˆ†å¸ƒå›¾ã€æØœåˆå›¾ã€ç”˜ç‰¹å›¾ä»¥åŠä¸€äº›äÈA表盘½{‰ç­‰ã€?/div> <div twffan="done"> <span twffan="done">      琴棋报表,国äñ”çš?.重点推荐,适合中国的情å†?开放源代码åQŒä‹É用完全免贏V€‚纯JAVA开发,适用多种¾pȝ»Ÿòq›_°ã€‚特别适合B/S¾l“构的系¾lŸã€‚官方网站有其优点介¾l?看来用它˜q˜æ˜¯ä¸é”™çš„选择,最重要的是支持国äñ”呵呵</span> </div> <div twffan="done"> </div> <div twffan="done">4:权限控制: Acegi</div> <div style="TEXT-INDENT: 21pt" twffan="done">Acegi是Spring Framework 下最成熟的安全系¾lŸï¼Œå®ƒæä¾›äº†å¼ºå¤§ç‰|´»çš„企业çñ”安全服务åQŒå¦‚完善的认证和授权机制åQŒHttp资源讉K—®æŽ§åˆ¶åQŒMethod 调用讉K—®æŽ§åˆ¶½{‰ç­‰,支持CAS</div> <div twffan="done">(耉™²å¤§å­¦çš„单点登陆技æœ?˜q™ä¸ªå•点登陆æ–ÒŽ¡ˆæ¯”较出名.我也˜q›è¡Œ˜q‡é…¾|®ä‹Éç”?可以æ ÒŽ®™å¹ç›®éœ€è¦?如果用户分布在不同的地方不同的系¾lŸé€šç”¨ä¸€å¥—登陆口令可以用它进行解å†?一般注册机登陆机就是这栯‚§£å†³çš„)</div> <div twffan="done"> <span twffan="done">       Acegi</span>只是于Spring¾l“合最好的安全框架,功能比较强大,当然˜q˜æœ‰ä¸€äº›å…¶ä»–的安全框架,˜q™é‡Œåˆ—ä‹D一些比较流行的是我从网上找到的,使用æ–ÒŽ³•看其官方文档把â€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">JAAS, Seraph, jSai - Servlet Security, Gabriel, JOSSO, Kasai, jPAM, OpenSAML都是些安全控制的框架..真够多的呵呵</div> <div twffan="done"> </div> <div twffan="done">5:全文‹‚€ç´?/div> <div twffan="done"> <span twffan="done">       1) Lucene</span> </div> <div twffan="done"> <span twffan="done">       Lucene</span>是一套全文烦引接å?可以通过它将数据˜q›è¡Œå€’排文äšg处理加入索引文äšg,它的索引速度和查询速度是相当快çš?查询百万¾U§æ•°æ®æ¯«¿U’çñ”出结æž?现在最火的Apache开源项ç›?版本更新速度很快现在已经åˆîCº†2.0,每个版本更新的都比较å¤?目前用的最多的版本应该æ˜?.4.3,但它有个不太斚w¢çš„地方单个烦引文件有2Gæ–‡äšg限制,现在2.0版本没有˜q™ä¸ªé™åˆ¶,我研½I¶çš„æ¯”较å¤?它的扩展性比较好,可以很方面的扩充其分词接口和查询接口.</div> <div twffan="done"> <span twffan="done">       </span>åŸÞZºŽå®ƒçš„开发的¾pȝ»Ÿå¾ˆå¤š,比如最常用的Eclipse的搜索功èƒ?˜q˜æœ‰ä¸€äº›å¼€æºçš„软äšg比如Compass,Nutch,Lius,˜q˜æœ‰æˆ‘最˜q‘做的InSearch(企业¾U§FTPæ–‡äšg¾|‘页搜烦)</div> <div twffan="done">6:公共Util¾c?/div> <div twffan="done"> <span twffan="done">       </span>主要是Jakarta-Commons¾cÕdº“,其中最常用得是以下几个¾cÕdº“</div> <div style="TEXT-INDENT: 21pt" twffan="done">1) Jakarta-Commons-Language</div> <div style="TEXT-INDENT: 21pt" twffan="done"> <span twffan="done">       </span>最常用得类是StringUtils¾c?提供了ä‹É用的字符串处理的常用æ–ÒŽ³•效率比较é«?/div> <div style="TEXT-INDENT: 21pt" twffan="done">2) Jakarta-Commons-Beantuils</div> <div style="TEXT-INDENT: 21pt" twffan="done"> <span twffan="done">       </span>主要用Beantuils能够获得反射函数ž®è£…及对嵌套属性,map,array型属性的è¯Õd–ã€?/div> <div style="TEXT-INDENT: 21pt" twffan="done">3) Jakarta-Commons-Collections</div> <div style="TEXT-INDENT: 21pt" twffan="done"> <span twffan="done">       </span>里面有很多Utilsæ–ÒŽ³•</div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div twffan="done">7 日志½Ž¡ç†</div> <div twffan="done"> <span twffan="done">       Log4J</span> </div> <div twffan="done"> <span twffan="done">       </span>ä»ÕdŠ¡æ˜¯æ—¥å¿—è®°å½?åˆ†äØ“Info,Warn,error几个层次可以更好的调试程åº?/div> <div twffan="done"> </div> <div twffan="done">8 开源的J2EE框架</div> <div twffan="done"> <span twffan="done">       1) Appfuse</span> </div> <div twffan="done"> <span twffan="done">              Appfuse</span>是Matt Raible 开发的一个指导性的入门¾U§J2EE框架, 它对如何集成‹¹è¡Œçš„Spring、Hibernate、iBatis、Struts、Xdcolet、JUnit½{‰åŸº¼‹€æ¡†æž¶¾l™å‡ºäº†ç¤ºèŒ? 在持久层åQŒAppFuse采用了Hibernate O/R映射工具åQ›åœ¨å®¹å™¨æ–šw¢åQŒå®ƒé‡‡ç”¨äº†Spring,用户可以自由选择Struts、Spring/MVCåQŒWebworkåQŒJSF˜q™å‡ ä¸ªWeb框架ã€?/div> <div twffan="done"> <span twffan="done">       </span> </div> <div twffan="done"> <span twffan="done">       2) SpringSide</span> </div> <div twffan="done"> <span twffan="done">       .SpringSide</span>较完整的演示了企业应用的各个斚w¢,是一个电子商务网站的应用 SpringSide也大量参考了Appfuse中的优秀¾léªŒã€‚最重要的是它是国内的一个开源项ç›?可以了解到国内现在的一些实际技术动态和方向很有指导意义â€?/div> <div twffan="done"> </div> <div twffan="done">9:模版 Template </div> <div style="TEXT-INDENT: 21pt" twffan="done">主要有Veloctiyå’ŒFreemarker</div> <div style="TEXT-INDENT: 21pt" twffan="done">模板用Servlet提供的数据动态地生成 HTML。编译器速度快,输出接近静态HTML<span twffan="done">             </span>™åµé¢çš„速度ã€?/div> <div style="TEXT-INDENT: 21pt" twffan="done"> </div> <div twffan="done">10:工作‹¹?/div> <div twffan="done"> <span twffan="done">       </span>我所知道比较出名的主要有JBpm Shark Osworkflow,ç”׃ºŽå¯¹å®ƒæ²¡æœ‰˜q‡å¤šçš„ç ”½I¶æ‰€ä»¥è¿˜ä¸æ˜¯å¾ˆæ¸…楚之间有什么区åˆ?</div> <div twffan="done"> </div> <div twffan="done">™å¹ç›®½Ž¡ç†è½¯äšg</div> <div style="TEXT-INDENT: 21pt" twffan="done">dotProject:是一个基于LAMP的开源项目管理èÊY件。最出名的项目管理èÊYä»?/div> <div style="TEXT-INDENT: 21pt" twffan="done">JIRA: ™å¹ç›®è®¡åˆ’åQŒä“Q务安排,错误½Ž¡ç†</div> <div style="TEXT-INDENT: 21pt" twffan="done">Bugzilla:提交和管理bug,å’Œeclipse集成,可以通过安装MyEclipse配置一下即可ä‹Éç”?/div> <div style="MARGIN-LEFT: 52.5pt; TEXT-INDENT: -31.5pt" twffan="done">BugFree借鉴微èÊY公司软äšg研发理念、免费开放源代码、基于Web的精½Ž€ç‰ˆBug½Ž¡ç†</div> <div style="TEXT-INDENT: 21pt" twffan="done">CVS:˜q™ä¸ªž®×ƒ¸ä»‹ç»äº†éƒ½åœ¨ç”¨.</div> <div style="MARGIN-LEFT: 47.25pt; TEXT-INDENT: -26.25pt" twffan="done">SVN: SubVersion已逐渐­‘…è¶ŠCVSåQŒæ›´é€‚应于JavaEE的项目。Apache用了它很久后åQŒSourceforge刚刚推出SVN的支持ã€?/div> <div style="MARGIN-LEFT: 47.25pt; TEXT-INDENT: -26.25pt" twffan="done">‹¹‹è¯•用例:主要JUnit单元‹¹‹è¯•,¾~–写TestCase,Spring也对Junit做了很好的支æŒ?/div> <div twffan="done"> </div> <div twffan="done">后记:</div> <div twffan="done"> <span twffan="done">       </span>以Springä¸ÞZ¸»çš„应用开发可选用的组件中间äšgçœŸæ˜¯çœÆDб¾~­äØ•,所以针对不同的™å¹ç›®éœ€æ±‚可以利用不同的开源äñ”品解å†?比如用Spring+Hibernate/ iBATIS或Spring+WebWork+Hibernate/ iBATIS或Spring+Struts+Hibernate/ iBATIS,合理的框架设计和代码复用设计寚w¡¹ç›®å¼€å‘效率和½E‹åºæ€§èƒ½æœ‰å¾ˆå¤§çš„æé«˜,也有利于后期的维æŠ?</div> <img src ="http://www.aygfsteel.com/liulu/aggbug/90547.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/liulu/" target="_blank">刘璐</a> 2006-12-28 17:36 <a href="http://www.aygfsteel.com/liulu/archive/2006/12/28/90547.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <a href="http://www.aygfsteel.com/" title="狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频">狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频</a> </div> </footer> Ö÷Õ¾Ö©Öë³ØÄ£°å£º <a href="http://" target="_blank">Ó¡½­</a>| <a href="http://" target="_blank">ƽ¹ûÏØ</a>| <a href="http://" target="_blank">Ë·ÖÝÊÐ</a>| <a href="http://" target="_blank">ÐÂÉÛÏØ</a>| <a href="http://" target="_blank">¶¨°²ÏØ</a>| <a href="http://" target="_blank">ÎýÁÖºÆÌØÊÐ</a>| <a href="http://" target="_blank">¶«°²ÏØ</a>| <a href="http://" target="_blank">ÎÂÈªÏØ</a>| <a href="http://" target="_blank">½¨ºþÏØ</a>| <a href="http://" target="_blank">ÄÏ¿µÊÐ</a>| <a href="http://" target="_blank">Èʲ¼ÏØ</a>| <a href="http://" target="_blank">ÁÙÏÄÊÐ</a>| <a href="http://" target="_blank">»ù¡ÊÐ</a>| <a href="http://" target="_blank">ÓñÌïÏØ</a>| <a href="http://" target="_blank">Èý¶¼</a>| <a href="http://" target="_blank">¸Á¶ûÏØ</a>| <a href="http://" target="_blank">íìíôÏØ</a>| <a href="http://" target="_blank">ÃÅÍ·¹µÇø</a>| <a href="http://" target="_blank">°¢À­ÉÆÓÒÆì</a>| <a href="http://" target="_blank">ÕØÇìÊÐ</a>| <a href="http://" target="_blank">ÇåÔ¶ÊÐ</a>| <a href="http://" target="_blank">ɳÑóÏØ</a>| <a href="http://" target="_blank">Ðû³ÇÊÐ</a>| <a href="http://" target="_blank">¬ÁúÏØ</a>| <a href="http://" target="_blank">Íú²ÔÏØ</a>| <a href="http://" target="_blank">Ë«Á÷ÏØ</a>| <a href="http://" target="_blank">ÉîÖÝÊÐ</a>| <a href="http://" target="_blank">ÕòÐÛÏØ</a>| <a href="http://" target="_blank">ƽÑôÏØ</a>| <a href="http://" target="_blank">Èø¸ÂÏØ</a>| <a href="http://" target="_blank">î¡ÄþÏØ</a>| <a href="http://" target="_blank">×ÊÔ´ÏØ</a>| <a href="http://" target="_blank">×Ó³¤ÏØ</a>| <a href="http://" target="_blank">º£ÄÏÊ¡</a>| <a href="http://" target="_blank">´ó°²ÊÐ</a>| <a href="http://" target="_blank">äàË®ÏØ</a>| <a href="http://" target="_blank">Îâ´¨ÊÐ</a>| <a href="http://" target="_blank">°ÔÖÝÊÐ</a>| <a href="http://" target="_blank">Î¢É½ÏØ</a>| <a href="http://" target="_blank">Õò¿µÏØ</a>| <a href="http://" target="_blank">¾ÞÂ¹ÏØ</a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>