【IT168 专稿】1 摸索用RMI实现Multi-Tier框架
1.1 实践描述
服务器实现数据源(DataSouce)提供,而服务端按照不同的数据源名称和SQL语句来获取数据集(ResultSet),这是典型的3层应用,即:客户端─应用程序服务器─数据库服务器。
1.2 实践过程1──以DataSouce作为远程接口函数返回值
通过前面实践,只要远程接口定义返回值的类型为序列化对象即可。而DataSouce对象似乎支持序列化:

图3:类BasicDataSouce似乎为序列化对象
1.2.1 远程接口定义
public interface Application extends Remote { //------------------------------------------------------------------------- //Get data source by name public BasicDataSource getDataSource(String __name) throws RemoteException;
1.2.2 执行类中核心函数
//Create basic data source object BasicDataSource bds = new BasicDataSource(); bds.setDriverClassName("com.mysql.jdbc.Driver"); bds.setUrl("jdbc:mysql://localhost/phome"); bds.setUsername("root"); bds.setPassword(""); //Add to map m_infoMap.put("MySQL.DataSource", bds); } //------------------------------------------------------------------------- //Get connection by name public BasicDataSource getDataSource(String __name) { return ((BasicDataSource)m_infoMap.get(__name)/**/); }
创建MySQL的DataSource对象,并按照名称存储到HashMap中,远程接口实现函数中通过DataSource名称来获取DataSource对象。
1.2.3 客户端的调用代码
String urlPrefix = "rmi://192.168.1.147:1099/"; //Look up the remote object by URL Application app = (Application)Naming.lookup(urlPrefix + "MultiTier.Application.Server"); //Invoke the remote interface BasicDataSource ds = app.getDataSource(__name); …… //Deal with the connection doConnection(ds.getConnection() );
通过DataSource名称来获取远程DataSouce对象,继而申请Connection对象。
上述代码似乎没有什么问题,且远程对象绑定也是正常的。但是当客户端调用执行时,提示BasicDataSource不支持序列化的错误,从而导致远程参数整理出错。所以这里才明白BasicDataSource不支持序列化,所以无法实现远程参数拷贝。
1.3 实践过程2──以Connection作为远程接口函数返回值
既然DataSouce继承的类不支持序列化,那么Connection总该支持序列化了吧?

图4:Connection似乎支持序列化
1.3.1 远程接口定义
public interface Application extends Remote { //------------------------------------------------------------------------- //Get data source by data source name public Connection getConnection() throws RemoteException;
1.3.2 服务端执行类的核心代码
final String connectStr = "jdbc:mysql://localhost/phome"; final String userName = "root"; …… if( (conn = FoolDB.openDB(connectStr, userName, passwd)) == null) ……
但是当执行客户端时,提示com.mysql.jdbc.SingleByteCharsetConverter不支持序列化的错误。虽然提供的只是Connection接口,但是RMI对Connection的解析已经进入到JDBC驱动内部,只要与Connection接口相关的所有的类都要进行是否序列化检查。所以这里以Connection为参数也时行不通。
1.4 实践过程3──以ResultSet作为远程接口函数返回值
同样的以ResultSet作为远程接口函数返回值,提示com.mysql.jdbc.JDBC4ResultSet不支持序列化的错误。所以这里以ResultSet为参数也时行不通。
1.5 摸索小结
通过上述实践,我们可以逐步对远程接口返回值是否支持序列化产生一个概念,只有当类实体或者父类实体支持序列化时,该对象才可能支持序列化。上述DataSouce,Connection和ResultSet都是只是接口。这里父接口支持序列化是没有多大意义的。
另外的一个问题,如果ResultSet类不能作为远程接口函数返回值,那么我们如何实现数据集的返回呢?难道由我们自定义返回值接口?
经过从很多应用资料中进行调查和推敲,RMI应用都没有使用Connection或者更高层的返回值,不仅出于复杂度,而且还出于系统安全性和集中控制考虑,都是采用自定义数据返回类型!有的应用干脆采用Vector容器拷贝。
在接下来的实践中,我们通过了嵌套ArrayList容器来自定义结果集类,当然,该类必须继承序列化类,才能保证其可以通过序列化机制拷贝。
2 在RMI框架中通过自定义结果集类实现Multi-Tier框架
2.1 自定义函数返回类型
自定义MyResultSet类,通过嵌套的ArrayList来存放记录列表和字段列表。
2.2 应用服务器执行类的核心代码
//Get records list by data source name and SQL public MyResultSet getResultSet(String __dsName, String __sql) { …… //Get data source by name BasicDataSource bds = (BasicDataSource)m_dsMap.get(__dsName); …… ArrayList recordsList = null; MyResultSet myRS = null; try { //Get connection, statement and result set Connection conn = bds.getConnection(); Statement stat = FoolDB.getQueryStat(conn); ResultSet rs = FoolDB.openQuery(stat, __sql); //Initialize the space for result set int rowsCount = FoolDB.getRowsCount(rs); int colsCount = FoolDB.getColsCount(rs); …… recordsList = new ArrayList(rowsCount); ArrayList fieldsList = null; myRS = new MyResultSet(fieldsList, rowsCount, colsCount); //Read each record by lines while(FoolDB.moveNext(rs) == true) { //Initialize the space for fields set fieldsList = new ArrayList(colsCount); //Read each field by column for(int i = 1; i <= colsCount; ++i) { fieldsList.add(rs.getObject(i) ); } //Append fields list recordsList.add(fieldsList); } …… } //Return records set return (myRS); }
2.3 客户端调用代码
public DBClient(String __dsName, String __sql) throws RemoteException, MalformedURLException, NotBoundException { String urlPrefix = "rmi://192.168.1.147:1099/"; //Look up the remote object by URL Application app = (Application)Naming.lookup(urlPrefix + "MultiTier.Application.Server"); //Invoke the remote interface MyResultSet rs = app.getResultSet(__dsName, __sql); …… int recordsCount = rs.getRowsCount(); //Get the rows count of record set int fieldsCount = rs.getColsCount(); //Get the columns count of record set }
发送数据源名称和SQL语句给应用程序服务端,由应用程序服务器根据数据源名称获取数据源,并申请连接对象,执行查询后将结果集返回给服务端。
2.4 客户端(Solaris 8)执行结果
图5:客户端执行输出
2.5 实践总结
虽然实现了实践目标。但是美中不足的是,返回给客户端的数据集需要用户自己管理,而不能作为诸如ResultSet等公用型的数据集接口,这样可能会加重客户端的开发任务。通过上述的尝试,我们应该对RMI的应用有了更深刻的认识:
(1)在基本篇中提到过,不要考虑将“权力”下放到客户端,而是要尽可能把处理逻辑放在应用程序端。在实践1和2中,试图将DataSource和Connection等高连接服务放到客户端,那么对应的业务逻辑也将放于客户端,这样应用程序服务器就丧失了其使用意图,只是成了简单的目录服务。
(2)远程接口定义中返回值的类型必须为支持序列化的实体类,用户自定义类型也必须遵照此原则进行定义。
(3)对于应用程序服务端与客户端的接口要尽可能地设计轻巧和标准。所谓设计轻巧是为了减少应用程序服务器与客户端的交互次数,因为通过序列化机制在应用程序服务器和客户端传递数据集的开销是相当大的。另外接口的标准是为了接口输出的使用通用化。这样用户就可以避开很多细节来对数据集进行使用(例如XML技术,流应用等)。
相关文章: