
요즘 바빠서 글을 쓰는게 늦었다.
지난 편에는 컴포넌트 스캔을 구현해서 메타데이터를 BeanDefinition에 넘겨주는 것까지 했다. 아래가 지난 편이다.
https://jaeiktech.tistory.com/222
순수 자바로 스프링 구현 (1) - 컴포넌트 스캔
아래에는 글쓰기 컨트롤러 글조회 컨트롤러가 있다.public class PostSearchController extends HttpServlet { private final PostService postService; public PostSearchController(PostService postService) { this.postService = postService; } @Ov
jaeiktech.tistory.com
2편을 시작해보자
public static void initialize(String basePackage) throws IOException, URISyntaxException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
Set<Class<?>> beanDefinition = BeanDefinition.initBeanDefinition(basePackage);
for (Class<?> clazz : beanDefinition) {
dependencyInject(clazz);
}
이것은 빈 초기화 메서드다 빈 정의에서 메타데이터를 가져와 포문을 돌면서 DI를 실행한다.
/**
* 의존관계 주입
* 이미 객체가 생성되어 있으면 건너뛴다.
* 리플렉션을 이용하여 클래스의 생성자와 매개변수를 가져온다. 재귀적으로 실행한다.
* boardController -> boardService -> boardRepository 순으로 DFS로 실행한다.
* 리플렉션을 이용하여 객체를 생성하고 빈에 삽입한다.
* 생성 직후 빈 후처리기를 적용하여 프록시가 필요한 빈은 프록시로 교체한다.
*/
public static <T> T dependencyInject(Class<T> clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException {
PostBeanProcessor postBeanProcessor = new PostBeanProcessor(new ProxyFactory());
if (beans.containsKey(clazz)) {
return clazz.cast(beans.get(clazz));
}
Constructor<?> constructor = clazz.getConstructors()[0];
Class<?>[] paramTypes = constructor.getParameterTypes();
Object[] dependencies = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
dependencies[i] = dependencyInject(paramTypes[i]);
System.out.println(dependencies[i] + "의존관계 주입 완료");
}
Object instance = constructor.newInstance(dependencies);
Object processed = postBeanProcessor.scanTargetProxy(instance, clazz);
beans.put(clazz, processed);
System.out.println(clazz + "빈 생성 완료");
return clazz.cast(processed);
}
코드를 이미 완성시킨 부분이 있어 빈 후처리기가 있지만 무시하자
private static Map<Class<?>, Object> beans = new HashMap<>();
빈 저장소다 이 Map은 메타데이터를 키로 객체를 값으로 가진다. 여기서의 값은 빈이다.
if (beans.containsKey(clazz)) {
return clazz.cast(beans.get(clazz));
}
DI전에 메타데이터가 빈에 이미 존재하는지 확인한다. 존재하면 cast로 형변환을 해서 즉시 반환한다.
컨테이너 안에서의 싱글톤을 유지해야 하기 때문이다.
Constructor<?> constructor = clazz.getConstructors()[0];
Class<?>[] paramTypes = constructor.getParameterTypes();
Object[] dependencies = new Object[paramTypes.length];
생성자를 가져오고 생성자에서 파라미터 타입을 추출한다. 같은 크기의 의존 객체 배열을 만든다.
for (int i = 0; i < paramTypes.length; i++) {
dependencies[i] = dependencyInject(paramTypes[i]);
System.out.println(dependencies[i] + "의존관계 주입 완료");
}
그다음 의존관계 주입을 실시하는데 재귀적으로 실행한다. 왜냐하면 예를들어 컨트롤러가 서비스를 의존, 서비스가 리포지터리를 의존할 때 재귀적으로 실행해야 하나의 체인이 완성이 된다. 재귀적으로 실행하지 않으면 객체가 완성이 되지 않아 주입이 실패할 것이다. 순환참조도 이 단계에서 확인하는 것이다. 다만 나는 순환 참조 체크를 구현하지 않았다. 아직 미니 스프링이기 때문이다.. 다른 것들 먼저 만들어 보겠다.
Object instance = constructor.newInstance(dependencies);
beans.put(clazz, instance);
그렇게 생성자를 완성시키면 그로부터 새로운 객체를 만들어 빈 저장소에 넣는다.
public static <T> T getBean(Class<T> clazz) {
return clazz.cast(beans.get(clazz));
}
빈을 가져오는 메서드는 이렇다
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring, C:\project\createSpring\out\production\classes\com\createspring
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board, C:\project\createSpring\out\production\classes\com\createspring\board
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/controller, C:\project\createSpring\out\production\classes\com\createspring\board\controller
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/dto, C:\project\createSpring\out\production\classes\com\createspring\board\dto
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/entity, C:\project\createSpring\out\production\classes\com\createspring\board\entity
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/event, C:\project\createSpring\out\production\classes\com\createspring\board\event
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/repository, C:\project\createSpring\out\production\classes\com\createspring\board\repository
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/board/service, C:\project\createSpring\out\production\classes\com\createspring\board\service
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/spring, C:\project\createSpring\out\production\classes\com\createspring\spring
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/spring/annotation, C:\project\createSpring\out\production\classes\com\createspring\spring\annotation
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/spring/bean, C:\project\createSpring\out\production\classes\com\createspring\spring\bean
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/spring/event, C:\project\createSpring\out\production\classes\com\createspring\spring\event
클래스로더 검사중 file:/C:/project/createSpring/out/production/classes/com/createspring/spring/proxy, C:\project\createSpring\out\production\classes\com\createspring\spring\proxy
class com.createspring.board.service.PostUtil빈 정의 삽입
class com.createspring.board.event.PostEventListener2빈 정의 삽입
class com.createspring.board.controller.PostCreateController빈 정의 삽입
class com.createspring.board.repository.BoardRepository빈 정의 삽입
class com.createspring.board.event.BoardEventPublisher2빈 정의 삽입
class com.createspring.board.event.BoardEventPublisher빈 정의 삽입
class com.createspring.board.controller.PostSearchController빈 정의 삽입
class com.createspring.board.event.PostEventListener빈 정의 삽입
class com.createspring.board.service.PostService빈 정의 삽입
class com.createspring.board.service.PostUtil빈 생성 완료
class com.createspring.board.event.BoardEventPublisher빈 생성 완료
com.createspring.board.service.PostUtil@6bf2d08e의존관계 주입 완료
class com.createspring.board.event.PostEventListener2빈 생성 완료
class com.createspring.board.event.BoardEventPublisher2빈 생성 완료
class com.createspring.board.repository.BoardRepository빈 생성 완료
com.createspring.board.repository.BoardRepository@9f70c54의존관계 주입 완료
com.createspring.board.event.BoardEventPublisher@234bef66의존관계 주입 완료
com.createspring.board.event.BoardEventPublisher2@737996a0의존관계 주입 완료
class com.createspring.board.service.PostService빈 생성 완료
com.createspring.board.service.PostService@13c78c0b의존관계 주입 완료
class com.createspring.board.controller.PostSearchController빈 생성 완료
com.createspring.board.service.PostUtil@6bf2d08e의존관계 주입 완료
class com.createspring.board.event.PostEventListener빈 생성 완료
com.createspring.board.service.PostService@13c78c0b의존관계 주입 완료
class com.createspring.board.controller.PostCreateController빈 생성 완료
자바를 실행시키면 위와 같은 로그가 뜬다.
Context context = tomcat.addContext("", new File(".").getAbsolutePath());
Tomcat.addServlet(context, "postSearchController", BeanFactory.getBean(PostSearchController.class));
context.addServletMappingDecoded("/post/search", "postSearchController");
Tomcat.addServlet(context, "postCreateController", BeanFactory.getBean(PostCreateController.class));
context.addServletMappingDecoded("/post/create", "postCreateController");
tomcat.start();
tomcat.getServer().await();
요청 시에 서블릿이 만들어진 빈을 이용하도록 매핑시킨다.
public class SingletonCheckTest {
@Test
public void singleton() throws IOException, URISyntaxException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
BeanFactory.initialize("com.createspring");
PostService service1 = BeanFactory.getBean(PostService.class);
PostService service2 = BeanFactory.getBean(PostService.class);
System.out.println(service1);
System.out.println(service2);
Assertions.assertSame(service1, service2);
}
}
싱글톤이 되었는지 간단한 테스트를 해보자
com.createspring.board.service.PostService@71c3b41
com.createspring.board.service.PostService@71c3b41
빈 팩토리에서 객체를 각각 가져왔지만 동일성을 확인했다.
다음편에서는 AOP를 활용해서 트랜잭셔널을 만들어보겠다.
'Spring > 스프링을 만들어보자' 카테고리의 다른 글
| 순수 자바로 스프링 구현 (1) - 컴포넌트 스캔 (0) | 2026.03.08 |
|---|