[스프링 부트] 1. 어노테이션(Annotation)과 리플렉션(Reflection)

KangHo Lee's avatar
Nov 14, 2024
[스프링 부트] 1. 어노테이션(Annotation)과 리플렉션(Reflection)

1. 리플렉션(Reflection)

💡
  • JVM은 클래스 정보를 클래스 로더를 통해 읽어와서 해당 정보를 JVM 메모리에 저장한다.
  • 그렇게 저장된 클래스에 대한 정보가 마치 거울에 투영된 모습과 닮아있어, 리플렉션이라는 이름을 가지게 되었다.
  • 리플렉션은 런타임에 클래스, 메소드, 필드 등을 동적으로 조사하고 조작할 수 있는 기능을 제공합니다.
  • 리플렉션은 사용하면 컴파일 시간에는 알 수 없는 정보에 접근할 수 있고, 객체의 타입이나 메서드를 동적으로 호출할 수 있습니다.

2. 어노테이션(Annotation)

💡
  • 코드에 메타 데이터를 추가하는 방법
  • 메타 데이터란 컴파일 과정과 실행 과정에서 코드를 어떻게 처리해야 하는지를 알려주기 위한 추가 정보
  • 리플렉션을 사용하면 클래스와 메소드에 어떤 어노테이션이 붙어 있는지 확인할 수 있다

어노테이션 만드는 법

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); }
  • @Retention(RetentionPolicy.RUNTIME)
    • 런타임(JVM)때 분석할지 컴파일(컴파일러, javac)단계에서 분석할지 설정
  • @Target(ElementType.METHOD)
    • 어노테이션의 위치를 설정
    • METHOD → 메서드 위에, TYPE → 클래스 위에, PARAMETER → 매개변수에 (ex: @RequestParam)
  • String value();
    • @RequestMapping(value = “/login”)
    • 이름이 value일 경우 생략이 가능하다
      • @RequestMapping(“/login”)

3. 어노테이션과 리플렉션 응용 소스 코드

1) 잘못된 소스 코드

  • 1차 개발자가 작성한 Router 클래스
/** * 1차 개발자가 작성하는 코드 */ public class Router { UserController uc; public Router(UserController uc) { this.uc = uc; } public void routing(String path) { if (path.equals("/login")) { uc.login(); } else if (path.equals("/join")) { uc.join(); } } }
  • 2차 개발자가 작성한 UserController 클래스
/** * 2차 개발자가 작성하는 코드 */ public class UserController { public void login() { System.out.println("로그인"); } public void join() { System.out.println("회원가입"); } }
  • Application
public class App { public static void main(String[] args) { Router router = new Router(new UserController()); Scanner sc = new Scanner(System.in); String path = sc.nextLine(); router.routing(path); } }
💡
2차 개발자가 UserController 에 logout()을 추가하려면 Router 클래스를 수정해야 한다.
→ 1차 개발자에게 연락해서 Router 클래스 수정 요청이 필요

2) 어노테이션을 활용, 유연하고 기능 확장이 가능한 코드

- 어노테이션

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); }

- 1차 개발자가 작성하는 Router

public class Router { UserController uc; public Router(UserController uc) { this.uc = uc; } public void routing(String path) { // 1. 메서드 찾아내기 Method[] methods = uc.getClass().getMethods(); // 2. 어노테이션 체크하기 for (Method m : methods) { // 어노테이션이 없으면 rm은 null RequestMapping rm = m.getAnnotation(RequestMapping.class); // 3. value와 path 일치 확인해서 일치하면 invoke 하기 if (rm == null) break; if (rm.value().equals(path)) { try { m.invoke(uc); } catch (Exception e) { throw new RuntimeException("메서드 실행중 오류가 발생했어요"); } } } } }
  1. Method[] methods = uc.getClass().getMethods();
      • import java.lang.reflect.Method;
      • UserController uc 안의 public한 메서드 모두를 methods에 넣습니다.
  1. RequestMapping rm = m.getAnnotation(RequestMapping.class);
      • methods 안에서 @RequestMapping이 있는 메서드를 선택
      💡
      • UserController 에 작성한 메서드는 3개 뿐이지만 Object를 상속하는 과정에서 보이지 않는 메서드가 더 있다.
      • 그렇기 때문에 rm 이 null이 될 수도 있기 때문에 if (rm == null) break; 로 null 처리를 해줘야 한다.
  1. m.invoke(uc);
      • invoke → 메서드 이름이 없어도 해당 메서드를 호출 가능합니다.
      • 매개변수 uc를 넣어줘야 합니다.
      • 메서드를 어느 객체의 인스턴스에서 호출할지 정해줘야 하기 때문입니다.

- 2차 개발자가 작성하는 UserController

public class UserController { // 이름이 value면 생략 가능 @RequestMapping("/login") public void login() { System.out.println("로그인"); } @RequestMapping("/join") public void join() { System.out.println("회원가입"); } @RequestMapping(value = "/logout") public void logout() { System.out.println("로그아웃"); } @RequestMapping("/userinfo") public void userinf() { System.out.println("유저정보"); } }

- Application

public class App { public static void main(String[] args) { Router router = new Router(new UserController()); Scanner sc = new Scanner(System.in); String path = sc.nextLine(); router.routing(path); } }
💡
UserController 에 기능을 추가해도 Router 클래스를 수정할 필요가 없어졌습니다.
 
Share article

devleekangho