文章目录
- 一般单元测试时总会有些代码会严重依赖系统当前时间,这种情况下的测试代码写起来可能就比较费劲,此处分享一种基于 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
一般单元测试时总会有些代码会严重依赖系统当前时间,这种情况下的测试代码写起来可能就比较费劲,此处分享一种基于 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;
}
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
发表回复