[기술정리] 리플렉션(Reflection)과 어노테이션(Annotation)

이나겸's avatar
Nov 14, 2024
[기술정리] 리플렉션(Reflection)과 어노테이션(Annotation)

리플렉션(Reflection)

💡
실행중인 프로그램의 정보(클래스, 메서드, 필드 등)를 동적으로 가져오고 조작할 수 있는 기능
⇒ 프로그램이 자신의 구조에 대한 정보 조사
⇒ 실행 중에 객체의 클래스 스캔해서 찾아내기
⇒ 메소드와 필드에 접근 및 메소드 호출
  • 런타임(실행) 시점에 클래스를 가져와서 사용해야 할 때 필요
    • 클래스의 파일 위치나 이름만 있으면 해당 클래스의 정보를 얻어내고, 객체 생성 가능(new 해줌)
  • 모든 코드를 하나하나 관리 ⇒ 리플렉션을 통한 접근은 느림
  • 장점
    • 구체적인 클래스를 알지 못해도 동적으로 클래스를 만들어 의존 관계 만들 수 있음
  • 단점
    • private 데이터도 접근 가능해서 캡슐화 어려움
    • 런타임 단계에서 에러 발생하기 때문에 디버깅 어려움
 

어노테이션(Annotation)

💡
리플렉션을 효율적으로 사용하기 위해 어노테이션 사용
⇒ 관리를 원하는 메소드에 어노테이션을 달면 해당 메소드만 조작 가능 ⇒ 찾아낼 수 있도록 깃발 꽂기
 

예제

어노테이션을 활용하지 않았을 때의 한계

  • 1차 개발자와 2차 개발자가 있고 1차 개발자가 개발한 코드에서 문제가 있다고 가정했을 때, 어노테이션을 활용하지 않으면 1차 개발자에게 연락해서 해결해야함
    • Router 클래스 : 1차 개발자가 작성하는 코드
    • UserController 클래스 : 2차 개발자가 작성하는 코드
    • App 클래스의 main에서 실행
package ex01; /* * 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(); } else if (path.equals("/logout")) { uc.logout(); } } } package ex01; /* * 2차 개발자가 작성하는 코드 */ public class UserController { public void login() { System.out.println("로그인"); } public void join() { System.out.println("회원가입"); } public void logout() { System.out.println("로그아웃"); } } package ex01; import java.util.Scanner; 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(); // 실행하고 /login을 입력하면 로그인 출력 // /join을 입력하면 회원가입 출력 // /logout을 입력하면 로그아웃 출력 router.routing(path); } }
 

어노테이션을 활용했을때

  • 어노테이션이 붙어있는(깃발이 꽂혀있는) 정보를 알아서 찾아내고 조작하는 리플렉션 가능
    • RequestMapping 인터페이스
      • @interface RequestMapping : RequestMapping이라는 사용자 정의 어노테이션 선언 (어노테이션 자체가 interface임을 참고)
      • @Target : 해당 어노테이션의 위치 결정
      • @Retention : 해당 어노테이션을 JVM이 언제 봐야하는지 정해줌
    • Router 클래스 : 1차 개발자가 작성하는 코드 ⇒ Routing이 Dispatcher Servlet의 역할
      • 메소드 찾아내기
      • 어노테이션 체크해서 조건에 맞는 메소드 호출(invoke)
      💡
      Dispatcher Servlet 참고
      • Spring MVC의 전면 컨트롤러
      • 클라이언트 요청을 처리, 적절한 Controller를 찾아내서 처리한 후 결과를 View에 렌더링
    • UserController 클래스 : 2차 개발자가 작성하는 코드
      • @RequestMapping 이용해서 경로 지정하고 매핑
    • App 클래스의 main에서 실행
package ex02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /* * @Target(ElementType.METHOD) : 이 어노테이션의 위치 결정 * => 이 경우 메소드 위 * 주의) (ElementType.Type이라고 작성돼있으면 클래스 위에 위치) * * @Retention(RetentionPolicy.RUNTIME) : 이 어노테이션을 JVM이 언제 봐야하는지 정해줌 * => 이 경우 runtime(실행) 시점에 * */ @Target(ElementType.METHOD) // 이 어노테이션의 위치 결정 @Retention(RetentionPolicy.RUNTIME) // 이 어노테이션을 JVM이 언제 봐야하는지 정해줌 public @interface RequestMapping { // RequestMapping이라는 사용자 정의 어노테이션 선언 // 이 어노테이션을 사용할 때 인자로 받도록 정의한 문자열 // => 이 경우 UserController에서 mapping할때 쓰임 String value(); } package ex02; /* * 1차 개발자가 작성하는 코드 */ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Router { // 의존하기 위해 필드로 데려옴 UserController uc; // 생성자 public Router(UserController uc) { this.uc = uc; } public void routing(String path) { // 1. 메서드 찾아내기 // uc(UserController)의 이름(getClass)의 메소드(getMethods) // => 캡슐화 // 결론적으로 return되는건 UserController의 메소드 Method[] methods = uc.getClass().getMethods(); // 2. 어노테이션 체크하기 for (Method m : methods) { // methods를 순회해서 어노테이션 붙어있는걸 찾아줌 RequestMapping rm = m.getAnnotation(RequestMapping.class); // 3. value와 path 일치 확인해서 일치하면 invoke(호출)하기 if(rm == null) break; // null이면 break(멈춤) // RequestMapping의 value와 실행하고나서 입력할 경로가 일치하면 if(rm.value().equals(path)) { try { // invoke : 호출하다 (호출할 메소드 이름을 몰라도 호출 가능) // => methods에 위치하는 UserController의 메소드 호출 m.invoke(uc); } catch (Exception e) { throw new RuntimeException("메소드 실행 중 오류 발생"); } } } } } package ex02; /* * 2차 개발자가 작성하는 코드 */ public class UserController { // RequestMapping 인터페이스에 String uri가 아니라 String value로 선언해놓음 // => value = ""에서 value =를 생략 가능 // uri의 경우에는 uri =를 생략 불가능 @RequestMapping("/login") public void login() { System.out.println("로그인"); } @RequestMapping("/join") public void join() { System.out.println("회원가입"); } @RequestMapping("/logout") public void logout() { System.out.println("로그아웃"); } @RequestMapping("/userinfo") public void userinfo() { System.out.println("유저정보"); } } package ex02; import ex02.Router; import ex02.UserController; import java.util.Scanner; 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(); // 실행하고 /login을 입력하면 로그인 출력 // /join을 입력하면 회원가입 출력 // /logout을 입력하면 로그아웃 출력 // /userinfo를 입력하면 회원정보 출력 router.routing(path); } }
Share article

Nakyeom's Study