리플렉션(Reflection)
리플렉션은 런타임에 클래스, 메서드, 필드 등의 정보를 조사하고 조작할 수 있는 기능입니다. 리플렉션을 사용하면 프로그램의 구조를 동적으로 분석하고 수정할 수 있어, 매우 유연한 코드를 작성할 수 있습니다.
스프링 프레임워크에서는 다음과 같은 목적으로 리플렉션을 사용합니다:
- 의존성 주입 (Dependency Injection, DI): 특정 클래스의 생성자, 필드, 메서드에 접근하여 동적으로 객체를 생성하고 주입합니다.
- AOP(Aspect Oriented Programming): 메서드 호출 전후에 특정 로직을 실행하기 위해 프록시 객체를 생성하고 관리합니다.
- 어노테이션 처리: 클래스나 메서드에 선언된 어노테이션을 읽고 이를 기반으로 특정 로직을 실행합니다.
어노테이션(Annotations)
어노테이션은 코드에 메타데이터를 추가하는 방법입니다. 주석과 비슷하지만, 어노테이션은 컴파일러나 런타임에 분석할 수 있어, 코드의 동작을 제어하거나 설정 정보를 제공하는 데 사용됩니다. 스프링에서는 다양한 어노테이션을 사용하여 설정과 동작을 정의합니다.
주요 어노테이션 예시는 다음과 같습니다:
- @Component: 스프링 컨테이너에 빈(Bean)으로 등록할 클래스를 표시합니다.
- @Autowired: 스프링이 의존성을 주입하도록 지정합니다.
- @Configuration: 자바 기반의 설정 클래스를 나타냅니다.
- @Bean: 메서드가 스프링 빈을 생성함을 나타냅니다.
- @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)에 따라 올바른 메서드를 호출하는 역할을 합니다. 이 클래스는 리플렉션과 어노테이션을 사용하여 주어진 경로와 일치하는 메서드를 찾아 실행합니다.동작 과정 요약:
- 메인 메서드에서
UserController
객체와Router
객체를 생성합니다.
- 생성한 Router객체의
routing
메서드를 호출하면,UserController
의 모든 @RequestMapping 어노테이션이 붙은 메서드를 찾습니다.
- 어노테이션의 value 값과 매개변수 String path 값을 확인하고, 서로 일치한다면 해당 메서드를 호출합니다.
수정 예제의 목적 : Router 클래스를 변경하지 않고, UserController 클래스의 작성 내용만 추가 및 수정하여도 정상적으로 작동할 수 있게 됩니다.
(개발 과정의 단축 및 유지 보수에 훨씬 유리.)
Share article