Skip to main content
 首页 » 编程设计

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

2022年07月19日135mfryf

使用 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>保存验证规则,同时保存验证规则和错误消息,当验证失败时以更直接的方式显示错误消息,读者也可以扩展更丰富的应用。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/84435284