001_자바 리플렉션과 어노테이션을 활용한 라우팅 시스템

Jan 26, 2024
001_자바 리플렉션과 어노테이션을 활용한 라우팅 시스템
자바의 스프링에 대해 배우기 전 꼭 알아야 할만한 내용이며 개념의 이론적인 흐름만 알아도 쉽게 이해가 될듯하다.

라우팅

  • 웹 개발에서 클라이언트의 요청(URL)을 서버의 특정 로직(컨트롤러)에 연결하는 과정이다.
  • 클라이언트의 요청에 맞는 적절한 응답을 돌려주기 위해 필요한 경로 설정이다.
  • 한마디로 라우터에서 “분기 처리”를 진행한다. → “분기 처리”는 역할을 분배한 것 이라고 생각해도 될듯하다.

컨트롤러(Controller)

  • 컨트롤러는 MVC(Model-View-Controller) 아키텍처에서 사용자의 입력을 처리하고, 해당 비즈니스 로직을 수행한 후 결과를 반환하는 부분입니다.
  • 컨트롤러는 유효성 검사, 요청 파싱, 적절한 DAO(Data Access Object)를 통한 데이터 조작 등을 담당합니다.
 

리플렉션(Reflection)

  • 리플렉션은 런타임 중에 클래스의 속성, 메서드, 타입 등을 조사하고 수정할 수 있는 자바 API입니다. 리플렉션을 사용하면 컴파일 시간에는 알 수 없던 클래스 정보에 접근하거나, 동적으로 객체를 생성하고 메서드를 호출할 수 있습니다.
  • 프로그램이 실행 중 경로에 존재하는 코드들을 하나하나 확인하는 것
 
 

어노테이션(Annotation)

  • 어노테이션은 코드에 추가 정보를 제공하는 방법으로, 리플렉션을 사용하여 런타임에 해석될 수 있습니다. 어노테이션은 코드의 의도를 명확히 하고, 프레임워크가 코드를 자동으로 처리하는 데 도움을 줄 수 있습니다.
  • 리플렉션을 효율적으로 하게 해주는 것
 
 

예시 코드에 대한 설명

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에 매핑된 메서드를 찾아 실행하는 로직입니다. 이는 리플렉션과 어노테이션을 활용한 라우팅의 한 예시입니다.
 

수정 및 추가 사항

리플렉션 설명 보완: 리플렉션은 코드를 '검사'하는 것뿐만 아니라, 코드의 구조를 '조작'할 수도 있습니다.어노테이션 설명 추가: 어노테이션은 리플렉션을 더 효율적으로 사용할 수 있게 해주며, 특정 메타데이터를 기반으로 코드를 자동으로 처리하는 데 중요한 역할을 합니다.
 
라우팅(Routing)클라이언트의 URL 요청을 서버의 적절한 로직(컨트롤러)에 연결사용자 요청에 따른 응답 처리 경로 설정컨트롤러(Controller)사용자의 입력 처리 및 비즈니스 로직 수행유효성 검사, 요청 파싱, 데이터 조작 담당리플렉션(Reflection)런타임 중 클래스 정보 조사 및 조작객체 생성, 메서드 호출 등 동적 처리 가능어노테이션(Annotation)코드에 메타데이터 제공리플렉션을 통해 런타임에 해석 및 처리

JDBC 라우터 만들기

분기 처리하는게 라우팅이다.
 
// http://bank.com/account GET (select, 계좌 전체 조회) // http://bank.com/account/10 GET (select, 계좌 부분 조회) // http://bank.com/account POST (insert, 계좌생성) // http://bank.com/account/1 DELETE (계좌삭제) // http://bank.com/account/1 PUT (계좌수정) // DELETE와 PUT은 반드시 body가 필요하다. // 뒤의 숫자는 자원명이 아닌 식별자(PK)이다. = 식별자 요청 // 1. GET은 HTTP 요청에 body가 없다 // 2. POST와 PUT은 반드시 body가 필요 하다.
 
MIME 타입의 필요 이유 개념 자체만
 
wwww.naver.com?password=1234&balance=1000
x-www-form-url 작성 형식
디펜전시 인젝션(의존성 주입)
컨트롤러에서의 유효성검사는 프론트 백 에서 둘다 막아야한다.
 
SRP 책임 분리
메인(동적할당 하고 실행) → Dispatcher(라우팅) → Controller(유효성 검사, 파싱, 적절한 DAO 찾기) → DAO(쿼리 코드) → DB
 
리플렉션 : 투영하다, 반영하다, 관찰하다 → 리플렉션을 하고 실행 시 런타임 때 모든 상황을 다 관찰하도록 만들 수 있다. → 다 확인하고 가서 오래 걸림 (이로 인해 과하게 쓰면 좋지 않다 → 속도가 느려짐) → 언제? 개발자가 서로 다른 경우에 유지보수를 할 때 유리하기에 사용한다. → 어노테이션일 붙이면 리플렉션이 효율적으로 변한다. → 어노테이션이 설정된 것만 리플렉션하면 되도록 하기 때문에 효율적으로 바뀐다.
 
 
컴포넌트스캔
패키지를 분석해서 → @Controller를 new 해서 set 자료형에 저장한다. set은 같은 타입이 중복되서 저장이 안된다. (안전성)
 

라우터와 컨트롤러에 대해 배웠는데
라우터를 라이브러리로 만들때
다른데에서 함수를 막쓰면 만들어줘야하니깐
그걸 안만들어줘도 되게끔
하는 리플렉션 이라는걸 배운다.
 
os의 관점에서는 디렉토리와 파일이 모두 파일이다.
replace → A를 B로 바꾼다. EX) replace(".class","");

리플렉션 3단계 1. 없음 2. 기본 틀만 기본 리플렉션 → 메서드 리플렉션 3. 완성 (패키지를 리플렉션 해서 컨트롤러를 다찾음)

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

  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
package ex03; @Controller public class BoardController { @RequestMapping(uri="/write") public void write(){ System.out.println("글쓰기 호출됨"); } }
 
Share article

chodong