티스토리 뷰

Spring

[Spring] IoC(Inversion of Control) 정리

hi_hannah 2020. 10. 3. 16:25

IoC

제어의 역전(Inversion of Control)?

  • 나 자신이 제어권을 가지고 있지 x, 나 이외의 누군가가 의존성에 대한 제어권을 가지고 있다.
    • 어떤 객체가 사용하는 의존 객체를 직접 만들어서 사용하는게 아니라 주입 받아서 사용하는 것을 말한다.

1. 의존성에 대한 제어권

일반적인 경우

"내가 쓸 놈은 내가 만들어 쓸게"

class OwnerController {
    private OwnerRepository repository = new OwnerRepository();
}
  • OwnerController가 OwnerRepository를 생성한다.
    • 의존성에 대한 제어권 => 본인(OwnerController)

IoC : 역전된 경우

"내가 쓸 놈은 이 놈인데... 누군가 알아서 주겠지"

class OwnerController {
    private OwnerRepository repo;

    public OwnerController (OwnerRepository repo) {
        this.repo = repo;
    }
}
  • OwnerController가 생성될 때 누군가가 OwnerRepository 객체를 준다.

    • 의존성에 대한 제어권 => 누군가(IoC Container)
  • class OwnerControllerTest {
      public void create() {
          OwnerRepository repo = new OwnerRepository ();
          OwnerController controller = new OwnerController(repo);
      }
    }
  • OwnerControllerTest가 OwnerRepository를 생성해서 이를 OwnerController에 주입해준다.

    • 의존성에 대한 제어권 => OwnerControllerTest

IoC의 장점

  • 제어권을 자신이 아닌 별도의 IoC 컨테이너에게 넘김으로서 객체가 자신이 사용할 대상의 생성이나 선택에 관한 책임으로부터 자유로워진다.
  • 인터페이스 기반으로 깔끔한 설계가 가능하고 유연성이 증가하며, 확장성이 좋아진다.

2. 애플리케이션 컨텍스트

ApplicatonContext = IoC 컨테이너 = Spring 컨테이너

스프링에서는 ApplicationContext를 IoC 컨테이너라고 한다. 간단히 Spring 컨테이너라고 한다.

public interface ApplicationContext
extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
  //...
}

역할

  • BeanFactory : 빈 생성 및 의존성 관리(ListableBeanFactory, HierarchicalBeanFactory)
  • MessageSource : 메시지 소스 처리 기능(i18n)
  • ApplicationEventPublisher : 이벤트 발행 기능
  • ResourceLoader : 리소스 로딩 기능 (ResourcePatternResolver)

3. 스프링 빈

스프링 IoC 컨테이너가 관리하는 객체를 빈(bean)이라고 한다. 따라서 IoC 컨테이너에 등록하지 않은 객체는 스프링 빈이라고 할 수 없다.

  • 예시
    • OwnerController(@Controller), OwnerService(@Service), OwnerRepository(@Repository)는 IoC 컨테이너에서 관리하는 빈(bean)이다.
    • Owner(Entity로서의 Owner)는 빈(bean)이 아니다.

빈 설정

빈에 대한 정의를 담고 있다.

  • 이름 또는 id
  • 클래스
  • 스코프
  • 생성자 아규먼트(construct)
  • 프로퍼티(setter)
  • 등등

스프링 빈의 장점

  • 의존성 관리
  • 스코프
    • 싱글톤 : 하나
    • 프로토타입 : 매번 다른 객체
  • 라이프사이클 인터페이스 : 빈이 만들어졌을 때 부가작업을 하고 싶을 경우 사용한다.
/* 초기화 메서드. 빈이 생성되고 DI 작업을 마친다음 실행된다. */
  @PostConstruct
  public void postConstruct() {
    System.out.println("=================");
    System.out.println("Hello");
  }

스프링은 스프링 IoC 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다.(유일하게 하나만 등록해서 공유) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

빈을 등록하는 방법

  1. Component Scanning
@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 {
  // ...
}

스프링 부트에서는 @SpringBootApplication라는 애노테이션을 사용하는데 이 애노테이션 안에 컴포넌트 스캔을 할 수 있는 @ComponentScan이 있다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
  // ...
}

@ComponentScan은 스캔할 패키지와 애노테이션에 대한 정보를 담고 있다. 실제 스캐닝은 ConfigurationClassPostProcessor라는 BeanFactoryPostProcesser에 의해 처리된다.

  • @Component : 이 애노테이션이 있으면 컴포넌트 스캔을 통해 스프링 빈으로 자동 등록된다. 아래 애노테이션들은 @Component를 확장했기 때문에 컴포넌트 스캔 대상이 된다.

    • @Repository
    • @Service
    • @Controller
    /* Component Scan 방식으로 스프링 빈(MemberService) 등록 */
    @Service 
    public class MemberService {
      private final MemberRepository memberRepository;
    
      public MemberService(MemberRepository memberRepository) {
          this.memberRepository = memberRepository;
      }
    }
  1. 직접 xml이나 자바 설정 파일에 등록

    • xml 설정 방식은 최근에는 잘 사용하지 않는다.
    • 자바 설정파일에서 빈 등록시 @Bean 애노테이션을 붙여야한다.
      • @Bean@Configuration 안에 정의해야 한다.
    /* 자바 설정파일로 스프링 빈(MemberService) 등록 */
    @Configuration
    public class SpringConfig {
    
       @Bean
       public MemberService memberService() {
           return new MemberService(memberRepository());
       }
    
       @Bean
       public MemberRepository memberRepository() {
           return new MemoryMemberRepository();
       }
    }

실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리** 같은 코드는 컴포넌트 스캔을 사용한다.
정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.

빈을 가져오는 방법

  1. @Autowired
    IoC 컨테이너가 연관된 객체를 찾아서 메소드나 생성자를 통해 주입해준다. (수동적)
    • 객체 의존관계를 외부에서 넣어주는 것 => DI(Dependency Injection), 의존관계 주입
@Service 
public class MemberService {
  private final MemberRepository memberRepository;

  @Autowired
  public MemberService(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }
}
  1. ApplicationContextgetBean()
    IoC 컨테이너에게 연관된 객체를 요청해서 받는다. (능동적)
    • 객체 의존관계를 스스로 검색해서 넣어주는 것 => DL(Dependency Lookup),의존관계 검색
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    MemberService memberService = ctx.getBean(MemberService.class);
}

+ @Autowired를 생략해도 되는 경우

어떤 빈에

    1. 생성자가 오직 하나만 있고
    1. 파라미터로 받는 타입(매개변수 타입)의 빈이 존재한다면
      @Autowired 애노테이션을 생략해도 의존성을 주입해준다. (스프링 4.3)
public class MemberService {
  private final MemberRepository memberRepository;

  //@Autowired
  public MemberService(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }
}

@Autowired를 통한 DI는 스프링이 관리하는 객체(빈)에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.

@Autowired / @inject를 어디에 붙일까?

  • 생성자
    • 이 클래스에 반드시 필요한 객체다 => 생성자
    • 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.
  • 필드
    • Setter가 없다 => 필드
  • Setter
    • Setter가 있다 => Setter

참고자료

  • 스프링 프레임워크 입문
  • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

'Spring' 카테고리의 다른 글

JDK 다이나믹 프록시와 CGLIB 프록시  (0) 2022.05.19
[Spring] AOP(Aspect Oriented Programming) 정리  (0) 2020.10.03
댓글