인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

윤승제님의 프로필 이미지
윤승제

작성한 질문수

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

정리

DI 적용 중 순환 참조 발생

작성

·

43

·

수정됨

1

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)

[질문 내용]

FrontControllerServletV5 에서 의존성 주입을 도전해보려고 다음과 같이 코드를 작성했습니다.

FrontControllerServletV5에 HandlerMappingConfig, HandlerAdapterConfig를 이용하여 handlerMappingMap, handlerAdapter를 생성자를 통한 의존성 주입을 시도했습니다.

FrontControllerServletV5

@RequiredArgsConstructor
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {

  private final Map<String, Object> handlerMappingMap;
  private final List<MyHandlerAdapter> handlerAdapters;

  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response)
//...
}

HandlerMappingConfig

@Configuration
public class HandlerMappingConfig {

  @Bean
  public Map<String, Object> handlerMappingMap() {
    Map<String, Object> handlerMappingMap = new HashMap<>();
    handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());

    handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());

    return handlerMappingMap;
  }
}

HandlerAdapterConfig

@Configuration
public class HandlerAdapterConfig {

  @Bean
  public List<MyHandlerAdapter> handlerAdapters() {
    List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
    handlerAdapters.add(new ControllerV3HandlerAdapter());
    handlerAdapters.add(new ControllerV4HandlerAdapter());
    return handlerAdapters;
  }
}

하지만 다음과 같이 순환 의존성 에러가 발생했는데, 원인을 찾지 못하고 있습니다.

정규 빈과 내부 빈이 서로 의존하고 있다는 것인가요..? 다른 수강생 분들의 코드와 다르게 작성해보려고 했는데, 이 에러가 발생해서 원인을 찾아보려고 했지만 찾지 못했습니다, 왜 발생한 것인지 도움 부탁드립니다..

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  frontControllerServletV5
↑     ↓
|  (inner bean)#61a91c9b defined in file [.../hello/servlet/web/frontcontroller/v5/FrontControllerServletV5.class]
└─────┘

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.

윤승제님께서 겪고 계신 순환 의존성 문제에 대해 설명드리겠습니다.

순환 의존성은 스프링 컨텍스트에서 서로 의존하고 있는 두 빈 또는 그 이상이 서로를 필요로 하여 발생하는 문제입니다. 윤승제님의 경우, FrontControllerServletV5 클래스와 해당 클래스에서 의존하는 핸들러 맵 및 어댑터 리스트에 대해 의존성이 순환되는 것처럼 보입니다.

현재 코드에서는 여러 다른 클래스와 빈을 사용하여 Map<String, Object>List<MyHandlerAdapter>를 DI로 주입받고 있습니다. 하지만, 서블릿(FrontControllerServletV5)은 스프링 컨텍스트의 다른 빈이 아닌 서블릿 자체를 웹 애플리케이션에 등록하기 때문에 스프링의 DI 메커니즘을 서블릿 클래스에 직접 사용하기 어렵습니다. 직접적으로 스프링의 빈 관리 하에 두지 않는 한, 서블릿은 스프링의 빈으로 등록되지 않습니다. 이런 경우, 의존성 주입이 잘못 설정되어 순환 의존성 문제가 발생할 수 있습니다.

이 문제를 해결하는 몇 가지 방법을 제안드리겠습니다:

  1. 생성자 주입 대신 빈 주입 사용: 서블릿은 직접적으로 @Autowired 주입을 받지 않도록 하고, 이를 스프링의 빈 관리 하에 있는 다른 클래스에서 주입받을 수 있도록 합니다. @Autowired 빈을 이용해 서블릿 초기화 로직을 별도의 스프링 빈으로 옮기는 것이 좋습니다.

  2. 빠른 초기화: 서블릿 컨텍스트 초기화 시점에서 수동으로 빈에 접근해 주입하는 방식으로 초기화할 수 있습니다. 그러나 이는 Spring Context와 Servlet Context를 잘못 엮어 순환 의존성을 초래할 수 있습니다.

  3. Spring의 DispatcherServlet을 사용: 일반적으로 스프링 웹 애플리케이션에서는 DispatcherServlet을 사용하여 MVC 패턴을 지원합니다. 이를 통해 직접 빈을 주입받고 순환 의존성 문제를 피할 수 있습니다.

추가적으로, 비슷한 문제를 다른 수강생분들께서도 겪으셨던 사례를 공유합니다:
1. “DI 적용해보기” - 이승훈님의 질문 보기
- 스프링의 빈 주입시 타입이 Object일 때 모든 빈이 주입됨에 따라 주의점을 설명한 답변입니다.

위의 링크는 문제 해결에 직접적인 도움이 될 수 있는 스프링의 다른 DI 사용 사례를 설명합니다. 이를 함께 참고하시면 도움이 될 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

윤승제님의 프로필 이미지
윤승제
질문자

답변 감사합니다! Object 대신 다른 타입으로 명시하니 해당 에러가 해결되었습니다.

질문이 2개 더 있습니다.

 

  1. rjf1138님의 DI 적용해보기 -

    이 질문에서는 프론트 컨트롤러가 스프링의 빈으로 등록되지 않은 상태에서도 의존성 주입이 되는 이유는 무엇인가요?

  2. FrontControllerServletV5는 서블릿이기 때문에 스프링의 관리 대상이 아니고, 이로써 순환 의존성 에러와 무관하다고 생각을 했는데, 이 순환 의존성 에러는 스프링이 모든 타입(Object 기반)의 빈을 주입하려고 할 때, FrontControllerServletV5도 주입하려고 시도해서 발생한 것으로 보면 될까요?

     

    handlerMappingMap에서 Object에서 ControllerV3로 수정하면 해결은 됐지만 수정하기 전에도 FrontControllerServletV5를 호출하는 코드가 없었기 때문에 순환 의존성이 발생하는 이유를 모르겠습니다.

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 윤승제님

1. 스프링 부트가 제공하는 @ServletComponentScan을 사용하게 되면, 스프링이 @WebServlet으로 사용되는 객체를 생성하면서, 이 때 스프링이 가지고 있는 빈들을 주입하는 역할도 함께 제공합니다.

참고로 @WebServlet 클래스가 스프링 빈으로 직접 등록되는 것은 아닙니다. 스프링 빈이 아니지만, 스프링 컨테이너가 가지고 있는 빈들을 의존관계는 주입해둔다고 생각하시면 됩니다.

2. Map<String, Object> 주입 받기

앞서 @WebServlet을 사용한 클래스의 경우 스프링 빈으로 등록하지 않는다고 했는데요. 해당 클래스를 직접 빈으로 등록하지는 않지만 스프링 부트가 서블릿 사용을 위해 ServletRegistrationBean이라는 빈을 대신 만들어서 등록합니다.

name: helloServlet, class=ServletRegistrationBean 이라고 생각하시면 됩니다.

ServletRegistrationBean은 스프링 부트를 통해 서블릿을 등록하기 위해 사용하는 빈입니다. 여기에 매핑 경로 같은 정보들이 포함되는 것이지요.

결과적으로 보면 helloServlet도 ServletRegistrationBean를 통해 빈으로 등록이 되어야 합니다.

여기서 순환참조 문제가 발생하게 됩니다.

스프링에서 Map<String, Object>를 통해 모든 빈을 주입 받는 경우, 문제가 자기 자신도 포함해서 주입을 받아야 하는데요. 이 경우 보통은 자기 자신은 제외하고 나머지 빈들을 주입해줍니다.

그런데 예를 들어 A -> B(자기자신) -> [모든 빈 참고] 이렇게 복잡한 경우 순환참조 문제가 발생합니다. @ServletComponentScan를 사용하는 이번 경우도 정확하지는 않지만 비슷하게 복잡한 순환참조 문제로 보입니다.

참고로 @WebServlet, @ServletComponentScan은 이해를 돕기위한 학습의 과정이고, 실무에서는 잘 사용하지 않기 때문에 이런 부분을 너무 파지는 않아도 괜찮다 생각합니다.

감사합니다.

윤승제님의 프로필 이미지
윤승제
질문자

답변 감사합니다 강사님!!!

뭔가 짐작은 되는데 확실하지는 않고 인터넷에 검색해도 Spring만 사용하는 예시들이 많아서 긴가민가 했는데, 강사님 덕분에 해결됐습니다 😄 감사합니다~!

윤승제님의 프로필 이미지
윤승제

작성한 질문수

질문하기