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("메서드 실행중 오류가 발생했어요");
}
}
}
}
}
- Method[] methods = uc.getClass().getMethods();
- import java.lang.reflect.Method;
- UserController uc 안의 public한 메서드 모두를 methods에 넣습니다.
- RequestMapping rm = m.getAnnotation(RequestMapping.class);
- methods 안에서 @RequestMapping이 있는 메서드를 선택
- UserController 에 작성한 메서드는 3개 뿐이지만 Object를 상속하는 과정에서 보이지 않는 메서드가 더 있다.
- 그렇기 때문에 rm 이 null이 될 수도 있기 때문에 if (rm == null) break; 로 null 처리를 해줘야 한다.
- 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