[기술정리] 리플렉션과 어노테이션

Stupefyee's avatar
Nov 14, 2024
[기술정리] 리플렉션과 어노테이션

1. 리플렉션 (Reflection)

1. 개념

  • 동적 클래스 정보: 실행 중에 클래스, 메서드, 필드 등의 정보를 동적으로 조회.
  • 런타임 메타데이터: 클래스, 인터페이스, 메서드, 생성자 등의 정보를 런타임에 얻을 수 있음.
  • 코드 유연성: 코드의 유연성을 높여 다양한 동적 작업 가능.

2. 주요 특징

  • 클래스 객체 조회: 클래스명으로 객체를 생성하거나 메서드를 호출.
  • 필드와 메서드 접근: 비공개(private) 멤버에도 접근 가능.
  • 컴파일 시 알 수 없는 정보 처리: 런타임에만 알 수 있는 정보 처리 가능.

3. 사용 예시

  • 프레임워크: 스프링, 하이버네이트 등에서 빈(bean) 생성, 의존성 주입에 사용.
  • 테스트 도구: 테스트 자동화 도구에서 테스트 메서드나 필드에 대한 정보를 동적으로 처리.
  • 동적 프록시: 런타임에 동적으로 프록시 객체를 생성.

4. 장점

  • 동적 처리: 실행 중에 객체를 다룰 수 있음.
  • 유연성: 코드 변경 없이 런타임에 동적으로 동작을 변경.

5. 단점

  • 성능 저하: 리플렉션을 사용하면 성능이 저하될 수 있음.
  • 타입 안전성 부족: 컴파일 타임에 오류를 잡기 어려움.
 

2. 어노테이션 (Annotation)

1. 개념

  • 메타데이터: 코드에 추가적인 정보를 제공하는 메타데이터.
  • 클래스, 메서드, 필드에 부착: 코드에 정보를 제공하거나 처리 방식 지정.
  • 런타임, 컴파일 타임 처리: 주석처럼 코드를 설명하거나, 컴파일 타임 또는 런타임에서 처리됨.

2. 주요 특징

  • 어노테이션 프로세서: 어노테이션을 분석하고 처리하는 도구.
  • 타입 안전성: 컴파일 시 검증을 통해 안정성을 높임.
  • 직관적: 코드의 의도를 명확하게 표현.

3. 사용 예시

  • Spring Framework: @Autowired, @Service, @Controller 등.
  • JUnit: @Test, @Before, @After 등.
  • 직접 정의: 사용자가 원하는 방식으로 어노테이션을 정의하여 사용.

4. 장점

  • 코드 가독성 향상: 코드의 의도를 명확하게 표현.
  • 플러그인 처리: 외부 라이브러리나 프레임워크에서 동적으로 어노테이션을 처리.
  • 유연성: 코드에 변화를 주지 않고 동작을 제어.

5. 단점

  • 어노테이션 의존성: 코드가 어노테이션에 의존적일 수 있음.
  • 런타임 처리 오버헤드: 런타임에 어노테이션을 처리해야 하므로 성능에 영향을 줄 수 있음.

3. 리플렉션과 어노테이션의 결합

1. 개념

  • 어노테이션: 코드에 메타데이터를 추가, 클래스, 메서드, 필드에 정보를 제공.
  • 리플렉션: 런타임에 클래스, 메서드, 필드 등 정보를 동적으로 조회하고 처리.

2. 함께 동작하는 방식

  1. 어노테이션으로 메타데이터 제공
      • 코드에서 어노테이션을 사용해 클래스, 메서드, 필드에 특별한 의미를 부여.
      • 예: @Service, @Autowired 등.
  1. 리플렉션으로 어노테이션 처리
      • 런타임에 리플렉션을 통해 어노테이션을 읽고, 이에 따라 동작 처리.
      • 예: @Autowired 필드에 의존성 자동 주입, @Service 클래스를 자동으로 등록.

3. 사용 예시

  • 스프링 프레임워크:
    • @Autowired: 의존성 주입을 위한 어노테이션.
    • @Service: 서비스 클래스에 대한 메타데이터 제공.
    • 리플렉션: 어노테이션이 붙은 클래스나 메서드를 런타임에 찾아 처리.

4. 코드예시

1. 어노테이션이 없을 때

/** * 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(); } } }
1차 개발자가 배포하는 Router.java
/** * 2차 개발자가 작성하는 코드 */ public class UserController { public void login() { System.out.println("로그인"); } public void join() { System.out.println("회원가입"); } public void logout() { System.out.println("로그아웃"); } }
Router.java를 사용해 2차 개발자가 만든 UserController.java
💡
기능을 확장하려면 1차 개발자에게 라이브러리 수정 요청을 해야하는 한계가 존재
public class App { public static void main(String[] args) { Router router = new Router(new UserController()); Scanner scan = new Scanner(System.in); String path = scan.nextLine(); router.routing(path); } }
실행을 위한 App.java
💡
“/logout”을 콘솔창에 입력하면 아무것도 출력 X

2. 어노테이션이 있을때

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value(); // value만 존재할 경우 어노테이션 파라미터 작성시 생략가능 }
1차 개발자가 생성한 어노테이션 RequestMapping .java
public class Router { UserController uc; public Router(UserController uc) { this.uc = uc; } public void routing(String path) { // 어노테이션 구현 순서 // 1. 메소드 찾아내기 Method[] methods = uc.getClass().getMethods(); // uc의 클래스의 모든 메소드를 가져옴 // 2. 어노테이션 체크 for (Method m : methods) { 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("메소드 실행 중 오류 발생"); } } } } }
만들어진 어노테이션을 구현하는 Router.java
public class UserController { @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("유저인포"); } }
1차 개발자가 제작한 어노테이션을 사용해 2차 개발자가 제작한 UserController
💡
@RequestMapping(value) 만 잘 붙인다면 제한 없이 메소드 구현 가능
App.java 동일.
Share article

stupefyee