【问题标题】:f:convertNumber on Double: ClassCastExceptionf:convertNumber 在 Double 上:ClassCastException
【发布时间】:2019-02-06 12:09:06
【问题描述】:

在 JSF 2.3 中,我有一个 h:inputText 来编辑一个 Double 值,它还具有 Bean-Validation 约束。 h:inputText 有一个 f:convertNumber。提交表单时,这会导致 ClassCastException(见下文)。

所以,f:convertNumber 似乎生成了一个 Long,然后无法将其转换为 Double 以验证 @DecimalMin 约束,对吧?

在 JSF 2.2 中按预期工作,升级到 JSF 2.3 后出现问题。

有人知道可能是什么问题吗?

我可以在 WildFly 15.0.1 中使用以下最小示例重现这一点,其中只有一个 facelet 和一个 lombok 作为依赖项:

@Data
public class Building {

    @DecimalMin("0.0")
    private Double area;
}

@Named
@ViewScoped
public class BuildingEditBean implements Serializable {

    @Getter
    @Setter
    private Building building;

    @PostConstruct
    public void init() {
        this.building = new Building();
    }

    public void submit() {
        System.out.println("Building: " + this.building.getArea());
    }
}

小脸:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:p="http://xmlns.jcp.org/jsf/passthrough">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form>
            <div class="form-group">
                <label>Area</label>
                <h:inputText id="area" value="#{buildingEditBean.building.area}">
                    <f:convertNumber />
                </h:inputText>
                <h:messages id="areaMessage" for="area" />
            </div>
            <h:commandButton value="Save" action="#{buildingEditBean.submit()}" />
        </h:form>
    </h:body>
</html>

表单提交后的 Stacktrace 具有有效值:

10:57:07,649 WARNING [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-1) HV000028: Unexpected exception during isValid call.: javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:177)
    at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:68)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
    at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
    at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateValueInContext(ValidatorImpl.java:781)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateValue(ValidatorImpl.java:210)
    at javax.faces.validator.BeanValidator.validate(BeanValidator.java:349)
    at javax.faces.component.UIInput.validateValue(UIInput.java:1248)
    at javax.faces.component.UIInput.validate(UIInput.java:1037)
    at javax.faces.component.UIInput.executeValidate(UIInput.java:1334)
    at javax.faces.component.UIInput.processValidators(UIInput.java:757)
    at javax.faces.component.UIForm.processValidators(UIForm.java:269)
    at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1298)
    at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1298)
    at javax.faces.component.UIViewRoot.processValidators(UIViewRoot.java:1332)
    at com.sun.faces.lifecycle.ProcessValidationsPhase.execute(ProcessValidationsPhase.java:77)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:201)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:670)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at io.opentracing.contrib.jaxrs2.server.SpanFinishingFilter.doFilter(SpanFinishingFilter.java:55)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:360)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Double
    at org.hibernate.validator.internal.constraintvalidators.bv.number.bound.decimal.DecimalMinValidatorForDouble.compare(DecimalMinValidatorForDouble.java:17)
    at org.hibernate.validator.internal.constraintvalidators.bv.number.bound.decimal.AbstractDecimalMinValidator.isValid(AbstractDecimalMinValidator.java:51)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:171)
    ... 69 more

更新:错误取决于用户输入。如果输入带有小数位的数字,一切都会按预期工作,但是一旦输入不带小数位的数字,就会发生所描述的错误

【问题讨论】:

  • 请提供完整的堆栈跟踪。而您的h:inputText 是有线混合html、jsf 和直通属性。
  • 添加了完整的堆栈跟踪。 h:inputText 有什么问题?它总是这样工作,我认为这就是传递的目的?
  • 同时发现了这个,如果它在旧 JSF 版本中被提及为错误:stackoverflow.com/questions/2919249/…
  • 这个答案能解决你的问题吗?
  • 并非如此。是的,该解决方法消除了错误。但是,它不允许本地化输入(或在表单中加载现有实体时的输出)。所以这并不是一个真正的解决方案。

标签: jsf bean-validation hibernate-validator mojarra jsf-2.3


【解决方案1】:

我查看了 JSF 2.2 和 2.3 的 NumberConverter 的源代码,并没有发现任何严重的差异(只是 cmets、版权等)。我隔离了进行实际转换的代码,并将其隔离在一些非常小的纯 Java 代码中。

NumberFormat nf = NumberFormat.getNumberInstance(Locale.getDefault());

System.out.println(nf.getClass());
System.out.println(Double.class.isAssignableFrom(BigDecimal.class));
System.out.println(nf.parse("10,0")+ " : " + nf.parse("10,0").getClass());
System.out.println(nf.parse("10,1")+ " : " + nf.parse("10,1").getClass());
System.out.println(nf.parse("10.0")+ " : " + nf.parse("10.0").getClass());
System.out.println(nf.parse("10.1")+ " : " + nf.parse("10.1").getClass());

这会导致以下输出。

class java.text.DecimalFormat
false
100 : class java.lang.Long
101 : class java.lang.Long
10 : class java.lang.Long
10.1 : class java.lang.Double

我最初在 JDK 8 上运行它,但后来也尝试了 7,两者结果相同。

所以我倾向于得出这样的结论,即不是 JSF 导致了问题(尽管我会说,由于转换器使用 the expected type to do some checking,因此返回预期类型而不是返回类型并不奇怪通过 NumberFormat。

但是当将 parseBigDecimal 设置为 true 时

((DecimalFormat)nf).setParseBigDecimal(true);

并且运行相同的“测试”,在 presision like mentioned in the converter 中没有损失,输出是

100 : class java.math.BigDecimal
101 : class java.math.BigDecimal
10.0 : class java.math.BigDecimal
10.1 : class java.math.BigDecimal

但这也不能转换为 Double。所以我会亲自开始研究 BeanValidator 代码。

【讨论】:

  • 是的,但是只有 BigDecimal 才能完成预期类型的​​事情。也许最后应该转换为预期的类型?我还查看了 Bean Validation 代码,但还没有全部了解。似乎,他们改变了很多,并根据预期的类型调用了所需的验证器?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多