问题描述
我正在尝试连接一个Wicket组件 DateTextField
,其类型为 java.time.LocalDate
的属性。
I'm trying to connect a Wicket component DateTextField
with a property of type java.time.LocalDate
.
按照的提示,我试过实现一个自定义的 CompoundPropertyModel
,其中包含一个 java.time.Localdate
属性。我正在使用Wicket 7.6和JDK 8。
Following the hints in this answer, I've tried to implement a custom CompoundPropertyModel
which contains a java.time.Localdate
property. I'm using Wicket 7.6 and JDK 8.
我是Wicket和Java泛型的新手。
I'm new to Wicket and Java generics.
这是我的代码:
Class LocaldateModel
import java.time.*;
import java.util.Date;
import org.apache.wicket.model.IModel;
public class LocalDateModel implements IModel<Date>
{
private static final long serialVersionUID = 7262517323706786573L;
private IModel<LocalDate> localDateModel;
public LocalDateModel(IModel<LocalDate> localDateModel)
{
this.localDateModel = localDateModel;
}
@Override
public Date getObject()
{
return Date.from(localDateModel.getObject().atStartOfDay(ZoneId.systemDefault()).toInstant());
}
@Override
public void setObject(Date object)
{
localDateModel.setObject(
Instant.ofEpochMilli(object.getTime()).atZone(ZoneId.systemDefault()).toLocalDate());
}
@Override
public void detach()
{
localDateModel.detach();
}
}
类LocalDateModelButAlsoWrapping P>
Class LocalDateModelButAlsoWrapping
import java.time.Localdate;
import java.util.Date;
public class LocalDateModelButAlsoWrapping<T> extends LocalDateModel implements IWrapModel<Date>
{
private static final long serialVersionUID = -8539316259078354206L;
private IModel<Date> dateModel;
public LocalDateModelButAlsoWrapping(IModel<LocalDate> localDateModel)
{
super(localDateModel);
LocalDate localDate=localDateModel.getObject();
dateModel.setObject(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
}
@Override
public IModel<Date> getWrappedModel()
{
return dateModel;
}
@Override
public void setObject(Date object)
{
dateModel.setObject(object);
}
}
MyLocalDateCompoundPropertyModel类 p>
Class MyLocalDateCompoundPropertyModel
import java.time.Localdate;
import org.apache.wicket.Component;
import org.apache.wicket.model.*;
public class MyLocalDateCompoundPropertyModel<T> extends CompoundPropertyModel<T>
{
private static final long serialVersionUID = 7112056989820105247L;
public MyLocalDateCompoundPropertyModel(IModel<T> model)
{
super(model);
}
public MyLocalDateCompoundPropertyModel(T object)
{
super(object);
}
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
}
使用LocalDate的类属性
import java.time.Localdate;
public class CustomerUI implements Serializable
{
private static final long serialVersionUID = -4071467669346190367L;
private CustomerDTO customer;
public LocalDate getActiveUntil()
{
return customer.getActiveUntil();
}
public void setActiveUntil(LocalDate activeUntil)
{
customer.setActiveUntil(activeUntil);
}
// more delegating getters and setters
}
创建组件
detailsDialog = new MyCustomerDetailsDialog("createDialog",
new MyLocalDateCompoundPropertyModel<CustomerUI>(Model.of(new CustomerUI())))
这些是错误消息:
Stacktrace:
Stacktrace:
org.apache.wicket.WicketRuntimeException: Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog
at org.apache.wicket.core.util.lang.PropertyResolver.getGetAndSet(PropertyResolver.java:393)
at org.apache.wicket.core.util.lang.PropertyResolver.getObjectWithGetAndSet(PropertyResolver.java:355)
at org.apache.wicket.core.util.lang.PropertyResolver.getObjectWithGetAndSet(PropertyResolver.java:261)
at org.apache.wicket.core.util.lang.PropertyResolver.getValue(PropertyResolver.java:111)
at org.apache.wicket.model.AbstractPropertyModel.getObject(AbstractPropertyModel.java:86)
at example.ui.models.MyLocalDateCompoundPropertyModel.wrapOnInheritance(MyLocalDateCompoundPropertyModel.java:35)
at org.apache.wicket.Component.initModel(Component.java:3841)
at org.apache.wicket.Component.getDefaultModel(Component.java:1625)
at org.apache.wicket.MarkupContainer$2.component(MarkupContainer.java:876)
at org.apache.wicket.MarkupContainer$2.component(MarkupContainer.java:872)
at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:144)
at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:123)
at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:192)
at org.apache.wicket.MarkupContainer.visitChildren(MarkupContainer.java:983)
at org.apache.wicket.MarkupContainer.setDefaultModel(MarkupContainer.java:871)
at example.ui.customer.MyCustomerDetailsDialog.<init>(MyCustomerDetailsDialog.java:44)
at example.ui.customer.MyCustomerOverviewPage$2.<init>(MyCustomerOverviewPage.java:95)
第35行是:
if (actualModel.getObject() instanceof LocalDate)
推荐答案
更新,更多succint答案
我的原始答案超越了你的实际问题,但直到之后才明白。这是一个更多的succint版本,但我会保持原来的底部。
Updated, more succint answer
My original answer went way beyond what your actual problem was, but I didn't quite realise until afterwards. Here's a more succint version, but I'll keep the original around at the bottom.
你所遇到的问题是因为我提出的天真的延伸有一个关键的缺陷
The issue you're having is because the naive extension I suggested has one critical flaw.
CompoundPropertyModel
将尝试实例化不支持 子组件的模型有一个模型。当您使用常规 CompoundPropertyModel
这不是问题;在这个模型是无关紧要的,它的对象永远不会被尝试获得。
CompoundPropertyModel
will try to instantiate a model for any child component that does not have a model. When you use regular CompoundPropertyModel
this is not an issue; where the model is irrelevant, its object is never attempted to be obtained.
这里发生的是这个模型的扩展不仅会为任何模型创建一个模型,但是,为了检查该属性的类型,还要尝试评估它。
What's happening here is that this extension of the model will not only create a model for any model-less child, but will also try to evaluate it for the sake of checking what the type of the property is.
问题在于,您可能有子组件(例如链接),其中模型不一定会被解决到任何东西。在这个具体的例子中,你似乎添加了一个ID为dialog的组件;即使它可能不一定需要一个模型, CompoundPropertyModel
仍将被调用,它将尝试从底层的对话框的名称获取一个属性 CustomerUI
对象。
The problem with that is, that you might have children components (e.g. links) where a model will not necessarily be resolved to anything. In this specific instance you seem to have added a component whose ID is "dialog"; even though it might not necessarily need a model, CompoundPropertyModel
will still be invoked for it, and it'll try to get a property by the name of "dialog" from the underlying CustomerUI
object.
所以为了不会遇到这个问题,你需要阻止你的扩展对每个模型进行评估。像
So in order to not encounter this issue you need to prevent your extension from evaluating the model every time. Something simple like
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
希望有帮助。
要了解为什么你的代码不工作,你需要了解如何 CompoundPropertyModel
的作品。
To understand why your code is not working, you need to understand how CompoundPropertyModel
works.
我将使用稍微修改后的代码段来说明这一点
I'll use a slightly modified code snippet of yours to illustrate this
IModel<CustomerUI> customerUIModel = Model.of(new CustomerUI());
detailsDialog = new MyCustomerDetailsDialog("createDialog", new CompoundPropertyModel<CustomerUI>(customerUIModel));
我们假设我们要添加一个标签到 detailsDialog
显示CustomerUI的activeDate属性。如果我们不使用 CompoundPropertyModel
,那么为了实现这一点,我们必须做如下操作:
Let's imagine we want to add a label to the detailsDialog
that displays the "activeDate" property of the CustomerUI. If we were not using a CompoundPropertyModel
, then in order to achieve that we'd have to do something like the following:
IModel<LocalDate> activeDateModel = PropertyModel(customerUIModel, "activeUntil");
detailsDialog.add(new Label("dialog", activeDateModel));
什么 CompoundPropertyModel
是让我们绕过提供一个模型到组件,并简单地
What CompoundPropertyModel
does is lets us bypass providing a model to the component and doing simply
detailsDialog.add(new Label("activeDate"))
然后当页面呈现时,组件将检测到它没有模型,因此它可以让父模型有机会提供一个模型。
Then when the page is being rendered, a component will detect that it has no model and so it gives the parent models a chance to provide a model to it.
这是 CompoundPropertyModel
将踢入的位置。它查看要求哪个组件有一个模型提供,并做到这一点。但是,怎么做呢?好的,很简单,它是
This is where CompoundPropertyModel
would kick in. It looks at what component is requesting to have a model provided and does exactly that. However, how does it do that? Well, quite simply it does
return new PropertyModel(innerModel, component.getId())
因此,由于这个简单的逻辑,您可以绕过在子组件上实例化自己的模型,并将模型提供给他们父母的 CompoundPropertyModel
。 但是,子组件的ID必须引用存储在CompoundPropertyModel 中的模型对象的某些属性。否则,您将得到准确的错误:
And so because of this simple piece of logic you can bypass instantiating your own models on the children components, and have the models provided to them by the parent's CompoundPropertyModel
. However the ID of the child component must be referring to some property of the model object that is stored in the CompoundPropertyModel. Otherwise you will get the exact error you're getting:
Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog
现在让我们看看您的CompoundPropertyModel的扩展名。当组件要求自己获取模型时,您的扩展将执行以下操作:
Now let's look at your extension of the CompoundPropertyModel. When a component requests to get a model for itself, your extension does the following:
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
它调用的默认行为是 CompoundPropertyModel
来实例化一个组件的模型,然后检测该模型是否返回一个 LocalDate
的实例,并将其包装模型,以便有一个模型返回 Date
。
What it does it invokes the default behavior of the CompoundPropertyModel
to instantiate a model for a component, and then it detects whether this model returns an instance of a LocalDate
and wraps that model in order to have a model that returns Date
instead.
由于您的扩展依赖于默认行为 CompoundPropertyModel
,它仍将使用组件的检票口ID来确定内部模型中的属性名称。所以发生的是这样的:
Because your extension still relies on the default behavior of CompoundPropertyModel
, it will still use the component's wicket ID to determine what the name of the property within the inner model is. And so what's happening is this:
- 你在
detailsDialog
中添加了一个组件wicket ID是
对话框 - 在线
actualModel = super.wrapOnInheritance(component);
你的引用$ code> PropertyModel 来引用你的
CustomerUI
模型及其属性对话框(因为这是
$ b - 在线$ code if if(actualModel.getObject()
instanceof LocalDate)您尝试获取此模型的值。
但是,$CustomerUI
对象和
之间不存在属性对话框,因此抛出错误
- You added a component to your
detailsDialog
whose wicket ID is"dialog" - On line
actualModel = super.wrapOnInheritance(component);
your extension creates aPropertyModel
to referring to yourCustomerUI
model and its property "dialog" (as that's the ID ofthe component provided) - On line
if (actualModel.getObject()instanceof LocalDate)
you attempt to get the value of this model.However, no property "dialog" exists on theCustomerUI
object andso the error is thrown
所以最后你完成了一切。唯一的问题是您添加了一个小组件,其小组件ID未引用有效属性。
So in the end you've done everything right. The only problem is that you added a child component whose wicket ID did not refer to a valid property.
哪个也显示问题。我提供的实现是天真的。每当添加一个没有模型的组件时,这个扩展模型将为它创建一个模型,但是它总是会尝试获取它的值。所以如果你添加一个甚至不应该有一个模型的子组件,复合属性模型就会踢入这些组件。
Which also showcases a problem. The implementation I provided is naive. Whenever a component is added that does not have a model, this extended model will create a model for it but also it will always try to get its value. So if you add a child component which isn't even supposed to have a model, the compound property model will kick in even for those components.
所以更永久的解决方案这将是一个不涉及试图获得模型的价值的东西。例如。您可以检查组件是否为 DateTextField
:
So the more permanent solution would be something that does not involve trying to get the value of the model. E.g. you could check if the component is a DateTextField
:
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
这将延迟查看模型,直到您建立您想要实例化的模型的组件确实需要一个LocalDate模型。
This would defer looking at the model until after you established that the component whose model you're trying to instantiate really does need a LocalDate model.
这篇关于java.time.LocalDate和Wicket 7 DateTextField的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!