Contents
📌 리플렉션의 핵심 개념📌 어노테이션을 함께 사용하면 좋은 이유연습 세팅1. 시나리오A회사B회사A회사B회사전체 코드리플렉션(Reflection)어노테이션(Annotation)메타 어노테이션사용자 정의 어노테이션1. 기본 어노테이션 작성 방법2. 여러 개의 속성(요소)을 가지는 어노테이션3. 기본값이 있는 어노테이션4. value() 속성 사용 시 생략 가능어노테이션을 활용한 코드 작성1. 메서드 시그니처2. 시나리오A회사B회사패키지 리플렉션ex04 패키지 안에 있는 모든 .class 파일 스캔@Component 어노테이션이 붙은 모든 클래스 set에 저장실행GPT의 설명리플렉션 복습⭐ex01ex02 - 리플렉션ex02 - 리플렉션 + 어노테이션ex03 - 컨트롤러 객체 주입런타임 환경에서 스캔 & 분석, 자동으로 메서드 실행(하나의 스레드가 담당한다)
멀티스레드라 리플렉션이 가능한 것
📌 리플렉션의 핵심 개념
개념 | 설명 |
거울(Reflection) | 프로그램이 자기 자신을 들여다볼 수 있음 |
런타임(Runtime) | 실행 중에도 클래스, 메서드 정보를 확인할 수 있음 |
동적 처리(Dynamic Execution) | 실행 중에 새로운 객체를 만들거나 메서드를 호출할 수 있음 |
📌 어노테이션을 함께 사용하면 좋은 이유
리플렉션으로 모든 메서드를 검사할 수도 있지만,
어노테이션을 사용하면 특정 메서드나 필드만 쉽게 구분하고 처리할 수 있습니다.
방법 | 문제점 / 장점 |
리플렉션만 사용 | 🔴 어떤 메서드를 실행할지 직접 조건을 설정해야 함 (비효율적) |
리플렉션 + 어노테이션 | ✅ 특정 기능을 갖춘 메서드만 쉽게 식별 가능 (더 효율적) |
연습 세팅

외부라이브러리가 필요 없기 때문에 인텔리j로 빌드시스템 설정
1. 시나리오
A회사
- 라우팅을 해주는 프로그램을 만들어 팔고 싶다
- 사용자의 클래스를 모르니 인터페이스를 만들어 코드를 짠다
interface Controller {
void login();
void join();
void logout();
}
- 라우팅 코드
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();
}
}
}
- 판매
B회사
- A회사의 라우팅 프로그램을 구매
- 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);
}
}
- 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");
}
}
- 아 우리 회사가 새로운 기능이 필요해
public void showUser() {
System.out.println("show user call");
}
- A회사에게 전화해서 만들어 달라고 요청
A회사
- 요청 받은 기능을 추가함
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();
}
}
}
- 다 만들었다고 B회사에 전화함
B회사
- 전화 내용 확인하고 기능 사용
- 또 새로운 기능이 필요함
- 다시 A회사에 전화 요청함
- 너무 불편해짐
전체 코드
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
→ 메서드 객체
uc
→m
이 실행될 객체
- 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);
}
}
}
}
}
- UserController에 있는 메서드 정보를 가져온다
- 모든 메서드들을 확인하면서 어노테이션을 확인한다
- 어노테이션에 입력된 값을 사용하여 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개만 사용할 테니 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);
}
}
}
}
}
- 컨트롤러에 있는 모든 메서드를 가져와 우리 회사 어노테이션이 있는지 확인하자
- 우리 회사 어노테이션에 들어간 값이 path와 같은지 비교!
- 같으면 이 어노테이션이 등록된 메서드를 실행하자
구분 | getMethods() | getDeclaredMethods() |
접근 제어자 | public 메서드만 가져옵니다. | 모든 접근 제어자( public , protected , default , private )의 메서드를 가져옵니다. |
상속 | 부모 클래스 및 구현한 인터페이스의 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");
}
}
- 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);
}
}
}
}

@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());
}
}
}

실행
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);
}
}

GPT의 설명
"스프링의 어노테이션은 스프링의 리플렉션이 있기 때문에 가능한거지?" 설명해줘
ChatGPT의 말:
네, 맞습니다! 스프링의 어노테이션은 리플렉션(reflection)을 활용하여 동적으로 클래스를 분석하고, 특정 동작을 수행할 수 있게 만들어집니다. 이 과정을 통해 스프링이 클래스와 객체의 메타데이터를 읽고, 애플리케이션의 실행 시 동적으로 처리할 수 있도록 해줍니다.
리플렉션이 무엇인지, 그리고 스프링에서 어떻게 사용되는지에 대해 좀 더 상세히 설명해드릴게요.
1. 리플렉션(Reflection)이란?
리플렉션은 프로그램 실행 중에 클래스, 메서드, 필드와 같은 객체의 메타데이터를 분석하고 수정할 수 있는 기능을 의미합니다. 자바에서 리플렉션을 사용하면, 컴파일 타임이 아니라 런타임에 클래스를 동적으로 검사하고, 객체를 생성하거나 메서드를 호출할 수 있습니다.
예를 들어:
- 클래스를 동적으로 로드하거나,
- 클래스의 메서드나 필드를 동적으로 호출하거나,
- 객체의 정보를 런타임에 읽어오는 것 등이 가능합니다.
2. 스프링에서 어노테이션 사용과 리플렉션:
스프링은 다양한 어노테이션(예:
@Autowired
, @Component
, @Service
, @RequestMapping
등)을 제공하는데, 이 어노테이션들은 스프링의 리플렉션 기능을 사용하여 작동합니다.어노테이션 처리 과정:
- 어노테이션이 클래스에 붙으면 스프링은 애플리케이션의 실행 시, 해당 클래스의 메타데이터를 리플렉션을 통해 읽어옵니다.
- 예를 들어,
@Autowired
어노테이션이 필드나 메서드에 붙으면, 스프링은 이 어노테이션이 붙은 위치를 찾아서 주입할 의존성을 자동으로 찾아서 주입합니다.
- 스프링 컨테이너(예:
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());
}
}
}
}
}

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