[Spring Boot] 01. 리플렉션과 어노테이션

서회정's avatar
Mar 13, 2025
[Spring Boot] 01. 리플렉션과 어노테이션

01. 리플젝션과 어노테이션 정의

📌
리플젝션(Reflection)
프로그램이 실행되는 동안 자기 자신을 검사하고 수정할 수 있는 기능. 즉, 컴파일 타임이 아니라 런타임에 클래스와 메서드, 변수 등의 정보를 확인하고 조작할 수 있는 기법이라는것.
어노테이션(Annotation)
코드에 추가하는 메타데이터. 주석처럼 보일 수 있지만 프로그램 실행에 영향을 주는 추가정보이다. JVM에게 제공되는 설명서 역할을 하며 이를 확인하여 특정 동작을 수행하게 한다. 스프링 등과 같은 프레임워크에서 코드를 자동으로 실행할 때 사용된다. 설정을 단순화하여 코드 가독성 또한 높일 수 있다.
 

리플렉션이 필요한 이유

  1. 런타임에 클래스 정보 확인 가능
  1. 객체 없이 메서드 실행 가능
  1. 어노테이션 기반 프로그래밍
⇒ 계속해서 유지보수가 필요하다면 이를 일일히 하지 않고 편리하게 처리할 수 있음
 
 

✔계속계속 유지보수가 필요한 경우

notion image
package ex01; import java.util.Scanner; 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("Jogin call"); } @Override public void logout() { System.out.println("Logout call"); } } public class App { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String path = scanner.nextLine(); UserController uc = new UserController(); Dispatcher ds = new Dispatcher(uc); ds.routing(path); } }
📌
B회사에서 기능을 추가할 때마다 A회사에 수정을 요청해야함.
A회사는 B회사를 포함해 Dispatcher를 판매한 모든 회사에서 기능 추가 시 수정해줘야함
⇒ 아주 비효율적/비생산적임
⇒ 리플렉션이 필요함
 

리플젝션 적용

notion image

App

package ex02; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class App { public static void main(String[] args) { UserController con = new UserController(); Method[] methods = con.getClass().getMethods(); for (Method method : methods) { try { if (method.getName().equals("join")) { method.invoke(con); } } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } // invoke() 찾아내서 호출함! } }

Dispatcher

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

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"); } }

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) //메서드 위에만 붙일 수 있음 public @interface RequestMapping { String value(); }

컴퍼넌트 스캔

Dispatcher

package ex04; import java.lang.reflect.Method; public class DispatcherServlet { UserController con; public DispatcherServlet(UserController con) { this.con = con; } public void componentScan() { } 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

package ex04; @Component 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"); } }

Component

package ex04; 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.TYPE) // 클래스 위! Component가 붙은 클래스만 public @interface Component { }

BoardController

package ex04; @Component public class BoardController { @RequestMapping("/write") public void write() { System.out.println("write call"); } @RequestMapping("/delete") public void delete() { System.out.println("delete call"); } }

App(ex04 패키지 안에 있는, ex04클래스 안에 있는 모든 파일 출력)

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
 

 

App(Component라는 어노테이션이 붙은 class파일 출력해줘)

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(); instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } } // for 종료 for (Object instance : instances) { System.out.println(instance.getClass().getName()); } } }

실행결과

notion image
 

2. 리플젝션과 어노테이션을 활용해 write 메서드 동적호출

DispatcherServlet

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", ""); //System.out.println(className); 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) { 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); } } } } } }
📌
  1. 특정 패키지 내의 클래스를 검색
  1. @Component 어노테이션이 붙어 있는 클래스의 객체를 생성.
  1. @RequestMapping 어노테이션이 있는 메서드를 찾아 실행함.
📌
  • routing() → 요청된 경로(path)와 일치하는 메서드를 찾아 실행.
  • componentScan() → 패키지에서 특정 어노테이션이 붙은 객체를 자동으로 생성.

App

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); } }
📌
서블릿 역할을 하는 객체를 생성.
ex04 내에 Component가 붙은 클래스를 찾아 객체 생성.
path값 (입력값) 과 일치하는 @RequestMapping(”입력값”)이 붙은 메서드를 찾아 실행.
 
Share article

clubnerdy