You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Spring验证的Entity类注入Java Clock以支持测试?

解决方案

核心思路是将验证逻辑从实体类中分离,通过自定义Spring校验注解+Spring管理的校验器实现Clock的依赖注入,让实体类仅作为数据载体,避免实体类依赖Spring容器的问题。

步骤1:重构Message实体类

移除Validator接口实现,回归实体类本职:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", length = 100, nullable = false, unique = true)
    private long id;

    @Column(name = "start", length = 20, nullable = true)
    @NotEarlierThanSixDaysAgo // 添加自定义校验注解
    private LocalDateTime start;

    public Message() {}

    // getter、setter方法
    public LocalDateTime getStart() {
        return start;
    }

    public void setStart(LocalDateTime start) {
        this.start = start;
    }
}

步骤2:创建自定义校验注解

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotEarlierThanSixDaysAgoValidator.class)
public @interface NotEarlierThanSixDaysAgo {
    String message() default "start不能早于当前日期6天前";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

步骤3:实现Spring管理的校验器

该校验器作为Spring Bean,可直接注入Clock:

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.Clock;

@Component
public class NotEarlierThanSixDaysAgoValidator implements ConstraintValidator<NotEarlierThanSixDaysAgo, LocalDateTime> {

    private final Clock clock;

    // 构造注入Clock
    @Autowired
    public NotEarlierThanSixDaysAgoValidator(Clock clock) {
        this.clock = clock;
    }

    @Override
    public void initialize(NotEarlierThanSixDaysAgo constraintAnnotation) {
        // 初始化逻辑(按需添加)
    }

    @Override
    public boolean isValid(LocalDateTime start, ConstraintValidatorContext context) {
        if (start == null) {
            return true; // 允许null,可根据需求调整
        }
        LocalDateTime sixDaysAgo = LocalDateTime.now(clock).minusDays(6);
        return !start.isBefore(sixDaysAgo);
    }
}

步骤4:配置Clock Bean

在配置类中定义默认Clock实现,测试时可替换:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Clock;

@Configuration
public class ClockConfig {
    @Bean
    public Clock clock() {
        return Clock.systemDefaultZone();
    }
}

步骤5:测试时替换固定时间的Clock

在测试类中通过@MockBean替换默认Clock,实现固定时间测试:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import jakarta.validation.Validator;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

@SpringBootTest
public class MessageValidationTest {

    @Autowired
    private Validator validator;

    @MockBean
    private Clock clock;

    @Test
    void testStartValidation() {
        // 固定当前时间为2024-05-20 12:00:00
        LocalDateTime fixedNow = LocalDateTime.of(2024, 5, 20, 12, 0);
        when(clock.instant()).thenReturn(fixedNow.atZone(ZoneId.systemDefault()).toInstant());
        when(clock.getZone()).thenReturn(ZoneId.systemDefault());

        Message message = new Message();
        // 测试6天前的时间:校验通过
        message.setStart(fixedNow.minusDays(6));
        var violations = validator.validate(message);
        assertTrue(violations.isEmpty());

        // 测试7天前的时间:校验失败
        message.setStart(fixedNow.minusDays(7));
        violations = validator.validate(message);
        assertFalse(violations.isEmpty());
        assertEquals("start不能早于当前日期6天前", violations.iterator().next().getMessage());
    }
}

方案优势

  • 实体类不再依赖Spring容器,从数据库加载时无注入问题
  • 校验器由Spring管理,可正常注入Clock,满足测试时替换固定时间的需求
  • 符合单一职责原则,实体类负责数据存储,验证逻辑单独维护

内容的提问来源于stack exchange,提问作者pbuchheit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 14:45:00