리플렉션 Reflection

@어노테이션(Annotation)
Sep 02, 2024
리플렉션 Reflection

1. 리플렉션 Reflection

 
Reflection 의 사전적 의미는 빛, 소리, 열 등의 반사, 성찰, 물체가 표면에 비친 이미지나 상을 뜻하고,
프로그래밍에서의 Reflection 은 객체나 클래스의 구조를 런타임에 동적으로 탐색하고 조작하는 기술을 의미한다.
Reflection 을 모르면 스프링을 모르는 것과 다름이 없다.
 

1-1. 어노테이션(Annotation) 이해하기

 
여기 오솔길과 그 옆에는 네 그루의 나무가 있다.
오솔길을 따라 걷는 인간에게 썩은 나무가 있으면 수리하고 가라고 시킬 것이고, 인간은 나무가 썩었는지 안썩었는지를 들여다 보고 앞으로 가야 한다.
 
일을 단순하게 시킨다면, 전체를 풀스캔 해야 한다.
‘속을 비추다’ 라는 의미를 가진 리플렉션은 ‘속을 분석하는 것’이다.
notion image
 
사람이 아니라, 컴파일러가 전체를 보는건 행위를 하는 것이지, 분석은 아니다.
RUNTIME 때 실행하는 게 보고 확인하고 보고 확인하고 .. 가는 것
 
표시가 된 것만 청소하라고 한다면 표시가 된 것만 분석 할 것이다.
이 때, 표시가 되는 것을 어노테이션(Annotation) 이라 한다. (안붙으면 풀스캔, 전체를 다 봐야한다.)
 
 
어노테이션(Annotation) 을 붙여서 그가 붙은 것만 일 시키는 것을 해보자.
 
💡
어노테이션(Annotation)
실행 시에 표시해주는 ‘깃발’.
프로그래밍 적으로 얘기하면 JVM 이 실행 될 때, 코드가 흘러갈 때, JVM 이 보는 힌트
// (주석)
개발자가 보는 힌트
 
행위를 정의하는건 메서드를 잘 넣으면 된다.
notion image
나무를 수리하는 건 동일한데, 어떤 나무는 3m..어떤 나무는 20m.. 상황이 다를 수 있다.
상황이 다른데 어떻게 동일한 동작으로 나무를 수리하지, 하는 게 어려운 것이라 한다.
관점에 따라 하나의 대상이 다르게 보인다. 이것도 마찬가지다. 어떤 관점이냐에 따라 수리하는 방식이 달라질 수 있다. 그걸 짜는 게 어렵다.
 

2. 어노테이션(Annotation) 깃발 달기

 
💡
어노테이션(Annotation)은 JVM 이 보는 힌트, JVM 이 보는 주석이다.
 

2-1. ex01

 
refapp 이라는 이름의 New Project 를 만든다.
notion image
 
src 에서 패키지(ex01) 를 만들고, App 과 UserController 두 개의 클래스를 만든다.
 
notion image
notion image
 
위의 경우, B 개발자가 뭔가를 만들 때마다 A 개발자가 계속해서 만들어야 하는 게 단점, 가능은 하나 매우 귀찮은 일이다.
 

2-2. ex02 어노테이션 설정 전

 
이제 리플렉션으로 코드 속을 분석해보자.
ex02 패키지에 App 과 UserController 는 그대로 가져오고, RequestMapping 어노테이션 파일을 만든다.
 
notion image
 
notion image
 
 
package ex02; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class App { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { String path = "/login"; UserController uc = new UserController(); //System.out.println(uc.getClass()); Method[] methods = uc.getClass().getDeclaredMethods(); // uc.getClass() 안에 어떤 메서드들이 있는지 리턴해줌 for(Method mt : methods) { System.out.println(mt.getName()); mt.invoke(uc); // mt 안에 어느 객체를 들고있는거야? uc 가 null 이면 터질수도있으니 throws 로 exception 던져야함 } } }
package ex02; public @interface RequestMapping { }
package ex02; public class UserController { public void login() { System.out.println("login 호출됨"); } public void join() { System.out.println("join 호출됨"); } }
깃발 설정을 하기 전이니, 모든 코드가 실행 되고 콘솔창에서 확인된다.
notion image
 
 

2-3. 어노테이션 설정하기

 
아래는 RequestMapping 어노테이션에 붙이는 것들.
 
@Target(ElementType.METHOD) // ElementType.METHOD 로, 어노테이션이 메서드 위에 붙일 수 있게 함 @Retention(RetentionPolicy.RUNTIME) // 힌트의 동작을 결정
 
notion image
 
@Retention
  • RUNTIME: 런타임까지 어노테이션이 유지되며, 리플렉션을 통해 읽을 수 있음. @Controller, @Autowired …
  • SOURCE: 컴파일러 단계에서만 의미가 있으며, 컴파일된 .class 파일에는 포함되지 않고 주로 컴파일러 힌트용으로 사용됨. @Override
 
 
notion image
 
notion image
 
@RequestMapping 어노테이션을 login() 메서드에만 붙이고, 이름과 URI 를 같이 넣어준다. (uri = “/login”)
만약, 어노테이션에서 타입이 int 였다면 (uri = 1) 이 될 것.
@RequestMapping에서 value 는 디폴트로 생략 가능
@RequestMapping(value = "/login") @RequestMapping("/login")
 
RequestMapping 어노테이션이 설정 된 메서드만 rm 에 담아서 path 의 경로와 동일할 경우만 invoke 로 값을 뽑는다.
 
notion image
 
실행하면, login()의 출력 값
notion image
혹은 아무것도 뜨지 않는다.
notion image
 
메서드의 순서는 상관없이 login(), join() 이 랜덤하게 나오는 것을 알 수 있다.
 
아래와 같이 join 메서드에도 @RequestMapping을 설정하고 , main 에서 path 를 찾는 url 을 바꿔서 실행하면, 리플렉션으로 인해 join() 의 값이 출력된다.
 
String path = "/join";
 
notion image
 
그렇다면 계속해서 메서드가 늘어나더라도, 리플렉션을 통해 원하는 경로의 값을 구할 수 있어 일을 대폭 줄일 수 있다.
 
package ex02; public class UserController { @RequestMapping(uri = "/login") public void login() { System.out.println("login 호출됨"); } @RequestMapping(uri = "/join") public void join() { System.out.println("join 호출됨"); } @RequestMapping(uri = "/cancel") public void cancel() { System.out.println("cancel 호출됨"); } @RequestMapping(uri = "/withdraw") public void withdraw() { System.out.println("withdraw 호출됨"); } }
 
아래는 다른 파일의 코드 일부이다. 이것 또한 return 되는 건 사실 String 이지만, 어노테이션처리가 되어있기 때문에 알아서 파일을 찾아서 reflection 하는 것으로 볼 수 있다.
 
notion image
 
Share article

eunmouse