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의 기능을 상속받는데 메시지 다국화, 정보 관리 등 부가적인 기능들이 더 추가되어있다.
Spring은 설정 메타정로를 BeanDefinition 인터페이스를 통해 관리한다. 이때문에 컨테이너 설정을 XML, Java로 할 수 있는 것이다.
클래스의 인스턴스가 딱 한개만 생성되는 것을 보장하는 디자인 패턴
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()를 통해 값을 조회하면 모든 출력값이 같은 주소를 가리킨다.
문제점
해결
@Configuration
public class DependencyConfig {
@Bean
public ~~ {
return ~~
}
}
@Configuration 클래스가 입력으로 제공되면 그 클래스가 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가 붙은 모든 클래스를 스프링 빈으로 등록해주는 편리한 기능이다. 이것이 @Autowired의 기능 또한 제공한다.
위의 것들은 모두 소스 코드에서 @Component를 포함하고 있다.
앞으로 우리는 거의 생성자 주입만을 사용하게 될 것이다. 이에 대한 이유를 알아보자.
불변
-> 의존 관계 주입은 변경되면 안된다. 수정자 주입은 애초에 public으로 열어두어 변경이 가능하도록 만드는 것이기 때문에 적합하지 않은 반면에 생성자 주입은 1번만 호출되므로 불변하게 설계할 수 있다.
누락
-> 생성자 주입은 데이터 누락 시 컴파일 오류가 뜬다.
final 키워드 사용가능
-> 생성자를 통해 값을 주입하면서 생성하는 것이기 때문에 final 키워드를 통해 불변하게 만들 수 있다.
순환 참조 방지