(Spring) context.getBean() 으로 Bean 가져오는 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PointChangeController {
private final ApplicationContext context;
void method() {
// common logic 1
PointChangeService pointChangeService = context.getBean(
partnerCode + "PointChangeService",
PointChangeService.class
);
// common logic 2
}
}
이거 괜찮은 패턴이 맞나?
- 예전에 이렇게 쓰는 경우를 종종 봤었는데… 이거 괜찮은 패턴이 맞나?
- 만약
getBean
안하고 개별 Bean을 생성자 DI 받아서 처리한다 치면, Controller 클래스도 partnerCode 개수 만큼 만들어야 한다. - 상속이나 컴포지션을 써서 공통 로직 중복은 없앤다고 쳐도, Controller 클래스 자체는 n개가 되어야 함.
- Controller를 1개로 유지하면서 Service만 갈아끼우는 방법은 이 방법 뿐인 것 같다.
단점은?
- getBean을 쓰다보니 런타임에 찾으려는 Bean이 없을 수도 있다는 것.
- 컴파일 타임 검증은 불가능하고 (어차피 생성자 DI도 타입이 interface이면 이름 기반 매칭이라 시작 시점 검증이다.)
@PostConstructor
에서 직접partnerCode
돌면서 Bean 존재 유무를 체크해서 애플리케이션 시작 시점 체크로 갈음 할 수 있다.- 그런거라면 런타임에
getBean
해서 가져오기 보다는@PostConstructor
에서 Map 하나 생성해서 넣어두는 방식이 더 맞다. - 그렇다면 그냥 아예
Map<Name, Bean>
을 DI 받는 것이 더 낫다.
ApplicationContext
를 직접 사용하다 보니 유닛 테스트가 곤란하다는 점.- Spring Context를 생성하고 그 안에
MockPointChangeService
를 넣어주는 방식으로 해야 테스트가 가능하다. - 반면 생성자 DI 받으면 Spring Context 없이 Mock 객체만 넘겨주면 되니 유닛 테스트 가능
- Spring Context를 생성하고 그 안에
즉, 완전 별로라고 할 수는 없는 패턴이지만 더 나은 방법이 있는데 굳이 저렇게 할 이유가 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PointChangeController(
val pointChangeServices: Map<String, PointChangeService>,
) {
fun method() {
// common logic 1
val pointChangeService = pointChangeServices[partnerCode + "PointChangeService"]
// common logic 2
}
@PostConstruct
fun init() {
PartnerCode.entries.all { it.name + "PointChangeService" in pointChangeServices.keys }
}
}
This post is licensed under CC BY 4.0 by the author.