danc
danc*dev
danc
  • 분류 전체보기
    • codestates_BE_bootcamp39
      • 주단위 일기
      • 회고
    • programming
      • JAVA
      • SPRING
      • GENERAL
      • LINUX
      • ALGORITHM
      • ERROR_HANDLING
    • web
      • NETWORK
      • DB
      • HTML
      • CSS
    • kr
    • nz

최근 글

인기 글

태그

  • HTTP
  • css
  • 회고
  • React에서 Authorization헤더
  • 윈도우 11 우분투
  • TIL
  • 코드스테이츠 백엔드
  • TIL일기
  • TIL 일기
  • AOP
  • 일기
  • 코드스테이츠

최근 댓글

티스토리

hELLO · Designed By 정상우.
danc

danc*dev

[TBC]IoC 와 DI
programming/SPRING

[TBC]IoC 와 DI

2022. 6. 18. 22:36

IoC(Inversion of Control) 

Inversion of Control(IoC)은 애플리케이션 흐름의 주도권이 뒤바뀐 것을 말한다. 

일반적인 자바 자체의 코드만으로 실행을 하면 main() 메서드를 먼저 호출한 뒤에
프로세스가 순차적으로 진행되는데 이것은 개발자가 작성한 코드를
순차적으로 실행하는 application의 일반적인 제어 흐름이다. 

서블릿 기반의 application을 예로 들어본다면 서블릿 기반 프로그램은
클라이언트에서 요청이 들어올 때마다 서블릿 컨테이너의 로직(service() 메서드)이
직접 서블릿을 실행시켜 주기 때문에 main() 메서드가 없다. 

이런 경우, 서블릿 컨테이너가 서블릿을 제어하고 있는 상황이기 때문에 application의 주도권은
서블릿 컨테이너가 쥐고 있다.

이때 서블릿과 웹 application 간에 IoC 개념이 적용되어 있는 것이다. 


DI(Dependency Injection)

위의 IoC가 서버 컨테이너 기술, 디자인 패턴, OOP적 설계 등에 적용되는 범용적인 개념이라면,
DI (의존성 주입)은 위의 IoC개념을 한층 더 구체화시킨 것으로, 객체 간의 관계를 느슨하게 해 준다. 

클래스의 의존 관계부터 정리해보면,

  1. A 클래스가 B 클래스의 기능을 사용하고 싶다. 
  2. A 클래스에서 B클래스의 객체를 생성한 뒤에 B 클래스의 해당 메서드를 사용한다 (의존관계 성립)
  3. 이때, A클래스가 B클래스에 의존한다. 

라고 표현할 수 있다. A클래스에서 B클래스의 기능 사용 시, A클래스는 B클래스에 의존한다. 


의존성 주입은 아래처럼 요약이 가능할 것이다.

 

  1. 의존성 주입은 생성자를 통해서 어떤 클래스의 객체를 전달받는 것을 말한다. 


  2. 생성자의 파라미터로 객체를 전달한다 == 외부에서 객체를 주입한다. 

 

의존성 주입에 있어, application 코드 내부에서 직접적으로 new 키워드를 사용하면 OOP적 관점에서
아래와 같은 중요한 문제가 발생할 수 있다.

new를 사용해서 객체를 생성하게 되면 참조할 클래스가 바뀌는 경우,
이 바뀐 클래스를 참조하는 모든 클래스를 전부 수정해야 한다.

new를 사용해서 의존 객체를 생성한다면 이 클래스들은 강하게 결합 (Tight Coupling)되어있다고 한다.


DI - 느슨한 결합

DI를 할 수 있더라도 실직적으로 DI를 제대로 이용하기 위해서는
느슨한 결합(Loose Coupling)을 사용해야 한다
.

객체들 이 느슨한 결합으로 되어있다면 여러 변경사항에 대해 유연한 대처가 가능하다.

느슨한 의존성 주입 (Loose Coupling)은 인터페이스를 사용하는 것이 대표적인 방법이다.
어떤 클래스가 인터페이스 같이 일반화된 구성 요소에 의존한다면 이 관계를 느슨한 결합이라 한다. 

Spring이 new로 객체를 생성하던 작업을 대신해줌으로써 결과적으로
내가 볼 때는 new를 사용하지 않은 것처럼 보인다.
해당 작업은 Spring Framework의 Config클래스 부분에서 이루어진다.


즉, 애플리케이션 코드에서 이루어지는 의존성 주입을 Spring에서 대신해준다.

 

항상 코드 작성 시 나중에 수정이 필요한 부분이 뭐가 있을까 하고 고민하는 습관을 갖는 것이 좋을 것 같다. 


Spring Container

스프링 컨테이너는 Spring Framework의 핵심 컴포넌트이다. 
핵심 역할로는 Bean의 생명주기 (Bean 생성, 관리, 제거 등)를 관리한다. 

 

ApplicationContext를 스프링 컨테이너라고 하며, 인터페이스로 구현되어 있다. (다형성 적용) 

스프링 컨테이너는 XML 그리고 Annotation 기반의 자바 설정 클래스로 만들 수 있다. 
예전에는 개발자가 직접 XML을 통해 모든 설정을 하였지만, 이제는 Springboot를 사용한다. 

Bean(인스턴스화 된 객체)의 생명주기를 관리하기 때문에, 개발자가 정의한 Bean을
객체로 만들어서 보관하고, 개발자가 필요로 할 때 제공한다. 

스프링 컨테이너는 의존성 주입(DI)을 통해 애플리케이션의 컴포넌트를 관리한다.
따라서 아래의 특징을 갖는다. 

  • 서로 다른 Bean을 연결해 애플리케이션의 Bean을 연결하는 역할을 함
  • 개발자는 모듈 간의 의존 / 결합으로 인한 문제로부터 멀어질 수 있다.
  • 매서드 호출을 위한 매개변수를 준비해서 전달하지 않는다. 

사용 이유

객체를 생성하려면 new를 통해서 새로 인스턴스를 만들어야 한다. 
일일이 객체를 생성해 사용한다면 필연적으로 객체 간 참조가 일어날 수밖에 없고 참조가 
많다면, 구현 객체에 대한 의존도가 높아진다. 이는 낮은 결합도 + 높은 캡슐화라는 
OOP의 핵심에 반하는 코드가 되어버린다. 

이때 스프링 컨테이너를 사용하면 구현 클래스에 있는 높은 의존성을 제거하고,
인터페이스에만 의존하도록 설계할 수 있다. 

생성 과정

스프링 컨테이너는 Configuration Metadata를 사용하며 파라미터로 들어온 설정 클래스
정보를 사용해서 스프링 빈을 등록한다. 

구성정보를 담당하는 것을 설정할 때 @Configuration을 붙인다. 

 

new AnnotationConfigApplicationContext(구성정보.class) 로
스프링에 있는 @Bean의 메서드를 등록한다.  


//생성 예시
// Annotation
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

// XML
ApplicationContext applicationContext = 
	new AnnotationConfigApplicationContext("services.xml", "daos.xml");


여기서 Appconfig.class 에 있는 구성 정보를 통해 스프링 컨테이너는 필요한 객체들을 생성한다.

최종적으로, 애플리케이션 클래스는 구성 메타데이터와 결합되어 ApplicationContext를 
생성 & 초기화 한 뒤 완전히 구성되고 실행 가능한 상태가 된다. 


요약하자면,

  • 객체 간의 의존성을 낮추기 위해 스프링 컨테이너를 사용

  • Bean들의 생명주기(Life Cycle)  관리

  • 관리를 위해 IoC - DI 사용
     
  •  BeanFactory, ApplicationContext 두 개의 컨테이너로
     DI가 이루어진 Bean들을 제어 및 관리 

Spring Container의 종류


BeanFactory (콩 공장)

Spring Container의 최상위 인터페이스

 

  • Bean의 등록 / 생성 / 조회 / 반환 등 전반적으로 Bean을 관리함

  • getBean() 메서드를 통해 Bean을 인스턴스화 할 수 있다.

  • @Bean이 뭍은 메서드의 이름을 Spring Bean의 이름으로 사용 - Bean 등록

ApplicationContext

  • BeanFactory의 기능을 상속받아 제공

  • BeanFactory의 Bean관리 기능 + 추가적인 부가기능 제공

    • MessageSource
      메시지 다국화를 위한 인터페이스

    • EnvironmentCapable
      개발, 운영, 환경변수 등으로 나눠 처리 + 애플리케이션 구동 시 
      필요한 정보를 관리하기 위한 인터페이스

    • ApplicationEventPublisher
      이벤트 관련 기능을 제공하는 인터페이스

    • ResourceLoader
      파일, 클래스 path 등 외부 리소스를 편리하게 조회

new와 생성자 주입 차이

 

new를 통해 객체를 직접 생성하는 방식

public class OrderServiceImpl implements OrderService{
    private final UserRepository userRepository = new UserRepositoryImpl();
    
    private final DiscountInfo discountInfo = new CurrentDiscountInfo();
    
    @Override
    public Order createOrder( Long userId, String itemName, int itemPrice)
    {
        User user = userRepository.FindByUserId(userId);
        int discountPrice = discountInfo.discount(user, itemPrice);

        return new Order(userId, itemName, itemPrice, discountPrice);
    }
}

 

생성자 주입을 통해 IoC & DI 방식

public class OrderServiceImpl implements OrderService{

    private final UserRepository userRepository;
  
    private final DiscountInfo discountInfo;
    
    //OrderServiceImpl클래스 생성자 주입 - IoC / DI 
    public OrderServiceImpl(UserRepository userRepository, DiscountInfo discountInfo)
    {
        this.userRepository = userRepository;
        this.discountInfo = discountInfo;
    }
    
    @Override
    public Order createOrder( Long userId, String itemName, int itemPrice)
    {
        User user = userRepository.FindByUserId(userId);
        int discountPrice = discountInfo.discount(user, itemPrice);

        return new Order(userId, itemName, itemPrice, discountPrice);
    }
}

 

정리: 

  • IoC & DI 방식에서는 new를 쓰는 대신, 생성자를 통해 의존 객체의 주입이 된다.
    즉 느슨한 의존 관계가 이루어진다. 

  • userRepository와 discountInfo의 구현 클래스는 Bean 설정에 따라 유연하게 변경됨

  • OrderServiceImpl 관점에서는 생성자를 통해 어떤 구현 객체가 
    들어올지 알 수 없고, 알 필요도 없다. 즉 실행에만 집중하게 된다.

  • 어떤 객체가 들어오는지는 외부(AppConfig)에서 결정할 수 있다.

 

싱글톤 - static과 비슷 *


Bean 

Spring Container에 의해 관리되는 재사용 소프트웨어 컴포넌트

 

빈 (Bean)은 인스턴스화 된 객체를 의미한다

즉 스프링 컨테이너가 관리하는 자바 객체이다. 

Bean의 특징

  • @Bean이 적힌 메서드를 전부 호출해서 반환된 '객체'를 스프링 컨테이너에 등록
  • 빈은 클래스의 등록 정보, Getter / Setter 메서드를 포함한다.

  • 빈은 컨테이너에 사용되는 설정 메타데이터로 생성된다. 
    • 설정 메타데이터
      • XML or 자바 애너테이션, 자바 코드로 표현
      • 컨테이너의 명령과 인스턴스화, 설정, 조립할 객체를 정의한다.

참고

Java Bean vs Spring Bean

두 가지는 전혀 다르다.

자바의 Bean은 단순하게 클래스에서 Getter/Setter만 가지고 있는 클래스를 의미
또한 jsp에서 데이터를 자바 빈이라는 클래스를 통해 관리  


Bean Scope 

BeanDefinition을 토대로 생성된 개체에서, Bean이 존재할 수 있는 범위를 의미

  • 스프링에서는 6개의 범위 지원 (4개는 ApplicationContext 사용 시만 적용 가능)
  • 6개의 범위 중에서 하나만 설정
Scope Description
singleton [default값] [가장 넓은 범위]

스프링 컨테이너의 시작과 종료까지 유지
prototype [매우짧은범위] 

Spring container가 prototype 빈의 생성과 DI까지만 관여함
request [웹] [ApplicationContext must be used]

웹 요청이 들어오고 나갈때 까지 유지
session [웹] [ApplicationContext must be used]

웹 세션이 생성되고 종료될 때 까지 유지
application [웹] [ApplicationContext must be used]

웹의 서블릿 컨텍스와 같은 범위로 유지
websocket [웹] [ApplicationContext must be used]

WebSocket의 라이프 사이클까지 유지 

 


singleton scope

클래스의 인스턴스가 딱 1개만 생성하는 것을 보장하는 디자인 패턴

해당 빈의 인스턴스를 오직 하나만 생성해서 사용한다


singleton 빈의 하나의 공유 인스턴스만 관리하게 된다. 
따라서 private 생성자를 사용해 외부에서 임의로 new를 사용하지 못하게 해야 함.

 


Bean Definition

Bean 설정 메타 정보

Spring Container는 BeanDefinition이라는 추상화를 통해 Spring Bean을 생성한다. 

자바에서는 @Bean으로, XML에서는 <bean>으로 설정하며, 각 1개씩 메타 정보가 생성된다.

AnnotationConfigApplicationContext ac = 
               new AnnotationConfigApplicationContext(AppConfig.class);

AppConfig.class를 파라미터로 ac(ApplicationContext)를 생성하게 되면
내부적으로는 AppConfig.class 정보를 바탕으로 BeanDefinition
인터페이스의 구현체 중 하나인 객체를 만듦.

AppConfig.class를 파라미터로 넘겼을 때 BeanDefinition의 구현체인
AnnotatedGenericBeanDefinition을 만든다.
해당 객체에서 Bean메타 정보를 갖고 Spring Container에서 Bean을 생성한다. 

여기서 스프링 컨테이너는 빈의 설정 형식이 XML이든 Java코드이던 알 필요 없다.

즉, 스프링 컨테이너가 빈의 definition을 보고 bean 생성하는 것을 기억하자.


추가적으로, BeanDefinition이 포함하고 있는 메타 데이터는 아래와 같다.

스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화하여 사용하는 것과
필요한 경우 아래와 같은 메커니즘이라는 것만 간단히 이해하고 가자 

 

  • 패키지 수식 클래스 이름
    - 일반적으로 정의되는 Bean의 실제 구현 클래스

  • Bean 동작 구성 요소
    - Container에서 Bean이 어떻게 동작해야 하는지 설명 (빈스코프, 빈의 수명주기 콜백 등)
  • Bean이 작업을 수행하는데 필요한 다른 Bean에 대한 참조

  • 새로 만든 개체에 설정할 기타 구성 설정

 

Class
Name
Scope
Constructor arguments
Properties
Autowiring mode
Lazy initialization mode  
Initialization method
Destruction method 

 


Bean 접근 방법

빈은 ApplicationContext를 사용해 접근할 수 있고 
getBean() 메서드를 사용해 빈의 인스턴스를 가져온다. 

하지만 응용 프로그램 코드에서는 getBean메서드를 사용해 호출해서 사용하면 안 되는데,
결론적으로는 의존성 때문에 그렇다.

스프링 컨테이너는 beandefinition만 바라보고 있고
추상화된 definition으로 객체가 만들어지면 컨테이너가 우리가 관리하는 빈을 만든다고 했다. 

하지만 getBean() 메서드를 사용하려면 Bean의 이름을 알아야 하는데
IoC에서는 객체의 이름을 알 필요가 없어야 한다. 
 
즉 IoC위반 케이스가 된다 


Annotation 짤막 정리

 

@Configuration

'Bean 메타정보를 가지고 있는 클래스'다 라는걸 알려줌

 

@Bean

**메서드 단위에서 사용
메서드가 생성자로 어떤 객체를 반환할 때 그 객체를 Bean으로  등록

등록된 빈은 '메서드 이름'으로 등록된다.

@Bean
public UserService userService() 
{
  //UserService 타입의 객체(UserServiceImpl)로 반환 
  return new UserServiceImpl(userRepository()); 
}
UserServiceImpl 객체를 반환하지만 Bean의 이름은 userService

 

@Component

**클래스 단위에서 사용 
해당 클래스를 통해 만들어진 객체를 Bean으로 사용하겠다고 정의

실제로는 @Service / @Repository / @Controller 이 3개를 주로 많이 사용한다. 
(내부에 Component를 가지고 있어서 가능) 

 

@ComponentScan

컴포넌트가 붙은 클래스들을 모두 찾아서 스프링 컨테이너에서 Bean으로 만들어줌

 

 

 

저작자표시 (새창열림)

'programming > SPRING' 카테고리의 다른 글

AOP - Pointcut 표현식 / 지시자  (0) 2022.06.20
AOP - Advice  (0) 2022.06.20
PSA  (0) 2022.06.15
Spring Framework  (0) 2022.06.14
POJO(Plain Old Java Object)  (0) 2022.06.14
    'programming/SPRING' 카테고리의 다른 글
    • AOP - Pointcut 표현식 / 지시자
    • AOP - Advice
    • PSA
    • Spring Framework
    danc
    danc
    Backend 개발자를 목표로 공부 중 입니다.

    티스토리툴바