아무나개발하자

JAVA Reflection 본문

JAVA

JAVA Reflection

개발천재나천재 2022. 12. 22. 11:32

Spring MVC 구조

자바 reflection에 대해  잘 이해하기 위해서는 우선 spring MVC의 개념에 대해 알아야한다.

출처 : https://linked2ev.github.io/gitlog/2019/09/15/springboot-mvc-13-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-MVC-Filter-%EC%84%A4%EC%A0%95/

- 전체적인 spring mvc 구조를 잘 보여주고 있는 그림이다. 사용자 즉 브라우저에서  HTTP 요청이 오면 우선 Filter로 요청이 가고 그 다음 DispatcherServlet으로 요청이 넘어가고 DispatcherServlet에서는 해당 요청을 처리할 수 있는 컨트롤러와 컨트롤러를 실행하기 위한 Adapter를 찾아 URl에 해당하는 컨트롤러를 실행한다. 그리고 머 상황에 따라 MVC 구조면 View까지 랜더링해서 응답하고 아니면 json형식으로 응답하는 방식으로 진행된다.

그럼 이야기를 왜 했는지 의문이 들을 수 있다. 그 이유는 이러한 과정이 모두 컴파일 시점에서 일어나는 것이 아닌 런타임 시점에서 일어난다는 것이다. 결론은 spring framework는 런타임시 이 모든것을 해결하는 엄청난 친구라는 것이다.

 

Reflection 이란?

구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들을 접근 할 수 있도록하는 자바 API이다. 그러면 왜 사용되나? 코드를 작성할 시점에서는 어떤 타입의 클래스와 메소드를 사용할지 모르지만 런타임 시점에서 결정되는 경우 되게 난감할 것이다. 그래서 리플렉션을 통해 런타임 시점에서 특정 클래스의 특정 메소드 사용을 강제 받을 때 리플렉션을 통해 해당 변수와 메소드의 접근을 하여 실행 할 수 있는것이다.

출처 : https://www.youtube.com/watch?v=P5fPc2tjOko&list=PL93mKxaRDidFGJu8IWsAAe0O7y6Yw9f5x&index=1

- 위의 그림을 보면 예를 들어 사용자가 /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