Wicket模型的干净方法

Apache Wicket Web框架的核心概念之一是模型和IModel作为其编程接口。 Wicket组件严重依赖模型,这使它们成为体系结构的重要组成部分。 Apache Wicket是一个有状态框架,将页面及其组件存储到通常位于HTTP会话中的页面存储中。 组件根据模型的内容创建面向公众的视图。 错误使用模型可能会导致尴尬的副作用,例如页面无法更新其内容或应用程序占用大量内存。 从我所看到的情况来看,新的Wicket用户遇到的最多问题是正确使用模型。 在这篇博客文章中,我将尝试解释在不同的用例中应该如何以及使用哪种模型。

Wicket IModel实现中最基本的是Model类。 它基本上是模型内容的简单占位符。 这非常适合模型内容不占用太多内存且更像是常量的情况。 一个简单的String可能是Model内容的理想选择。

IModel<String> name = new Model<String>("John");

您必须了解,即使创建包含模型的页面,创建模型对象时其内容也将保持不变。 此行为是由于使用静态模型引起的。 每次从模型询问值时,动态模型都可以更改实际内容。 新的Wicket用户经常使用静态模型而不是动态模型。 如果您不清楚静态模型和动态模型的概念,则应阅读《 Wicket in Action 》一书中的第4章“了解模型

IModel<Date> date = new Model<Date>() {
    @Override
    public Date getObject() {
        return new Date();
    }
};

通过覆盖getObject()方法,可以从Model创建上述动态日期模型为匿名类。 模型将始终返回包含当前时间的Date的新副本。

Wicket是有状态的Web框架,模型通常存储在用户的HTTP会话中。 模型中的数据很可能是从数据库或通过某些Web服务获取的。 如果使用静态模型方法,则很快将消耗大量内存,并且如果重新加载页面,将无法从数据源获得全新视图。 如果我们决定像对Date对象那样使用动态模型,则最终可能会在一页加载中进行大量数据库搜索或Web服务调用。 匿名内部类也不是很好看的代码。 Wicket有一个针对此问题的内置解决方案,LoadableDetachableModel可以缓存模型的内容,直到可以安全丢弃为止,从而提供了有效但仍动态的模型。

public class PersonModel extends LoadableDetachableModel<Person> {
 
    private Long id;
    private PersonService personService;
 
    public PersonModel(final Long id, final PersonService personService) {
        super();
        this.id = id;
        this.personService = personService;
    }
 
    public PersonModel(final Person person, final PersonService personService) {
        super(person);
        this.id = person.getId();
        this.personService = personService;
    }
 
    @Override
    protected Person load() {
        return personService.findById(id);
    }
}

上面的示例构造了一个可加载和可拆卸的模型,用于通过其唯一标识符加载一个人。 该模型有两个构造函数,这是有原因的。 之一来创建在拆卸状态下的延迟加载模式,将加载内容当getObject()方法被调用第一次,和一个以在安装状态的模型,不需要到PersonService呼叫时的getObject()方法是叫。 最好为所有LoadableDetachableModels提供这两个构造函数。 当您已经拥有内容时,可以在附加状态下创建模型,或者在只有标识符可用时以分离状态创建模型。

可装载可分离模型的警告是模型的私有字段。 Wicket将模型以及组件存储在页面存储中,这可能需要对模型进行序列化。 当模型被序列化时,私有字段也被序列化(实际内容被分离)。 我们的id字段不是问题,因为它是可序列化的,但是PersonService可能是一个问题。 通常,服务层的接口默认情况下是不可序列化的。 至少有两个不错的解决方案:使服务可序列化,或者更好的方法是将服务包装在可序列化的代理中。 代理行为可以例如通过与不同的依赖项注入框架(例如)集成来实现。 春天(wicker-spring)或Guice(wicket-guice)。 集成模块确保在注入服务代理时将它们可序列化。

public class ProfilePage extends WebPage {
 
    @SpringBean
    private PersonService personService;
 
    public ProfilePage(final PageParameters parameters) {
        super(parameters);
 
        Long id = parameters.get("id").toLongObject();
        IModel<Person> personModel =
                new PersonModel(id, personService);
        add(new ProfilePanel("profilePanel", personModel));
    }
}

上面的示例使用了wicket-spring集成,将人员服务注入到所需的页面。 @SpringBean批注提供了一个可序列化的代理,因此,当我们创建人员模型时,我们不必担心服务的序列化。 Wicket不允许构造函数注入,因为当我们调用super()构造函数时,所有注入魔术实际上都会发生。 这意味着我们在Component的基本构造函数完成之后初始化注入的值。 只需记住对数据使用可序列化的标识符或定位符,对服务使用可序列化的代理。

通常,在MVC Web框架中,视图层使用某种数据传输对象来构建视图。 DTO组成并继承以创建不同类型的视图。 为这些对象建立工厂或映射器可能容易出错或令人沮丧。 Wicket针对此问题提供了不同的解决方案。 在Wicket中,您可以认为IModel接口的工作方式类似于关系数据库视图,允许您以不同方式显示域的所需部分。

public class PersonFriendsModel extends AbstractReadOnlyModel<String> {
 
    private IModel<Person> personModel;
 
    public PersonFriendsModel(final IModel<Person> personModel) {
        super();
        this.personModel = personModel;
    }
 
    @Override
    public String getObject() {
        Person person = personModel.getObject();
        Iterable<String> friendNames =
                Iterables.transform(person.getFriends(),
                        new PersonNameFunction());
        return person.getName()
                + "'s friends are "
                + Joiner.on(", ").join(friendNames);
    }
 
    @Override
    public void detach() {
        personModel.detach();
    }
 
    private class PersonNameFunction implements Function<Person, String> {
        public String apply(final Person input) {
            return input.getName();
        }
    }
}

在这里,我们建立一个模型,该模型可以构成我们先前创建的PersonModel。 我们将其用作来源,以建立该人的好友列表。 我们正在扩展的模型是Wicket提供给我们的AbstractReadOnlyModel。 它基本上是普通模型,但不允许设置模型内容。 这非常有道理,因为我们正在构建一个联接的String,并且解析一个相似的列表并从该列表中设置该人的朋友会很尴尬。 我们仅将此模型用于只读目的,以在视图中显示朋友列表。 您可以在此处看到视图的类比,我们仍在使用相同的Person域模型,但是在模型的帮助下公开了自定义视图。 请注意,我们将detach方法委托给了组合模型。 这可以确保,如果我们在任何组件中都没有直接引用组成的模型,则仍然可以将其与好友模型分离。 多次调用detach()方法无害,因此即使从多个组件中使用也很安全。

我们仅创建了一个简单的模型合成示例。 您应该探索Wicket的内置模型实现,并花一些时间来研究如何使用它们从领域模型中创建合理的模型。 只需记住一件事:组成和扩展模型,而不是域对象。

我最后要谈的是属性模型。 它们在许多Wicket应用程序中得到了广泛使用,甚至《 Wicket in Action》一书也鼓励使用它们,但是它们具有一些不需要的功能。 属性模型的代码编写速度快且易于使用,但是它们使您的代码成为“字符串类型”。 这是我们不希望使用Java这样的类型安全语言的东西。

PropertyModel<String> nameModel =
        new PropertyModel<String>(personModel, "name");

我们使用PropertyModel从人的名字创建一个模型。 名称模型可以使用直接的Person对象或为我们提供人物的IModel。 可以通过实现Wicket的IChainingModel接口来实现此功能。 我们这里有两个问题。 第一个是名称模型的类型。 我们定义名称必须为String,但是实际上编译器无法确保该名称已绑定到String getName() JavaBean属性。 第二个问题来自重构,我们使用String值定义属性名称。 如果使用IDE重构Person对象,并将getName()方法挂接getLastName() ,则应用程序将损坏 ,编译器将再次无法注意到这一点。

让我们在这里停留片刻,看看IChainingModel接口。 其主要目的是允许使用普通对象或链接模型作为模型的内容。 PropertyModel可以与提供人物的模型或普通人物对象一起使用。 如果查看PropertyModel的源代码,则会注意到实现IChainingModel需要转换,而我认为还需要样板代码。 可以重构我们先前创建的PersonFriendsModel,以实现IChainingModel,这样,与其仅采用模型作为内容,还可以直接采用人员。这真的必要吗? 并不是的。 如果我们要使用普通人,则可以从该人创建一个新的静态模型,从而为我们提供与IChainingModel提供的功能相同的功能。

CompoundPropertyModel为模型处理增加了更多的魔力。 它是许多组件的根模型,可以通过将属性名称与组件ID匹配来自动绑定到该组件。

public class PersonForm extends Form<Person> {
 
    public PersonForm(final String id, final IModel<Person> personModel) {
        super(id, new CompoundPropertyModel<Person>(personModel));
 
        add(new TextField<String>("name"));
        add(new TextField<Integer>("age"));
    }
}

在这里,我们创建一个表单以显示该人的姓名和年龄的输入字段。 这两个文本字段均未附加任何模型,但是我们仍然能够将“名称”部分绑定到该人的姓名,并将“年龄”部分绑定到该人的年龄。 我们在构造函数中创建的复合属性模型是表单的根模型,并通过组件ID将表单组件自动绑定到对象的属性。 复合属性模型和属性模型都存在相同的问题,但我们甚至还要添加一个。 现在,我们将组件ID直接锁定为属性名称。 这意味着我们具有从HTML模板到域对象属性名称的直接依赖关系。 这听起来不太合理。

如果要使用属性模型,则取决于您,但是在我看来,由于前面所述的问题,应避免使用它们。 有诸如bindgen-wicket之类的项目尝试在不损失类型安全性的情况下实现属性模型的行为。 但是,即使bindgen在重构中也不能很好地起作用。 我们如何以类型实现名称模型并以安全的方式重构呢?

public class NameModel implements IModel<String> {
 
    private IModel<Person> personModel;
 
    public NameModel(final IModel<Person> personModel) {
        this.personModel = personModel;
    }
 
    @Override
    public String getObject() {
        return personModel.getObject().getName();
    }
 
    @Override
    public void setObject(String object) {
        personModel.getObject().setName(object);
    }
 
    @Override
    public void detach() {
        personModel.detach();
    }
}

该模型比属性模型具有更多的行。 是的,的确如此,但是您是否想失去类型安全性和重构功能,并可能很容易破坏应用程序。 该模型位于不同的文件中,因此您仍然可以将其用作一个衬纸,因此,如果使用属性模型或我们刚刚创建的模型,则没有任何区别。 您永远可以记住Java是非常冗长的语言这一事实​​。

Wicket还为我们提供了ResourceModel和StringResourceModel,它们为创建组件的本地化内容提供了强大的工具。 我不会在本文中讨论它们,因为《 Wicket in Action》一书对此有很好的参考。 我试图提出一些现实生活中的示例,说明如何使用不同类型的模型及其目的。 我希望这能给您更多有关Wicket模型以及如何正确使用它们的知识。

参考:来自RAINBOW WORLDS博客的JCG合作伙伴 Tapio Rautonen 对Wicket模型的一种干净方法

翻译自: https://www.javacodegeeks.com/2013/09/a-clean-approach-to-wicket-models.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值