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

Spring Boot Bean循环依赖问题求助(附相关代码示例)

解决Spring Bean循环依赖问题

问题背景

我正在开发一个调度项目,启动时提示Bean之间存在循环依赖错误。

相关代码

RoomController.java

@RestController
@RequestMapping("/api/v1/rooms")
public class RoomController {
    private final RoomService roomService;

    public RoomController(RoomService roomService) {
        this.roomService = roomService;
    }

    @GetMapping
    public ResponseEntity<List<RoomDto>> getAllRooms() {
        List<RoomDto> rooms = roomService.findAll();
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(rooms);
    }

    @PostMapping
    public ResponseEntity<RoomDto> createRoom(@Valid @RequestBody CreateRoomDto createRoomDto) {
        RoomDto roomDto = roomService.save(createRoomDto);
        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(roomDto);
    }

    @DeleteMapping("/{id}")
    public void deleteRoomById(@PathVariable Long id) {
        roomService.deleteById(id);
    }
}

RoomService.java

@Service
@Transactional
public class RoomServiceImpl implements RoomService {
    private final RoomRepository roomRepository;
    private final RoomMapper roomMapper;

    public RoomServiceImpl(RoomRepository roomRepository, RoomMapper roomMapper) {
        this.roomRepository = roomRepository;
        this.roomMapper = roomMapper;
    }

    @Override
    public RoomDto save(CreateRoomDto createRoomDto) {
        Room room = roomMapper.toRoom(createRoomDto);
        return roomMapper.toRoomDto(roomRepository.save(room));
    }

    @Override
    public List<RoomDto> findAll() {
        return roomRepository.findAll().stream().map(roomMapper::toRoomDto).collect(Collectors.toList());
    }

    @Override
    public RoomDto findById(Long id) {
        Room room = roomRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Room with id " + id + " not found!"));
        return roomMapper.toRoomDto(room);
    }

    @Override
    public void deleteById(Long id) {
        roomRepository.deleteById(id);
    }
}

RoomMapper.java

@Service
public class RoomMapperImpl implements RoomMapper {
    private final LectureMapper lectureMapper;

    public RoomMapperImpl(LectureMapper lectureMapper) {
        this.lectureMapper = lectureMapper;
    }

    @Override
    public RoomDto toRoomDto(Room room) {
        return RoomDto.builder()
                .id(room.getId())
                .name(room.getName())
                .description(room.getDescription())
                .number(room.getNumber())
                .lectures(room.getLectures().stream().map(
                        lectureMapper::toLectureDto
                ).collect(Collectors.toList()))
                .build();
    }

    @Override
    public Room toRoom(CreateRoomDto createRoomDto) {
        return Room.builder()
                .name(createRoomDto.getName())
                .description(createRoomDto.getDescription())
                .number(createRoomDto.getNumber())
                .build();
    }

    @Override
    public RoomSmallDto toRoomSmallDto(Room room) {
        return RoomSmallDto.builder()
                .id(room.getId())
                .name(room.getName())
                .description(room.getDescription())
                .number(room.getNumber())
                .build();
    }
}

LectureMapper.java

@Service
public class LectureMapperImpl implements LectureMapper {
    private final CohortMapper cohortMapper;
    private final InstructorMapper instructorMapper;
    private final RoomMapper roomMapper;

    public LectureMapperImpl(CohortMapper cohortMapper, InstructorMapper instructorMapper, RoomMapper roomMapper) {
        this.cohortMapper = cohortMapper;
        this.instructorMapper = instructorMapper;
        this.roomMapper = roomMapper;
    }

    @Override
    public LectureDto toLectureDto(Lecture lecture) {
        return LectureDto.builder()
                .id(lecture.getId())
                .title(lecture.getTitle())
                .description(lecture.getDescription())
                .startDate(lecture.getStartDate())
                .endDate(lecture.getEndDate())
                .cohort(cohortMapper.toCohortSmallDto(lecture.getCohort()))
                .instructor(instructorMapper.toInstructorSmallDto(lecture.getInstructor()))
                .room(roomMapper.toRoomSmallDto(lecture.getRoom()))
                .build();
    }
}

CohortMapper.java

@Service
public class CohortMapperImpl implements CohortMapper {
    private final LectureMapper lectureMapper;

    public CohortMapperImpl(LectureMapper lectureMapper) {
        this.lectureMapper = lectureMapper;
    }

    @Override
    public CohortDto toCohortDto(Cohort cohort) {
        return CohortDto.builder()
                .id(cohort.getId())
                .major(cohort.getMajor())
                .year(cohort.getYear())
                .lectures(cohort.getLectures().stream().map(
                        lectureMapper::toLectureDto
                ).collect(Collectors.toList()))
                .build();
    }

    @Override
    public CohortSmallDto toCohortSmallDto(Cohort cohort) {
        return CohortSmallDto.builder()
                .id(cohort.getId())
                .major(cohort.getMajor())
                .year(cohort.getYear())
                .build();
    }
}

实体类

@Data
@Builder
public class CohortSmallDto {
    private Long id;
    private String major;
    private int year;
}

---------------------------------------

@Data
@Builder
public class CohortDto {
    private Long id;
    private String major;
    private int year;
    private List<LectureDto> lectures;
}

---------------------------------------

@Data
@Builder
public class LectureDto {
    private Long id;
    private String title;
    private String description;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    private CohortSmallDto cohort;
    private InstructorSmallDto instructor;
    private RoomSmallDto room;
}

循环依赖分析

代码中存在两条循环依赖链:

  • RoomMapperImpl → LectureMapperImpl → RoomMapperImpl:RoomMapper需要LectureMapper转换RoomDto中的lectures列表,而LectureMapper又需要RoomMapper转换LectureDto中的room字段。
  • LectureMapperImpl → CohortMapperImpl → LectureMapperImpl:CohortMapper需要LectureMapper转换CohortDto中的lectures列表,而LectureMapper又需要CohortMapper转换LectureDto中的cohort字段。

解决方案

方案1:使用@Lazy延迟初始化

在循环依赖的其中一个Bean的构造函数参数上添加@Lazy注解,Spring会创建代理对象延迟初始化依赖,避免启动时的循环问题。比如修改RoomMapperImpl的构造函数:

public RoomMapperImpl(@Lazy LectureMapper lectureMapper) {
    this.lectureMapper = lectureMapper;
}

或者在LectureMapperImpl的RoomMapper参数上添加@Lazy,任选一处即可打破第一条循环链;同理,在CohortMapperImpl的LectureMapper参数上加@Lazy可打破第二条循环链。

方案2:重构代码打破循环

观察Dto转换逻辑,循环依赖是因为全量Dto转换时互相引用,但部分场景只需要小型Dto(比如RoomSmallDto、CohortSmallDto),可以针对性拆分:

  1. 打破RoomMapper与LectureMapper的循环:
    LectureMapper转换LectureDto时只需要RoomSmallDto,而RoomSmallDto的转换不需要依赖LectureMapper,因此可以直接在LectureMapper中手动构建RoomSmallDto,不再依赖RoomMapper:
@Override
public LectureDto toLectureDto(Lecture lecture) {
    RoomSmallDto roomSmallDto = null;
    if (lecture.getRoom() != null) {
        roomSmallDto = RoomSmallDto.builder()
                .id(lecture.getRoom().getId())
                .name(lecture.getRoom().getName())
                .description(lecture.getRoom().getDescription())
                .number(lecture.getRoom().getNumber())
                .build();
    }
    return LectureDto.builder()
            .id(lecture.getId())
            .title(lecture.getTitle())
            .description(lecture.getDescription())
            .startDate(lecture.getStartDate())
            .endDate(lecture.getEndDate())
            .cohort(cohortMapper.toCohortSmallDto(lecture.getCohort()))
            .instructor(instructorMapper.toInstructorSmallDto(lecture.getInstructor()))
            .room(roomSmallDto)
            .build();
}

修改后,LectureMapper不再依赖RoomMapper,第一条循环链被打破。

  1. 打破CohortMapper与LectureMapper的循环:
    CohortMapper只有转换CohortDto时需要LectureMapper,而LectureMapper转换LectureDto时只需要CohortSmallDto(不需要LectureMapper)。可以把CohortMapper拆分为两个独立的Mapper:
  • CohortSmallMapper:只负责转换CohortSmallDto,不依赖任何其他Mapper;
  • CohortFullMapper:负责转换包含lectures列表的CohortDto,依赖LectureMapper。
    然后LectureMapper依赖CohortSmallMapper而非原CohortMapper,即可打破循环。

方案3:使用Setter注入替代构造函数注入

Spring默认支持Setter注入解决循环依赖,将其中一个Mapper的构造函数注入改为Setter注入。比如修改RoomMapperImpl:

@Service
public class RoomMapperImpl implements RoomMapper {
    private LectureMapper lectureMapper;

    @Autowired
    public void setLectureMapper(LectureMapper lectureMapper) {
        this.lectureMapper = lectureMapper;
    }

    // 其他方法保持不变
}

Spring会先创建RoomMapperImpl实例,再注入LectureMapper,避免启动时的循环问题。同理可处理其他循环链。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:07:01