一个简单高效的随机对象生成神器

遇到的问题

在日常开发过程中我们往往有这样的需求:生成随机对象,特别在编写单元测试或集成测试的时候。这样的对象,我们往往不关心其字段的具体是什么值,只要里面的字段符合一定的规则要求即可(避免空指针、满足数据库表非空约束、参数校验约束等)。

java.utiil.random 包给我们提供了 nextInt(), nextLong(), nextDouble(), nextFloat(), nextBytes(), nextBoolean() 和 nextGaussian() 这样的函数,用来生成原始类型对象,但是对于其他对象、字符串等,则没有提供方便的 API。

例如,我们有以下这个 User 对象

@Data
public class User {
	@NotBlank
    private String username;
    @Valid
    private Address address;
    @Pattern("^1(3|4|5|6|7|8|9)d{9}$");
    private String phone;
    @Email
    private String email;
}

@Data
public class Address {
    private String province;
    private String city;
    private String street;
}

这时我们不得不手动设置每个字段的值

User user = new User();
user.setUsername("张三"):
Address address = new Address();
address.setProvince("浙江");
...
user.setAddress(address);
user.setPhone("13811110000");
user.setEmail("dadiyang@aliyun.com");

当这样的冗余代码充斥在单元测试中时,我们的单元测试会越来越难以阅读和维护,最终导致很难坚持下去,变成烂尾测试。

神器来求场——EasyRandom

EasyRandom,一个简单到傻子都会用的 Java 对象生成器

The simple, stupid random Java™ beans generator

github地址:https://github.com/j-easy/easy-random

用它来生成随机对象,一行代码搞定:

User user = new EasyRandom().nextObject(User.class);

对于一般的场景,我们直接使用即可。而对于一些个性化的需求,这个生成器还提供了很多个性化的配置选项

如何使用?

引入maven依赖

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-random-core</artifactId>
    <version>4.3.0</version>
    <exclusions>
    	<!-- 跟 SpringAOP 引入的 objenesis 有冲突,要排除 -->
        <exclusion>
            <groupId>org.objenesis</groupId>
            <artifactId>objenesis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 支持根据参数校验逻辑生成对象字段 -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-random-bean-validation</artifactId>
    <version>4.3.0</version>
</dependency>

注:4.3.0 之前

封装工具类

import org.jeasy.Random.EasyRandom;
import org.jeasy.Random.EasyRandomParameters;
import org.jeasy.Random.api.Randomizer;
import org.jeasy.Random.api.RandomizerRegistry;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import java.util.stream.*;

/**
 * 随机工具,封装 EasyRandom 提供的对象和 List 的随机生成方法
 * @author dadiyang
 * @since 2020/11/15
 */
public class RandomUtil {
    private static final EasyRandom easyRandom
    private RandomUtil() {
        throw new UnsupportedOperationException("静态工具类不允许被实例化");
    }

    static {
        EasyRandomParameters param = new EasyRandomParameters();
        param.setStringLengthRange(new EasyRandomParameters.Range<>(5, 10));
        // 注册自定义随机生成器
        param.RandomizerRegistry(new BigDecimalRegistry());
        // 生成的对象是一个是接口或者抽象类,则扫描类路径找到它的一个具体实现类
        param.setScanClasspathForConcreteTypes(true);
        param.setObjectPoolSize(100);
        param.setRandomizationDepth(12);
        easyRandom = new EasyRandom(param);
    }

    /**
     * 根据给定的类型生成一个随机的对象
     */
    public static <T> T nextObject(Class<T> clz) {
        return easyRandom.nextObject(clz);
    }
    /**
     * 根据给定的类型和大小生成一个随机对象的列表
     */
    public static <T> List<T> nextList(Class<T> clz, int size) {
        return objects(clz, size).collect(Collectors.toList());
    }
    /**
     * 根据给定的类型和大小生成一个随机对象的集合
     */
    public static <T> Set<T> nextSet(Class<T> clz, int size) {
        return objects(clz, size).collect(Collectors.toSet());
    }
    /**
     * 根据给定的类型和大小生成一个随机对象流
     */
    public static <T> Stream<T> objects(Class<T> clz, int size) {
        return easyRandom.objects(clz, size);
    }
    /**
     * 默认生成的 BigDecimal 实例精度过大,将会导致用于插入数据库时超过精度而报错,所以我们默认精度取为5
     */
    private static class BigDecimalRegistry implements RandomizerRegistry {
        static final BigDecimalRandomizer bigDecimalRandomizer = new BigDecimalRandomizer();

        @Override
        public void init(EasyRandomParameters easyRandomParameters) { }
        @Override
        public Randomizer<?> getRandomizer(Field field) {
            if (field.getType() == BigDecimal.class) {
                return bigDecimalRandomizer;
            } else {
                return null;
            }
        }
        @Override
        public Randomizer<?> getRandomizer(Class<?> aClass) {
            if (aClass == BigDecimal.class) {
                return bigDecimalRandomizer;
            } else {
                return null;
            }
        }
    }
    private static class BigDecimalRandomizer implements Randomizer<BigDecimal> {
        @Override
        public BigDecimal getRandomValue() {
          return BigDecimal.valueOf(nextDouble(Integer.MIN_VALUE, Integer.MAX_VALUE))
                  .setScale(5, BigDecimal.ROUND_HALF_UP);
      }
  }
}

使用工具类

// 随机生成一个 user 对象
User user = RandomUtil.nextObjet(User.class);
// 随机生成一个 user 对象列表
List<User> users = RandomUtil.nextList(User.class, 10);

One More Thing

不用构造器就能生成对象

学习这个工具时,我发现一个很有意思的点。
这个工具使用反射来生成对象并设置里面的值,所以一般我们认为,一个对象应包含一个默认的构造器,这样框架才能正确地使用 clz.newInstance() 来生成一个对象。但是它的强大之处在于,就算是没有默认构造器的对象,它也能够正确地生成对象!

换句话说,在没有默认构造器的场景下,它可以在不需要构造器的情况下,生成一个对象! 是不是非常神奇?原来,它底层是使用了 Objenesis 提供的能力来实现的。这个 Objenesis 在 SpringAOP 的底层也发挥了很大的作用,有兴趣的同学可以继续深入了解一下哦。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值