๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐Ÿ—๏ธ IoC Container

IoC(Inversion of Control) ์ปจํ…Œ์ด๋„ˆ๋Š” Sprout Framework์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ, ์˜์กด์„ฑ ์ฃผ์ž…, ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์š”โ€‹

Sprout์˜ IoC ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  • ์ปดํฌ๋„ŒํŠธ ์Šค์บ”: Reflections ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค ์ž๋™ ๊ฐ์ง€
  • ์ƒ์„ฑ์ž ์ฃผ์ž…: ํƒ€์ž… ์•ˆ์ „ํ•œ ์˜์กด์„ฑ ํ•ด๊ฒฐ (ํ•„๋“œ ์ฃผ์ž… ๋ฏธ์ง€์›)
  • ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ: ๋‹จ๊ณ„๋ณ„(Phase) ๋นˆ ์ƒ์„ฑ, ์ดˆ๊ธฐํ™”, ์†Œ๋ฉธ
  • ์ˆœํ™˜ ์˜์กด์„ฑ ๊ฐ์ง€: BeanGraph๋ฅผ ํ†ตํ•œ ์œ„์ƒ ์ •๋ ฌ๊ณผ ์ˆœํ™˜ ์ฐธ์กฐ ๊ฐ์ง€
  • ์ˆœ์„œ ์ง€์›: @Order๋ฅผ ํ†ตํ•œ ๋นˆ ์ดˆ๊ธฐํ™” ๋ฐ ์ปฌ๋ ‰์…˜ ์ˆœ์„œ ์ œ์–ด
  • CGLIB ํ”„๋ก์‹œ: @Configuration ํด๋ž˜์Šค์˜ ์‹ฑ๊ธ€ํ†ค ๋ณด์žฅ
  • ์ „๋žต ํŒจํ„ด ๊ธฐ๋ฐ˜ ํ™•์žฅ์„ฑ: ๋นˆ ์ƒ์„ฑ ์ „๋žต๊ณผ ์˜์กด์„ฑ ํ•ด๊ฒฐ ์ „๋žต์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ

์ปจํ…Œ์ด๋„ˆ ์•„ํ‚คํ…์ฒ˜โ€‹

ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธโ€‹

Sprout์˜ IoC ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‹ค์Œ ์ฃผ์š” ํด๋ž˜์Šค๋“ค๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ปจํ…์ŠคํŠธ ๋ฐ ํŒฉํ† ๋ฆฌโ€‹

  • SproutApplicationContext: ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ
  • DefaultListableBeanFactory: ํ•ต์‹ฌ ๋นˆ ํŒฉํ† ๋ฆฌ ๊ตฌํ˜„
  • ClassPathScanner: ํด๋ž˜์ŠคํŒจ์Šค ์Šค์บ” ๋ฐ ๋นˆ ์ •์˜ ์ƒ์„ฑ
  • BeanGraph: ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„์™€ ์œ„์ƒ ์ •๋ ฌ

๋นˆ ์ƒ์„ฑ ์ „๋žต (Strategy Pattern)โ€‹

  • BeanInstantiationStrategy: ๋นˆ ์ธ์Šคํ„ด์Šคํ™” ์ „๋žต ์ธํ„ฐํŽ˜์ด์Šค
    • ConstructorBasedInstantiationStrategy: ์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜ ๋นˆ ์ƒ์„ฑ
    • FactoryMethodBasedInstantiationStrategy: ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ๊ธฐ๋ฐ˜ ๋นˆ ์ƒ์„ฑ

์˜์กด์„ฑ ํ•ด๊ฒฐ ์ „๋žต (Chain of Responsibility Pattern)โ€‹

  • DependencyResolver: ์˜์กด์„ฑ ํ•ด๊ฒฐ ์ธํ„ฐํŽ˜์ด์Šค
    • CompositeDependencyResolver: ์—ฌ๋Ÿฌ resolver๋ฅผ ์กฐํ•ฉํ•˜๋Š” ๋ณตํ•ฉ resolver
  • DependencyTypeResolver: ํƒ€์ž…๋ณ„ ์˜์กด์„ฑ ํ•ด๊ฒฐ ์ „๋žต
    • SingleBeanDependencyResolver: ๋‹จ์ผ ๋นˆ ์˜์กด์„ฑ ํ•ด๊ฒฐ
    • ListBeanDependencyResolver: List ํƒ€์ž… ์˜์กด์„ฑ ํ•ด๊ฒฐ

์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ (Phase Pattern)โ€‹

  • BeanLifecycleManager: ์ƒ๋ช…์ฃผ๊ธฐ ๋‹จ๊ณ„ ์‹คํ–‰ ๊ด€๋ฆฌ์ž
  • BeanLifecyclePhase: ์ƒ๋ช…์ฃผ๊ธฐ ๋‹จ๊ณ„ ์ธํ„ฐํŽ˜์ด์Šค
    • InfrastructureBeanPhase: Infrastructure ๋นˆ ์ƒ์„ฑ (order=100)
    • BeanPostProcessorRegistrationPhase: BeanPostProcessor ๋“ฑ๋ก (order=200)
    • ApplicationBeanPhase: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นˆ ์ƒ์„ฑ (order=300)
    • ContextInitializerPhase: ContextInitializer ์‹คํ–‰ (order=400)

ํƒ€์ž… ๋งค์นญ ์„œ๋น„์Šคโ€‹

  • BeanTypeMatchingService: ํƒ€์ž… ๊ธฐ๋ฐ˜ ๋นˆ ๊ฒ€์ƒ‰ ๋ฐ ๋งค์นญ ๋กœ์ง ์ค‘์•™ ๊ด€๋ฆฌ

์ปจํ…Œ์ด๋„ˆ ์ดˆ๊ธฐํ™” ๊ณผ์ •โ€‹

public class SproutApplication {
public static void run(Class<?> primarySource) throws Exception {
// 1. ํŒจํ‚ค์ง€ ์Šค์บ” ์„ค์ •
List<String> packages = getPackagesToScan(primarySource);

// 2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
ApplicationContext applicationContext =
new SproutApplicationContext(packages.toArray(new String[0]));

// 3. ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™” (refresh)
applicationContext.refresh();

// 4. ์„œ๋ฒ„ ์‹œ์ž‘
HttpServer server = applicationContext.getBean(HttpServer.class);
server.start(port);
}
}

์ปดํฌ๋„ŒํŠธ ์Šค์บ”โ€‹

์ง€์›๋˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜โ€‹

Sprout๋Š” ๋‹ค์Œ ์ปดํฌ๋„ŒํŠธ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค:

@Component         // ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ
@Service // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ณ„์ธต
@Repository // ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ณ„์ธต
@Controller // ์›น ๊ณ„์ธต
@Configuration // ๊ตฌ์„ฑ ํด๋ž˜์Šค
@Aspect // AOP ์• ์ŠคํŽ™ํŠธ
@ControllerAdvice // ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
@WebSocketHandler // WebSocket ํ•ธ๋“ค๋Ÿฌ

์Šค์บ” ๊ณผ์ •โ€‹

// ClassPathScanner์˜ ์Šค์บ” ๋กœ์ง
public Collection<BeanDefinition> scan(ConfigurationBuilder configBuilder,
Class<? extends Annotation>... componentAnnotations) {
// 1. Reflections๋ฅผ ์ด์šฉํ•œ ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค ํƒ์ƒ‰
Set<Class<?>> componentCandidates = new HashSet<>();
for (Class<? extends Annotation> anno : componentAnnotations) {
componentCandidates.addAll(r.getTypesAnnotatedWith(anno));
}

// 2. ๊ตฌ์ฒด ํด๋ž˜์Šค๋งŒ ํ•„ํ„ฐ๋ง (์ธํ„ฐํŽ˜์ด์Šค, ์ถ”์ƒํด๋ž˜์Šค ์ œ์™ธ)
Set<Class<?>> concreteComponentTypes = componentCandidates.stream()
.filter(clazz -> !clazz.isInterface() &&
!clazz.isAnnotation() &&
!Modifier.isAbstract(clazz.getModifiers()))
.collect(Collectors.toSet());

// 3. @Bean ๋ฉ”์„œ๋“œ ๊ธฐ๋ฐ˜ ๋นˆ ํƒ์ƒ‰
Set<Class<?>> configClasses = r.getTypesAnnotatedWith(Configuration.class);
for (Class<?> configClass : configClasses) {
for (Method method : configClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Bean.class)) {
beanMethodReturnTypes.add(method.getReturnType());
}
}
}
}

์ปดํฌ๋„ŒํŠธ ์Šค์บ” ํ™œ์„ฑํ™”โ€‹

๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค์— @ComponentScan์„ ์‚ฌ์šฉํ•˜์„ธ์š”:

@ComponentScan("com.myapp")  // ํŠน์ • ํŒจํ‚ค์ง€ ์Šค์บ”
@ComponentScan({"com.myapp.web", "com.myapp.service"}) // ์—ฌ๋Ÿฌ ํŒจํ‚ค์ง€
public class Application {
public static void main(String[] args) throws Exception {
SproutApplication.run(Application.class);
}
}

์˜์กด์„ฑ ์ฃผ์ž…โ€‹

์ƒ์„ฑ์ž ์ฃผ์ž… ์ „๋žตโ€‹

Sprout๋Š” ์ƒ์„ฑ์ž ์ฃผ์ž…๋งŒ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋งŽ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•œ ์ƒ์„ฑ์ž๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

// ์ƒ์„ฑ์ž ํ•ด๊ฒฐ ๋กœ์ง
private Constructor<?> resolveUsableConstructor(Class<?> clazz, Set<Class<?>> allKnownBeanTypes) {
return Arrays.stream(clazz.getDeclaredConstructors())
.filter(constructor -> Arrays.stream(constructor.getParameterTypes())
.allMatch(param -> isResolvable(param, allKnownBeanTypes)))
.max(Comparator.comparingInt(Constructor::getParameterCount))
.orElseThrow(() -> new NoSuchMethodException("No usable constructor"));
}

์˜์กด์„ฑ ํ•ด๊ฒฐ ์•„ํ‚คํ…์ฒ˜โ€‹

Sprout v2.0๋ถ€ํ„ฐ ์˜์กด์„ฑ ํ•ด๊ฒฐ์— Chain of Responsibility ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ํ™•์žฅ์„ฑ์„ ํฌ๊ฒŒ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

DependencyResolver ๊ตฌ์กฐโ€‹

// ์˜์กด์„ฑ ํ•ด๊ฒฐ ์ธํ„ฐํŽ˜์ด์Šค
public interface DependencyResolver {
Object[] resolve(Class<?>[] dependencyTypes, Parameter[] params, BeanDefinition targetDef);
}

// ํƒ€์ž…๋ณ„ ์˜์กด์„ฑ ํ•ด๊ฒฐ ์ „๋žต
public interface DependencyTypeResolver {
boolean supports(Class<?> type);
Object resolve(Class<?> type, Parameter param, BeanDefinition targetDef);
}

๊ธฐ๋ณธ ์ œ๊ณต Resolverโ€‹

  1. ListBeanDependencyResolver: List ํƒ€์ž… ์˜์กด์„ฑ ์ฒ˜๋ฆฌ

    • List ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ์ง€ํ•˜๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ
    • ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜์—ฌ pending ๋ชฉ๋ก์— ๋“ฑ๋ก
    • ๋‚˜์ค‘์— postProcessListInjections()์—์„œ ์‹ค์ œ ๋นˆ๋“ค์„ ์ฃผ์ž…
  2. SingleBeanDependencyResolver: ๋‹จ์ผ ๋นˆ ์˜์กด์„ฑ ์ฒ˜๋ฆฌ

    • ์ผ๋ฐ˜์ ์ธ ํƒ€์ž…(List๊ฐ€ ์•„๋‹Œ)์— ๋Œ€ํ•ด BeanFactory์—์„œ ๋นˆ ์กฐํšŒ
    • ํƒ€์ž… ๋งค์นญ ๋ฐ @Primary ์„ ํƒ ๋กœ์ง ํ™œ์šฉ

CompositeDependencyResolverโ€‹

์—ฌ๋Ÿฌ DependencyTypeResolver๋ฅผ ์ฒด์ธ์œผ๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค:

public class CompositeDependencyResolver implements DependencyResolver {
private final List<DependencyTypeResolver> typeResolvers;

@Override
public Object[] resolve(Class<?>[] dependencyTypes, Parameter[] params, BeanDefinition targetDef) {
Object[] deps = new Object[dependencyTypes.length];

for (int i = 0; i < dependencyTypes.length; i++) {
Class<?> paramType = dependencyTypes[i];
Parameter param = params[i];

// ์ ์ ˆํ•œ resolver๋ฅผ ์ฐพ์•„์„œ ์˜์กด์„ฑ ํ•ด๊ฒฐ
for (DependencyTypeResolver resolver : typeResolvers) {
if (resolver.supports(paramType)) {
deps[i] = resolver.resolve(paramType, param, targetDef);
break;
}
}
}
return deps;
}
}

ํ™•์žฅ ๋ฐฉ๋ฒ•โ€‹

์ƒˆ๋กœ์šด ์˜์กด์„ฑ ํƒ€์ž…(์˜ˆ: Optional, Provider)์„ ์ง€์›ํ•˜๋ ค๋ฉด DependencyTypeResolver๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  DefaultListableBeanFactory ์ƒ์„ฑ์ž์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

public class OptionalBeanDependencyResolver implements DependencyTypeResolver {
@Override
public boolean supports(Class<?> type) {
return Optional.class.isAssignableFrom(type);
}

@Override
public Object resolve(Class<?> type, Parameter param, BeanDefinition targetDef) {
// Optional ์ฒ˜๋ฆฌ ๋กœ์ง
Class<?> genericType = extractGenericType(param);
try {
Object bean = beanFactory.getBean(genericType);
return Optional.of(bean);
} catch (Exception e) {
return Optional.empty();
}
}
}

์˜ˆ์ œ: ๊ธฐ๋ณธ ์˜์กด์„ฑ ์ฃผ์ž…โ€‹

@Service
public class UserService {
private final UserRepository repository;
private final EmailService emailService;

// ์ƒ์„ฑ์ž ์ฃผ์ž… - @Autowired ๋ถˆํ•„์š”
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}

@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;

public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}

์ปฌ๋ ‰์…˜ ์ฃผ์ž…โ€‹

ํŠน์ • ํƒ€์ž…์˜ ๋ชจ๋“  ๋นˆ์„ List๋กœ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

public interface EventHandler {
void handle(Event event);
}

@Component
@Order(1)
public class EmailEventHandler implements EventHandler {
public void handle(Event event) { /* ์ด๋ฉ”์ผ ์ฒ˜๋ฆฌ */ }
}

@Component
@Order(2)
public class LogEventHandler implements EventHandler {
public void handle(Event event) { /* ๋กœ๊ทธ ์ฒ˜๋ฆฌ */ }
}

@Service
public class EventProcessor {
private final List<EventHandler> handlers;

// ๋ชจ๋“  EventHandler ๋นˆ์ด @Order ์ˆœ์„œ๋Œ€๋กœ ์ฃผ์ž…๋จ
public EventProcessor(List<EventHandler> handlers) {
this.handlers = handlers;
}

public void processEvent(Event event) {
handlers.forEach(handler -> handler.handle(event));
}
}

์ปฌ๋ ‰์…˜ ์ฃผ์ž… ์ฒ˜๋ฆฌ ๋กœ์งโ€‹

// DefaultListableBeanFactory์˜ ์ปฌ๋ ‰์…˜ ์ฃผ์ž… ํ›„์ฒ˜๋ฆฌ
protected void postProcessListInjections() {
for (PendingListInjection pending : pendingListInjections) {
Set<Object> uniqueBeansForList = new HashSet<>();
for (Object bean : singletons.values()) {
if (pending.getGenericType().isAssignableFrom(bean.getClass())) {
uniqueBeansForList.add(bean);
}
}

// @Order ์–ด๋…ธํ…Œ์ด์…˜์— ๋”ฐ๋ผ ์ •๋ ฌ
List<Object> sortedBeansForList = uniqueBeansForList.stream()
.sorted(Comparator.comparingInt(bean -> {
Class<?> clazz = bean.getClass();
Order order = clazz.getAnnotation(Order.class);
return (order != null) ? order.value() : Integer.MAX_VALUE;
}))
.toList();

pending.getListToPopulate().clear();
pending.getListToPopulate().addAll(sortedBeansForList);
}
}

๋นˆ ์ •์˜์™€ ์ƒ์„ฑโ€‹

๋นˆ ์ •์˜ ํƒ€์ž…โ€‹

Sprout๋Š” ๋‘ ๊ฐ€์ง€ ๋นˆ ์ƒ์„ฑ ๋ฐฉ์‹์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

  1. ์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜ ๋นˆ (ConstructorBeanDefinition)
  2. ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ๋นˆ (MethodBeanDefinition)

๋นˆ ์ธ์Šคํ„ด์Šคํ™” ์ „๋žต (Strategy Pattern)โ€‹

Sprout v2.0๋ถ€ํ„ฐ ๋นˆ ์ƒ์„ฑ ๋กœ์ง์— Strategy Pattern์„ ์ ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์ƒ์„ฑ ๋ฐฉ์‹์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

BeanInstantiationStrategy ์ธํ„ฐํŽ˜์ด์Šคโ€‹

public interface BeanInstantiationStrategy {
Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception;
boolean supports(BeanCreationMethod method);
}

๊ตฌํ˜„์ฒด๋“คโ€‹

1. ConstructorBasedInstantiationStrategy

์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ๋นˆ ์ƒ์„ฑ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค:

public class ConstructorBasedInstantiationStrategy implements BeanInstantiationStrategy {
@Override
public Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception {
Constructor<?> constructor = def.getConstructor();

// ์˜์กด์„ฑ ํ•ด๊ฒฐ
Object[] deps = dependencyResolver.resolve(
def.getConstructorArgumentTypes(),
constructor.getParameters(),
def
);

// Configuration ํด๋ž˜์Šค์˜ ๊ฒฝ์šฐ CGLIB ํ”„๋ก์‹œ ์ƒ์„ฑ
if (def.isConfigurationClassProxyNeeded()) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(def.getType());
enhancer.setCallback(new ConfigurationMethodInterceptor(beanFactory));
return enhancer.create(def.getConstructorArgumentTypes(), deps);
} else {
constructor.setAccessible(true);
return constructor.newInstance(deps);
}
}

@Override
public boolean supports(BeanCreationMethod method) {
return method == BeanCreationMethod.CONSTRUCTOR;
}
}

2. FactoryMethodBasedInstantiationStrategy

ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ(@Bean)๋ฅผ ํ†ตํ•œ ๋นˆ ์ƒ์„ฑ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค:

public class FactoryMethodBasedInstantiationStrategy implements BeanInstantiationStrategy {
@Override
public Object instantiate(BeanDefinition def, DependencyResolver dependencyResolver, BeanFactory beanFactory) throws Exception {
Object factoryBean = beanFactory.getBean(def.getFactoryBeanName());
Method factoryMethod = def.getFactoryMethod();

Object[] deps = dependencyResolver.resolve(
def.getFactoryMethodArgumentTypes(),
factoryMethod.getParameters(),
def
);

factoryMethod.setAccessible(true);
return factoryMethod.invoke(factoryBean, deps);
}

@Override
public boolean supports(BeanCreationMethod method) {
return method == BeanCreationMethod.FACTORY_METHOD;
}
}

DefaultListableBeanFactory์˜ ์ „๋žต ํ™œ์šฉโ€‹

public class DefaultListableBeanFactory implements BeanFactory, BeanDefinitionRegistry {
private final List<BeanInstantiationStrategy> instantiationStrategies;
private final DependencyResolver dependencyResolver;

public DefaultListableBeanFactory() {
// ์ „๋žต ์ดˆ๊ธฐํ™”
this.instantiationStrategies = new ArrayList<>();
this.instantiationStrategies.add(new ConstructorBasedInstantiationStrategy());
this.instantiationStrategies.add(new FactoryMethodBasedInstantiationStrategy());

// ์˜์กด์„ฑ resolver ์ดˆ๊ธฐํ™”
List<DependencyTypeResolver> typeResolvers = new ArrayList<>();
typeResolvers.add(new ListBeanDependencyResolver(pendingListInjections));
typeResolvers.add(new SingleBeanDependencyResolver(this));
this.dependencyResolver = new CompositeDependencyResolver(typeResolvers);
}

public Object createBean(BeanDefinition def) {
// ์ ์ ˆํ•œ ์ „๋žต ์„ ํƒ
BeanInstantiationStrategy strategy = findStrategy(def);

// ์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ๋นˆ ์ƒ์„ฑ
Object beanInstance = strategy.instantiate(def, dependencyResolver, this);

// BeanPostProcessor ์ฒ˜๋ฆฌ
Object processedBean = applyBeanPostProcessors(beanInstance, def.getName());

return processedBean;
}

private BeanInstantiationStrategy findStrategy(BeanDefinition def) {
for (BeanInstantiationStrategy strategy : instantiationStrategies) {
if (strategy.supports(def.getCreationMethod())) {
return strategy;
}
}
throw new IllegalArgumentException("No strategy found for: " + def.getCreationMethod());
}
}

์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜ ๋นˆโ€‹

@Component
public class NotificationService {
private final EmailService emailService;
private final SmsService smsService;

public NotificationService(EmailService emailService, SmsService smsService) {
this.emailService = emailService;
this.smsService = smsService;
}
}

ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ๋นˆโ€‹

@Configuration
public class DataSourceConfig {

@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/myapp");
return new HikariDataSource(config);
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}

@Configuration ํ”„๋ก์‹œโ€‹

@Configuration ํด๋ž˜์Šค๋Š” CGLIB์„ ์ด์šฉํ•ด ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์‹ฑ๊ธ€ํ†ค์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

@Configuration(proxyBeanMethods = true)  // ๊ธฐ๋ณธ๊ฐ’
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB()); // ๋™์ผํ•œ serviceB ์ธ์Šคํ„ด์Šค ๋ฐ˜ํ™˜
}

@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}

์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌโ€‹

Sprout v2.0๋ถ€ํ„ฐ Phase Pattern์„ ๋„์ž…ํ•˜์—ฌ ๋นˆ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ปจํ…Œ์ด๋„ˆ ์ดˆ๊ธฐํ™” ๊ณผ์ • (๋ฆฌํŒฉํ† ๋ง ํ›„)โ€‹

@Override
public void refresh() throws Exception {
// 1. ๋นˆ ์ •์˜ ์Šค์บ”
scanBeanDefinitions();

// 2. BeanLifecycleManager๋ฅผ ํ†ตํ•œ ๋‹จ๊ณ„๋ณ„ ์‹คํ–‰
BeanLifecyclePhase.PhaseContext context = new BeanLifecyclePhase.PhaseContext(
beanFactory,
infraDefs,
appDefs,
basePackages
);

lifecycleManager.executePhases(context);
}

์ด์ „ ๋ฒ„์ „์˜ ๋ณต์žกํ•œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ(instantiateInfrastructureBeans(), instantiateAllSingletons() ๋“ฑ)์ด ๋ชจ๋‘ Phase๋กœ ์บก์Аํ™”๋˜์–ด 19์ค„์—์„œ 10์ค„๋กœ ๋‹จ์ˆœํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

BeanLifecyclePhase ์ธํ„ฐํŽ˜์ด์Šคโ€‹

๊ฐ ์ƒ๋ช…์ฃผ๊ธฐ ๋‹จ๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค:

public interface BeanLifecyclePhase {
String getName();
int getOrder();
void execute(PhaseContext context) throws Exception;

class PhaseContext {
private final BeanFactory beanFactory;
private final List<BeanDefinition> infraDefs;
private final List<BeanDefinition> appDefs;
private final List<String> basePackages;
// getters...
}
}

์ƒ๋ช…์ฃผ๊ธฐ ๋‹จ๊ณ„ (Phases)โ€‹

1. InfrastructureBeanPhase (order=100)โ€‹

Infrastructure ๋นˆ(BeanPostProcessor, InfrastructureBean)์„ ๋จผ์ € ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

public class InfrastructureBeanPhase implements BeanLifecyclePhase {
@Override
public void execute(PhaseContext context) throws Exception {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();

// ์œ„์ƒ ์ •๋ ฌ ํ›„ ์ˆœ์„œ๋Œ€๋กœ ์ƒ์„ฑ
List<BeanDefinition> order = new BeanGraph(context.getInfraDefs()).topologicallySorted();
order.forEach(factory::createBean);

// List ์ฃผ์ž… ํ›„์ฒ˜๋ฆฌ
factory.postProcessListInjections();

// PostInfrastructureInitializer ์‹คํ–‰
List<PostInfrastructureInitializer> initializers =
factory.getAllBeans(PostInfrastructureInitializer.class);
for (PostInfrastructureInitializer initializer : initializers) {
initializer.afterInfrastructureSetup(factory, context.getBasePackages());
}
}

@Override
public int getOrder() { return 100; }
}

2. BeanPostProcessorRegistrationPhase (order=200)โ€‹

๋ชจ๋“  BeanPostProcessor๋ฅผ BeanFactory์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค:

public class BeanPostProcessorRegistrationPhase implements BeanLifecyclePhase {
@Override
public void execute(PhaseContext context) {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();

List<BeanPostProcessor> allBeanPostProcessor =
factory.getAllBeans(BeanPostProcessor.class);

for (BeanPostProcessor beanPostProcessor : allBeanPostProcessor) {
factory.addBeanPostProcessor(beanPostProcessor);
}
}

@Override
public int getOrder() { return 200; }
}

3. ApplicationBeanPhase (order=300)โ€‹

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นˆ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

public class ApplicationBeanPhase implements BeanLifecyclePhase {
@Override
public void execute(PhaseContext context) {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();

// ์œ„์ƒ ์ •๋ ฌ ํ›„ ์ˆœ์„œ๋Œ€๋กœ ์ƒ์„ฑ
List<BeanDefinition> order = new BeanGraph(context.getAppDefs()).topologicallySorted();
order.forEach(factory::createBean);

// List ์ฃผ์ž… ํ›„์ฒ˜๋ฆฌ
factory.postProcessListInjections();
}

@Override
public int getOrder() { return 300; }
}

4. ContextInitializerPhase (order=400)โ€‹

๋ชจ๋“  ContextInitializer๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค:

public class ContextInitializerPhase implements BeanLifecyclePhase {
@Override
public void execute(PhaseContext context) {
BeanFactory beanFactory = context.getBeanFactory();

List<ContextInitializer> contextInitializers =
beanFactory.getAllBeans(ContextInitializer.class);
for (ContextInitializer initializer : contextInitializers) {
initializer.initializeAfterRefresh(beanFactory);
}
}

@Override
public int getOrder() { return 400; }
}

BeanLifecycleManagerโ€‹

๋ชจ๋“  Phase๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋Š” ๋งค๋‹ˆ์ €์ž…๋‹ˆ๋‹ค:

public class BeanLifecycleManager {
private final List<BeanLifecyclePhase> phases;

public BeanLifecycleManager(List<BeanLifecyclePhase> phases) {
this.phases = phases.stream()
.sorted(Comparator.comparingInt(BeanLifecyclePhase::getOrder))
.toList();
}

public void executePhases(BeanLifecyclePhase.PhaseContext context) throws Exception {
for (BeanLifecyclePhase phase : phases) {
System.out.println("--- Executing Phase: " + phase.getName() +
" (order=" + phase.getOrder() + ") ---");
phase.execute(context);
}
}
}

์ƒ๋ช…์ฃผ๊ธฐ ํ™•์žฅโ€‹

์ƒˆ๋กœ์šด ๋‹จ๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด BeanLifecyclePhase๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  SproutApplicationContext ์ƒ์„ฑ์ž์— ๋“ฑ๋กํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

public class CustomPhase implements BeanLifecyclePhase {
@Override
public String getName() {
return "Custom Initialization Phase";
}

@Override
public int getOrder() {
return 250; // BeanPostProcessor ๋“ฑ๋ก ํ›„, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นˆ ์ƒ์„ฑ ์ „
}

@Override
public void execute(PhaseContext context) throws Exception {
// ์ปค์Šคํ…€ ์ดˆ๊ธฐํ™” ๋กœ์ง
}
}

// SproutApplicationContext ์ƒ์„ฑ์ž์—์„œ
List<BeanLifecyclePhase> phases = new ArrayList<>();
phases.add(new InfrastructureBeanPhase());
phases.add(new BeanPostProcessorRegistrationPhase());
phases.add(new CustomPhase()); // ์ปค์Šคํ…€ Phase ์ถ”๊ฐ€
phases.add(new ApplicationBeanPhase());
phases.add(new ContextInitializerPhase());
this.lifecycleManager = new BeanLifecycleManager(phases);

๋นˆ ์ƒ์„ฑ ์ˆœ์„œโ€‹

๊ฐ Phase ๋‚ด์—์„œ BeanGraph๋ฅผ ์ด์šฉํ•ด ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์œ„์ƒ ์ •๋ ฌํ•˜์—ฌ ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ๋กœ ๋นˆ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

// ์œ„์ƒ ์ •๋ ฌ๋กœ ์˜์กด์„ฑ ์ˆœ์„œ ๊ฒฐ์ •
List<BeanDefinition> order = new BeanGraph(defs).topologicallySorted();

// ์ˆœ์„œ๋Œ€๋กœ ๋นˆ ์ƒ์„ฑ
order.forEach(beanFactory::createBean);

// ์ปฌ๋ ‰์…˜ ์ฃผ์ž… ํ›„์ฒ˜๋ฆฌ
beanFactory.postProcessListInjections();

@Primary์™€ ๋นˆ ์„ ํƒโ€‹

๋™์ผํ•œ ํƒ€์ž…์˜ ๋นˆ์ด ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๋•Œ @Primary๋กœ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

private String choosePrimary(Class<?> requiredType, Set<String> candidates) {
// 1. @Primary๊ฐ€ ๋ถ™์€ ๋นˆ ์ฐพ๊ธฐ
List<String> primaries = candidates.stream()
.filter(name -> {
BeanDefinition d = beanDefinitions.get(name);
return d != null && d.isPrimary();
})
.toList();

if (primaries.size() == 1) return primaries.get(0);
if (primaries.size() > 1)
throw new RuntimeException("@Primary beans conflict for type " + requiredType.getName());

return null;
}

๋นˆ ํ›„์ฒ˜๋ฆฌ (Bean Post Processing)โ€‹

// ๋นˆ ์ƒ์„ฑ ํ›„ BeanPostProcessor ์ ์šฉ
Object processedBean = beanInstance;
for (BeanPostProcessor processor : beanPostProcessors) {
Object result = processor.postProcessBeforeInitialization(def.getName(), processedBean);
if (result != null) processedBean = result;
}
for (BeanPostProcessor processor : beanPostProcessors) {
Object result = processor.postProcessAfterInitialization(def.getName(), processedBean);
if (result != null) processedBean = result;
}

์ˆœํ™˜ ์˜์กด์„ฑ ๊ฐ์ง€โ€‹

Sprout๋Š” ์‹œ์ž‘ ์‹œ BeanGraph๋ฅผ ํ†ตํ•ด ์ˆœํ™˜ ์˜์กด์„ฑ์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘์„ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.

@Component
public class ServiceA {
public ServiceA(ServiceB serviceB) { /* ... */ }
}

@Component
public class ServiceB {
public ServiceB(ServiceC serviceC) { /* ... */ }
}

@Component
public class ServiceC {
public ServiceC(ServiceA serviceA) { /* ... */ } // ์ˆœํ™˜ ์˜์กด์„ฑ!
}

// ์œ„์ƒ ์ •๋ ฌ ์‹œ ์ˆœํ™˜ ์˜์กด์„ฑ์ด ๊ฐ์ง€๋˜์–ด ์‹œ์ž‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ

๋นˆ ๋“ฑ๋ก๊ณผ ๊ฒ€์ƒ‰โ€‹

ํƒ€์ž…๋ณ„ ๋นˆ ๋งคํ•‘โ€‹

// ํƒ€์ž…๋ณ„ ๋นˆ ์ด๋ฆ„ ๋งคํ•‘ (์ธํ„ฐํŽ˜์ด์Šค, ์ƒ์œ„ํด๋ž˜์Šค ํฌํ•จ)
private void registerInternal(String name, Object bean) {
singletons.put(name, bean);

Class<?> type = bean.getClass();
primaryTypeToNameMap.putIfAbsent(type, name);
typeToNamesMap.computeIfAbsent(type, k -> new HashSet<>()).add(name);

// ์ธํ„ฐํŽ˜์ด์Šค ๋“ฑ๋ก
for (Class<?> iface : type.getInterfaces()) {
primaryTypeToNameMap.putIfAbsent(iface, name);
typeToNamesMap.computeIfAbsent(iface, k -> new HashSet<>()).add(name);
}

// ์ƒ์œ„ ํด๋ž˜์Šค ๋“ฑ๋ก
for (Class<?> p = type.getSuperclass();
p != null && p != Object.class;
p = p.getSuperclass()) {
primaryTypeToNameMap.putIfAbsent(p, name);
typeToNamesMap.computeIfAbsent(p, k -> new HashSet<>()).add(name);
}
}

๋นˆ ๊ฒ€์ƒ‰โ€‹

@Override
public <T> T getBean(Class<T> requiredType) {
// 1. ์ด๋ฏธ ์ƒ์„ฑ๋œ ๋นˆ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
T bean = getIfPresent(requiredType);
if (bean != null) return bean;

// 2. ํ›„๋ณด ์ˆ˜์ง‘
Set<String> candidates = candidateNamesForType(requiredType);
if (candidates.isEmpty()) {
throw new RuntimeException("No bean of type " + requiredType.getName() + " found");
}

// 3. Primary ์„ ํƒ
String primary = choosePrimary(requiredType, candidates);
if (primary == null) {
if (candidates.size() == 1) primary = candidates.iterator().next();
else throw new RuntimeException("No unique bean of type " + requiredType.getName());
}

// 4. ํ•„์š”์‹œ ์ƒ์„ฑ ํ›„ ๋ฐ˜ํ™˜
return (T) createIfNecessary(primary);
}

๋ชจ๋ฒ” ์‚ฌ๋ก€โ€‹

1. ์ƒ์„ฑ์ž ์ฃผ์ž… ์‚ฌ์šฉโ€‹

// ๊ถŒ์žฅ: ์ƒ์„ฑ์ž ์ฃผ์ž…์œผ๋กœ ๋ถˆ๋ณ€์„ฑ ๋ณด์žฅ
@Service
public class UserService {
private final UserRepository repository;

public UserService(UserRepository repository) {
this.repository = repository;
}
}

2. ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜ ์„ค๊ณ„โ€‹

// ๊ถŒ์žฅ: ์ธํ„ฐํŽ˜์ด์Šค์— ์˜์กด
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor;

public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}

@Component
public class StripePaymentProcessor implements PaymentProcessor {
// ๊ตฌํ˜„
}

3. ์ˆœํ™˜ ์˜์กด์„ฑ ๋ฐฉ์ง€โ€‹

// ๊ถŒ์žฅ: ์ด๋ฒคํŠธ๋กœ ์ˆœํ™˜ ๋Š๊ธฐ
@Service
public class OrderService {
private final EventPublisher eventPublisher;

public void processOrder(Order order) {
// ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
eventPublisher.publish(new OrderProcessedEvent(order));
}
}

@Component
public class InventoryService {
@EventListener
public void handleOrderProcessed(OrderProcessedEvent event) {
// ์žฌ๊ณ  ์—…๋ฐ์ดํŠธ
}
}

4. @Order๋กœ ์ˆœ์„œ ์ œ์–ดโ€‹

@Component
@Order(1)
public class ValidationFilter implements Filter {
// ๋จผ์ € ์‹คํ–‰
}

@Component
@Order(2)
public class AuthenticationFilter implements Filter {
// ๊ฒ€์ฆ ํ›„ ์‹คํ–‰
}

์„ฑ๋Šฅ ์ตœ์ ํ™”โ€‹

์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ์ฆ‰์‹œ ๋กœ๋”ฉโ€‹

Sprout๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ์‹ฑ๊ธ€ํ†ค ๋นˆ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ์‹œ์ž‘ ์‹œ ์„ค์ • ์˜ค๋ฅ˜ ์กฐ๊ธฐ ๋ฐœ๊ฒฌ
  • ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์˜ˆ์ธก ๊ฐ€๋Šฅ

๋นˆ ์Šค์ฝ”ํ”„โ€‹

ํ˜„์žฌ Sprout๋Š” ์‹ฑ๊ธ€ํ†ค ์Šค์ฝ”ํ”„๋งŒ ์ง€์›ํ•˜๋ฉฐ, ๋ชจ๋“  ๋นˆ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค๋งŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

ํ™•์žฅ ํฌ์ธํŠธโ€‹

BeanDefinitionRegistrarโ€‹

์‚ฌ์šฉ์ž ์ •์˜ ๋นˆ ์ •์˜๋ฅผ ๋™์ ์œผ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

public class MyFeatureAutoConfiguration implements BeanDefinitionRegistrar {
@Override
public Collection<BeanDefinition> registerAdditionalBeanDefinitions(
Collection<BeanDefinition> existingDefs) {
// ์กฐ๊ฑด๋ถ€ ๋นˆ ๋“ฑ๋ก ๋กœ์ง
return additionalBeans;
}
}

BeanPostProcessorโ€‹

๋นˆ ์ƒ์„ฑ ๊ณผ์ •์— ๊ฐœ์ž…ํ•˜์—ฌ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Component
public class TimingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(String beanName, Object bean) {
if (bean.getClass().isAnnotationPresent(Timed.class)) {
return createTimingProxy(bean);
}
return bean;
}
}

์•„ํ‚คํ…์ฒ˜ ๋ฆฌํŒฉํ† ๋ง ์š”์•ฝ (v2.0)โ€‹

๋ณ€๊ฒฝ ๋™๊ธฐโ€‹

Sprout v1.x์˜ IoC ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค:

  • DefaultListableBeanFactory๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์ฑ…์ž„์„ ๊ฐ€์ง (SRP ์œ„๋ฐ˜)
  • ๋นˆ ์ƒ์„ฑ ๋กœ์ง์ด ๋‹จ์ผ ๋ฉ”์„œ๋“œ์— ์ง‘์ค‘๋˜์–ด ํ™•์žฅ์ด ์–ด๋ ค์›€
  • ์˜์กด์„ฑ ํ•ด๊ฒฐ ๋กœ์ง์ด ๊ฒฝ์ง๋˜์–ด ์ƒˆ๋กœ์šด ํƒ€์ž… ์ถ”๊ฐ€๊ฐ€ ํž˜๋“ฆ
  • ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ๊ฐ€ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์ƒˆ๋กœ์šด ๋‹จ๊ณ„ ์ถ”๊ฐ€๊ฐ€ ๋ณต์žกํ•จ
  • ํƒ€์ž… ๋งค์นญ ๋กœ์ง์ด ์ค‘๋ณต๋จ (BeanGraph vs BeanFactory)

์ ์šฉ๋œ ๋””์ž์ธ ํŒจํ„ดโ€‹

1. Strategy Pattern (๋นˆ ์ƒ์„ฑ ์ „๋žต)โ€‹

Before:

// createBean ๋ฉ”์„œ๋“œ ๋‚ด์— ๋ชจ๋“  ์ƒ์„ฑ ๋กœ์ง ์ง‘์ค‘ (50+ ์ค„)
if (def.getCreationMethod() == BeanCreationMethod.CONSTRUCTOR) {
// ์ƒ์„ฑ์ž ๋กœ์ง
} else if (def.getCreationMethod() == BeanCreationMethod.FACTORY_METHOD) {
// ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ๋กœ์ง
}

After:

// ์ „๋žต ํŒจํ„ด์œผ๋กœ ๋ถ„๋ฆฌ
BeanInstantiationStrategy strategy = findStrategy(def);
Object beanInstance = strategy.instantiate(def, dependencyResolver, this);

์ด์ :

  • ์ƒˆ๋กœ์šด ์ƒ์„ฑ ๋ฐฉ์‹(๋นŒ๋” ํŒจํ„ด, ์ •์  ํŒฉํ† ๋ฆฌ ๋“ฑ) ์ถ”๊ฐ€ ์šฉ์ด
  • ๊ฐ ์ „๋žต์„ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • OCP(๊ฐœ๋ฐฉ-ํ์‡„ ์›์น™) ์ค€์ˆ˜

2. Chain of Responsibility Pattern (์˜์กด์„ฑ ํ•ด๊ฒฐ)โ€‹

Before:

// resolveDependencies ๋ฉ”์„œ๋“œ์—์„œ if-else ๋ถ„๊ธฐ
if (List.class.isAssignableFrom(paramType)) {
// List ์ฒ˜๋ฆฌ
} else {
// ๋‹จ์ผ ๋นˆ ์ฒ˜๋ฆฌ
}

After:

// Resolver ์ฒด์ธ์œผ๋กœ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ
for (DependencyTypeResolver resolver : typeResolvers) {
if (resolver.supports(paramType)) {
return resolver.resolve(paramType, param, targetDef);
}
}

์ด์ :

  • Optional, Provider ๋“ฑ ์ƒˆ๋กœ์šด ํƒ€์ž… ์ง€์› ์‰ฌ์›€
  • ๊ฐ resolver๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ
  • ํ™•์žฅ์„ฑ ๊ทน๋Œ€ํ™”

3. Phase Pattern (์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ)โ€‹

Before:

// refresh() ๋ฉ”์„œ๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ˆœ์„œ (19์ค„)
scanBeanDefinitions();
instantiateInfrastructureBeans();
instantiateAllSingletons();
// ContextInitializer ์‹คํ–‰...

After:

// Phase ํŒจํ„ด์œผ๋กœ ๋‹จ์ˆœํ™” (10์ค„)
scanBeanDefinitions();
BeanLifecyclePhase.PhaseContext context = new BeanLifecyclePhase.PhaseContext(...);
lifecycleManager.executePhases(context);

์ด์ :

  • ์ƒˆ๋กœ์šด lifecycle ๋‹จ๊ณ„ ์ถ”๊ฐ€๊ฐ€ ๊ฐ„๋‹จํ•จ
  • ๊ฐ ๋‹จ๊ณ„์˜ ์ฑ…์ž„์ด ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ๋จ
  • ํ…Œ์ŠคํŠธ ๋ฐ ๋””๋ฒ„๊น… ์šฉ์ด

4. Service ๋ถ„๋ฆฌ (ํƒ€์ž… ๋งค์นญ)โ€‹

Before:

  • BeanGraph.getBeanNamesForType()
  • DefaultListableBeanFactory.candidateNamesForType()
  • ์ค‘๋ณต๋œ ๋กœ์ง

After:

// BeanTypeMatchingService๋กœ ํ†ตํ•ฉ
public class BeanTypeMatchingService {
public Set<String> findCandidateNamesForType(Class<?> type) { ... }
public String choosePrimary(Class<?> requiredType, ...) { ... }
public Set<String> getBeanNamesForType(Class<?> type) { ... }
}

์ด์ :

  • ํƒ€์ž… ๋งค์นญ ๋กœ์ง์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌ
  • BeanGraph์™€ BeanFactory ๊ฐ„ ์ค‘๋ณต ์ œ๊ฑฐ
  • ์บ์‹ฑ ์ „๋žต ์ ์šฉ ๊ฐ€๋Šฅ

๊ฐœ์„  ๊ฒฐ๊ณผโ€‹

์ •๋Ÿ‰์  ๊ฐœ์„ โ€‹

  • SproutApplicationContext.refresh(): 19์ค„ โ†’ 10์ค„ (47% ๊ฐ์†Œ)
  • DefaultListableBeanFactory: 357์ค„ โ†’ 280์ค„ (22% ๊ฐ์†Œ)
  • ์ฑ…์ž„ ๋ถ„๋ฆฌ: 1๊ฐœ ํด๋ž˜์Šค โ†’ 15๊ฐœ ํด๋ž˜์Šค (๋‹จ์ผ ์ฑ…์ž„ ์›์น™)

์ •์„ฑ์  ๊ฐœ์„ โ€‹

  • โœ… ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ ์ฑ…์ž„์ด ๋ช…ํ™•ํ•ด์ง
  • โœ… ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€๊ฐ€ ์‰ฌ์›Œ์ง (OCP ์ค€์ˆ˜)
  • โœ… ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ ํ–ฅ์ƒ
  • โœ… ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ
  • โœ… Spring๊ณผ ์œ ์‚ฌํ•œ ํ™•์žฅ์„ฑ ํ™•๋ณด

ํ•˜์œ„ ํ˜ธํ™˜์„ฑโ€‹

๋ชจ๋“  ๊ธฐ์กด ๋™์ž‘ 100% ๋ณด์กด:

  • Infrastructure ๋นˆ ์šฐ์„  ๋“ฑ๋ก
  • BeanPostProcessor ์ ์‹œ ๋“ฑ๋ก
  • PostInfrastructureInitializer์— ํŒจํ‚ค์ง€ ์ •๋ณด ์ „๋‹ฌ
  • List ์ฃผ์ž… ํ›„์ฒ˜๋ฆฌ
  • ์œ„์ƒ ์ •๋ ฌ ๊ธฐ๋ฐ˜ ์˜์กด์„ฑ ์ˆœ์„œ ๋ณด์žฅ
  • ์ˆœํ™˜ ์˜์กด์„ฑ ๊ฐ์ง€

ํ–ฅํ›„ ํ™•์žฅ ๋ฐฉํ–ฅโ€‹

๋ฆฌํŒฉํ† ๋ง๋œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์ƒˆ๋กœ์šด ์˜์กด์„ฑ ํƒ€์ž… ์ง€์›

    • Optional<T>: ์„ ํƒ์  ์˜์กด์„ฑ
    • Provider<T>: ์ง€์—ฐ ๋กœ๋”ฉ
    • Map<String, T>: ์ด๋ฆ„๋ณ„ ๋นˆ ๋งคํ•‘
  2. ์ƒˆ๋กœ์šด ๋นˆ ์ƒ์„ฑ ๋ฐฉ์‹

    • ๋นŒ๋” ํŒจํ„ด ๊ธฐ๋ฐ˜ ์ƒ์„ฑ
    • ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ
    • ํ”„๋กœํ† ํƒ€์ž… ์Šค์ฝ”ํ”„
  3. ์ƒˆ๋กœ์šด ์ƒ๋ช…์ฃผ๊ธฐ ๋‹จ๊ณ„

    • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ™•์žฅ์„ฑ
    • Lazy ์ดˆ๊ธฐํ™” ์ง€์›
    • ๋นˆ ์ƒ์„ฑ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง

๊ฒฐ๋ก โ€‹

Sprout์˜ IoC ์ปจํ…Œ์ด๋„ˆ๋Š” Spring๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ ๋” ๋‹จ์ˆœํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. v2.0 ๋ฆฌํŒฉํ† ๋ง์„ ํ†ตํ•ด ์ „๋žต ํŒจํ„ด, ์ฑ…์ž„ ์ฒด์ธ ํŒจํ„ด, Phase ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ํ™•์žฅ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋Œ€ํญ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ์ž ์ฃผ์ž…๋งŒ์„ ์ง€์›ํ•˜๊ณ , ๋ช…ํ™•ํ•œ ๋นˆ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋””๋ฒ„๊น…๊ณผ ์ดํ•ด๊ฐ€ ์‰ฌ์šด ๊ฒƒ์ด ํŠน์ง•์ž…๋‹ˆ๋‹ค.