10/14 Dependency Injection

강호수·2022년 10월 15일
0

DI

Dependency Injection은 의존성 주입이라 표현할 수 있다.

public class MemberService {
	private final MemberRepository memberRepository = new MemberRepository();
    ~~
}

위의 코드는 MemberService라는 객체에서 MemberRepository라는 객체에 의존성을 가지고 있다.
이 때 만약 MemeberRepository라는 것을 다른 것으로 바꾸고 싶다면 저렇게 선언되어있는 모든 코드를 바꿔야 할 것이다. 이는 매우 비효율적이라 할 수 있다.
따라서 이 때 필요한 것이 의존성 주입이다.

public class MemberService {
	private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;
    }
    ~~
}

일반적으로 위와 같이 생성자를 통한 의존관계 주입을 사용한다. 자세한 것은 조금 뒤에 알아보자.

스프링 컨테이너

객체를 생성하기 위해서는 new 생성자를 써야한다. 애플리케이션에서 이러한 객체가 무수히 존재하고 서로 참조하게 되는데, 이럴 수록 서로 의존성이 높아짐으로서 객체지향의 원칙을 온전히 이루지 못한다.
이 때 필요한 것이 Spring 컨테이너이다.

스프링 컨테이너에는 최상위 인터페이스로 BeanFactory가 존재한다. 이것은 빈을 등록, 생성 그리고 돌려주는 등 빈을 관리하는 역할을 한다.
그리고 ApplicationContext는 BeanFactory의 기능을 상속받는데 메시지 다국화, 정보 관리 등 부가적인 기능들이 더 추가되어있다.

빈 (Bean)

  • bean -> 인스턴스화된 객체
  • 스프링 컨테이너에 등록된 객체를 스프링 이라고 한다
  • @Bean이 붙어있으면 모두 호출해 반환한 객체를 스프링 컨테이너에 등록한다

Spring은 설정 메타정로를 BeanDefinition 인터페이스를 통해 관리한다. 이때문에 컨테이너 설정을 XML, Java로 할 수 있는 것이다.

싱글톤

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

  • 싱글톤 빈이 하나의 공유 인스턴스만 관리한다 -> private 생성자를 통해 임의로 생성할 수 없게 해야한다.
  • 단일 인스턴스는 싱글톤 빈의 캐시에 저장된다.
  • 싱글톤 스코프의 스프링 빈은 여러번 호출해도 같은 인스턴스 참조 주소값을 가진다.
  • 스프링 컨테이너 종료시 소멸 메서드도 자동으로 실행된다.

public class SingletonTest {
	static DependencyConfig dependencyConfig = new DependencyConfig();
    
    static MemberService memberService1 = dependencyConfig.memberService();
    static MemberService memberService2 = dependencyConfig.memberService();
    
    ~~

위의 코드에서 memberService1과 memberService2을 출력해 보았을 때 다른 주소값을 가르킨다. 즉 싱글톤 패턴이라고 할 수 없는 것이다.

public class DependencyConfig {
	public MemberService memberService() {
    	return new MemberService(memberRepository());
  	}

  	public MemberRepository memberRepository() {
    	return new MemberRepository();
  	}
  	~~
}

DependencyConfig class를 보면 알 수 있는데, 이는 memberService를 호출할 때마다 new MemberRepository를 선언하며 객체를 계속 생성하는 것을 볼 수 있다. 이것을 싱글톤 패턴으로 바꿔보자.


public class SingletonService {
	private static final SingletonService instance = new SingletonService();
    
    public static SingletonService getInstance() {
    	return instance;
    }
    
    private SingletonService(){}
}

미리 static 영역에 객체 인스턴스를 1개만 생성해둔다. 그리고 이것을 final로 선언함으로서 바꿀 수도 없게 만든다. 그리고 이것을 조회하고자 할 때에는 getInstance()로 조회할 수 있게 한다. 또한 private 생성자를 만듦으로써 외부에서 생성자를 new로 생성할 수 없게 한다.

그 후에 SingletonService.getInsance()를 통해 값을 조회하면 모든 출력값이 같은 주소를 가리킨다.


싱글톤 패턴의 문제점과 해결방법

문제점

  • 구현하는 코드 자체가 많다
  • 클라이언트가 구체 클래스에 의존한다
  • 지정해서 가져오기 때문에 테스트하기 어렵다
  • private를 사용하여 자식 클래스를 만들기 어려워서 유연성이 떨어짐

해결

  • 객체 인스턴스를 싱글톤으로 관리한다 -> 스프링 컨테이너가 싱글톤 컨테이너 역할
  • @Configuration, @Bean 등을 통해 관리한다.

Java 기반 컨테이너 설정

@Configuration
public class DependencyConfig {
	
    @Bean
    public ~~ {
    	return ~~
	}
}

@Configuration (@Component 포함)

@Configuration 클래스가 입력으로 제공되면 그 클래스가 Bean 정의로 등록되고, 클래스 내에서 선언된 모든 @Bean 메서드도 Bean 정의로 등록된다.

@Bean

아래의 코드를 보자.

@Configuration
public class DependencyConfig {
	
    @Bean
    public ClientService clientService1() {
    	ClientServiceImpl clientService = new ClientServiceImpl();
        ~~
	}
    
    @Bean
    public ClientService clientService2() {
    	ClientServiceImpl clientService = new ClientServiceImpl();
        ~~
	}
    ~~
}

clientService라는 객체는 두번 만들어졌다. 원래였다면 두 객체는 서로 다른 주소값을 가지고 있었을 것이다. 하지만 스프링 컨테이너에서의 특징을 생각해보면 이는 무조건 싱글톤 패턴을 가지게 된다. 따라서 두 객체는 같은 값을 가지게 된다.

스프링 빈으로 등록하게 될 때 이미 선언되어 있는 메서드가 있다면 스프링 컨테이너에서 기존에 만들어졌던 것을 반환해 준다.
즉 새로운 것들만 Bean을 생성하고 스프링 컨테이너에 등록한다.

Component Scan

이는 설정 정보에 붙여주면 되는데, @Component가 붙은 모든 클래스를 스프링 빈으로 등록해주는 편리한 기능이다. 이것이 @Autowired의 기능 또한 제공한다.

Component Scan 대상

  • @Component
  • @Controller & @RestController -> 컨트롤러에 사용
  • @Service -> 비즈니스 로직에서 사용
  • @Repository -> 데이터 접근 계층에서 사용
  • @Configuration -> 설정 정보에서 사용

위의 것들은 모두 소스 코드에서 @Component를 포함하고 있다.

의존관계 주입 방법

  • 생성자 주입
    • 생성자가 1개일 때는 @Autowired가 없어도 작동하는데, 이는 생성자를 부르는 것이 강제되기 때문이다.
  • 수정자 주입 (setter)
    • 선택과 변경 가능성이 있는 의존 관계에 사용
    • @Autowired 필수 입력
  • 필드 주입
    • 외부에서 변경 불가능해 테스트하기 힘들다
    • 정상적으로 작동하려면 결국 setter가 필요해 수정자 주입을 하는게 더 좋다
  • 일반 메서드 주입

생성자 주입 사용 이유

앞으로 우리는 거의 생성자 주입만을 사용하게 될 것이다. 이에 대한 이유를 알아보자.

  • 불변
    -> 의존 관계 주입은 변경되면 안된다. 수정자 주입은 애초에 public으로 열어두어 변경이 가능하도록 만드는 것이기 때문에 적합하지 않은 반면에 생성자 주입은 1번만 호출되므로 불변하게 설계할 수 있다.

  • 누락
    -> 생성자 주입은 데이터 누락 시 컴파일 오류가 뜬다.

  • final 키워드 사용가능
    -> 생성자를 통해 값을 주입하면서 생성하는 것이기 때문에 final 키워드를 통해 불변하게 만들 수 있다.

  • 순환 참조 방지

0개의 댓글