Reflaction 응용
Reflaction Application
지난 포스팅에서 리플렉션 에 대한 기본적인 개념을 다루었다.
짧게 요약하자면 클래스 로더의 동적 로딩 을 통해서 런타임에 classpath 로 부터 .class 파일을 읽고 해당 Class의 메타 정보를 갖는 Class class 를 활용하여 로딩된 클래스의 Class 인스턴스 를 생성한다고 하였다.
또한 Class 인스턴스는 Class.forName(“path”) or Path.class or instance.getClass() 를 이용하여 얻을 수 있는 것도 알았다.
그렇다면 이렇게 Class의 메타 정보를 갖는 Class 인스턴스 를 사용해서 어떤 작업을 할 수 있을까?
물론 여러 작업을 할 수 있겠지만 기존에 만들던 미니 MVC 실습 코드에 적용된 Reflaction을 보자.
미니 MVC의 구조.

위의 사진 처럼 미니 MVC의 흐름은 다음과 같다.
-
클라이언트로 부터의 요청은 DispatcherServlet 이 받는다.
-
DispatcherServlet 은 클라이언트의 요청을 처리할 Page Controller 에게 Request Parameter 가 필요한지 묻는다.
-
Request Parameter 가 필요한 Page Controller 는 DataBinding 인터페이스를 구현하여 getDataBinder() 를 통해 필요한 데이터 명, 데이터 타입 을 응답한다.
-
Page Controller 로 부터 필요한 데이터 항목을 Object[] 형식으로 응답받은 DispatcherServlet 은 해당 데이터를 준비하기 위해 ServletDataBinder 와 메시지를 주고받는다.
-
DispatcherServlet 으로부터 필요한 데이터를 준비해 달라는 메시지를 받은 ServletDataBinder 는 Reflaction 을 이용하여 클라이언트가 보낸 request Parameter 를 적절히 Binding 한다.
미니 MVC의 소스코드
- ContextLoaderListener
- DispatcherServlet
- Page Controller
- ServletRequestDataBinder
- MemberVO
어플리케이션 구동부터 클라이언트 요청까지
흐름을 좀 더 이해하기 쉽게 어플리케이션 구동시 초기화 과정부터 클라이언트의 요청을 받아 응답에 이르기 까지 간단하게 살펴보기 위해 ContextLoaderListener 소스코드 까지 준비해 봤다.
ContextLoaderListener 는 보시다시피 ServletContextListener 를 구현한 이벤트 핸들러 이다. 어플리케이션 구동시 contextInitialized() 메서드가 실행됨으로써 MemberDao 와 같이 필요한 공유 객체를 미리 준비하고 각 Page Controller 들을 생성하여 준비한 MemberDao 를 주입한다.
그리고 준비된 Page Controller 들을 ServletContext 보관소에 저장함으로써 서블릿 사이에서 공유된다.
클라이언트의 모든 요청을 수신하는 DispatcherServlet 도 마찬가지로 HttpServlet 을 상속받은 서블릿 이기 때문에 ServletContext 에 접근이 가능하다.
이제 DispatcherServlet 은 클라이언트의 요청 URL을 받아 이와 일치하는 Page Controller를 ServletContext 보관소로 부터 꺼내어 해당 Page Controller에 실행을 위임할 것이다.
멤버를 추가할래요
- 요청 URL : memberAdd.do
- 요청 파라미터 : name, email, password
클라이언트가 브라우저에서 멤버를 추가하기 위해 memberAdd.do URL로 name, email, password 파라미터와 함께 요청을 했다고 가정하자.
이제 DispatcherServlet 은 요청 URL(memberAdd.do)를 수신하여 이와 매핑되는 PageController 인스턴스를 ServletContext로 부터 꺼낼 것이다 (sc.getAttribute())
그리고 Page Controller에게 클라이언트의 요청 파라미터가 필요한지 묻는다. 만일 Page Controller가 DataBinding 인터페이스를 구현했다면 요청 파라미터가 필요하다는 의미이다.
MemberAddController 는 DataBinding 을 구현하였으므로 요청 파라미터가 필요 함을 알 수 있다.
요청 파라미터가 필요함을 응답받은 DispatcherServlet 은 데이터를 준비하기 위해 ServletDataBinder 와 협업을 시작한다.
데이터를 준비해주세요.
DispatcherServlet 이 MemberAddController 가 필요한 데이터를 준비하기 위해서 MemberAddController 에게 getDataBinder 메시지를 보낸다.
필요한 데이터 명과 데이터 타입 을 Object[] 형태로 응답한 MemberAddController 는 이제 DispatcherServlet 이 데이터를 준비하여 자신이 멤버 추가 책임을 수행할 수 있게끔 execute() 메시지와 함께 Model Map 에 필요한 데이터를 넣어 보내주기를 기다린다.
DispatcherServlet 은 이제 데이터를 준비하는 책임을 수행하는데 스스로 준비하기에는 본인의 역할이 너무 난잡한 것 같다.(응집력이 떨어지는 것 같다.) 그래서 ServletRequestDataBinder 라는 친구와 협업을 한다.
ServletRequestDataBinder 는 DispatcherServlet 으로부터 필요한 데이터 타입, 데이터 명, 데이터 값 을 받아서 데이터 타입으로 바인딩 하여 응답해줄 책임이 있다.
여기서부터 이제 Reflaction 이 활용되기 시작한다.
필요한 데이터 타입의 인스턴스 는 넘겨받은 Class 인스턴스 로 부터 생성할 수 있다.
그리고 필요한 데이터 타입의 인스턴스 가 VO라면 데이터 명 과 매핑되는 데이터 값 이 저장되어야 한다. (MemberVO가 Email, name, password 상태를 지녀야 한다.)
그리고 위와 같이 VO에 상태값을 저장하기 위해서는 역시 Reflaction 의 도움이 필요하다.
Reflaction 을 통해 Class 인스턴스 로 부터 Method 정보를 가져와서 메시지를 보낼 수 있기 때문이다.
ServletRequestDataBinder 의 findSetter() 메서드를 살펴보면 Class 인스턴스 로 부터 setter Method 객체를 return해 준다.
물론 여기서 return 되는 setter Method 는 클라이언트가 요청한 요청 파라미터의 데이터 명 에 매핑되는 setter 이다.
이제 setter Method 와 필요한 데이터 타입의 인스턴스(MemberVO) 가 Reflaction 을 활용하여 준비되었다.
남은건 필요한 데이터 타입의 인스턴스 의 setter Method 에 필요한 데이터 값 을 저장함으로써 상태 값 을 갱신하여 DispatcherServlet 에게 넘겨주는 일 뿐이다.
여기서 조금 생소한 구문이 나온다. 보통 어떤 인스턴스의 메서드를 실행할 때는 인스턴스.메서드() 와 같은 문법으로 동작한다.
하지만 우리는 Reflaction 을 사용함에 유의하자 Reflaction API 가 정의하는 Method 인스턴스 실행은 Method.(인스턴스, 인자) 이다.
위와 같은 구문을 실행하면 인스턴스.메서드(인자) 와 동일하다.
위에서는 Method 가 Reflaction 으로 얻은 객체 이기 때문에 당연히 기존 방식과는 다를 수 밖에 없다.