[SB] 1. 리플렉션(Reflection)

최재원's avatar
Mar 13, 2025
[SB] 1. 리플렉션(Reflection)
런타임 환경에서 스캔 & 분석, 자동으로 메서드 실행(하나의 스레드가 담당한다)
멀티스레드라 리플렉션이 가능한 것

📌 리플렉션의 핵심 개념

개념
설명
거울(Reflection)
프로그램이 자기 자신을 들여다볼 수 있음
런타임(Runtime)
실행 중에도 클래스, 메서드 정보를 확인할 수 있음
동적 처리(Dynamic Execution)
실행 중에 새로운 객체를 만들거나 메서드를 호출할 수 있음

📌 어노테이션을 함께 사용하면 좋은 이유

리플렉션으로 모든 메서드를 검사할 수도 있지만,
어노테이션을 사용하면 특정 메서드나 필드만 쉽게 구분하고 처리할 수 있습니다.
방법
문제점 / 장점
리플렉션만 사용
🔴 어떤 메서드를 실행할지 직접 조건을 설정해야 함 (비효율적)
리플렉션 + 어노테이션
✅ 특정 기능을 갖춘 메서드만 쉽게 식별 가능 (더 효율적)

연습 세팅

notion image
외부라이브러리가 필요 없기 때문에 인텔리j로 빌드시스템 설정

1. 시나리오

A회사

  1. 라우팅을 해주는 프로그램을 만들어 팔고 싶다
  1. 사용자의 클래스를 모르니 인터페이스를 만들어 코드를 짠다
interface Controller { void login(); void join(); void logout(); }
  1. 라우팅 코드
class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } } }
  1. 판매

B회사

  1. A회사의 라우팅 프로그램을 구매
  1. dispatcher에 우리 회사의 컨트롤러를 붙여서 사용하자
public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); UserController uc = new UserController(); Dispatcher ds = new Dispatcher(uc); ds.routing(path); } }
  1. A회사의 라우팅에는 어떤 방식을 지원하는지 확인하고 구현
class UserController implements Controller { @Override public void login() { System.out.println("Login call"); } @Override public void join() { System.out.println("Join call"); } @Override public void logout() { System.out.println("Logout call"); } }
  1. 아 우리 회사가 새로운 기능이 필요해
public void showUser() { System.out.println("show user call"); }
  1. A회사에게 전화해서 만들어 달라고 요청

A회사

  1. 요청 받은 기능을 추가함
interface Controller { void login(); void join(); void logout(); void showUser(); }
class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } else if (path.equals("/showUser")) { controller.showUser(); } } }
  1. 다 만들었다고 B회사에 전화함

B회사

  1. 전화 내용 확인하고 기능 사용
  1. 또 새로운 기능이 필요함
  1. 다시 A회사에 전화 요청함
  1. 너무 불편해짐

전체 코드

package ex01; import java.util.Scanner; // A회사 입장에서 B회사의 어떤 코드가 추가될지 알 수 없다 // B회사는 필요할 때마다 전화해서 알려줘야 한다 interface Controller { void login(); void join(); void logout(); } // 1. A회사 class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } } } // 2. B회사 (구매할께 - Controller 구현해서 만들어) class UserController implements Controller { @Override public void login() { System.out.println("Login call"); } @Override public void join() { System.out.println("Join call"); } @Override public void logout() { System.out.println("Logout call"); } } public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); UserController uc = new UserController(); Dispatcher ds = new Dispatcher(uc); ds.routing(path); } }

리플렉션(Reflection)

리플렉션(Reflection)은 Java 프로그램이 실행 중에 클래스, 메서드, 필드 등의 정보를 동적으로 분석하고 조작할 수 있도록 해주는 기능입니다. 즉, 컴파일 시점이 아니라 런타임에 클래스와 객체를 다룰 수 있는 기능입니다. heap메모리에 있는 클래스가 아니라 disk에 있는 정보를 확인함
 
리플렉션을 사용하면 다음과 같은 작업이 가능합니다.
  • 클래스 이름, 메서드, 필드, 생성자 등의 정보 가져오기
  • private 필드나 메서드에 접근하여 값 변경 및 호출
  • 동적으로 객체 생성 및 메서드 실행
  • 어노테이션 정보를 읽고 처리
 
package ex02; import java.lang.reflect.Method; public class App { public static void main(String[] args) { UserController uc = new UserController(); Class<?> ucClass = uc.getClass(); Method[] methods = ucClass.getMethods(); for (Method m : methods) { try { if (m.getName().equals("logout")) { // 같은지 분석 m.invoke(uc); // ()안에 표기된 heap 데이터에 m 함수를 call해줌 } } catch (Exception e) { throw new RuntimeException(e); } } } }
Class<?> ucClass = uc.getClass() uc 클래스의 정보를 가져옴(disk에 있는 정보)
  • ? → 와일드카드 타입, 모든 타입을 나타냄
  • getClass() → 클래스 정보 가져오기
Method[] methods = ucClass.getMethods() uc 클래스 정보에서 모든 public 메서드 목록을 가져옴(disk 있는 정보)
  • getMethods(), getDeclaredMethods() → 메서드 정보 가져오기
m.invoke(uc) heap에 있는 uc객체에 존재하는 m메서드를 실행
  • m → 메서드 객체
  • ucm 이 실행될 객체
    • heap 에 있는 데이터를 넣어줘야 함. 메모리에 있어야 실행시켜줌
  • invoke() → 동적으로 메서드 실행

어노테이션(Annotation)

어노테이션은 코드에 추가적인 정보를 제공하는 메타데이터 입니다.
컴파일러, 프레임워크(Spring, JUnit 등), 도구들이 어노테이션을 읽고 특정 동작을 수행 할 수 있습니다.
주요 특징
  • @ 기호로 시작함. (@Override, @Deprecated 등)
  • 주석과 비슷하지만 코드에 영향을 미칠 수 있음.
  • 컴파일러에게 힌트를 주거나, 실행 중 리플렉션을 통해 읽을 수 있음.
  • 런타임(실행 시간)에도 유지 가능한 어노테이션이 있음.

메타 어노테이션

메타 어노테이션은 어노테이션을 정의할 때 사용되는 어노테이션입니다.
어노테이션
설명
@Target
어노테이션 적용 대상 지정 (메서드, 필드, 클래스 등)
@Retention
어노테이션 유지 기간 지정 (컴파일, 런타임 등)
@Documented
문서화할 때 포함 여부 지정 (Javadoc에서 사용)
@Inherited
자식 클래스에 자동 상속 가능하도록 설정
@interface
사용자 정의 어노테이션

사용자 정의 어노테이션

package ex02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 언제 작동해야 하나 (런타임 or 컴파일) (시점에 따라 오류 알려줌) @Target(ElementType.METHOD) // 어느 타입위에 붙여야 하나 (class or method) public @interface RequestMapping { String value(); }

어노테이션을 정의하는 다양한 방법

1. 기본 어노테이션 작성 방법

public @interface MyAnnotation { String value(); }
속성(요소, Element) 정의: String value();
기본 사용 예시:
@MyAnnotation("Hello") public void myMethod() {

2. 여러 개의 속성(요소)을 가지는 어노테이션

어노테이션은 하나 이상의 속성(요소, Element)을 가질 수 있습니다.
public @interface MyAnnotation { String name(); int age(); }
✔ 사용 예시:
@MyAnnotation(name = "John", age = 30) public void myMethod() {}
속성이 여러 개 있을 경우, 모든 속성을 지정해야 합니다.

3. 기본값이 있는 어노테이션

속성에 기본값을 지정하면, 어노테이션을 사용할 때 해당 속성을 생략할 수 있음.
public @interface MyAnnotation { String name() default "Unknown"; int age() default 0; }
✔ 사용 예시:
@MyAnnotation // 기본값 사용 public void myMethod() {} @MyAnnotation(name = "Alice") // age는 기본값 0 public void anotherMethod() {}
기본값이 있는 경우, 생략 가능.

4. value() 속성 사용 시 생략 가능

어노테이션에 value()라는 속성만 있다면, value = "값"을 생략할 수 있음.
public @interface MyAnnotation { String value(); }
✔ 사용 예시 (생략 가능):
@MyAnnotation("Hello") // value 생략 가능 public void myMethod() {} @MyAnnotation(value = "Hello") // 동일한 결과 public void anotherMethod() {}
value 속성이 하나만 존재하면 value = "값"을 생략할 수 있음.
하지만 속성이 여러 개라면 생략할 수 없음.

어노테이션을 활용한 코드 작성

A회사

package ex02; import java.lang.reflect.Method; public class Dispatcher { UserController con; public Dispatcher(UserController con) { this.con = con; } public void routing(String path) { // /login Method[] methods = con.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; // 다음 for문으로 바로 넘어감 if (rm.value().equals(path)) { try { method.invoke(con); } catch (Exception e) { throw new RuntimeException(e); } } } } }
  1. UserController에 있는 메서드 정보를 가져온다
  1. 모든 메서드들을 확인하면서 어노테이션을 확인한다
    1. RequestMapping rm = method.getAnnotation(RequestMapping.class);

      1. 메서드 시그니처

      <T extends Annotation> T getAnnotation(Class<T> annotationClass)
      제네릭 타입 T → 주어진 클래스 타입에 해당하는 어노테이션을 반환함.
      파라미터: annotationClass가져오려는 어노테이션 타입 (예: MyAnnotation.class)
      반환값: 지정한 어노테이션이 존재하면 해당 어노테이션 객체 반환, 없으면 null 반환
  1. 어노테이션에 입력된 값을 사용하여 path와 같은지 비교하고 같으면 해당 어노테이션이 등록된 메서드를 실행

B회사

package ex02; public class UserController { @RequestMapping("/login") public void login() { System.out.println("login call"); } @RequestMapping("/join") public void join() { System.out.println("join call"); } @RequestMapping("/logout") public void logout() { System.out.println("logout call"); } @RequestMapping("/userinfo") public void userinfo() { System.out.println("userinfo call"); } }
  • 사용하고 싶은 각각의 메서드에 어노테이션을 사용
  • 각 어노테이션에 힌트를 작성
    • 여기선 경로를 나타냄

2. 시나리오

리플렉션 & 어노테이션을 추가한다면

A회사

package ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); }
  1. 우리 회사 프로그램에 사용할 어노테이션 작성
  1. 문자열 값을 1개만 사용할 테니 value()만 만들자
package ex03; import java.lang.reflect.Method; public class Dispatcher { Controller con; public Dispatcher(Controller con) { this.con = con; } public void routing(String path) { // /login Method[] methods = con.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; if (rm.value().equals(path)) { try { method.invoke(con); } catch (Exception e) { throw new RuntimeException(e); } } } } }
  1. 컨트롤러에 있는 모든 메서드를 가져와 우리 회사 어노테이션이 있는지 확인하자
  1. 우리 회사 어노테이션에 들어간 값이 path와 같은지 비교!
  1. 같으면 이 어노테이션이 등록된 메서드를 실행하자
구분
getMethods()
getDeclaredMethods()
접근 제어자
public 메서드만 가져옵니다.
모든 접근 제어자(publicprotecteddefaultprivate)의 메서드를 가져옵니다.
상속
부모 클래스 및 구현한 인터페이스의 public 메서드를 포함합니다.
현재 클래스에 직접 선언된 메서드만 가져오고, 부모로부터 상속받은 메서드는 포함하지 않습니다.
주요 사용 목적
객체의 공개된 기능(Public API)을 동적으로 호출하고자 할 때 사용합니다.
클래스 자체의 내부 구조를 분석하거나, private 메서드를 테스트하는 등의 특별한 목적에 사용합니다.

B회사

package ex03; public class UserController { @RequestMapping("/login") public void login() { System.out.println("login call"); } @RequestMapping("/join") public void join() { System.out.println("join call"); } @RequestMapping("/logout") public void logout() { System.out.println("logout call"); } @RequestMapping("/userinfo") public void userinfo() { System.out.println("userinfo call"); } }
  1. A회사에서 제공한 어노테이션을 사용해보자

패키지 리플렉션

ex04 패키지 안에 있는 모든 .class 파일 스캔

package ex04; import java.io.File; import java.net.URL; public class App { public static void main(String[] args) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource("ex04"); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = "ex04." + file.getName().replace(".class", ""); System.out.println(className); } } } }
notion image

@Component 어노테이션이 붙은 모든 클래스 set에 저장

package ex04; import java.io.File; import java.net.URL; import java.util.HashSet; import java.util.Set; public class App { public static void main(String[] args) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 Set<Object> instances = new HashSet(); ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource("ex04"); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = "ex04." + file.getName().replace(".class", ""); //System.out.println(className); try { Class cls = Class.forName(className); if (cls.isAnnotationPresent(Component.class)) { Object instance = cls.getDeclaredConstructor().newInstance();// 클래스 new instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } } // for 종료 for (Object instance : instances) { System.out.println(instance.getClass().getName()); } } }
notion image

실행

package ex04; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.util.HashSet; import java.util.Set; public class DispatcherServlet { public Set<Object> componentScan(String packageName) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 Set<Object> instances = new HashSet(); ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource(packageName); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = packageName + "." + file.getName().replace(".class", ""); try { Class cls = Class.forName(className); if (cls.isAnnotationPresent(Component.class)) { Object instance = cls.getDeclaredConstructor().newInstance(); instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } } // for 종료 return instances; } public void routing(Set<Object> instances, String path) { // /login for (Object instance : instances) { Method[] methods = instance.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; // 다음 for문으로 바로 넘어감 if (rm.value().equals(path)) { try { method.invoke(instance); } catch (Exception e) { throw new RuntimeException(e); } } } } } }
package ex04; import java.util.Scanner; import java.util.Set; public class App { public static void main(String[] args) { // RequestMapping, Component, DispatcherServlet (돈 주고 삼 = SpringWeb라이브러리) Scanner sc = new Scanner(System.in); String path = sc.nextLine(); // /write DispatcherServlet dispatcherServlet = new DispatcherServlet(); Set<Object> instances = dispatcherServlet.componentScan("ex04"); dispatcherServlet.routing(instances, path); } }
notion image
 

GPT의 설명

"스프링의 어노테이션은 스프링의 리플렉션이 있기 때문에 가능한거지?" 설명해줘

ChatGPT의 말:

네, 맞습니다! 스프링의 어노테이션리플렉션(reflection)을 활용하여 동적으로 클래스를 분석하고, 특정 동작을 수행할 수 있게 만들어집니다. 이 과정을 통해 스프링이 클래스와 객체의 메타데이터를 읽고, 애플리케이션의 실행 시 동적으로 처리할 수 있도록 해줍니다.
리플렉션이 무엇인지, 그리고 스프링에서 어떻게 사용되는지에 대해 좀 더 상세히 설명해드릴게요.

1. 리플렉션(Reflection)이란?

리플렉션은 프로그램 실행 중에 클래스, 메서드, 필드와 같은 객체의 메타데이터를 분석하고 수정할 수 있는 기능을 의미합니다. 자바에서 리플렉션을 사용하면, 컴파일 타임이 아니라 런타임에 클래스를 동적으로 검사하고, 객체를 생성하거나 메서드를 호출할 수 있습니다.
예를 들어:
  • 클래스를 동적으로 로드하거나,
  • 클래스의 메서드나 필드를 동적으로 호출하거나,
  • 객체의 정보를 런타임에 읽어오는 것 등이 가능합니다.

2. 스프링에서 어노테이션 사용과 리플렉션:

스프링은 다양한 어노테이션(예: @Autowired, @Component, @Service, @RequestMapping 등)을 제공하는데, 이 어노테이션들은 스프링의 리플렉션 기능을 사용하여 작동합니다.

어노테이션 처리 과정:

  1. 어노테이션이 클래스에 붙으면 스프링은 애플리케이션의 실행 시, 해당 클래스의 메타데이터를 리플렉션을 통해 읽어옵니다.
  1. 예를 들어, @Autowired 어노테이션이 필드나 메서드에 붙으면, 스프링은 이 어노테이션이 붙은 위치를 찾아서 주입할 의존성을 자동으로 찾아서 주입합니다.
  1. 스프링 컨테이너(예: ApplicationContext)가 실행될 때, 스프링은 리플렉션을 사용하여 어노테이션이 붙은 클래스나 메서드를 찾아 해당 처리를 자동으로 해줍니다.

3. 리플렉션을 통한 스프링의 주요 기능:

1) 의존성 주입(Dependency Injection):

  • 스프링에서 @Autowired와 같은 어노테이션을 사용하면, 스프링이 자동으로 객체의 의존성을 주입해줍니다.
  • 리플렉션을 통해 @Autowired가 붙은 필드를 찾고, 해당 필드에 필요한 객체를 찾아서 주입합니다.
  • 예를 들어, @Autowired가 붙은 필드나 메서드를 스프링이 런타임에 리플렉션을 통해 분석하여 해당 타입의 빈(Bean)을 자동으로 주입합니다.

2) 컴포넌트 스캔(Component Scanning):

  • @Component, @Service, @Controller 등의 어노테이션은 스프링이 자동으로 빈을 등록할 수 있게 해줍니다.
  • 스프링은 애플리케이션이 실행될 때 클래스 패스를 스캔하여 어노테이션이 붙은 클래스들을 찾아 빈으로 등록합니다.
  • 이 과정에서도 리플렉션이 사용되어, 어노테이션이 붙은 클래스를 찾아서 빈으로 등록하고, 이후 의존성 주입이나 빈 관리 등을 할 수 있도록 도와줍니다.

3) AOP (Aspect-Oriented Programming):

  • 스프링의 AOP(관점 지향 프로그래밍)에서 어노테이션을 사용하는 경우에도 리플렉션이 중요한 역할을 합니다.
  • 예를 들어, @Transactional 어노테이션이 붙은 메서드를 스프링은 리플렉션을 통해 찾아내어, 그 메서드가 실행될 때 트랜잭션을 시작하고 종료하는 처리를 합니다.
  • 이 과정에서 스프링은 어노테이션이 붙은 메서드를 런타임에 동적으로 감지하고, 필요한 기능을 적용합니다.

4. 왜 리플렉션을 사용하는가?

리플렉션을 사용하면 애플리케이션을 더 유연하고 동적으로 만들 수 있기 때문입니다. 스프링에서는 리플렉션을 활용하여 코드를 수정하지 않고도 클래스, 메서드, 필드의 정보를 런타임에 동적으로 처리할 수 있습니다. 이로 인해 다음과 같은 장점이 있습니다:
  • 유연한 의존성 주입: 개발자가 의존성 주입을 명시적으로 처리할 필요 없이, 스프링이 자동으로 적절한 객체를 주입합니다.
  • 개발 편의성: 다양한 기능을 어노테이션만으로 선언적으로 처리할 수 있어, 복잡한 코드 작성이 필요 없어집니다.
  • AOP 구현: 트랜잭션 처리나 로깅 등을 코드의 변경 없이 어노테이션으로 간편하게 처리할 수 있습니다.

결론:

스프링의 어노테이션리플렉션을 통해 동작합니다. 스프링은 애플리케이션 실행 시 리플렉션을 사용하여 어노테이션이 붙은 클래스, 메서드, 필드 등을 찾아내고, 이를 기반으로 다양한 작업(의존성 주입, 컴포넌트 등록, AOP 등)을 자동으로 처리합니다. 리플렉션 덕분에 스프링은 복잡한 설정을 간소화하고, 개발자가 더 적은 코드로 강력한 기능을 활용할 수 있게 도와줍니다.
 

리플렉션 복습⭐

ex01

리플렉션이 없을 때

App

package ex01; import java.util.Scanner; // A 개발자가 만든 것 public class App { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String path = scanner.nextLine(); UserController userController = new UserController(); // path = /login -> login(), /join -> join() if (path.equals("/login")) { userController.login(); } else if (path.equals("/join")) { userController.join(); } else if (path.equals("/logout")) { userController.logout(); } else { System.out.println("지원하지 않는 주소입니다"); } } }

UserController

package ex01; // B 개발자가 만든 것 public class UserController { public void login() { System.out.println("login 호출됨"); } public void join() { System.out.println("join 호출됨"); } public void logout() { System.out.println("logout 호출됨"); } }

ex02 - 리플렉션

App

package ex02; import java.lang.reflect.Method; import java.lang.reflect.Parameter; // A 개발자가 만든 것 public class App { public static void main(String[] args) { String path = "/login"; Method[] methods = UserController.class.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); System.out.println("메서드 이름=========================="); if (method.getName().equals("login")) { // int paramCount = method.getParameterCount(); // System.out.println(paramCount); Parameter[] params = method.getParameters(); for (Parameter param : params) { System.out.println(param.getName()); System.out.println(param.getType()); } } } } }
notion image

UserController

package ex02; // B 개발자가 만든 것 public class UserController { public void login(int a, String b) { System.out.println("login 호출됨"); } public void join() { System.out.println("join 호출됨"); } public void logout() { System.out.println("logout 호출됨"); } }

ex02 - 리플렉션 + 어노테이션

RequestMapping

package ex02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 언제 실행될 것인지 @Target(ElementType.METHOD) // METHOD -> 메서드 위에서만 동작함 public @interface RequestMapping { // 어노테이션은 @interface 로 만든다 String value(); // value 는 기본 키워드임 생략 가능함 }

App

package ex02; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; // A 개발자가 만든 것 public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); Method[] methods = UserController.class.getDeclaredMethods(); UserController uc = new UserController(); for (Method method : methods) { Annotation anno = method.getDeclaredAnnotation(RequestMapping.class); RequestMapping rm = (RequestMapping) anno; if (rm.value().equals(path)) { try { method.invoke(uc); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } } }

UserController

package ex02; // B 개발자가 만든 것 public class UserController { @RequestMapping("/login") public void login() { System.out.println("login 호출됨"); } @RequestMapping("/join") public void join() { System.out.println("join 호출됨"); } @RequestMapping("/logout") public void logout() { System.out.println("logout 호출됨"); } }
  • 사용만 하면 됨

ex03 - 컨트롤러 객체 주입

App

package ex03; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Scanner; // A 개발자가 만든 것 public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); // 컨트롤러의 모든 메서드 찾기 Method[] methods = UserController.class.getDeclaredMethods(); // 컨트롤러 메서드를 invoke 하기 위한 heap 생성 // 여기서 method 를 찾아서 invoke 해줌 UserController uc = new UserController(); // 세션 유저는 미리 만들어 놓음 SessionUser sessionUser = SessionUser.getInstance(); // 컨트롤러의 모든 메서드 순회 for (Method method : methods) { Annotation anno = method.getDeclaredAnnotation(RequestMapping.class); RequestMapping rm = (RequestMapping) anno; // 어노테이션과 경로가 같은 메서드 찾음 if (rm.value().equals(path)) { try { // 메서드의 모든 파라미터 찾음 Parameter[] parameters = method.getParameters(); // 실제 실행 되어야할 메서드의 파라미터에 주입하기 위한 배열 Object[] argsToInvoke = new Object[parameters.length]; // 모든 파라미터 순회 for (int i = 0; i < parameters.length; i++) { // 파라미터 가져오는 세팅 Parameter param = parameters[i]; Class<?> paramType = param.getType(); Object instance = null; // 로직 // 어노테이션이 있으면 행위는 확정이다 if (param.isAnnotationPresent(Principal.class)) { instance = sessionUser; } else { // 어노테이션이 없으면 찾아서 넣는다 Method getInstanceMethod = paramType.getMethod("getInstance"); instance = getInstanceMethod.invoke(null); } argsToInvoke[i] = instance; } // invoke 할때 1번은 heap 공간, 2번은 파라미터 배열 method.invoke(uc, argsToInvoke); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } } }
  • 특정 어노테이션이 있을 때와 없을 때의 차이가 있어야 한다

    UserController

    package ex03; // B 개발자가 만든 것 public class UserController { // 어노테이션 보고 세션유저 넣어주기 @RequestMapping("/userinfo") public void userinfo(@Principal SessionUser sessionUser) { System.out.println(sessionUser.getId()); System.out.println(sessionUser.getUsername()); System.out.println("userinfo 호출됨"); } // 그냥 Model 있는거 보고 넣어주기 @RequestMapping("/login") public void login(Model model) { System.out.println(model.getAttribute("username")); // ssar 이 나오면 됨 System.out.println("login 호출됨"); } @RequestMapping("/join") public void join() { System.out.println("join 호출됨"); } @RequestMapping("/logout") public void logout() { System.out.println("logout 호출됨"); } }

    Principal - 어노테이션

    package ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Principal { }

    Model

    package ex03; import java.util.HashMap; import java.util.Map; public class Model { private static Model instance = new Model(); private Model() { attributes.put("username", "ssar"); } public static Model getInstance() { return instance; } private Map<String, Object> attributes = new HashMap<>(); public void addAttribute(String name, Object value) { attributes.put(name, value); } public Object getAttribute(String name) { return attributes.get(name); } }
    • 싱글톤으로 생성

    SessionUser

    package ex03; public class SessionUser { private int id; private String username; private static SessionUser instance = new SessionUser(); private SessionUser() { this.id = 1; this.username = "ssar"; } public static SessionUser getInstance() { return instance; } public int getId() { return id; } public String getUsername() { return username; } }
    • 싱글톤으로 생성

    RequestMapping - 어노테이션

    package ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 언제 실행될 것인지 @Target(ElementType.METHOD) // METHOD -> 메서드 위에서만 동작함 public @interface RequestMapping { // 어노테이션은 @interface 로 만든다 String value(); // value 는 기본 키워드임 생략 가능함 }
    Share article

    jjack1