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

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Apache Commons Net 库可以轻松地实现FTP文件的下载,以下是一个简单的示例代码: ```java import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class FTPDownloadExample { public static void main(String[] args) { String server = "ftp.example.com"; int port = 21; String user = "username"; String password = "password"; String remoteFile = "/path/to/file.txt"; String localFile = "file.txt"; FTPClient ftpClient = new FTPClient(); try { // 连接FTP服务器 ftpClient.connect(server, port); ftpClient.login(user, password); ftpClient.enterLocalPassiveMode(); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 下载文件 OutputStream outputStream = new FileOutputStream(localFile); ftpClient.retrieveFile(remoteFile, outputStream); outputStream.close(); // 断开连接 ftpClient.logout(); ftpClient.disconnect(); System.out.println("文件下载完成"); } catch (IOException ex) { System.out.println("下载文件时出错:" + ex.getMessage()); ex.printStackTrace(); } } } ``` 在这个示例中,我们首先创建一个 FTPClient 对象并连接到 FTP 服务器。然后,我们使用 login() 方法进行身份验证,并通过 enterLocalPassiveMode() 方法设置被动模式。接下来,我们使用 setFileType() 方法设置文件类型为二进制文件类型,然后使用 retrieveFile() 方法将远程文件下载到本地文件中。最后,我们使用 logout() 和 disconnect() 方法断开与 FTP 服务器的连接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值