코딩스토리

Spring 컨테이너와 Dependency Injection 본문

Spring/Spring 공부

Spring 컨테이너와 Dependency Injection

kimtaehyun98 2022. 1. 23. 17:47

Spring에서는 객체들을 Spring 컨테이너에 Spring 빈으로 등록하고 적재적소에 의존 관계를 만들어 줌으로써 객체 지향적 설계 원칙을 지킨다. 

 

https://kimtaehyun98.tistory.com/115

 

좋은 객체지향 설계의 5가지 원칙 - SOLID

SOLID란? "클린 코더"로 유명한 로버트 마틴이 좋은 객체지향 설계를 하기 위한 5가지의 원칙을 제시한 것이다. SOLID는 각각의 원칙의 앞글자를 따서 만들어졌다. SOLID Principles 1. SGP : 단일 책임 원

kimtaehyun98.tistory.com

그럼 어떻게 Spring이 객체 지향적으로 작동하는지 알아보자.

 

 

 

Spring 컨테이너와 @Configuration을 사용한 의존관계 수동 주입


Spring 컨테이너가 뭐고 이게 어떻게 Spring이 객체 지향적으로 작동하도록 도와준다는 것일까?

 

이 질문에 앞서 먼저 싱글톤이라는 게 뭔지 알아야 한다.

 

싱글톤에 대한 Wiki 백과의 정의는 아래와 같다.

싱글턴 패턴(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 
생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.
이와 같은 디자인 유형을 싱글턴 패턴이라고 한다.

 

즉 new를 통해 Instance를 요청할 때마다 객체를 생성하지 않는다는 것이다.

 

🤔 ???

 

지금까지 JAVA를 써왔던 사람이라면 이게 무슨 말인지 이해가 잘 가지 않을 것이다.

이해가 잘 가지 않는다는 표현보단 어떻게?라는 생각이 든다는 표현이 더 맞는 것 같다.

저도 그랬습니다.. 네..

 

결론부터 말하자면 Spring 컨테이너가 이러한 기능을 제공한다.

 

누군가 객체를 처음으로 생성하면서 이를 Srping Bean(Spring 컨테이너가 관리하는 객체)으로 등록한다면 Spring 컨테이너는 이를 가지고 있는다.

이후에 누군가 다시 똑같은 객체를 생성하려고 한다면, Spring은 새롭게 객체를 생성하지 않고 Spring 컨테이너가 가지고 있던, 이전에 생성된 객체를 반환한다.

 

결국 Spring은 Spring 컨테이너를 사용함으로써 알아서 싱글톤이 되게 보장한다는 것이다.

 

그래도 한 가지 의문점이 남을 수 있다. 🤔

 

그럼 Spring 컨테이너 자체가 알아서 판단하고 객체를 반환하는 것인가?

그게 가능한가? 컨테이너는 말 그대로 저장소인데 어떻게..?

 

답은 당연히 아니다. 😁

Spring이 @CGLIB이라는 byte code 조작 라이브러리를 사용하여

Spring 컨테이너에 Bean으로 등록하려는 클래스를 상속받은 새로운 클래스를 만들고(ex. AppConfig@CGLIB)

새롭게 만들어진 클래스를 Bean으로 등록한다.

 

즉 사실상 Spring 컨테이너에는 내가 만들려고 했던 객체가 아니라 조작된 새로운 객체가 등록되는 것이다.

 

그럼 이 조작된 객체, 즉 Bean은 이후 똑같은 객체를 생성하려고 하면 자신을 반환하는 이러한 검증 과정을 거칠 수 있게 된다.

 

여기서 헷갈릴 수 있는데 위의 클래스에서 testService나 testRepository 같은 객체들은 CGLIB이 작동되는 것이 아니다!

AppConfig 클래스 객체만(@Configuration이 붙은 클래스) 아래의 그림처럼 CGLIB으로 조작된 객체가 bean으로 등록되고

AppConfig 내부의 의존관계들이 주입될 때 검증과정을 거치는 것이다! 

 

 

이렇게 새롭게 만들어진 클래스가 싱글톤을 유지하게 해 준다.

 

단, 반드시 설정 클래스에 @Configuration 에노테이션이 있어야 한다. 

단순히 @Bean 들만 등록하면 싱글톤 깨지게 된다. (위에서 빨간 글씨로 언급한 부분을 이해했다면.. 당연한 거겠죠?)

 

그렇기 때문에 싱글톤 패턴을 유지하면서 Spring이 작동하게 하려면 이러한 의존관계 설정 정보들을 모아놓은 클래스를 만들고, 그 클래스에 @Configuration을 붙이면 된다.

 

이러한 과정이 바로 의존관계를 수동으로 주입하는 과정이다.

(여기서 수동이라는 말을 꼭 기억하자!)

 

마지막으로 싱글톤 패턴에 대해 내가 생겼던 궁금증을 얘기해보려고 한다.

뭐 어쩌고저쩌고 해서 Spring이 싱글톤 패턴을 유지하게 해주는 것은 알겠다.

근데 어떤 Client든 이미 생성된 똑같은 객체를 제공한다고? 이게 말이 됨?? 🙄

 

말이 되진 않는데 말이 되게 만들면 된다. (아주 적절한 표현인 것 같아요. 물론 온전히 제 판단)

즉 싱글톤 패턴은 객체가 Stateless 하게 설계되어야 한다는 것이다.

 

- 특정 클라이언트에 의존적인 필드가 있으면 안 된다

- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안 된다

- 가급적 읽기만 가능해야 한다

- 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다

 

너무 간단한 해결책이었다.

그냥 내가 생각한 나쁜 경우가 안 생기도록 설계하라는 것이다.

잘못되면 무조건 개발자 탓이다~

 

사실 생각해보면 우리가 bean으로 등록하는 것들은 controller, service, repository 등인데 신기하게도 무상태로 설계되어있다.

 

 

@Component, @Autowired를 사용한 의존관계 자동 주입


자 이제 궁금한 점

위의 방법은 말 그대로 수동으로 bean을 등록하는 것이다.

우리는 실제 개발할 때 @bean을 잘 쓰지 않는다.

 

이제 우리는 @ComponentScan을 알아야 한다. 

@ComponentScan이란 모든 클래스에서 @Component가 붙은 것들을 자동으로 스캔해서 모두 Spring bean으로 등록시켜준다.

 

우리가 알고 있는 @Controller, @Repository, @Service 모두 @Component를 가지고 있다

그렇기 때문에 자동으로 Bean으로 등록되는 것이다 (사실상 ComponentScan을 하고 있었다는 거죠)

 

근데 우리는 ComponentScan을 쓴 적이 없는데?

아닐걸 쓰고 있었을걸..?

아래는 mainApplicaition에 붙어있는 @SpringBootApplication이다.

 

즉 main 프로그램을 실행시키면 알아서 Component들이 Spring Bean으로 등록되어서 관리된다는 것이다.

(물론 ComponentScan의 탐색할 패키지 위치를 설정할 수도 있다.)

 

그럼 여기서 드는 의문점은 어떻게 서로 간의 의존관계를 설정하지?

 

위의 사진은 AppConfig 클래스에서 의존관계 주입을 해주고 있는 것이다.

이렇게 설정 클래스가 있다면 우리가 수동으로 설정 클래스에서 의존관계를 명시해서 주입시켜주면 된다.

 

하지만 @ComponentScan을 사용하게 되면 이런 과정이 생략되기 때문에 의존관계 설정을 누군가 대신해줘야 한다.

 

그 역할을 담당하는 것이 바로 @Autowired이다.

 

@Autowired가 붙어있다면 자동으로 컨테이너에서 빈을 찾아와서 의존관계를 설정한다.

 

쉽게 말하면 AppConfig가 하고 있던 저런 과정들을 자동으로 해준다는 것이다.

 

(저도 이 부분이 잘 이해가 가지 않아서 찾아보았는데

@Autowired가 자동으로 Spring 컨테이너에서 해당 객체가 있는지 없는지 보고 있으면 가져와서 주입해준다는 것이다.

즉, CGLIB으로 조작된 객체가 해주던 검증 과정을 @Autowired가 해준다는 의미로 이해했다.

해당 부분이 잘못됐다면 댓글로 남겨주시면 감사하겠습니다!)

 

언제? 각 클래스 생성 시에!

 

아래는 @Autowired를 사용하는 방법들이다. 

 

@Autowired를 사용한 의존관계 주입 (DI)

- 생성자 주입 : 불변, 필수

- 수정자 주입(Setter) : 선택, 변경

- 필드 주입 : DI 프레임워크(Spring 컨테이너)가 없으면 아무것도 못함, 외부에서 변경할 수가 없어서 테스트하기가 힘듦

- 일반 메서드 주입

 

여러 방법이 있지만 결국 생성자 주입을 최대한 쓰는 게 좋다고 한다.

 

왜냐하면 생성자 주입은 @Autowired를 생성자에 사용하기 때문에 불변, 필수 의존관계를 설정해주고

의존관계는 변하면 안 되는 경우가 대부분이기 때문에 좋다는 것이다.

(생성자 호출 시점에 딱 1번만 호출 보장)

 

아래 코드는 final 키워드를 넣었기 때문에 @RequiredArgsConstructor을 사용하면 생성자 코드가 없어도 자동으로 의존관계를 주입해준다.

사실상 위아래 동일한 코드임

 

저 코드는 제가 실제 진행주인 프로젝트 코드를 가져온 건데 나는 개인적으론 음.. 아래 코드가 더 깔끔하지만 아직까진 위의 코드로 구현하고 있다. (이유는 잘 모르겠지만 그냥 정이 안 간 다해야 되나..?)

 

그럼 반드시 자동 의존 관계가 좋을까?

결론은 편리하니까 자동 기능을 기본으로 사용하는 게 좋다! 

 

수동 주입은 설정 정보만 봐도 어떤 빈들이 주입되는지 파악할 수 있다는 장점이 있기 때문에 상황에 따라 필요하다면 수동 주입을 사용하는 것도 좋다고 한다.

 

이렇게 Spring의 의존관계 주입에 대한 전체적인 흐름을 살펴보았다.

최근 개발하면서 든 생각인데 오류가 났을 때 어떠한 부분에서 난 건지 정확히 이해하려면 정말 기본이 중요하다는 것을 느꼈다.

 

이젠 보다 더 Spring을 이해할 수 있게 된 것 같다 😁

 

'Spring > Spring 공부' 카테고리의 다른 글

Spring 프로젝트로 Docker Image 만들기  (0) 2022.07.27
HttpMessageConverter  (0) 2022.01.16
REST API  (1) 2022.01.16
좋은 객체지향 설계의 5가지 원칙 - SOLID  (2) 2021.07.08
Comments