如何修改java中的核心api?

问题描述 投票:0回答:1

例如:我想在非生产环境下更改 LocalDateTime.now() 返回结果, 我尝试调用以下代码,但出现错误:


   @Test
    public void test3() {
        System.out.println(LocalDateTime.now());
        Instrumentation instrumentation = ByteBuddyAgent.install();
        Class<?>[] cls = instrumentation.getAllLoadedClasses();
        for (Class<?> cl : cls) {
            if ("java.time.LocalDateTime".equals(cl.getName())) {
                try {
                    LocalDateTime mockData = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0);
                    byte[] bytes = new ByteBuddy()
                            .redefine(LocalDateTime.class)
                            .method(ElementMatchers.named("now").and(ElementMatchers.takesNoArguments()))
                            .intercept(FixedValue.value(mockData))
                            .make()
                            .getBytes();
                    ClassDefinition classDefinition = new ClassDefinition(cl, bytes);
                    instrumentation.redefineClasses(classDefinition);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(LocalDateTime.now());
    }

2024-03-03T15:33:59.291699 java.lang.UnsupportedOperationException:类重定义失败:尝试更改架构(添加/删除字段) 在 java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(本机方法) 在 java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:195) 在 com.yi.component.test.agent.bytecode.ByteBuddyTest.test3(ByteBuddyTest.java:76) 在 java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(本机方法) 在 java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) 在java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.base/java.lang.reflect.Method.invoke(Method.java:568) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) 在 org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) 在org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) 在org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) 在 org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) 在 org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) 在 org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:413) 在 org.junit.runner.JUnitCore.run(JUnitCore.java:137) 在com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) 在 com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) 在com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) 在 com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) 在 com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) 在 com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

我想在非生产环境中更改 LocalDateTime.now() 返回结果

java instrumentation java-bytecode-asm byte-buddy
1个回答
0
投票

听起来你真正想做的是能够控制测试的时间,这是非常明智的。

更简单的方法是使用依赖注入:不要让您想要测试的类了解系统时钟,而是传入时钟,以便您可以用您在测试中控制的时钟替换它。

举个例子,假设您需要计算逾期借出的物品(例如图书馆或其他物品)的罚款。您的业务规则可能是:

  • 逾期每天罚款1次
  • 没有逾期的话罚款是0

以下是借出物品的定义:

import java.time.LocalDate;

public record LoanedItem(String description, LocalDate dueOn) {}

以及表达行为的测试用例:

import org.junit.jupiter.api.Test;

import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class FineCalculatorTest {
    @Test
    public void the_fine_is_1_for_every_day_the_item_is_overdue() {
        LocalDate dueOn = LocalDate.of(2024, 2, 1);
        LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);

        int daysOverdue = 2;
        LocalDate today = dueOn.plusDays(daysOverdue);
        Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);

        FineCalculator fineCalculator = new FineCalculator(clock);

        long fine = fineCalculator.fineFor(loanedItem);

        assertEquals(2, fine);
    }

    @Test
    public void the_fine_is_0_if_the_item_is_not_overdue() {
        LocalDate dueOn = LocalDate.of(2024, 2, 1);
        LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);

        LocalDate today = dueOn.minusDays(7);
        Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);

        FineCalculator fineCalculator = new FineCalculator(clock);

        long fine = fineCalculator.fineFor(loanedItem);

        assertEquals(0, fine);
    }
}

要注意的关键是

FineCalculator
java.time.Clock
的实例作为构造函数参数。

Clock.fixed
创建一个
Clock
,其时间固定为您给出的时间。在第一个测试中,我创建了一个固定在项目到期日后两天的时钟:

int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);

所以我们预计罚款 2。

第二种情况类似:我通过了一个固定在预产期前7天的时钟,所以我们预计罚款为0。

FineCalculator
的实现然后使用时钟来计算今天是什么并计算罚款:

import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;

public class FineCalculator {
    private Clock clock;
    public FineCalculator(Clock clock) {
        this.clock = clock;
    }

    public long fineFor(LoanedItem loanedItem) {
        LocalDate today = clock.instant().atZone(ZoneOffset.UTC).toLocalDate();

        long daysOverdue = ChronoUnit.DAYS.between(loanedItem.dueOn(), today);

        if (daysOverdue < 0) {
            daysOverdue = 0;
        }

        long fine = 1 * daysOverdue;

        return fine;
    }
}

当您在生产代码中创建

FineCalculator
时,您将传递系统时钟。这是一个示例
main

import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;

public class Main {
    public static void main(String[] args) {
        LocalDate dueOn = LocalDate.of(2024, 9, 1);
        LoanedItem item = new LoanedItem("Screwdriver", dueOn);

        Clock clock = Clock.system(ZoneOffset.UTC);
        FineCalculator fineCalculator = new FineCalculator(clock);

        System.out.println("Loaned item: " + item);
        System.out.println("Fine is: " + fineCalculator.fineFor(item));
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.