双人间的f:convertNumber:ClassCastException

时间:2019-02-06 12:09:06

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

在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中重现这一点,其中仅一个面和一个龙目岛作为依赖项:

@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

更新:该错误取决于用户输入。如果输入了带小数位的数字,一切都会按预期进行,但是一旦输入了无小数位的数字,就会发生上述错误

1 个答案:

答案 0 :(得分:1)

我已经查看了JSF 2.2和2.3的NumberConverter的源代码,但没有发现严重的差异(只是注释,版权等)。我隔离了进行实际转换的代码,并将其隔离在一些非常小的纯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);

并运行相同的“测试”,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代码。