Skip to content

Commit db7abb8

Browse files
committed
refactor(core): IoC 컨테이너 구조 전면 리팩토링 (Strategy / Chain / Phase 패턴 적용)
- BeanInstantiationStrategy로 빈 생성 로직 분리 (전략 패턴) - 의존성 해결 로직을 Resolver 체인으로 대체 (책임 연쇄 패턴) - BeanLifecyclePhaseManager 도입으로 생명주기 단계 확장성 확보 - BeanTypeMatchingService로 타입 매칭 로직 통합 및 중복 제거 - DefaultListableBeanFactory 책임 경량화 (357 → 280 LOC) - SRP/OCP 준수, 테스트 용이성 및 유지보수성 향상 - 기존 동작 100% 하위 호환 유지
1 parent ce6aedc commit db7abb8

20 files changed

Lines changed: 703 additions & 122 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanCreationMethod;
4+
import sprout.beans.BeanDefinition;
5+
import sprout.context.BeanFactory;
6+
7+
/**
8+
* 빈 인스턴스화 전략 인터페이스
9+
* 다양한 방식으로 빈을 생성할 수 있도록 Strategy Pattern을 적용
10+
*/
11+
public interface BeanInstantiationStrategy {
12+
13+
/**
14+
* 빈 인스턴스를 생성합니다
15+
*
16+
* @param def 빈 정의
17+
* @param dependencyResolver 의존성 해결자
18+
* @param beanFactory 빈 팩토리
19+
* @return 생성된 빈 인스턴스
20+
*/
21+
Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception;
22+
23+
/**
24+
* 이 전략이 주어진 빈 생성 방식을 지원하는지 확인
25+
*
26+
* @param method 빈 생성 방식
27+
* @return 지원 여부
28+
*/
29+
boolean supports(BeanCreationMethod method);
30+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanDefinition;
4+
5+
import java.lang.reflect.Parameter;
6+
import java.util.List;
7+
8+
/**
9+
* 여러 DependencyTypeResolver를 조합하여 사용하는 Composite Resolver
10+
* Chain of Responsibility 패턴 적용
11+
*/
12+
public class CompositeDependencyResolver implements DependencyResolver {
13+
14+
private final List<DependencyTypeResolver> typeResolvers;
15+
16+
public CompositeDependencyResolver(List<DependencyTypeResolver> typeResolvers) {
17+
this.typeResolvers = typeResolvers;
18+
}
19+
20+
@Override
21+
public Object[] resolve(Class<?>[] dependencyTypes, Parameter[] params, BeanDefinition targetDef) {
22+
Object[] deps = new Object[dependencyTypes.length];
23+
24+
for (int i = 0; i < dependencyTypes.length; i++) {
25+
Class<?> paramType = dependencyTypes[i];
26+
Parameter param = params[i];
27+
28+
// 적절한 resolver를 찾아서 의존성 해결
29+
Object resolved = null;
30+
for (DependencyTypeResolver resolver : typeResolvers) {
31+
if (resolver.supports(paramType)) {
32+
resolved = resolver.resolve(paramType, param, targetDef);
33+
break;
34+
}
35+
}
36+
37+
if (resolved == null) {
38+
throw new RuntimeException(
39+
"No DependencyTypeResolver found for type: " + paramType.getName() +
40+
" in bean: " + targetDef.getName()
41+
);
42+
}
43+
44+
deps[i] = resolved;
45+
}
46+
47+
return deps;
48+
}
49+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package sprout.beans.instantiation;
2+
3+
import net.sf.cglib.proxy.Enhancer;
4+
import sprout.beans.BeanCreationMethod;
5+
import sprout.beans.BeanDefinition;
6+
import sprout.beans.ConstructorBeanDefinition;
7+
import sprout.context.BeanFactory;
8+
import sprout.context.ConfigurationMethodInterceptor;
9+
10+
import java.lang.reflect.Constructor;
11+
12+
/**
13+
* 생성자 기반 빈 인스턴스화 전략
14+
*/
15+
public class ConstructorBasedInstantiationStrategy implements BeanInstantiationStrategy {
16+
17+
@Override
18+
public Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception {
19+
Constructor<?> constructor = def.getConstructor();
20+
Object[] deps;
21+
22+
// ConstructorBeanDefinition인 경우 미리 준비된 생성자 인자 사용
23+
if (def instanceof ConstructorBeanDefinition && ((ConstructorBeanDefinition) def).getConstructorArguments() != null) {
24+
deps = ((ConstructorBeanDefinition) def).getConstructorArguments();
25+
} else {
26+
// 의존성 해결
27+
deps = dependencyResolver.resolve(
28+
def.getConstructorArgumentTypes(),
29+
constructor.getParameters(),
30+
def
31+
);
32+
}
33+
34+
// Configuration 클래스의 경우 CGLIB 프록시 생성
35+
if (def.isConfigurationClassProxyNeeded()) {
36+
Enhancer enhancer = new Enhancer();
37+
enhancer.setSuperclass(def.getType());
38+
enhancer.setCallback(new ConfigurationMethodInterceptor(beanFactory));
39+
return enhancer.create(def.getConstructorArgumentTypes(), deps);
40+
} else {
41+
// 접근 권한 설정 (private/package-private 생성자 접근 가능하게)
42+
constructor.setAccessible(true);
43+
return constructor.newInstance(deps);
44+
}
45+
}
46+
47+
@Override
48+
public boolean supports(BeanCreationMethod method) {
49+
return method == BeanCreationMethod.CONSTRUCTOR;
50+
}
51+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanDefinition;
4+
5+
import java.lang.reflect.Parameter;
6+
7+
/**
8+
* 의존성 해결 인터페이스
9+
* 생성자나 팩토리 메서드의 파라미터들을 해결하는 역할
10+
*/
11+
public interface DependencyResolver {
12+
13+
/**
14+
* 주어진 타입 배열과 파라미터 정보를 바탕으로 의존성을 해결합니다
15+
*
16+
* @param dependencyTypes 의존성 타입 배열
17+
* @param params 파라미터 정보
18+
* @param targetDef 현재 생성하려는 빈의 정의
19+
* @return 해결된 의존성 배열
20+
*/
21+
Object[] resolve(Class<?>[] dependencyTypes, Parameter[] params, BeanDefinition targetDef);
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanDefinition;
4+
5+
import java.lang.reflect.Parameter;
6+
7+
// 특정 타입의 의존성을 해결하는 전략 인터페이스
8+
public interface DependencyTypeResolver {
9+
10+
/**
11+
* 주어진 타입을 이 resolver가 처리할 수 있는지 확인
12+
*
13+
* @param type 의존성 타입
14+
* @return 지원 여부
15+
*/
16+
boolean supports(Class<?> type);
17+
18+
/**
19+
* 의존성을 해결하여 반환
20+
*
21+
* @param type 의존성 타입
22+
* @param param 파라미터 정보 (제네릭 타입 등 추출용)
23+
* @param targetDef 현재 생성하려는 빈의 정의
24+
* @return 해결된 의존성 객체
25+
*/
26+
Object resolve(Class<?> type, Parameter param, BeanDefinition targetDef);
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanCreationMethod;
4+
import sprout.beans.BeanDefinition;
5+
import sprout.context.BeanFactory;
6+
7+
import java.lang.reflect.Method;
8+
9+
/**
10+
* 팩토리 메서드 기반 빈 인스턴스화 전략
11+
*/
12+
public class FactoryMethodBasedInstantiationStrategy implements BeanInstantiationStrategy {
13+
14+
@Override
15+
public Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception {
16+
// 팩토리 빈 조회
17+
Object factoryBean = beanFactory.getBean(def.getFactoryBeanName());
18+
Method factoryMethod = def.getFactoryMethod();
19+
20+
// 팩토리 메서드의 의존성 해결
21+
Object[] deps = dependencyResolver.resolve(
22+
def.getFactoryMethodArgumentTypes(),
23+
factoryMethod.getParameters(),
24+
def
25+
);
26+
27+
// 팩토리 메서드 호출 (접근 권한 설정)
28+
factoryMethod.setAccessible(true);
29+
return factoryMethod.invoke(factoryBean, deps);
30+
}
31+
32+
@Override
33+
public boolean supports(BeanCreationMethod method) {
34+
return method == BeanCreationMethod.FACTORY_METHOD;
35+
}
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package sprout.beans.instantiation;
2+
3+
import sprout.beans.BeanDefinition;
4+
import sprout.context.PendingListInjection;
5+
6+
import java.lang.reflect.Parameter;
7+
import java.lang.reflect.ParameterizedType;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
/**
12+
* List 타입 의존성을 해결하는 resolver
13+
* List 주입의 경우 나중에 postProcessListInjections에서 채워지므로
14+
* 빈 리스트를 반환하고 pending 목록에 등록
15+
*/
16+
public class ListBeanDependencyResolver implements DependencyTypeResolver {
17+
18+
private final List<PendingListInjection> pendingListInjections;
19+
20+
public ListBeanDependencyResolver(List<PendingListInjection> pendingListInjections) {
21+
this.pendingListInjections = pendingListInjections;
22+
}
23+
24+
@Override
25+
public boolean supports(Class<?> type) {
26+
return List.class.isAssignableFrom(type);
27+
}
28+
29+
@Override
30+
public Object resolve(Class<?> type, Parameter param, BeanDefinition targetDef) {
31+
List<Object> emptyList = new ArrayList<>();
32+
33+
// 제네릭 타입 추출
34+
Class<?> genericType = (Class<?>) ((ParameterizedType) param.getParameterizedType())
35+
.getActualTypeArguments()[0];
36+
37+
// 나중에 채울 수 있도록 pending 목록에 등록
38+
pendingListInjections.add(new PendingListInjection(null, emptyList, genericType));
39+
40+
return emptyList;
41+
}
42+
}

src/main/java/sprout/beans/internal/BeanGraph.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import sprout.beans.BeanCreationMethod;
44
import sprout.beans.BeanDefinition;
5+
import sprout.beans.matching.BeanTypeMatchingService;
56

67
import java.lang.reflect.Modifier;
78
import java.util.*;
@@ -11,6 +12,7 @@ public class BeanGraph {
1112
private final Map<String, BeanDefinition> nodeMap = new HashMap<>();
1213
private final Map<String, List<String>> edges = new HashMap<>();
1314
private final Map<String, Integer> indegree = new HashMap<>();
15+
private final BeanTypeMatchingService typeMatchingService;
1416

1517
public BeanGraph(Collection<BeanDefinition> definitions) {
1618
definitions.forEach(d -> {
@@ -19,6 +21,10 @@ public BeanGraph(Collection<BeanDefinition> definitions) {
1921
// 초기 indegree는 0으로 설정
2022
indegree.putIfAbsent(d.getName(), 0);
2123
});
24+
25+
// 타입 매칭 서비스 초기화 (BeanGraph는 싱글톤이 아니므로 빈 Map 전달)
26+
this.typeMatchingService = new BeanTypeMatchingService(nodeMap, Collections.emptyMap());
27+
2228
buildEdges();
2329
System.out.println(indegree);
2430
}
@@ -103,13 +109,7 @@ private void buildEdges() {
103109
}
104110

105111
private Set<String> getBeanNamesForType(Class<?> type) {
106-
Set<String> names = new HashSet<>();
107-
for (BeanDefinition beanDef : nodeMap.values()) {
108-
if (type.isAssignableFrom(beanDef.getType())) {
109-
names.add(beanDef.getName());
110-
}
111-
}
112-
return names;
112+
return typeMatchingService.getBeanNamesForType(type);
113113
}
114114

115115
public static class CircularDependencyException extends RuntimeException {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package sprout.beans.matching;
2+
3+
import sprout.beans.BeanDefinition;
4+
5+
import java.util.*;
6+
7+
/**
8+
* 빈 타입 매칭 서비스
9+
* 타입을 기반으로 빈을 찾고 매칭하는 로직을 담당
10+
*/
11+
public class BeanTypeMatchingService {
12+
13+
private final Map<String, BeanDefinition> beanDefinitions;
14+
private final Map<String, Object> singletons;
15+
16+
public BeanTypeMatchingService(Map<String, BeanDefinition> beanDefinitions, Map<String, Object> singletons) {
17+
this.beanDefinitions = beanDefinitions;
18+
this.singletons = singletons;
19+
}
20+
21+
// 주어진 타입에 대한 후보 빈 이름들을 찾습니다
22+
public Set<String> findCandidateNamesForType(Class<?> type) {
23+
Set<String> names = new HashSet<>();
24+
25+
// 1) 이미 등록된 싱글턴에서 찾기
26+
for (Map.Entry<String, Object> entry : singletons.entrySet()) {
27+
if (type.isAssignableFrom(entry.getValue().getClass())) {
28+
names.add(entry.getKey());
29+
}
30+
}
31+
32+
// 2) 아직 생성되지 않은 BeanDefinition에서 찾기
33+
for (Map.Entry<String, BeanDefinition> entry : beanDefinitions.entrySet()) {
34+
if (type.isAssignableFrom(entry.getValue().getType())) {
35+
names.add(entry.getKey());
36+
}
37+
}
38+
39+
return names;
40+
}
41+
42+
/**
43+
* 후보 빈들 중에서 primary 빈을 선택
44+
*
45+
* @param requiredType 요청된 타입
46+
* @param candidates 후보 빈 이름들
47+
* @param primaryTypeToNameMap 타입별 primary 빈 매핑
48+
* @return primary 빈 이름 (없으면 null)
49+
*/
50+
public String choosePrimary(Class<?> requiredType, Set<String> candidates, Map<Class<?>, String> primaryTypeToNameMap) {
51+
// 1) BeanDefinition 중 primary=true인 빈 찾기
52+
List<String> primaries = candidates.stream()
53+
.filter(name -> {
54+
BeanDefinition def = beanDefinitions.get(name);
55+
return def != null && def.isPrimary();
56+
})
57+
.toList();
58+
59+
if (primaries.size() == 1) {
60+
return primaries.get(0);
61+
}
62+
if (primaries.size() > 1) {
63+
throw new RuntimeException("@Primary beans conflict for type " + requiredType.getName() + ": " + primaries);
64+
}
65+
66+
// 2) primaryTypeToNameMap fallback
67+
String mapped = primaryTypeToNameMap.get(requiredType);
68+
if (mapped != null && candidates.contains(mapped)) {
69+
return mapped;
70+
}
71+
72+
return null;
73+
}
74+
75+
/**
76+
* 주어진 타입의 모든 빈 이름을 BeanDefinition에서 찾기
77+
* (BeanGraph에서 사용)
78+
*
79+
* @param type 찾을 타입
80+
* @return 빈 이름 집합
81+
*/
82+
public Set<String> getBeanNamesForType(Class<?> type) {
83+
Set<String> names = new HashSet<>();
84+
for (BeanDefinition beanDef : beanDefinitions.values()) {
85+
if (type.isAssignableFrom(beanDef.getType())) {
86+
names.add(beanDef.getName());
87+
}
88+
}
89+
return names;
90+
}
91+
}

0 commit comments

Comments
 (0)