随着Seam Remoting 3.0测试版的临近发布,我想谈谈我认为最令人兴奋的新功能之一——模型API。去年在安特卫普与Max聊天时,我们讨论了通过AJAX远程操作持久化对象所面临的挑战。处理延迟加载的关联、分离的实体、如何应用更新等问题,都是开发基于RPC风格API的AJAX用户界面时遇到的问题。
这让我开始思考。JSF这样的视图框架在这一领域具有优势,因为它以组件为中心,而不是以RPC为中心。每个JSF控件通常绑定到服务器端业务模型中的某个组件属性。当你从JSF中调用一个动作(例如,使用命令按钮提交表单)时,用户在表单中输入的任何值(在验证后)都应用于后端模型(即更新模型阶段),然后调用该动作。结合Seam提供的扩展管理持久化上下文,你就拥有了强大的组合。
模型API旨在以类似组件为中心的方式在AJAX中使用服务器端模型,但让我们直接进入一些有趣的内容,看看一些代码。
首先,我们创建一个模型对象(顺便说一句,这是客户端JavaScript)
var model = new Seam.Model();
到目前为止一切顺利。模型对象是Model API的入口。它提供了定义bean属性、获取模型、扩展模型(我稍后会解释这是什么意思)和应用模型更新等方法。因此,第一步是定义我们希望操作的bean属性。假设我们有一个PersonActionbean,允许我们创建新的人或编辑现有的人(现在是我们服务器端的Java代码)
public @ConversationScoped class PersonAction implements Serializable { @PersistenceContext EntityManager entityManager; @Inject Conversation conversation; private Person person; @WebRemote public void createPerson() { conversation.begin(); person = new Person(); person.setAddresses(new ArrayList<Address>()); } @WebRemote public void editPerson(Integer personId) { conversation.begin(); person = entityManager.find(Person.class, personId); } @WebRemote public void savePerson() throws Exception { if (person.getPersonId() == null) { entityManager.persist(person); } else { person = entityManager.merge(person); } conversation.end(); } public Person getPerson() { return person; } }
如你所见,除了@WebRemote注解外,这个类并没有什么特殊之处。它是一个@ConversationScopedbean,因为我们需要在多个请求之间操作其状态。实体Person非常简单——它包含一些基本属性和一个延迟加载的Addresses
@Entity public class Person implements Serializable { private Integer personId; private String firstName; private String lastName; private Date dateOfBirth; private Collection<Address> addresses; @Id @GeneratedValue public Integer getPersonId() { return personId; } public void setPersonId(Integer personId) { this.personId = personId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "person", cascade = CascadeType.ALL) public Collection<Address> getAddresses() { return addresses; } public void setAddresses(Collection<Address> addresses) { this.addresses = addresses; } }
配置模型属性
回到我们的客户端代码,让我们为我们的模型配置一个bean属性。使用addBeanProperty()方法需要三个参数 - 一个别名、一个bean类和属性名。在这个例子中,bean的person属性中的值(即PersonActionPersonAction.getPerson()返回的值)将在本地别名person
下可用。模型检索后,可以通过使用
model.addBeanProperty("person", "org.jboss.seam.remoting.examples.model.PersonAction", "person");
getValue()方法通过其别名访问bean属性值。方法
model.getValue("person")
可以在检索模型之前任何时候配置多个bean属性 - 模型检索后,无法配置额外的bean属性。
检索模型
为了检索模型,我们使用fetch()操作。在检索模型时,我们还可以指定一个可选的操作。在这个例子中,假设我们想要编辑一个ID为42的现有人员对象。我们需要调用PersonAction.editPerson()方法从持久存储中加载数据。为此,我们定义一个操作如下Person一旦定义了操作,我们就可以检索我们的模型
var action = new Seam.Action() .setBeanType("org.jboss.seam.remoting.examples.model.PersonAction") .setMethod("editPerson") .addParam(42);
当服务器处理检索操作时,它将首先调用指定的操作方法(如果指定),然后将模型属性值序列化并发送回客户端。在这个例子中,我们调用的操作方法有一个调用
model.fetch(action);
conversation.begin(),它开始一个长时间运行的会话。会话ID在客户端响应中返回,并且模型会自动维护对该会话的引用以供后续请求。模型检索后,我们可以随意修改模型属性
扩展模型
model.getValue("person").setFirstName("John"); model.getValue("person").setLastName("Smith");
记得我们的
实体有一个懒加载的Person对象集合吗?当Address对象被序列化并发送至客户端时,addresses属性有一个值为Personundefined。这是JavaScript中一个非常具体的对象状态,与null不同.
如果我们想处理人员的地址呢 - 例如添加新地址或修改现有地址?这正是模型扩展能帮助我们的地方。可以使用expand()方法通过加载未初始化的关联来扩展模型。在这种情况下,我们希望加载人员的地址并将它们附加到现有的模型上。使用Model API来做这个操作很简单 - 该方法接受两个参数,包含未初始化值的模型属性和未初始化属性的名称expand()。这个操作将发送一个请求,在服务器上加载人员的地址,将地址对象返回给客户端,并用初始化的地址集合替换之前未初始化的(即undefined)addresses属性,然后我们可以随意修改它。很酷吧?
model.expand(model.getValue("person"), "addresses");
应用我们的更改
一旦我们对模型进行了更改,我们就可以使用
applyUpdates()操作应用这些更改。与操作一样,我们也可以指定一个要调用的可选操作,但与操作不同,操作是在应用模型更新之后调用的。fetch()操作
在这个例子中,我们希望通过调用PersonAction.savePerson()方法保存我们对人员(及其地址)所做的更改。再次,我们创建一个操作对象来定义我们希望调用的操作,然后将它传递给操作应用这些更改。与方法
var action = new Seam.Action() .setBeanType("org.jboss.seam.remoting.examples.model.PersonAction") .setMethod("savePerson"); model.applyUpdates(action);
When the操作应用这些更改。与当调用操作时,客户端会计算出一个包含已获取的原模型值与用户修改后的当前值之间差异的增量。这里的Model API魔法主要发生在这里——简要来说,客户端将这个增量发送到服务器,在将增量应用到适当的托管实体实例之前,将其刷新到数据库。
动态类型加载
我还有另一件事要提到,那就是Seam Remoting的新特性——动态类型加载。之前,如果你想在客户端处理某些对象类型,你必须导入那些对象的JavaScript存根,否则Remoting API无法识别它们。有了动态类型加载,当Remoting API遇到它不认识的bean类型时,现在它会向服务器发送一个单独的请求来获取这些bean类型的元数据。所以,在我们的例子中,当客户端接收到包含对Person和Addressbean的引用的模型响应时,Remoting API会透明地获取这些类型的定义,然后继续处理原始响应,不会错过任何细节。
总结
最后,我希望Model API能为动态Web应用中AJAX的有趣应用开辟一些新的可能性。我个人有一些(至少我认为是这样的)非常酷的想法,关于一些基于这些内容的新视图层相关技术,这将非常有趣,也很期待看到其他人能想出什么。你现在可以通过查看SVN中的Remoting模块来尝试使用Model API
http://anonsvn.jboss.org/repos/seam/modules/remoting/trunk
但是请注意,它仍在开发中,所以如果发现任何问题,请告诉我。Seam目前还没有非JSF请求的事务支持(因此我们查看的示例实际上在最后并没有提交事务),但我们应该很快就会有这个功能。祝您玩得开心!