01. 리플렉션과 어노테이션을 활용한 라우팅 시스템

박선규's avatar
Jan 25, 2024
01. 리플렉션과 어노테이션을 활용한 라우팅 시스템
디펜더시 인젝션 (의존성 주입)
💡
라우팅 ◦ 클라이언트의 URL 요청을 서버의 적절한 로직(컨트롤러)에 연결 ◦ 사용자 요청에 따른 응답 처리 경로 설정컨트롤러(Controller) ◦ 사용자의 입력 처리 및 비즈니스 로직 수행 ◦ 유효성 검사, 요청 파싱, 데이터 조작 담당리플렉션(Reflection) ◦ 런타임 중 클래스 정보 조사 및 조작 ◦ 객체 생성, 메서드 호출 등 동적 처리 가능어노테이션(Annotation) ◦ 코드에 메타 데이터 제공 ◦ 리플렉션을 통해 런타임에 해석 및 처리
 
컨트롤러 :MVC 패턴에서 적절한DAO를 찾아 DAO를 통하여 DB에 접근해서 해당 데이터를 찾아 다시 반환하는 역할 까지 한다.
API :다른 애플리케이션 또는 시스템 간에 데이터를 주고 받거나 기능을 호출 할 수 있도록 하는 방법
 
리플랙션(reflection):runtime 때 코드를 분석하는 시스템으로 API이다. 어노테이션(annotation):컴파일 시점에 jvm이 볼 수 있는 힌트로(코드 위에 골뱅이로 표현)
annotation
notion image
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 패키지를 분석 URL packageUrl = classLoader.getResource("ex03"); // 어떤 패키지를 할건지 선택 File dir = new File(packageUrl.toURI()); // 지정한 ex03 폴더를 가져온다 Set<Object> instances = new HashSet(); // 뭐가 들어올지 모르기에 Object 자료형 for (File file : dir.listFiles()){ // dir.listFiles() -> ex03이 들고 있는 모든 파일을 들고 와서 for문을 돈다 (자기 경로만 체크) 실질적으로 out의 실행파일을 가져옴 //System.out.println(file.getName()); if(file.getName().endsWith(".class")){ // endsWith -> 파일의 끝이름 String className = "ex03" + "." + file.getName().replace(".class",""); // 이름을 이렇게 만들어야 new할 수 있다. //System.out.println(className); Class cls = Class.forName(className); // new 하기 if(cls.isAnnotationPresent(Controller.class)){ // 파일에 class에 @Controller가 있으면 true Object instance = cls.newInstance(); instances.add(instance); // UserController, BoardController } } } findUrl(instances, "/login");
제공된 코드는 패키지 내의 클래스들을 스캔하여 @Controller 어노테이션이 붙은 클래스를 인스턴스화하고, 이 인스턴스들 중 특정 URL에 매핑된 메서드를 찾아 실행하는 로직입니다. 이는 리플렉션과 어노테이션을 활용한 라우팅의 한 예시입니다.
리플랙션시 깃발(어노테이션)이 붙어있는것을 확인 할 수 있다. 컴포넌트 스캔 기준 패키지 내부에 전부 @Controller, @RestController가 붙은 애들을 인스턴스화 해서 ioc(inversion of controller)컨테이너에 담는다.
컴포넌트 스캔 기준점 정하는 코드 및 결과
notion image
notion image
os관점에서는 파일도 파일이고 디렉토리(폴더)도 파일이다. 즉 어떤 정보를 담고 있는 파일이다.
실제로 출력시 out 폴더에서 읽는다.
notion image
 
IOC 인버전 오브 컨트롤 제어의 역전이란 뜻이다.
new를 원래 개발자가 하는데 spring이 가져 가기때문에 개발자는 @(어노테이션)만 잘 붙이면 된다.
가비지 컬렉션이 되지 않는다.
 
 

개념 예시

💡
일꾼:개발자
나무가 썩었는지 확인하고 썩었으면 일꾼이 유지 보수한다.
 
나무를 만드는 일꾼(a)이 있고
나무를 유지 보수하는 일꾼(b)이 있는데,
a가 b에게 리플렉션 해서 썩은 나무를 확인하고
나무를 배라고 일을 시키면 b는 새로운 나무가 만들어질때 마다 새로운 나무도 썩었는지 확인해야 되므로
일 거리가 늘어난다.
그때 a가 썩은 나무들에 어노테이션을 붙이면
b가 리플렉션할때 어노테이션 붙은애들만 확인하고 일처리를 할 수 있어서 일처리가 빨라진다.
개념그림
notion image
 
리플랙션을 적용해서 일꾼이 일하게 만든다.
리플랙션을 일꾼에게 적용하면 나무 하나 하나를 분석한다.
장단점이 있을텐데 적당하게 쓰지 못하면 검사하는 시간이 오래걸린다.
쓰는 시점:유지 보수에서 역할이 다른 개발자들(나무를 만드는 개발자, 나무를 유지 보수하는 개발자)이 일 할 때 리플렉션을 적용한다.
리플렉션에 어노테이션을 붙이면 분석을 효율적(어노테이션 붙은 것만 확인)으로 한다.
붙이는 방법으로는 나무에 깃발(골뱅이)붙어져있는지 확인만 하면 된다.
 

어노테이션 종류

💡
@RequsetMapping :부여되는 메소드가 URI 경로와 매핑(경로와 특정 메소드를 연결)되도록 지정하는 역할 @Controller 어노테이션은 해당 클래스가 컨트롤러임을 나타냅니다. 이 어노테이션이 적용된 클래스는 웹 애플리케이션에서 HTTP 요청을 처리하고, 응답을 생성하는 역할을 합니다.
 
 

리플렉션과 어노테이션의 사용 이유와 이해 코드

💡
1. 리플렉션과 어노테이션을 사용하지 않은 코드
App
package ex01; public class App { public static void main(String[] args) { String path = "/login"; UserController con = new UserController(); if(path.equals("/login")){ con.login(); } else if (path.equals("/join")) { con.join(); } } }
UserController
package ex01; public class UserController { // /login public void login(){ System.out.println("로그인 호출됨"); } // /join public void join(){ System.out.println("회원가입 호출됨"); } public void userinfo(){ System.out.println("유저정보 보기"); } }
  1. 리플렉션과 어노테이션을 활용한 코드
App (리플렉션)
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) { String path = "/update-password"; UserController con = new UserController(); Method[] methods = con.getClass().getDeclaredMethods(); // 리플렉션 코드 //System.out.println(methods.length); for(Method method : methods){ //System.out.println(method.getName()); RequestMapping rm = method.getDeclaredAnnotation(RequestMapping.class); // Annotation anno = method.getDeclaredAnnotation(RequestMapping.class); //RequestMapping rm = (RequestMapping) anno; // Annotation anno 일때 다운 캐스팅 해야한다. if(rm == null) continue; if(rm.uri().equals(path)){ try { method.invoke(con); // == con.login(); break; } catch (Exception e) { e.printStackTrace(); } } } } }
UserController
package ex02; public class UserController { @RequestMapping(uri="/login") // 어노테이션 public void login(){ System.out.println("로그인 호출됨"); } @RequestMapping(uri="/join") public void join(){ System.out.println("회원가입 호출됨"); } @RequestMapping(uri="/userinfo") public void userinfo(){ System.out.println("유저정보 보기"); } @RequestMapping(uri="/update-password") public void updatePassword(){ System.out.println("패스워드 수정하기"); } }
RequsetMapping(어노테이션)
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 uri(); }
  1. 리플렉션과 어노테이션과 라우터를 활용한 코드
App (리플렉션)
package ex03; import java.io.File; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class App { public static void findUrl(List<Object> instances, String path){ for (Object instance : instances){ Method[] methods = instance.getClass().getDeclaredMethods(); for(Method method : methods){ RequestMapping rm = method.getDeclaredAnnotation(RequestMapping.class); if(rm == null) continue; if(rm.uri().equals(path)){ try { method.invoke(instance); // == con.login(); break; } catch (Exception e) { e.printStackTrace(); } } } } } // 스캔해서 @Controller를 모두 찾음 public static List<Object> componentScan(String pkg) throws URISyntaxException, ClassNotFoundException, InstantiationException, IllegalAccessException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 패키지를 분석 URL packageUrl = classLoader.getResource(pkg); // 어떤 패키지를 할건지 선택 File dir = new File(packageUrl.toURI()); // 지정한 ex03 폴더를 가져온다 List<Object> instances = new ArrayList<>(); // 뭐가 들어올지 모르기에 Object 자료형 for (File file : dir.listFiles()){ // dir.listFiles() -> ex03이 들고 있는 모든 파일을 들고 와서 for문을 돈다 (자기 경로만 체크) 실질적으로 out의 실행파일을 가져옴 //System.out.println(file.getName()); if(file.getName().endsWith(".class")){ // endsWith -> 파일의 끝이름 String className = pkg + "." + file.getName().replace(".class",""); // 이름을 이렇게 만들어야 new할 수 있다. //System.out.println(className); Class cls = Class.forName(className); // new 하기 if(cls.isAnnotationPresent(Controller.class)){ // 파일에 class에 @Controller가 있으면 true Object instance = cls.newInstance(); instances.add(instance); // UserController, BoardController } } } return instances; } public static void main(String[] args) throws URISyntaxException, ClassNotFoundException, InstantiationException, IllegalAccessException { List<Object> instances = componentScan("ex03"); findUrl(instances, "/login"); } }
UserController
package ex03; @Controller public class UserController { @RequestMapping(uri="/login") // 어노테이션 public void login(){ System.out.println("로그인 호출됨"); } @RequestMapping(uri="/join") public void join(){ System.out.println("회원가입 호출됨"); } @RequestMapping(uri="/userinfo") public void userinfo(){ System.out.println("유저정보 보기"); } @RequestMapping(uri="/update-password") public void updatePassword(){ System.out.println("패스워드 수정하기"); } }
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) public @interface RequestMapping { String uri(); }
Controller (라우팅)
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.TYPE) // 클래스 위에 public @interface Controller { }
BoardController

통신 tip

💡
keep-Alive:반이중 통신에서 통신을 지속하는 시간을 의미한다.
 
Share article
RSSPowered by inblog