스프링 공식 문서 뿌수기(13), Bean

2023. 5. 10. 01:09자바/스프링

728x90
반응형

늦어도 한참 늦은 것 같은 Bean 정리이다. ㅋㅋㅋㅋ

순서로 따지면 한참 이전에 진행했어야했지만 이제야 정리를 한다.

 

사실 Bean에 대해서는 알고 있지만 Bean을 정리하지 않고 공식문서 뿌수기 시리즈를 한다는 것이 말이 안되기 때문에 늦게나마 Bean에 대해 정리해보고자 한다.


Bean

 

Spring의 IoC container는 하나 이상의 빈들을 관리한다. 이러한 빈들은 configuration metadata와 함께 생성이 된다. 이러한 빈은 자바 코드로도 구성할 수 있고 XML로도 구성할 수 있다. 

 

컨테이너에서 빈을 관리할 때는 다음과 같은 BeanDefinition을 가지고 있다. 

 

여기에서 Dependency Injection과 Scope를 중심으로 알아볼 예정이다. (빈 이름 짓기 등등에 대한 내용은 이전에 다뤘던 내용에도 포함되어있고, 구글링해봐도 많이 나오기 때문이다.)

 

 

Dependency Injection

 

Dependency Injection은 스프링에서 중요한 개념 중 하나이다. ApplicationContext는 빈을 관리해주고, 이 빈들의 의존성을 간단하게 해결해줄 수 있기 때문이다. 스프링에서 빈에 대한 의존성을 주입해줄 때에는 일반적으로는 세 가지 방법을 사용한다. 각각에 대해서 알아보도록 하자.

 

 

Constructor-based Dependency Injection


위와 같이 생성자를 정의해두고, SimpleMovieLister를 @Conponent로 등록하면(혹은 xml로 등록하면) 생성자를 통해서 자동으로 MovieFinder 타입의 객체가 등록된다. 이때 물론 MovieFinder 객체도 빈으로 등록이 되어있어야한다. 

 

이때 Constructor Argument Resolution matching이 동작한다. Constructor argument resolution matching은 인자에 빈으로 등록된 객체를 주입할 때 이를 매칭시켜주는 역할을 한다. 만약 어떤 빈을 주입할지 모호하지 않다면 이를 명시적으로 지정하지 않아도 생성자로 잘 전달된다. 

 

하지만 상속관계가 얽혀있어서 어떤 빈을 주입할지 명확하지 않는 경우에는 빈의 이름을 기반으로 주입한다고 생각하면 된다. 

 

참고로 어노테이션을 기반으로 의존성을 주입받기 위해서는 @Autowired 어노테이션을 사용해야하지만 이 경우에는 생성자가 하나만 있기에 그 생성자를 통해서 자동으로 주입받을 수 있다.

 

Setter-based Dependency Injection


 

위에서는 setter를 만들어서 movieFinder를 주입받고 있다. setter를 사용한 주입은 constructor를 사용한 주입이 이뤄진 이후에 동작한다. (이 부분 때문에 순환참조와 관련된 문제를 해결할 수 있다고도 하지만 순환참조는 DI에 대해 모두 싹 살펴본 뒤에 한번 다뤄보도록 하겠다.)

 

개인적으로 setter 주입은 지양하는 편이다. 일단 setter로 빈의 값을 열어준다는 것은 객체의 상태를 변경할 수 있는 여지를 남겨주기 때문이다.

 

 

Field based Dependency Injection


이거는 공식문서 어디에서 설명하고 있는지 잘 모르겠다. ㅋㅋㅋㅋ Field에서 주입하는 것은 @Autowired 어노테이션을 사용해서 주입하는 것을 의미한다. 이 경우에는 필드의 위쪽에 @Autowired 어노테이션을 붙이면 된다. 위와 같이 사용하면 된다.

 

하지만 이러한 필드 주입은 프로덕션 코드 상에서는 사용하지 않는 것을 추천한다. 필드 주입을 하는 경우에는 빈을 사용하지 않는 의존성 주입에 대해서는 관리하기가 어려워지기 때문이다. 즉, 너무 스프링에 의존적인 코드가 되어버리고, 테스트도 어려워진다. 

 

순환 참조 문제

 

아까 위쪽에서 순환 참조 문제에 대해서 잠깐 언급했었다. 순환 참조 상황이란 둘 이상의 빈들이 서로를 참조하고 있을 때 발생하는 문제이다. 생성자 주입의 경우에는 빈이 생성됐을 때 주입이 되기 때문에 순환 참조 문제가 발생하게 되는 것이다. 

 

예시를 한번 들어보자.

 

두 클래스 모두 빈으로 등록되어있고 서로를 참조하고 있다. 이때 생성자 주입을 사용하고 있다. 

 

그리고 한번 실행을 해보면!

 

바로 예외가 터지는 것을 알 수 있다. (그림도 이쁘다.)

 

순환참조를 하고 있기 때문에 문제가 발생하는 건데, 실행하자마자 이러한 순환 참조 문제를 확인할 수 있기 때문에 설계 상의 문제를 바로 파악할 수 있다. 

 

공식문서에서는 아래와 같이 순환 참조에 대한 내용을 설명하고 있다.

 

 

생성자 주입을 사용하는 경우 해결할 수 없는 순환 종속성을 관리할 수 있다. 생성자 주입을 사용하는 경우에는 BeanCurrnetlyInCreateionException을 발생시킨다한다. 그래서 만약 이러한 순환 참조 문제를 해결하고자 한다면 setter를 사용하는 것이 하나의 해결방법이 될 수 있다고 한다. setter는 빈의 지연 생성 덕분에 이러한 순환 참조 예외를 터트리지 않는 것이다. 

 

그러면 한번 setter를 사용해서 구현해보자.

 

 

이번에는 setter를 사용해서 의존성을 주입받고 있다. 그리고 한번 실행해보면!

 

생성자 주입과 마찬가지로 예외가 터지는 것을 볼 수 있다.

 

이에 관해서는 우테코 크루인 홍실이 블로그에서 잘 정리를 해두었으니 한번 참고해보면 좋을 것 같다.

https://velog.io/@hong-sile/Setter%EC%A3%BC%EC%9E%85%EC%9D%80-%EC%A0%95%EB%A7%90-%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0%EB%A5%BC-%EA%B2%80%EC%A6%9D-%EC%95%88-%ED%95%A0%EA%B9%8C

 

결론은 스프링에서는 이를 검증하지 않고, 스프링 부트에서 순환참조를 검증해주고 있는 것이다. 

이 글을 읽고 내린 결론은 다음과 같다.

 

순환 참조를 의도한 경우에는 setter를 사용해 의존성을 주입하고 설정을 따로 해 순환 참조를 허용해준다. 만약 그 경우가 아니라면 생성자 주입을 하는 것이 좋다.

 

근데 아직까지 간단한 프로젝트만 진행하고 있기에 순환참조가 필요한 예시를 잘 모르겠다. 지금 단계에서는 순환참조가 필요하다고 하면 한번 설계를 의심해보고 최대한 생성자 주입을 사용하도록 하는 게 좋겠다.

 

Bean Scope

 

 

빈 스코프는 여섯 가지로 나눠볼 수 있다. 스코프를 설정할 때에는 @Scope 어노테이션을 설정해주면 된다.

 

가장 디폴트로 설정되는 scope는 바로 singleton 빈이다. Spring IoC container에 등록되는 빈이 하나라는 의미이다.(콩하나?) 싱글톤으로 빈이 등록되기 때문에 상태가 변하게 된다면 다른 곳에서 해당 빈을 호출할 때에도 변경된 상태를 유지하고 있기 때문에 이를 조심해야한다.

 

prototype 빈은 여러 개 생성될 수 있다. 

 

딱 봐도 성질이 다른 두 가지 타입의 빈을 사용하려고 한다면 문제가 발생할 수 있다.

 

 


자, 다음과 같은 상황을 한번 가정해보자.

싱글톤 빈이 프로토타입 빈을 참조하고 있다고 가정해보자.

싱글톤 빈은 컨테이너 생성 시점에 생성되기 때문에 그 시점에 프로토타입 빈을 주입받게 된다. 그리고 싱글톤 빈은 처음 주입 받은 프로토타입 빈을 그대로 가지고 있기 때문에 프로토타입 빈이 반복적으로 생성되지 않는 문제가 발생한다. 이러한 문제를 방지하기 위해 ObjectProvider를 사용할 수 있다.

 


아까 위에서 봤던 나머지 4가지 스코프는 웹과 관련된 스코프이기 때문에 웹 스코프라고도 한다. 웹 스코프의 경우에는 각각 요청/세션/어플리케이션/웹소켓의 생명주기와 동일하다. 뭐... 아직 스프링 걸음마 수준이기에 이 정도까지만 정리하고 넘어가면 좋을 것 같다.

728x90
반응형