JAVA

리플렉션(+어노테이션)

승운노트 2025. 11. 14. 16:34

경직된 설계에 문제점을 해결할 수 있는 코드를 만들어 보자.

꼬리표 붙이기 (Annotation) UserController의 login() 메서드에는
"/login"이라는 정보를, join() 메서드에는 "/join"이라는 정보를 담은 어노테이션을 붙인다.

 

스캐너로 읽기 (Reflection) App.java는 더 이상 if-else로 분기하지 않는다.
대신 UserController 클래스 전체를 리플렉션으로 스캔한다.

 

동적 매핑 및 실행 App.java는 스캔한 모든 메서드를 확인하며,
사용자가 입력한 requestPath와 어노테이션의 path 값이 일치하는 메서드를 찾아 invoke()로 실행한다.

 

package ex04;

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();
}

 

 

UserController

 

package ex04;

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(".............");
    }

}

 

 

App

 

package ex04;

import java.lang.reflect.Method;
import java.util.Scanner;

public class App {

    // 정적 메서드
    public static void 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;
           }
        }
        // for 문을 다 돌고 여기로 내려 오면 일치하는 메서드를 못 찾았다.
        System.out.println("해당 메소드를 찾을 수 없음 : 404 Not Found");
    }


    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        String requestPath = scanner.nextLine();

        UserController userController = new UserController();
        // 수동으로 new BoardController() 문제가 남아 있다.
        findMethod(userController, requestPath);
    }
}

 

 

App.java의 main 메서드에서 경직된 if-else 분기문이 완전히 사라졌다.

App.java는 더 이상 login(), join()이라는 메서드 이름에 의존하지 않는다.
오직 @RequestMapping이라는 '약속(꼬리표)'에만 의존한다

OCP 준수 (확장성 확보) 만약 UserController에 logout() 기능을 추가하고 싶다면 어떻게 해야 할까?
UserController.java에 위 메서드를 추가하기만 하면 된다.
App.java는 단 한 줄도 수정할 필요 없이 /logout 경로를 정상적으로 처리한다

이것이 바로 '확장에는 열려있고, 수정에는 닫혀있는' OCP의 좋은 예이다.

남아있는 문제점

App.java는 메서드 레벨에서는 유연해졌지만, 여전히 new UserController()라는 코드가 main 메서드에 하드코딩되어 있다.

만약 BoardController라는 새로운 컨트롤러 클래스가 추가된다면, App.java는 new BoardController() 코드를 추가하고 findMethod를 또 호출해야 하는 if-else 문제에 다시 부딪히게 된다.