Java 基于 ByteBuddy 重写系统当前时间

背景

一般单元测试时总会有些代码会严重依赖系统当前时间,这种情况下的测试代码写起来可能就比较费劲,此处分享一种基于 ByteBuddy 的覆盖 System.currentTimeMillis 的解决方案。

添加依赖

ByteBuddy 是啥以及作用等详细介绍可以参照官网,此处直接上代码了:

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.8.22</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.8.22</version>
    <scope>test</scope>
</dependency>

拦截类

此处有两个注意点,一是系统方法被覆盖后咱们也拿不到当前时间了,这个问题基于 nanoTime 做一下手动计时,二是 classloader 的限制会拿不到成员变量,通过反射绕了一下

public static class SystemAdvice {

    public static long startMill;
    public static long startNano;
    public static long offset;

    @Advice.OnMethodExit
    public static void currentTimeMillis(@Advice.Return(readOnly = false) long x) {
        try {
            Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("com.*****$SystemAdvice");
            x = clazz.getField("startMill").getLong(null)
                    + (System.nanoTime() - clazz.getField("startNano").getLong(null)) / 1000000
                    + clazz.getField("offset").getLong(null);
        } catch (Throwable e) {
            e.printStackTrace();
        }

    }
}

加载

public static void resetTimeTo(Date date) {
    AgentBuilder a = new AgentBuilder.Default()
            .enableNativeMethodPrefix("wmsnative")
            .with(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE))
            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
            .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
            .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
            .ignore(ElementMatchers.none())
            .type(ElementMatchers.named("java.lang.System"))
            .transform((builder, typeDescription, classLoader, module) -> {
                return builder.method(ElementMatchers.named("currentTimeMillis"))
                        .intercept(
                                Advice.to(SystemAdvice.class).wrap(StubMethod.INSTANCE)
                        );
            });
    Instrumentation inst = ByteBuddyAgent.install();
    SystemAdvice.startMill = System.currentTimeMillis();
    SystemAdvice.startNano = System.nanoTime();
    a.installOn(inst);

    SystemAdvice.offset = date.getTime() - SystemAdvice.startMill;
}

测试

简单执行一下测试代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
resetTimeTo(sdf.parse("2021-11-11 23:23:23"));
for(int i = 0; i < 10; i ++) {
    System.out.println(sdf.format(new Date()));
    Thread.sleep(1000);
}

得到有序打印的时间序列:

2021-11-11 23:23:23
2021-11-11 23:23:24
2021-11-11 23:23:25
2021-11-11 23:23:26
2021-11-11 23:23:27
2021-11-11 23:23:28
2021-11-11 23:23:29
2021-11-11 23:23:30
2021-11-11 23:23:31
2021-11-11 23:23:32

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注