클래스의 동적 탐색과 생성
마지막 남은 문제점: 정적인 객체 생성
이전 코드에서는 메서드 호출 부분에서는 매우 유연해졌지만, main 메서드 안에는 여전히 다음과 같은 코드가 존재한다.
UserController uc = new UserController();
findMethod(uc, path);
만약 BoardController라는 새로운 컨트롤러가 추가된다면,
우리는 App.java를 수정하여 new BoardController() 코드를 추가해야만 한다.
이번 new 키워드를 사용하지 않고, 특정 패키지 안에 있는 꼬리표가(@Controller) 있는 클래스들을 자동으로 찾아내어(Scan)
객체로 만드는 '컴포넌트 스캔(Component Scan)' 기능을 구현해 보자. 이는 스프링 프레임워크의 핵심 기능 중 하나이다.
package ex05;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
package ex05;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String url();
}
package ex05;
@Controller // 커스텀 어노테이션 활용
public class UserController {
@RequestMapping(url = "/login")
public void login() {
System.out.println("login() 메서드 호출 됨");
}
@RequestMapping(url = "/join")
public void join() {
System.out.println("join() 메서드 호출 됨");
}
@RequestMapping(url = "/logout")
public void logout() {
System.out.println("logout() 메서드 호출 됨");
}
public void otherMethod() {
System.out.println(".............");
}
}
package ex05;
@Controller
public class BoardController {
@RequestMapping(url = "/list")
public void list() {
System.out.println("board list() 호출 됨");
}
}
'컴포넌트 스캐너' 구현 (App.java)
이제 App.java에 지정된 패키지를 스캔하여 @Controller가 붙은 모든 클래스를 찾아
객체로 만드는 componentScan 메서드를 구현한다. 이 부분이 이번 장의 핵심이다.
package ex05;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class App {
// 컴포넌트 스캔 기능 구현
public static Set<Object> componentScan(String packageName) throws Exception {
// JDK (자바 개발 키드 - 컴파일러, 디버거 등)
// JRE (자바 실행 환경 - 자바 실행하는데 최소한의 환경, API , JVM 포함)
// --- JVM (자바 가상 머신)
// 1. class 객체를 활용해서 인스턴스 화 시킨 객체들을 담을 자료구조 선언
Set<Object> controllers = new HashSet<>();
// 2. UserController.class 파일을 찾아야 한다 - 클래스 로더 가져 옴
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// C:\workspace3\class_reflection\src\ex05\
// URL
// 패키지 구조 표기 법 class_reflection.src.ex05
URL packageUrl = classLoader.getResource(packageName.replace(".", "/"));
System.out.println("packageUrl(classpath) : " + packageUrl);
// 실제 파일에 접근해서 조작할 수 있는 객체를 가져오자.
// File은 실제 a.txt를 가리키는게 아니라 접근(경로) 할 수 있고
// 조작할 수 있는 중간 매개체 클래스 이다.
File packageDir = new File(packageUrl.toURI());
System.out.println("절대 경로 확인 : " + packageDir.getAbsolutePath());
// 넘겨 받은 해당 디렉토리의 모든 파일을 순회 한다.
for(File file : packageDir.listFiles()) {
// .java 찾을게 아니라 .class 파일을 찾아야한다
if (file.getName().endsWith(".class")) {
String className = file.getName().replace(".class", "");
//
String fullClassName = packageName + "." + className;
//
Class<?> clazz = Class.forName(fullClassName);
if(clazz.isAnnotationPresent(Controller.class)) {
// 동적으로 Heap 메모리에 객체를 올려라
Object instance = clazz.newInstance(); // new UserController();
System.out.println(instance.getClass().getName() + " 가 heap 메모리에 올라감 !!!");
controllers.add(instance);
}
}
}
return controllers;
}
// 정적 메서드
public static boolean findMethod(Object controller, String requestPath) throws Exception {
Class<?> clazz = controller.getClass();
Method[] methods = clazz.getMethods();
// 1. 넘겨받은 클래스 객체 안에 @ReqeustMapping 어노테이션 가진 메서드만
// 탐색해 보자.
for(Method m : methods) {
// 우리가 사용한 어노테이션 가져오는 코드
RequestMapping anno = m.getAnnotation(RequestMapping.class);
if(anno != null && anno.url().equals(requestPath)) {
m.invoke(controller);
return true;
}
}
// for 문을 다 돌고 여기로 내려 오면 일치하는 메서드를 못 찾았다.
System.out.println("해당 메소드를 찾을 수 없음 : 404 Not Found");
return false;
}
public static void main(String[] args) throws Exception {
System.out.println("============= 컴퍼넌트 스캔 시작 =============");
Set<Object> controllers = componentScan("ex05");
Scanner scanner = new Scanner(System.in);
String requestPath = scanner.nextLine();
boolean isFound = false;
for(Object controller : controllers) {
isFound = findMethod(controller, requestPath);
if(isFound) {
break;
}
}
if(!isFound) {
System.out.println(requestPath + "404 Not Fount");
}
}
}
마무리 정리
이제 스프링 프레임워크의 DispatcherServlet과 @ComponentScan이 동작하는 원리를 매우 유사하게 흉내 낸,
작은 프레임워크가 되었다.
App.java는 이제 UserController나 BoardController라는 클래스의 이름조차 모른다.
오직 "xxx"라는 패키지와 @Controller, @RequestMapping이라는 '약속(어노테이션)'에만 의존한다.
IoC (제어의 역전)
기존에는 개발자(main 메서드)가 new를 통해 객체를 생성하고 제어했다.
이제는 프레임워크(componentScan 메서드)가 알아서 객체를 생성하고 관리한다.
객체 생성의 제어권이 개발자에게서 프레임워크로 역전되었다.
- 참고 자료
https://velog.io/@plz_no_anr/Java-JVM-메모리-구조
[Java] JVM 메모리 구조
코딩만 할 줄 알면 됐지 JVM까지 알아야 되니?
velog.io

'JAVA' 카테고리의 다른 글
| [3단계] 데이터베이스 연동과 JPA (DIP) (0) | 2025.11.14 |
|---|---|
| [2단계] CRUD API 구현과 계층형 아키텍처 (SRP) (0) | 2025.11.14 |
| 리플렉션(+어노테이션) (0) | 2025.11.14 |
| 우당탕탕 소셜로그인 구현(for kakao) -- 진행중 (0) | 2025.11.11 |
| 리플렉션(동적 분석 도구 API) (0) | 2025.10.27 |