[Spring] Spring 에서 의존성 주입이란 무엇일까?
Spring으로 의존성 주입 하기
저번 글 에서 아래의 코드와 같이 내부에서 의존성을 직접 만드는 것이 아니라 외부에서 의존성을 주입해주는 것에 대해서 알아보았습니다.
(저번 글을 안보고 오셨다면 먼저 보고 오시는 것을 추천합니다.)
public class Car {
private Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
}
즉, 내부에서 Tire 의존성을 만들면 Car
와 Tire
는 강한 결합
이 생기고, 외부에서 Tire를 주입해주면 Car
와 Tire
는 느슨한 결합
이 될 수 있습니다. (좀 더 유연한 구조가 되는 것입니다.)
위와 같이 의존성 주입
을 직접할 수도 있지만, 스프링
에서는 Bean
으로만 등록되면 IoC 컨테이너
가 의존성 주입을 알아서 해줍니다. Bean..?
, IoC 컨테이너..?
생소한 용어가 나왔습니다. 하나씩 어떤 것인지 본격적으로 스프링에 대해서 알아보겠습니다.
Spring IoC 컨테이너란 무엇일까?
Bean 설정 소스로부터 Bean 정의를 읽어들이고, Bean을 구성하고 제공하는 역할을 합니다.
Bean들의 의존 관계를 설정해줍니다.(객체의 생성을 책임지고, 의존성을 관리합니다.)
IoC 컨테이너의 핵심이라고 할 수 있는 인터페이스는 BeanFactory, ApplicationContext 입니다.
스프링 IoC 컨테이너
를 구성하는 인터페이스는 위와 같이 엄청나게 많습니다. 그 중에서 BeanFactory
, ApplicationContext
가 Bean 설정과 의존성 주입의 핵심이라고 할 수 있습니다.
public class Car {
private Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
}
즉, 지금까지 계속 보았던 외부에서 의존성을 주입
해주는 코드는 아래와 같이 표현할 수 있습니다.
위와 같이 IoC 컨테이너가 인터페이스의 구현 클래스를 만들어
의존성 주입을 해주는 것입니다.
위에서도 간단히 보았지만, Spring Docs로 한번 더 보면 BeanFactory
하위 인터페이스는 정말 많은 것을 볼 수 있습니다. 이렇게만 보아도 스프링이 얼마나 거대한지 짐작할 수 있습니다.
Spring 컨테이너 종류
BeanFactory
- 스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스이다.
Bean 객체를 생성하고 관리하는 인터페이스이다. 디자인패턴의 일종인 팩토리 패턴을 구현한 것이다. BeanFactory 컨테이너는 구동될 때 Bean 객체를 생성하는 것이 아니라. 클라이언트의 요청이 있을 때(getBean()) 객체를 생성한다.
ApplicationContext
- ListableBeanFactory(BeanFactory에 하위 인터페이스이며, Bean을 Listable하게 보관하는 인터페이스를 말한다. 대표적으로 DefaultListableBeanFactory 클래스)를 상속하고 있으며, 여러 기능(ResourceLoader, ApplicationEventPublisher, MessageSource, Bean Lifecycle)을 추가로 제공한다.
BeanFactory를 상속받은 interface이며, ApplicationContext 컨테이너는 구동되는 시점에 등록된 Bean 객체들을 스캔하여 객체화한다
IoC 컨테이너를 구성하는 인터페이스를 정의로 표현하면 위와 같은데.. 가볍게 이렇구나~? 하고 보면 될 것 같습니다.
컨테이너란 무엇일까?
컨테이너는 말 그대로 어떤 것들을 담는 용기 같은 것이라 생각하면 됩니다. IoC 컨테이너에는 Bean으로 등록된 객체들을 담고있다고 생각하면 됩니다. 지금까지 계속 보았던 Car
, Tire
와의 관계를 그림으로 표현하면 아래와 같습니다.
방법 2
에서 외부에서 의존성을 주입하는 것을 IoC 컨테이너
가 해준다고 했습니다. 그것을 그림으로 표현하면 아래와 같습니다.
즉 Bean으로 등록된 객체
들을 IoC 컨테이너가 의존성을 만들어서 외부에서 주입을 해주는 것입니다. (계속 반복되는 말을 하고 있지만, 그럼에도 처음 보면 알듯말듯?한 느낌이실 겁니다.) 저 또한 완~벽하게 의존성 주입을 써야한다는 것이 온 몸으로 체감이 되지는 않습니다. 어느정도는 체감이 되어도 어느정도는 받아들이면서 사용하고 있기는 한데.. 좀 더 스프링으로 코드를 짜고 하다 보면 좀 더 왜 의존성 주입을 사용해야 하는지를 알 수 있을 것입니다.
의존성 주입을 사용해야 하는 이유
재사용성을 높여줍니다.
테스트 하기에 용이합니다.(내부에서 주입을 하지 않고 외부에서 주입을 하면 본인이 원하는 주입을 외부에서 만들어 넣은 후에 테스트 할 수 있습니다.)
객체간의 결합도가 낮추기 때문에 변경에 민감하지 않고 유연성과 확장성을 향상 시킬 수 있습니다.
빈(Bean)이란 무엇일까?
위에서 계~속 Bean
이라는 단어를 사용했습니다. 이것이 무엇인지 궁금했을텐데요. Bean을 간단하게 정의하면 아래와 같습니다.
스프링 IoC 컨테이너가 관리하는 객체
Bean으로 등록하면 어떤 장점이 있을까요?
기존에 스프링 없이 의존성 주입을 의존 관계를 직접 다 설정해서 의존성을 직접 넣어주어야 했지만, IoC 컨테이너에 등록된 Bean들은 스프링이 의존성 관리 및 주입을 해주기 때문에 의존성 관리가 수월해집니다.
Spring IoC 컨테이너에 등록된 Bean들은 싱클톤의 형태로 관리됩니다.
싱글톤
은 객체를 한번만 생성하고 계속 재사용한다는 뜻입니다. (Default가 싱글톤이지만, 설정을 바꾸면 prototype으로도 만들 수 있습니다.)
Bean 등록하는 방법
지금은 Spring Boot 기반이 아니라 Spring으로만 사용했을 때 Bean을 어떻게 등록하는지를 알아보겠습니다. (이 부분은 그냥 예전에는 그렇게 했구나 정도만 봐도 좋을 거 같습니다.)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="bookService" class="com.example.demo.BookService">
<property name="bookRepository" ref="bookRepository" />
</bean>
<bean id="bookRepository" class="com.example.demo.BookRepository">
</bean>
</beans>
위와 같이 xml에 Bean으로 등록하고자 하는 클래스들을 등록을 해줍니다.
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
String[] getBeanDefinitionNames = context.getBeanDefinitionNames();
System.out.println(Arrays.toString(getBeanDefinitionNames)); // [bookService, bookRepository]
}
}
그리고 main 메소드에 보면 위에서 계속 말했던 ApplicationContext
가 보일 것입니다. 이것을 통해서 Bean 설정 파일 xml을 읽어와서 클래스들을 Bean으로 등록하는 과정을 진행합니다.
Component-san으로 Bean 등록
하지만 이렇게 Bean으로 하나 하나 등록하는 것은 상당히 번거롭기 때문에 새로운 기능이 나왔습니다. 그것이 바로 Component-scan
이라느 것입니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
</beans>
위와 같이 base-package
를 지정하고 그 아래의 @Repository,
@Service등등 Bean으로 등록하는 어노테이션을 스캔한 후에 Bean으로 등록하도록 만들었습니다. 하지만
여기서 다시 xml이 아닌 자바 코드로 작성할 수 있는 없을까?`라는 생각에 다른 방법이 하나 더 나왔습니다.
Java 코드로 Bean 등록
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
public BookRepository bookRepository() {
return new BookRepository();
}
@Bean
public BookService bookService() {
BookService bookService = new BookService();
bookService.setBookRepository(bookRepository());
return bookService;
}
}
실제로 @Configuration
어노테이션을 통해서 해당 파일이 Bean 설정 파일
이라는 것을 알린 후에 @Bean
어노테이션을 사용해서 Bean으로 등록합니다. 이 방법은 현재 Spring Boot 기반으로 진행하더라도 상당히 많이 사용되기 때문에 꼭 알아두어야 합니다.
하지만 이것도 하나하나 빈으로 등록해야 하는 번거로움이 있기 때문에 이것으로만 Bean으로 등록하기에는 한계가 있습니다. 그래서 이러한 단점을 극복하기 위해서 현재의 Spring Boot
에서 사용하고 있는 방식까지 발전한 것입니다.
Spring Boot에서 Bean을 등록하는 방식
Spring Boot는 @Controller
, @RestController
, @Service
, @Component
, Configuration
등등 Bean으로 등록되는 어노테이션들이 존재합니다.
Compoent-scan
의 방식으로 위의 어노테이션들을 스캔해서 Bean으로 등록하는 방식을 사용하고 있습니다.
실제로 @SpingBootApplication
을 들어가보면 @ComponentScan
이 존재하는 것을 볼 수 있습니다. 위의 @ComponentScan
이 이미 존재하기 때문에 Spring Boot에서 IoC 컨테이너에 Bean을 쉽게 등록해서 의존성 주입을 사용할 수 있었던 것입니다.
@ComponentScan 이란?
ComponentScan의 특징에 대해서 좀 더 알아보겠습니다.
Spring Boot로 프로젝트를 만들면 위와 같이 만들어집니다. 여기서 @SpringBootApplication
어노테이션이 존재하는 파일의 패키지가 존재합니다. @ComponentScan
은 해당 패키지와 같거나 하위 패키지에 존재하는 어노테이션들만 스캔해서 Bean으로 등록할 수 있습니다.
즉, 위의 경우라면 com.example.demo
패키지와 같거나 하위 패키지가 아니라 다른 패키지라면 @Service
와 같은 어노테이션을 적어도 Bean으로 등록되지 않는다는 특징을 가지고 있습니다.