服务器集成与GWT代码生成器的实现
在开发应用程序时,服务器集成是一个关键环节。同时,处理数据的序列化与反序列化也是实现服务器交互的重要部分。本文将介绍几种常见的服务器集成技术,以及如何使用GWT代码生成器来简化数据序列化和反序列化的过程。
1. 服务器集成概述
客户端应用程序通过DAO接口与服务器进行消息的发送和接收。服务器的实现方式有很多种,通常会根据开发者或开发团队的熟悉程度来选择。为了满足不同的服务器端技能集,服务器集成技术可分为以下三类:
-
Actions
-
REST
-
RPC
2. 使用Actions进行服务器集成
传统的Web应用程序常使用HTML表单的Actions来构建。表单的
action
属性指向接收表单提交的URL,该URL通常对应服务器上的一个脚本,脚本会读取提交的参数并执行相应操作,最后返回响应给用户。
在我们的应用中,为了支持更复杂的层次数据和对象数据,我们将使用JSON或XML作为编码格式,而不是传统的
application/x-www-form-urlencoded
。以下是使用PHP实现Actions来处理DAO操作的示例,使用JSON作为编码格式:
// 使用json_encode和json_decode方法进行编码和解码
下图展示了Story的DAO如何通过Action接口与服务器端集成:
| DAO方法 | Action接口URL |
| ---- | ---- |
| getById() | story_byid.php?id=1 |
| getAll() | story_all.php |
| getAllFrom() | story_digs.php?id=1 |
| save() | story_create.php |
| delete() | story_delete.php?id=1 |
| addTo() | story_digs_add.php?id=1 |
3. 使用REST进行服务器集成
REST API由于其相对简单性,近年来变得越来越流行。它是一种基于HTTP协议的基础方法,服务器端是一组由URL标识的无状态资源,可以使用四种HTTP方法(GET、POST、PUT和DELETE)进行操作,对应传统的CRUD操作。
以下是Story的DAO通过REST接口与服务器端集成的示例:
| DAO方法 | REST接口URL |
| ---- | ---- |
| getById() | GET /stories/1 |
| getAll() | GET /stories |
| getAllFrom() | GET /stories/1/digs |
| save() | POST /stories |
| delete() | DELETE /stories/1 |
| addTo() | POST /stories/1/digs |
REST的服务器端实现通常更简单,客户端需要学习的API也更简洁,不同的REST实现之间的相似性更高,技能更具可迁移性。
4. 使用RPC进行服务器集成
远程过程调用(RPC)是应用程序处理的第三种服务器集成技术。RPC提供了一种编程方式来定义与服务器的接口,试图模仿非分布式应用中的模块集成,对于熟悉编程接口的开发者来说比较熟悉。
本应用将使用GWT的RPC实现。以下是Story的DAO通过RPC接口与服务器端集成的示例:
| DAO方法 | RPC接口 |
| ---- | ---- |
| getById() | getById() |
| getAll() | getAll() |
| getAllFrom() | getAllFrom() |
| save() | save() |
| delete() | delete() |
| addTo() | addTo() |
5. 编写通用的GWT代码生成器
在实现服务器交互之前,需要处理数据序列化和反序列化的复杂性。手动转换JSON或XML数据格式是一个繁琐且容易出错的过程,尤其是当应用程序的模型增长时。因此,我们可以使用GWT的代码生成功能来自动完成这个过程。
以下是几种生成代码以实现类自动序列化的方法:
1. 为每个实现序列化接口的对象运行生成器,使用GWT的延迟绑定机制创建带有新序列化方法的实体对象。但这种方法要求用户每次实例化实体对象时都使用
GWT.create
方法,容易出错。
2. 为一个作为序列化工厂的单一接口运行生成器,只需调用一次
GWT.create
方法触发代码生成。代码生成会为每个实现
Serializable
接口的类生成一个新的序列化器类,并将其实例添加到序列化器工厂中。在应用程序中,只需调用工厂的
toXML/fromXML
或
toJSON/fromJSON
方法即可。
为了简化代码生成器,我们将创建一个通用的DOM包装器,用于GWT的XML-DOM和JSON-DOM,然后创建一个使用通用DOM的代码生成器。这样可以生成一组同时支持JSON和XML格式的序列化和反序列化类。
以下是使用这种方法进行序列化的示例代码:
// 使用延迟绑定创建序列化器
Serializer serializer = (Serializer)GWT.create( Serializer.class );
// 使用通用DOM创建JSON和XML文档
JSONDocument jsonDocument = new JSONDocument();
XMLDocument xmlDocument = new XMLDocument();
// 将story对象序列化为JSON和XML
serializer.serializeToDocumentObject( story, jsonDocument.createObject() );
serializer.serializeToDocumentObject( story, xmlDocument.createObject() );
// 获取字符串值
String storyAsJSON = jsonDocument.toString();
String storyAsXML = xmlDocument.toString();
6. 通用DOM接口
在实现代码生成器之前,我们需要定义通用DOM接口,包括
DocumentAdapter
和
DocumentObject
:
public interface DocumentAdapter {
public String getFormatName();
public DocumentObject createObject();
public DocumentObject[] createCollection( String name, int size );
public String toString();
public void parse( String value );
public DocumentObject getObject();
public DocumentObject[] getCollection();
}
public interface DocumentObject {
public void setAttribute( String name, String value );
public String getAttribute( String value );
public void setName( String name );
public String getName();
}
7. 代码生成过程
以下是使用GWT代码生成器进行序列化的过程:
graph TD;
A[GWT编译器发现Serializer接口实例化] --> B[调用SerializationGenerate的generate方法];
B --> C[构建新类Serializer_TypeSerializer];
C --> D[为每个实现Serializable接口的类添加内部类];
D --> E[在构造函数中添加ObjectSerializer实例到映射中];
E --> F[代码生成完成,返回新的Serializer实例];
F --> G[调用serializeToDocumentObject或deserializeFromDocumentObject方法];
G --> H[定位并调用相应的ObjectSerializer实例];
-
GWT编译器发现
Serializer接口使用GWT.create实例化,调用SerializationGenerate的generate方法。 -
代码生成器开始构建新类
Serializer_TypeSerializer,该类继承自Serializer。 -
为代码中每个实现
Serializable接口的类添加实现ObjectSerializer接口的内部类,如Story_SerializableImpl和User_SerializableImpl,每个内部类都有自定义的serialize和deserialize方法。 -
在
Serializer_TypeSerializer的构造函数中,将每个生成的ObjectSerializer实例添加到一个映射中,键为类名,以便Serializer可以找到正确的ObjectSerializer。 -
代码生成完成,
GWT.create方法返回一个新的Serializer实例。 -
当调用
serializeToDocumentObject或deserializeFromDocumentObject方法时,Serializer实现能够定位到合适的ObjectSerializer实例并调用其serialize或deserialize方法。
8. SerializationGenerator类的实现
SerializationGenerator
类需要扩展GWT的
Generator
类并实现其
generate
方法。以下是
generate
方法的实现:
public class SerializationGenerator extends Generator {
private JClassType serializeInterface;
private JClassType stringClass;
private SourceWriter srcWriter;
private String className;
public String generate( TreeLogger logger, GeneratorContext ctx,
String requestedClass) throws UnableToCompleteException {
// 获取TypeOracle
TypeOracle typeOracle = ctx.getTypeOracle();
assert (typeOracle != null);
serializeInterface = typeOracle.findType(Serializable.class.getName());
assert( serializeInterface!= null );
stringClass = typeOracle.findType(String.class.getName());
assert( stringClass!= null );
// 从TypeOracle获取类
JClassType serializeClass = typeOracle.findType(requestedClass);
if (serializeClass == null) {
logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
+ requestedClass + "'", null);
throw new UnableToCompleteException();
}
// 创建SourceWriter
String packageName = serializeClass.getPackage().getName();
className = serializeClass.getSimpleSourceName()+ "_TypeSerializer";
PrintWriter printWriter = ctx.tryCreate(
logger, packageName, className);
if (printWriter == null) {
return packageName+"."+className;
}
ClassSourceFileComposerFactory composerFactory =
new ClassSourceFileComposerFactory( packageName, className );
composerFactory.addImport(DocumentObject.class.getName());
composerFactory.addImport(ObjectSerializer.class.getName());
composerFactory.addImport(Serializable.class.getName());
composerFactory.setSuperclass("Serializer");
srcWriter = composerFactory.createSourceWriter(ctx, printWriter);
if (srcWriter == null) {
return packageName+"."+className;
}
// 为每个支持Serializable的接口创建序列化器
JClassType[] subTypes = serializeInterface.getSubtypes();
for( int i=0; i< subTypes.length; ++i )
{
srcWriter.println("public class "+ subTypes[i].getName()+
"_SerializableImpl implements ObjectSerializer{");
srcWriter.indent();
srcWriter.println("public "+
subTypes[i].getName()+"_SerializableImpl(){}");
writeSerialize(logger,subTypes[i]);
writeDeserialize(logger,subTypes[i]);
srcWriter.outdent();
srcWriter.println("}");
}
// 在类构造函数中添加每个序列化器
srcWriter.println("public "+className+"(){");
srcWriter.indent();
for( int i=0; i< subTypes.length; ++i )
{
srcWriter.println("addObjectSerializer(\""+ subTypes[i].getName()+
"\", new "+subTypes[i].getName()+"_SerializableImpl() );");
}
srcWriter.outdent();
srcWriter.println("}");
srcWriter.commit(logger);
return packageName+"."+className;
}
}
在
generate
方法中,首先获取
TypeOracle
,用于获取正在编译的类型信息。然后创建
SourceWriter
,用于编写新类的代码。接着为每个实现
Serializable
接口的类创建内部类,并在构造函数中添加
ObjectSerializer
实例。最后,调用
commit
方法将代码写入文件,并返回生成类的全名。
为了让GWT知道使用这个代码生成器,需要在模块的XML文件中添加以下指令:
<generate-with class="com.gwtapps.serialization.SerializationGenerator">
<when-type-assignable class="com.gwtapps.serialization.client.Serializer"/>
</generate-with>
9. 序列化和反序列化方法的实现
SerializationGenerator
类的
writeSerialize
和
writeDeserialize
方法分别用于生成序列化和反序列化代码。
writeSerialize
方法的实现如下:
public void writeSerialize( TreeLogger logger, JClassType classType)
throws UnableToCompleteException {
String fullName = classType.getQualifiedSourceName();
srcWriter.println(
"public void serialize( Serializable obj, DocumentObject document ){");
srcWriter.indent();
// 将对象转换为具体类
srcWriter.println(fullName+" objImpl = ("+fullName+")obj;");
// 遍历getter方法
JMethod[] methods = classType.getMethods();
for( int i=0; i < methods.length; i++ ){
String methodName = methods[i].getName();
if( "get".equals( methodName.substring(0,3) )&& methods[i].isPublic()){
// 调用document对象的setAttribute方法
JType returnType = methods[i].getReturnType();
if( returnType.isClass() != null &&
returnType.isClass().isAssignableTo( stringClass ) ){
String attributeName = methodName.substring(3);
srcWriter.println( "document.setAttribute(\""+
attributeName.toLowerCase()+ "\",
objImpl."+methodName+"());");
}
}
}
srcWriter.outdent();
srcWriter.println("}");
srcWriter.println();
}
writeDeserialize
方法的实现如下:
public void writeDeserialize( TreeLogger logger,JClassType classType)
throws UnableToCompleteException {
String fullName = classType.getQualifiedSourceName();
srcWriter.println(
"public Serializable deserialize( DocumentObject document ){");
srcWriter.indent();
// 创建新的具体对象
srcWriter.println(fullName+" obj = new "+fullName+"();");
// 遍历setter方法
JMethod[] methods = classType.getMethods();
for( int i=0; i < methods.length; i++ ){
String methodName = methods[i].getName();
if( "set".equals( methodName.substring(0,3) )&& methods[i].isPublic()){
// 确保只有一个参数
JParameter[] parameters = methods[i].getParameters();
if( parameters.length == 1 &&
parameters[0].getType().isClass() != null &&
parameters[0].getType().isClass().isAssignableTo( stringClass ) ){
// 获取属性的字符串值
String attributeName = methodName.substring(3);
srcWriter.println("obj."+methodName+"( document.getAttribute(\""+
attributeName.toLowerCase()+"\") );");
}
}
}
srcWriter.println("return obj;");
srcWriter.outdent();
srcWriter.println("}");
srcWriter.println();
}
10. 自动序列化为XML和JSON
通过实现通用DOM接口,我们可以自动将对象序列化为XML和JSON。
自动序列化为XML
Serializer serializer = (Serializer)GWT.create( Serializer.class );
XMLDocument xmlDocument = new XMLDocument();
serializer.serializeToDocumentObject( story, xmlDocument.createObject() );
String storyAsXML = xmlDocument.toString();
自动序列化为JSON
Serializer serializer = (Serializer)GWT.create( Serializer.class );
JSONDocument jsonDocument = new JSONDocument();
serializer.serializeToDocumentObject( story, jsonDocument.createObject() );
String storyAsJSON = jsonDocument.toString();
通过以上方法,我们可以实现高效、自动的服务器集成和数据序列化,提高开发效率,减少错误。
服务器集成与GWT代码生成器的实现
11. 总结与优势分析
通过上述对服务器集成技术(Actions、REST、RPC)以及GWT代码生成器的介绍,我们可以总结出以下优势:
| 技术类型 | 优势 |
|---|---|
| Actions |
传统的Web开发方式,很多开发者熟悉。通过灵活设置表单的
method
和
encoding
属性,可实现不同的数据提交方式。在我们的应用中,使用JSON或XML编码,能处理更复杂的数据结构。
|
| REST | 相对简单,遵循HTTP协议的设计初衷,服务器端实现简单,客户端API学习成本低,不同实现之间相似性高,技能可迁移性强。 |
| RPC | 为开发者提供了熟悉的编程接口,模仿非分布式应用中的模块集成,易于理解和使用。 |
| GWT代码生成器 | 自动处理数据的序列化和反序列化,减少手动转换的繁琐和错误,尤其是在应用模型增长时,能提高开发效率。通过通用DOM包装器,可同时支持JSON和XML格式。 |
12. 实际应用中的注意事项
在实际应用中,使用这些技术和代码生成器时,还需要注意以下几点:
-
代码生成器的配置
:确保在模块的XML文件中正确配置代码生成器的指令,如
<generate-with>和<when-type-assignable>,否则GWT无法正确调用代码生成器。 - 数据格式的选择 :根据应用的需求和场景,选择合适的数据格式(JSON或XML)。JSON通常更轻量级,易于解析和处理;XML则更适合需要严格格式和层次结构的数据。
- 服务器端实现的兼容性 :在选择服务器集成技术时,要考虑服务器端的技能集和现有基础设施。例如,如果团队熟悉PHP,那么使用Actions可能更合适;如果希望使用简单的REST API,Ruby on Rails是一个不错的选择。
13. 未来发展趋势
随着Web技术的不断发展,服务器集成和数据序列化技术也在不断演进。以下是一些可能的未来发展趋势:
- 更高效的序列化算法 :随着数据量的增加,对序列化和反序列化的性能要求也越来越高。未来可能会出现更高效的序列化算法,进一步提高应用的性能。
- 统一的数据格式标准 :虽然JSON和XML已经被广泛使用,但未来可能会出现更统一的数据格式标准,简化不同系统之间的数据交互。
- 自动化开发工具的普及 :代码生成器等自动化开发工具将变得更加普及,帮助开发者更快速、高效地完成开发任务。
14. 示例应用流程
为了更好地理解上述技术的应用,我们可以通过一个简单的示例应用流程来展示:
graph LR;
A[客户端应用] --> B[选择服务器集成技术];
B --> C{技术类型};
C -- Actions --> D[编写PHP脚本处理表单提交,使用JSON或XML编码];
C -- REST --> E[使用Ruby on Rails实现REST API,使用XML传输数据];
C -- RPC --> F[使用GWT的RPC实现,调用服务器端方法];
D --> G[使用GWT代码生成器进行数据序列化和反序列化];
E --> G;
F --> G;
G --> H[自动生成JSON或XML数据];
H --> I[将数据发送到服务器];
I --> J[服务器处理数据并返回响应];
J --> K[客户端接收响应并更新界面];
15. 常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 代码生成器未触发 |
检查模块的XML文件中是否正确配置了
<generate-with>
和
<when-type-assignable>
指令。
|
| 序列化和反序列化出错 |
检查代码生成器生成的代码是否正确,确保实现了
Serializable
接口的类有正确的
getter
和
setter
方法。
|
| 服务器端无法接收数据 | 检查服务器端脚本的配置,确保URL和参数传递正确,同时检查数据编码格式是否一致。 |
16. 进一步学习资源
如果读者希望深入学习服务器集成和GWT代码生成器的相关知识,可以参考以下资源:
- 官方文档 :GWT的官方文档提供了详细的代码生成和RPC实现的说明。
- 在线教程 :许多技术博客和在线学习平台提供了关于服务器集成和数据序列化的教程和示例代码。
- 开源项目 :查看一些开源的GWT项目,了解它们是如何实现服务器集成和数据序列化的。
通过本文的介绍,我们详细了解了服务器集成的三种技术(Actions、REST、RPC)以及如何使用GWT代码生成器来自动处理数据的序列化和反序列化。这些技术和工具可以帮助我们提高开发效率,减少错误,实现高效、稳定的Web应用。在实际应用中,根据具体需求选择合适的技术和工具,并注意相关的注意事项,将有助于我们顺利完成项目开发。
超级会员免费看
5

被折叠的 条评论
为什么被折叠?



