Java/JavaFX企业级应用开发平台(fxEAP)
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

值对象校验

简述

值对象是否可空、最大长度、最大值、最小值等有效性校验是业务处理过程中必须又繁琐的一项工作。

fxEAP基于元数据以及注解,实现以下两个层面的值对象有效性校验,能够极大减少校验值对象有效性的代码量:

  • 基于元数据,在创建界面组件时,添加对其输入的有效性进行限制;
  • 基于注解,供代码层面对值对象进行有效性校验,例如保存、更新等;

fxEAP中,以上两种情况均不需要编辑代码。

界面有效性校验

FormView

以在FormView创建一个字符串编辑器为例,在创建编辑器时,需要提供 最大长度、默认值、基于正则的校验规则 三个参数:

    /**
     * 构造一个StringField。可以设置字符串的最大长度、默认值
     *
     * @param property 实现IPropertyItem接口的属性
     * @return StringField属性编辑器
     */
    public static IPropertyEditor<?> createStringEditor(IFormPropertyItem property) {

        return new AbstractPropertyEditor<String, RegularStringField>(property, new RegularStringField(
                property.getMaxLength(), property.getDefaultValue(), property.getRegularExpression())) {

            {
                enableAutoSelectAll(getEditor());
            }

            @Override
            protected StringProperty getObservableValue() {

                return getEditor().textProperty();
            }

            @Override
            public void setValue(String value) {

                getEditor().setText(value);
            }
        };
    }

TableView

以在TableView中创建一个字符串编辑为例,创建一个LRTextFieldTableCell:

        } else if (DataTypeDefine.JAVA_TYPE_STRING.getJavaType().equalsIgnoreCase(java_type)) {
            // String
            col.setResizable(true);
            col.setCellValueFactory(new PropertyValueFactory<>(property.getPropertyCode()));
            if (property.getMetadataEditable()) {
                col.setCellFactory(column -> new LRTextFieldTableCell(property));
            }
        }

LRTextFieldTableCell

在调用父类构造器时,传入了上述三个参数。

public class LRTextFieldTableCell<S, T> extends LRBaseTextFieldTableCell<ValueObject, String> {

    public LRTextFieldTableCell(ITablePropertyItem propertyItem) {

        super(propertyItem.getPropertyCode(), propertyItem.getMaxLength(), propertyItem.getDefaultValue(), propertyItem.getRegularExpression());

        this.propertyItem = propertyItem;
        textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
            if (!isNowFocused) {
                commitEdit(textField.getText());
            } else {
                this.textField.setEditable(TableCellEditableUtils.checkFieldEditableByFormula(propertyItem, getTableView().getItems().get(getIndex())));
            }
        });
    }
}

StringField

    public StringField(Integer maxLength, final String defaultValue) {

        super();
        // 是否可编辑
        editFlagProperty().addListener((observable, oldValue, newValue) -> setDisable(!newValue));
        // 限制输入最大长度
        textProperty().addListener((observableValue, oldValue, newValue) -> {

            if (!checkValueLengthValid(newValue)) {
                setText(oldValue);
            }
        });
        // 设置最大允许长度
        if (maxLength == null) {
            maxLength = -1;
        }
        setMaxLength(maxLength);
        if (StringUtils.isNotBlank(defaultValue)) {
            // 设置默认值
            setText(defaultValue);
        }

    }

    protected boolean checkValueLengthValid(final String value) {

        if (getMaxLength() != null && getMaxLength() > 0 && StringUtils.isNotBlank(getText()) && StringUtil.lenOfChinesString(value) > getMaxLength()) {
            logger.error(String.format("非法的值:%s", value));
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

特性

  • 基于元数据自动构建,无需手工编写代码
  • 平台已经针对已知不同的情况,提供了对应的界面组件

示例

Validation

可以比较明显地看到,有3个必输字段。

基于注解的界面有效性校验

提供两个工具类,区别为是否基于Spring提供的容器内MessageResource机制:

  • com.lirong.eap.platform.pub.validation.BeanBasedValidatorUtils:容器外校验工具。
  • com.lirong.eap.platform.pub.validation.SpringBasedValidatorUtils:容器内校验工具,fxEAP默认使用。

以集团信息管理(Group)为例

共有3个必输只输入2个,并进行保存操作:

Validation

以代码进行示例

代码片断

    /**
     * 买入价 BigDecimal
     */
    public static final String BUYING_RATE_PROPERTY_CODE = "buyingRate";
    public static final String BUYING_RATE_PROPERTY_NAME = "买入价"; // $NON-NLS$
    @PropertyName(BUYING_RATE_PROPERTY_NAME)
    @NotNull(message = "{PLATFORM.SM.ExchangeDailyVO.BuyingRate.NotBlank}")
    @DecimalMin("0.01")
    @DecimalMax("9999999999.99")
    private BigDecimal buyingRate;

buyingRate字段,不允许为空,限定了最小值、最大值。

为同时验证国际化机制是否有效,以下测试基于en_US环境进行。

测试用的值对象

@Data
class DemoBigDecimal {

    /**
     * 买入价 BigDecimal
     */
    public static final String BUYING_RATE_PROPERTY_NAME = "买入价"; // $NON-NLS$
    @PropertyName(BUYING_RATE_PROPERTY_NAME)
    @NotNull(message = "{PLATFORM.SM.ExchangeDailyVO.BuyingRate.NotBlank}")
    @DecimalMin("0.01")
    @DecimalMax("9999999999.99")
    private BigDecimal buyingRate;
}

非空测试

代码

    @Test
    public void testNotNull() {

        DemoBigDecimal vo = new DemoBigDecimal();

        final String strValidation = BeanBasedValidatorUtils.getInstance().validateEntity(vo);
        if (StringUtils.isNotBlank(strValidation)) {
            throw new RuntimeException(strValidation);
        }
    }

控制台输出

java.lang.RuntimeException: 
null : DemoBigDecimal.buyingRate must be not blank;

数值越界

代码

    @Test
    public void testRange() {

        DemoBigDecimal vo = new DemoBigDecimal();
        vo.setBuyingRate(new BigDecimal("-1"));

        final String strValidation = BeanBasedValidatorUtils.getInstance().validateEntity(vo);
        if (StringUtils.isNotBlank(strValidation)) {
            throw new RuntimeException(strValidation);
        }
    }

控制台输出

java.lang.RuntimeException: 
-1 : DemoBigDecimal.must be greater than or equal to 0.01

完整的测试代码

package com.lirong.eap.test.validation;

import com.lirong.eap.platform.pub.validation.BeanBasedValidatorUtils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.javers.core.metamodel.annotation.PropertyName;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Locale;

/**
 * <p>Title: LiRong Java Enterprise Application Platform</p>
 * <p>Description:  </p>
 * Copyright: CorpRights lrJAP.com<br>
 * Company: lrJAP.com<br>
 *
 * @author jianjun.yu
 * @version 3.0.0-SNAPSHOT
 * @date 2022-05-31
 * @since 1.0.0-SNAPSHOT
 */
public class HelloBigDecimalValidation {

    /**
     * 测试测试时使用en_US语言环境
     */
    @BeforeClass
    public static void init() {

        Locale.setDefault(Locale.US);
    }

    /**
     * 非空测试
     */
    @Test
    public void testNotNull() {

        DemoBigDecimal vo = new DemoBigDecimal();

        final String strValidation = BeanBasedValidatorUtils.getInstance().validateEntity(vo);
        if (StringUtils.isNotBlank(strValidation)) {
            throw new RuntimeException(strValidation);
        }
    }

    /**
     * 值越界测试
     */
    @Test
    public void testRange() {

        DemoBigDecimal vo = new DemoBigDecimal();
        vo.setBuyingRate(new BigDecimal("-1"));

        final String strValidation = BeanBasedValidatorUtils.getInstance().validateEntity(vo);
        if (StringUtils.isNotBlank(strValidation)) {
            throw new RuntimeException(strValidation);
        }
    }
}

@Data
class DemoBigDecimal {

    /**
     * 买入价 BigDecimal
     */
    public static final String BUYING_RATE_PROPERTY_NAME = "买入价"; // $NON-NLS$
    @PropertyName(BUYING_RATE_PROPERTY_NAME)
    @NotNull(message = "{PLATFORM.SM.ExchangeDailyVO.BuyingRate.NotBlank}")
    @DecimalMin("0.01")
    @DecimalMax("9999999999.99")
    private BigDecimal buyingRate;
}