使用 Apache Commons JEXL 实现pojo动态验证

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/neweastsun/article/details/84435284

使用 Apache Commons JEXL 实现pojo动态验证

Apache Commons JEXL是简洁的同样表达式语言,可以实现动态脚本。本文应用其实现一种需求————动态pojo验证。

对象验证是实际应用中非常普遍,且有很多种方式实现。但是通过JEXL,我们可以把验证规则存在在配置文件中或数据库表中,JEXL加载并实现运行时验证pojo。下面通过示例详细说明。

1、需求说明

假设Person类有四个属性:SSN, firstName, lastName and birthYear,不同环境有两个不同验证规则:

(1)规则1:person对象在计算奖金福利时,需要 SSN != null and birthYear < 1990
(2)规则2:person对象在图书预定时,需要 firstName != null and lastName != null

Person 类定义如下:

@Setter
@Getter
@AllArgsConstructor
public class Person {
    private String ssn;
    private String firstName;
    private String lastName;
    private Integer birthYear;
}

Person类实例p接受上述两个验证规则,我们用4个表达式表示:

“p.ssn != null”, “p.birthYear < 1990”, “p.firstName != null”, and “lastName != null”

2、验证类

首先定义抽象类BaseValidator:

package com.dataz.validate;

import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author : jack
 */
@NoArgsConstructor
public abstract class BaseValidator {
    protected List<String> rules;

    public BaseValidator(List<String> rules) {
        this.rules = rules;
    }

    /**
     * JEXL expression based general object validation.
     *
     * @param obj to validate object
     * @return Return null if no error found.
     *         Otherwise first error of the JEXL expression validation rules.
     */
    protected abstract ValidationError validate(Object obj) ;
}

该类中定义了 List rules 属性,用于保存一组基于字符串的验证规则。ValidationError validate(Object obj) 方法基于特定jexlExprRules验证给定对象。ValidationError类简单保存错误消息,定义如下:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
@AllArgsConstructor
public class ValidationError {
    private String errCode;
    private String errMsgs;
}

下面定义Person的验证类PersonObjectValidator,继承抽象类。

import com.dataz.pojp.Person;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;

import java.util.List;

/**
 * @author : tommy
 */
@Log4j2
public class PersonObjectValidator extends BaseValidator {
    public PersonObjectValidator(List<String> jexlExprRules) {
        super(jexlExprRules);
    }

    @Override
    public ValidationError validate(Object obj) {
        Person person = (Person) obj;

        JexlEngine jexl = new JexlEngine();
        JexlContext context = new MapContext();
        context.set("p", person);
        for (String expr : rules) {
            Expression e = jexl.createExpression(expr);
            Boolean isValid = (Boolean) e.evaluate(context);
            if (!isValid) {
                log.error("{} validate fail on {}.", e.getExpression(), person);
                return new ValidationError("personErr", e.getExpression());
            }
        }
        return null;
    }
}

validate(Object obj)验证方法中,我们首先转换参数为Person类型,然后初始化JexlContext 上下文。然后把person对象赋值给上下文中命名为p的key。

然后我们迭代验证规则列表,针对每个表达式规则评估p对象。如果有任何规则失败,停止并返回保存错误信息的ValidationError对象,否则返回null。

上述过程,Jexl验证需三个步骤:

1)基于JexlEngine对象创建基于Jexl语法字符串的Jexl表达式。
2)使用JexlContext对象保存验证对象(实际应用中可能有多个)。
3)使用步骤1中的表达式验证存储在上下文中的对象。

你可能对上面代码中这行有疑问:
Boolean isValid = (Boolean) e.evaluate(context);

因为我们定义的表达式总是返回boolean值。JEXL表达式可以返回任何对象————可以是Integer或特定领域对象等。

3、应用验证过程

下面定义单元测试,验证上述需求。

import com.dataz.pojp.Person;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

public class PersonObjectValidatorTest {
    private List<String> rule1;
    private List<String> rule2;
    private Person p;

    @Before
    public void init() {
        // Rule 1
        rule1 = new ArrayList<String>();
        rule1.add("p.ssn != null");
        rule1.add("p.birthYear < 1990");
        // Rule 2
        rule2 = new ArrayList<String>();
        rule2.add("p.firstName != null");
        rule2.add("p.lastName != null");
        // Person object to be validated
        p = new Person("123-45-1234","John", null, 1980);
    }

    // The person object is valid in one run-time application case
    @Test
    public void testValidPersonObjectInBonusPrizeDrawComponent() {
        PersonObjectValidator validator = new PersonObjectValidator(rule1);
        ValidationError err = validator.validate(p);
        assertNull(err);
    }

    // The same person object is invalid in another run-time application case
    @Test
    public void testInValidPersonObjectInGuestbookSignupComponent() {
        PersonObjectValidator validator = new PersonObjectValidator(rule2);
        ValidationError err = validator.validate(p);
        // The lastName is null to cause the person object being invalid
        assertNotNull(err);
    }
}

需要理解的是,JEXL使用java反射API,“p.firstName != null” 规则实际执行的语句为“p.getFirstName() != null”。实际应用中表达式可以包含任何对象方法调用。

4、依赖说明

为了让代码更简洁,项目使用了以下gradle依赖:

    compile group: 'org.apache.commons', name: 'commons-jexl', version: '2.1.1'
    compile group: 'org.projectlombok', name: 'lombok', version: '1.18.4'

    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1'

5、总结

上述示例比较简单,仅演示了动态pojo验证。我们仅把验证规则定义在List中,实际中可以从配置文件或数据库中加载,从而实现动态验证pojo。我们也可以使用Map<String,String>保存验证规则,同时保存验证规则和错误消息,当验证失败时以更直接的方式显示错误消息,读者也可以扩展更丰富的应用。

猜你喜欢

转载自blog.csdn.net/neweastsun/article/details/84435284