DI & IoC & Bean

🙇🏼‍♂️Reference🙇🏻

gillog-[Spring] DI, IoC 정리
leveloper - [스프링] IoC(Inversion of Control), DI(Dependency Injection), Spring Container, Bean 정리

DI(Dependency Injection)란?

스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로,
객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.
DI(의존성 주입)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.

첫번째 방법은 A객체가 B와 C 객체를 New 생성자를 통해 직접 생성하는 방법이고, 두번째 방법은 외부에서 생성된 객체를 setter()를 통해 사용하는 방법이다.
바로 이 두번째 방법이 의존성 주입의 예시인데, A 객체에서 B, C 객체를 사용(의존)할 때 A 객체에서 직접 생성하는 것이 아니라 외부(IOC컨테이너)에서 생성된 B, C 객체를 조립(주입)시켜 setter 혹은 생성자를 통해 사용하는 방식이다.

또 다른 예시를 살펴보자.
아래 코드는 Test라는 클래스에서 Apple 객체를 불러오는 예제이다. 의존 관계는 간단히 말해 new 라는 키워드를 통해 생성된다.

class Test{
	private Apple apple = new Apple();
}

아래는 TestSample 이라는 클래스에서 Apple 객체를 생성한 뒤 Test 클래스의 생성자로 주입시켜 준다. 여기서는 Test가 직접 Apple을 생성하는 것이 아니라 생성자로 주입 받는다.

class Test{
	private Apple apple;
    
    Public Test(Apple apple) {
    	this.apple = apple;
    }
}

class TestSample {
	Apple apple = new Apple();
    Test test = new Test(apple);
}

이처럼 첫번째 예시에서는 Apple 객체의 제어권이 Test에게 있었다면, 두번째 예시에서는 Test에게 있는 것이 아니라 TestSample에게 있다. 이처럼 의존성을 역전시켜 제어권을 직접 갖지 않는 것을 IoC라고 하며, 의존성을 외부에서 주입시켜 주는 것을 DI라고 한다.

IoC(Inversion Of Control)란?

제어의 역전 이라는 의미로, 말 그대로 메소드나 객체의 호출 작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.
기존에는 다음과 같은 순서로 객체가 만들어지고 실행되었다.

  1. 객체 생성
  2. 의존성 객체 생성 (클래스 내부에서 생성)
  3. 의존성 객체 메소드 호출

하지만, 스프링에서는 다음과 같은 순서로 객체가 만들어지고 실행된다.

  1. 객체 생성
  2. 의존성 객체 주입 (스스로가 만드는 것이 아니라 제어권을 스프링에게 위임하여 스프링이 만들어 놓은 객체를 주입한다.)
  3. 의존성 객체 메소드 호출

스프링이 모든 의존성 객체를 스프링이 실행될 때 다 만들어 주고 필요한 곳에 주입시켜준다. 해당 Bean들은 싱글턴 패턴의 특징을 가지며 위에서 언급한 바와 같이 제어의 흐름을 사용자가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리하는 제어의 역전이 된다.

무슨 의미가 있는 것일까?

스프링에서는 Spring 컨테이너, IoC 컨테이너라는 개념을 사용한다. 컨테이너는 보통 인스턴스의 생명 주기를 관리하며, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것이라 할 수 있다.
다시 말해, 컨테이너란 당신이 작성한 코드의 처리 과정을 위임받은 독립적인 존재라고 생각하면 된다. 컨테이너는 적절한 설정만 되어 있다면 누구의 도움 없이도 프로그래머가 작성한 코드를 스스로 참조한 뒤 알아서 객체의 생성과 소멸을 컨트롤 해준다. 스프링 컨테이너는 스프링 프레임워크의 핵심부에 위치하며, 종속 객체 주입을 이용하여 애플리케이션을 구성하는 컴포넌트들을 관리한다. 이때 스프링 컨테이너에서 생성되는 객체를 Bean이라고 한다.

Bean

Bean은 Spring Bean Container에 존재하는 객체를 말한다. Bean Container는 의존성 주입을 통해 Bean 객체를 사용할 수 있도록 해준다. Bean은 보통 싱글턴 패턴으로 존재한다. Beans는 우리가 컨테이너에 공급하는 설정 메타 데이터(XML 파일)에 의해 생성된다.

Container에서 Bean이 탄생하는 과정

  1. Component scan

스프링부트로 애플리케이션을 만들게 되면 가장 상위 클래스에 @SpringBootApplication 이 있는데 intelliJ 기준으로 command 키를 누른 상태로 해당 어노테이션을 클릭하게 되면 @SpringBootApplication을 구현한 코드를 확인할 수 있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

@ComponentScan은 @Component 어노테이션이나 stereotype(@Service, @Controller, @Repository 등) 어노테이션이 부여된 class를 찾아 자동으로 Bean으로 등록해주는 역할을 한다.
여기서 @ComponentScan을 해주는 범위가 해당 어노테이션이 붙은 위치보다 하위의 class들만 스캔해주기 때문에 @SpringBootApplication의 위치가 가장 상위 위치에 있어야 한다.
@Service, @Controller, @Repository 어노테이션도 command 키를 누른 후 클릭해보면 @component가 있는 것을 확인할 수 있다. 아래는 @Controller 이다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

추가적으로 Bean으로 등록하고 싶은 class가 있다면 @Component 어노테이션을 붙여줘도 상관 없다.

  1. @Configuration 에서 @Bean으로 등록

@Configuration 어노테이션을 사용해서 직접 @Bean을 등록해주는 방법이다. @Bean 어노테이션을 사용하면 자동으로 Bean으로 등록된다.

@Configuration
public class HttpConfig {
	
    @Bean
    public RestTemplate createRestTemplate() {
    	return new RestTemplate();
    }
}

끝으로

의존성 주입이란 개념은 아직도 익숙치 않다.
기존 자바 코드 작성 시에 인스턴스를 생성한다면 항상 new를 붙이고 새로 생성해줬었다면 스프링에서는 스프링 컨테이너가 싱글턴 패턴을 사용하여 Bean으로 객체를 관리하고 Service 나 Controller 같은 한번만 호출해도 되는 객체들을 Bean으로 등록하여 관리하면 결합도가 낮아져 유지 보수에 용이하다고 하니 뭔가 자주 호출되는 객체는 Bean으로 일단 등록하는걸로 알고 가보자.
항해 강의를 통해 Bean 등록을 안하면 코드 자체가 확실히 더럽다는 것은 봤다.

좋은 웹페이지 즐겨찾기