리플렉션, 어노테이션

화낼거양's avatar
Nov 14, 2024
리플렉션, 어노테이션
 
 
 
 

리플렉션(Reflection)

 
리플렉션은 런타임에 클래스, 메서드, 필드 등의 정보를 조사하고 조작할 수 있는 기능입니다. 리플렉션을 사용하면 프로그램의 구조를 동적으로 분석하고 수정할 수 있어, 매우 유연한 코드를 작성할 수 있습니다.
 
스프링 프레임워크에서는 다음과 같은 목적으로 리플렉션을 사용합니다:
  1. 의존성 주입 (Dependency Injection, DI): 특정 클래스의 생성자, 필드, 메서드에 접근하여 동적으로 객체를 생성하고 주입합니다.
  1. AOP(Aspect Oriented Programming): 메서드 호출 전후에 특정 로직을 실행하기 위해 프록시 객체를 생성하고 관리합니다.
  1. 어노테이션 처리: 클래스나 메서드에 선언된 어노테이션을 읽고 이를 기반으로 특정 로직을 실행합니다.
 

어노테이션(Annotations)

 
어노테이션은 코드에 메타데이터를 추가하는 방법입니다. 주석과 비슷하지만, 어노테이션은 컴파일러나 런타임에 분석할 수 있어, 코드의 동작을 제어하거나 설정 정보를 제공하는 데 사용됩니다. 스프링에서는 다양한 어노테이션을 사용하여 설정과 동작을 정의합니다.
 
주요 어노테이션 예시는 다음과 같습니다:
  1. @Component: 스프링 컨테이너에 빈(Bean)으로 등록할 클래스를 표시합니다.
  1. @Autowired: 스프링이 의존성을 주입하도록 지정합니다.
  1. @Configuration: 자바 기반의 설정 클래스를 나타냅니다.
  1. @Bean: 메서드가 스프링 빈을 생성함을 나타냅니다.
  1. @Service, @Repository, @Controller: 각각 서비스 레이어, 리포지토리 레이어, 컨트롤러 레이어 클래스를 나타냅니다.
 
💡
리플렉션과 어노테이션은 스프링 프레임워크의 핵심 기능을 구현하는 데 중요한 역할을 합니다. 리플렉션은 런타임에 동적으로 코드를 조작할 수 있게 해주며, 어노테이션은 설정과 메타데이터를 간결하게 표현할 수 있게 해줍니다.
 
 
 
 
 
 

추가 예제(어노테이션 생성 및 활용) :

 
 
 
  • 개발자 1이 작성하는 코드 내용입니다.
routing 메서드에서 받는 path의 값이 ‘/login’ , ‘/join’ 과 같을 때 UserController의 특정 메서드를 실행합니다.
 
/** * 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가 작성하는 코드 내용입니다.
logout 메서드를 작성하였으나, 해당 메서드를 적용시키려면 개발자 1의 코드를 수정하여야 합니다.
 
/** * 2차 개발자가 작성하는 코드 */ public class UserController { public void login() { System.out.println("로그인"); } public void join() { System.out.println("회원가입"); } public void logout() { System.out.println("로그아웃"); } }
 
💡
결과적으로 위의 방식과 같이 작업을 진행한다면, 개발 뿐만 아니라 유지 보수를 진행하기에 매우 어렵습니다.
 
 
 
 

문제점 해결 예제 :

 
 

RequestMapping 어노테이션 생성(새 코드)

 
@Target(ElementType.METHOD) // 클래스 위, 메서드 위, 필드 위 등등 어디에 어노테이션을 붙일지 결정 @Retention(RetentionPolicy.RUNTIME) // 런타임 시점에 동작하는 어노테이션 public @interface RequestMapping { String value(); }
 
@Target : 괄호안의 값에 따라 클래스 위, 메소드 위 등등 어디에 어노테이션을 붙일지 정할 수 있습니다.
@Retention : 컴파일 시점 또는 런타임 시점 등 어느 시점에 동작할지 정할 수 있습니다.
String value(); << 어노테이션을 작성할 때 value 값을 문자열로 받는다는 의미입니다.
 
 

UserController 클래스에 적용(기존 코드)

 
public class UserController { @RequestMapping(value = "/login") public void login() { System.out.println("로그인"); } // value는 생략하여 적어도 정상 적용 된다. @RequestMapping("/join") public void join() { System.out.println("회원가입"); } @RequestMapping("/logout") public void logout() { System.out.println("로그아웃"); } }
 
위에서 작성한 RequestMapping 어노테이션의 구성 요소대로, 각각의 메소드 위에 어노테이션을 추가 작성하였습니다.
 

실행을 위한 Router 클래스(기존 코드)

 
public class Router { UserController uc; public Router(UserController uc) { this.uc = uc; } public void routing(String path) { // 1. uc 클래스의 모든 메서드를 배열로 들고 오기 Method[] methods = uc.getClass().getMethods(); // 2. 어노테이션 체크하기 for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) break; // 3. value와 path가 일치하는지 확인해서 일치하면 invoke 하기 if (rm.value().equals(path)) { try { // 메서드 실행 method.invoke(uc); } catch (Exception e) { throw new RuntimeException("메서드 실행 중 오류가 발생함"); } } } } }
 

클래스 개요:

Router 클래스는 UserController 클래스의 인스턴스를 사용하여 주어진 경로(path)에 따라 올바른 메서드를 호출하는 역할을 합니다. 이 클래스는 리플렉션과 어노테이션을 사용하여 주어진 경로와 일치하는 메서드를 찾아 실행합니다.
 
 

동작 과정 요약:

  1. 메인 메서드에서 UserController 객체와 Router 객체를 생성합니다.
  1. 생성한 Router객체의 routing 메서드를 호출하면, UserController 의 모든 @RequestMapping 어노테이션이 붙은 메서드를 찾습니다.
  1. 어노테이션의 value 값과 매개변수 String path 값을 확인하고, 서로 일치한다면 해당 메서드를 호출합니다.
 
 
💡
수정 예제의 목적 : Router 클래스를 변경하지 않고, UserController 클래스의 작성 내용만 추가 및 수정하여도 정상적으로 작동할 수 있게 됩니다.
(개발 과정의 단축 및 유지 보수에 훨씬 유리.)
 
Share article

moohyun