리플렉션(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)
- Spring MVC의 전면 컨트롤러
- 클라이언트 요청을 처리, 적절한 Controller를 찾아내서 처리한 후 결과를 View에 렌더링
- UserController 클래스 : 2차 개발자가 작성하는 코드
- @RequestMapping 이용해서 경로 지정하고 매핑
- App 클래스의 main에서 실행
Dispatcher Servlet 참고
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