내용은 주로 전문가를 위한 스프링5에 기반한다.
IOC vs DI
- 책에서는 di를 ioc의 한 형태라고 정의한다. 또한 ioc와 di를 혼용해서 사용하더라도 큰 의미차이는 없다고 한다. 해당 의미에 혼동이 와서 찾아본 결과 좀 다르다고 한다.
- ioc는 제어의 역전 즉 코드의 흐름의 주체가 바뀌는것. 오히려 DIP가 이런 개념이 적용된 한형태라고 볼수 있다고한다.
- DIP란 추상화에 의존하라는 것이다. 추상화의 의존 하기위한 기법중 하나로 di가 존재한다.
- ioc container란 dip가 적용된 모듈의 조립을 도와주는 도구 이다. 이게 스프링 컨테이너라고 볼수 있을거 같다. 하지만 di는 container 없이도 구현이 가능함으로 한 형태라고 보기는 힘들지 않을까?
의존성을 해결하는 방법(dip를 구현한 기법)
- di
- 필요한 객체들을 스스로 생성하는게 아닌 외부로 부터 주입받는 기법이다.
- 종류
- 생성자 주입
- 세터 주입
- 메서드 호출 주입 (인자로 전달)
- 인터페이스 주입 (주입할 의존성을 명시하기 위해 인터페이스 사용)
public interface DpInjectable {
public void inject(DiscountPolicy policy)
}
public class Movie implements DpInjectable {
@override
public void inject(DiscountPolicy policy){
this.policy = policy
}
}
- 어떤 주입방식을 상황에 맞게 써야할까?
- 생성자
- 명시적으로 의존성을 표현하기 위해, 불변객체를 만들기위해
- 세터
- 필수적인지 명시적인지 알 수 없고 누락으로 인한 비정상 상태로 객체가 생성될 가능성 있음
- 런타임에 의존성 변경이 필요한경우 유용하다.
- 저자는 인터페이스에 의존성을 선언하고 나타낼수 있어 가장 좋다고 주장하나 한편으로는 인터페이스에 특정 의존성을 들어내지 않도록 노려해야한다고 주장한다. 왜냐하면 모든 구현체들이 특정한 의존성을 필요하다고 확신을 못하니까.
- 메서드
- 해당 메서드에만 의존성이 필요하다면 좋다.
- 장점
- 접착코드 감소?
- 애프리케이션 구성 단순화 (의존성 구현체 변경이 쉬움)?
- 단일저장소에 공통 의존성관리??
- 테스트 편의성
- 좋은 설계 도출 (인터페이스를 정의하고 이를 컨테이너를 사용해 연결함으로 인터페이스 사용 권장?)
2 service locator(책에서 말하는 의존성 룩업과 비슷한듯?)
- 외부에서 객체에게 의존성을 주입하는게 아닌 service locator에게 의존성을 해결해줄 것을 요청한다.
public class Movie {
private String tile
private Duration runnigTime;
private Policy discountPolicy
public Movie( String title, Duration runnigTime) {
this.title = title;
this.runnigTime = runnigTime
this.policy = ServicLocator.discountPolicy();
}
}
- 장점
- 의존성 주입을 지원하는 프레임워크를 사용 못하거나 깊은 호출 계층에 걸쳐 동일한 객체를 계속 전달해야 하는 경우라면 고려해볼수 있음.
- 단점
- 의존성이 감쳐져있다. 이는 캡술화 위반이다. 즉 의존성을 이해하기 위해 코드 내부 구현을 이해할 것을 강요한다.
- 의존성 관련 문제가 컴파일 타임이 아닌 런타임에 발견될 가능성 높음 (생성시 숨겨진 의존성을 해결하지 않았을때)
- 테스트 하기 힘듬 (service locator 인스턴스 추가 및 삭제 작업 필요)
- 의존성 대상을 설정하는 시점과 해결되는 시점이 떨어져 있어 코드를 이해하고 디버깅 하기 힘듬
빈생성 방식
- 기본은 싱글턴 방식
- 스프링은 문제의 여지가 있는 싱글턴 디자인 패턴을 사용하지 않고도 싱글턴으로 인스턴스를 사용하게 해준다.
- 싱글톤의 문제
- 구체 클래스를 직접 사용하여 의존성 증가
- 인터페이스로 변경 불가
- 테스하기 어렵다.( 인퍼에스 기반 싱글턴이 아니라면 그리고 static메소드를 mocking 하기 힘듬)
- 전역상태가 만들어져 아무 객체나 자유롭게 접근하여 수정 가능
- 싱글톤을 생성자로 넘겨주면 해결안되나?
- 싱글턴이 적절한 상황
- 상태가 없는 객체 (동기화 필요 없음으로)
- 읽기전용 상태를 갖는 공유객체
- 공유 상태를 갖는 공유 객체 (모두가 공유되어야하는 상태를 지닌 객체)
- 쓰기 가능 상태를 갖는 대용량 처리 객체 (새 인스턴스 생성이 많은 자원이 소모되거나,객체의 값을 쓰는 일이 적다면 쓰기접근을 빈 상태에서 동기화하는게 많은 인스턴스 생성보다 성능상 이점이 있을수 있음) 어떻게 하지??
- 싱글턴이 적절지 않은 상황
- 쓰기 가능한 상태를 갖는 객체 (새로운 인스턴스 생성이 동기화보다 비용이 덜드는 상황인지 고려 필요)
autowiring
소규모 어프리케이션에서 시간을 절약할 수 있지만 대규모 어플리케이션에서는 유연성이 오히려 떨어진다. 왜냐하면 하나 이상의 동일한 타입의 빈을 관리하기 힘들고 적절하게 자동으로 주입받기 위해서는 byName등을 통해 명시해야하기 때문이다. 이는 더 복잡해지고 문제발생 요지가 많아진다. 와이어링을 명시적으로 정의해서 같은 타입의 인스턴스 관리하도록 권장한다.
아래와 같은 방식이 책의 저자가 권장하는 방식인가?
@Configuration
public class AnnotationConfig {
@Bean
public Car car(Engine engine) {
return new Car(engine);
}
@Bean
public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
return new CombustionEngine(camshaft, crankshaft);
}
@Bean
public Camshaft camshaft() {
return new Camshaft();
}
@Bean
public Crankshaft crankshaft() {
return new Crankshaft();
}
}