일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 가장 가까운 단어
- 프로그래머스
- http
- 문자열 내마음대로 정렬하기
- Spring
- 크기가 작은 부분 문자열
- CPU
- 문자열
- DICTIONARY
- deque
- 2차원 배열 출력
- Queue
- Split
- process
- dns
- green thread
- java
- 십진수 이진수 전환
- stack
- reflection
- port
- TCP/IP
- 동시성문제
- URL
- IO bound
- frontPattern
- springMVC
- annotation
- 코딩테스트
- CPU bound
- Today
- Total
아무나개발하자
JAVA Reflection 본문
Spring MVC 구조
자바 reflection에 대해 잘 이해하기 위해서는 우선 spring MVC의 개념에 대해 알아야한다.
- 전체적인 spring mvc 구조를 잘 보여주고 있는 그림이다. 사용자 즉 브라우저에서 HTTP 요청이 오면 우선 Filter로 요청이 가고 그 다음 DispatcherServlet으로 요청이 넘어가고 DispatcherServlet에서는 해당 요청을 처리할 수 있는 컨트롤러와 컨트롤러를 실행하기 위한 Adapter를 찾아 URl에 해당하는 컨트롤러를 실행한다. 그리고 머 상황에 따라 MVC 구조면 View까지 랜더링해서 응답하고 아니면 json형식으로 응답하는 방식으로 진행된다.
그럼 이야기를 왜 했는지 의문이 들을 수 있다. 그 이유는 이러한 과정이 모두 컴파일 시점에서 일어나는 것이 아닌 런타임 시점에서 일어난다는 것이다. 결론은 spring framework는 런타임시 이 모든것을 해결하는 엄청난 친구라는 것이다.
Reflection 이란?
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들을 접근 할 수 있도록하는 자바 API이다. 그러면 왜 사용되나? 코드를 작성할 시점에서는 어떤 타입의 클래스와 메소드를 사용할지 모르지만 런타임 시점에서 결정되는 경우 되게 난감할 것이다. 그래서 리플렉션을 통해 런타임 시점에서 특정 클래스의 특정 메소드 사용을 강제 받을 때 리플렉션을 통해 해당 변수와 메소드의 접근을 하여 실행 할 수 있는것이다.
- 위의 그림을 보면 예를 들어 사용자가 /user라고 요청을 하면 /user에 해당하는 컨트롤러 메서드를 실행하고 싶을 것이다. 만약에 /join으로 요청하면 /join에 해당하는 컨트롤러를 실행하고, 그러면 어떻게 이걸 런타임 시점에서 결정할 수 있을지에 대해 이야기 해보자!
UserController -1
//@Controller
public class UserController {
public void join() {
System.out.println("join 함수 요청됨");
}
public void login() {
System.out.println("login 함수 요청됨");
}
public void user() {
System.out.println("user 함수 요청됨");
}
}
- userController는 다음과 같다.
Dispatcher Class -1
public class Dispatcher implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
System.out.println("컨텍스트패스 : " + request.getContextPath()); // 프로젝트 시작주소
System.out.println("식별자주소 : " + request.getRequestURI()); // 끝주소
System.out.println("전체주소 : " + request.getRequestURL()); // 전체주소
String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), "");
System.out.println("엔드포인트 : " + endPoint);
UserController userController = new UserController();
//리플렉션 -> 메서드를 런타임 시점에서 찾아내서 실행한다.
Method[] methods = userController.getClass().getDeclaredMethods();
for(Method method: methods){
if(endPoint.equeals("/"+method.getName())){
try{
method.invoke(userController);
} cathch(Exception e){
e.printStackTrace();
}
}
}
}
- 리플렉션을 직접 만들어봤다. url과 동일한 이름의 컨트롤러를 실행하는 코드이다. 하지만 이렇게 작성을 하면 물론 url이름과 컨트롤러의 이름이 같으면 사용할 수 있지만 제약사항이 많다. 그래서 어노테이션 기반으로 url과 컨트롤러의 이름이 동일한지의 여부를 판단하는 것이 아닌 어노테이션과 일치하는지의 여부를 판단하여 해당 컨트롤러를 실행하는 코드로 수정을 해보겠다.
RequestMapping Class
// Retention을 통해 RequestMapping이 런타임시점에서 동작하도록 설정 (compile시점에서 동작하는것도 가능)
@Retention(RetentionPolicy.RUNTIME)
// Target을 통해 이 어노테이션을 어디다 설정할지를 결정 (TYPE : 클래스, METHOD : 메서드, FIELD : 필드)
@Target({ElementType.METHOD})
public @interface RequestMapping {
String value;
}
- 우선 RequestMapping이라고 어노테이션을 하나 설정한다. 나는 런타임 시점에서 호출되고, 메서드에만 어노테이션을 설정할 수 있도록 하였다.
UserController -2
//@Controller
public class UserController {
@RequestMapping("/join")
public void join() {
System.out.println("join 함수 요청됨");
}
@RequestMapping("/login")
public void login() {
System.out.println("login 함수 요청됨");
}
@RequestMapping("/user")
public void user() {
System.out.println("user 함수 요청됨");
}
}
- userController에 어노테이션을 설정하였다. 여기서 value값은 "/join", "/login", "/user" 가된다.
Dispatcher Class -2
public class Dispatcher implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
System.out.println("컨텍스트패스 : " + request.getContextPath()); // 프로젝트 시작주소
System.out.println("식별자주소 : " + request.getRequestURI()); // 끝주소
System.out.println("전체주소 : " + request.getRequestURL()); // 전체주소
String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), "");
System.out.println("엔드포인트 : " + endPoint);
UserController userController = new UserController();
Method[] methods = userController.getClass().getDeclaredMethods();
for (Method method : methods) { // 리플렉션한 메서드 개수만큼 순회함
Annotation annotation = method.getDeclaredAnnotation(RequestMapping.class);
RequestMapping requestMapping = (RequestMapping) annotation;
// requestMapping의 value와 endPoint를 비교하는 코드
if (requestMapping.value().equals(endPoint)) {
try {
method.invoke(userController);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
}
- Dispatcher 클래스를 수정하였다. 이전과 다르게 해당 컨트롤러의 어노테이션의 value를 보고 판단하여, 메소드를 실행 할 수 있게 코드를 수정 하였다. 다음 단계에 대해서는 다음 글에서 다루도록 하겠다.
출처 : https://www.youtube.com/watch?v=HSmBi3YMuho&list=PL93mKxaRDidFGJu8IWsAAe0O7y6Yw9f5x&index=3
'JAVA' 카테고리의 다른 글
JAVA Reflection 정리2 (0) | 2022.12.27 |
---|