public interface Controller {
ModelView process(Map<String, String> paramMap);
}
paramMap의 제너릭이 String, String인 이유는 HTTP요청은 모두 String타입으로 들어오기때문이다.
후에 첫번째 String은 "name"의 key를 갖고, 두번째 String은 "hong"이라는 value를 갖게된다.
age와 같은 int타입은 "age"라는 key값과 "20"이라는 실제나이 입력데이터를 String으로 받지만 구현 컨트롤러에서 파싱처리하여 사용된다.
!!!!!!!!!중요!!!!!!!!!!!!! paramMap과 Model 필자는 이 부분의 개념이 정확하게 잡히지 않았던 것 같아서 이해하는데 어려움을 겪었던 것 같다. paramMap은 이전에도 언급했듯이 request의 역할을 대신 수행한다.
//request로 요청을 처리했던 코드
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParamter("age"));
Member member = new Member(name, age);
//Map<String, String> paramMap으로 요청을 처리한 코드
String name = paramMap.get("name"); //hong
int age = Integer.parseInt(paramMap.get("age")); //20
Member member = new Member(name, age);
paramMap은 요청받은 데이터 즉, HTML form태그로 입력받거나 HTTP message body를 통해 넘어온 쿼리파라미터 형식의 문자열을 처리해주는 역할을 수행할 뿐이며 Model과는 다른 역할을 수행합니다.
Model은 FrontController에서 View로 데이터를 보낼때 데이터를 담고있는 바구니정도로 생각하면 이해에 도움이 될거라고 생각합니다. 즉 Model객체에는 비즈니스로직을 수행한 데이터의 결과를 Model에 담아서 View로 전송하고 View에서는 이 Model에 담긴 데이터를 UI로 표시해줍니다.
아래 그림으로 정리해보겠습니다.
먼저 전체적인 흐름을 다시한번 살펴보자면 1. 클라이언트에서 HTML Form태그를 통해 "name", "age"를 입력 2. 프론트 컨트롤러에서 request.getParameter를 통해 "name", "age"값을 꺼내 paramMap에 입력
본론으로 돌아와서 그림에서 1,2,3,4숫자가 있는데 1의 상태에서는 쿼리파라미터 형식의 문자열로 name=hong&age=20의 문자열 형태이다. 2의 과정에서는 프론트 컨트롤러를 통해 paramMap형태로 변환되고 3,4에서는 비즈니스 로직처리 과정에서 model형태로 바뀌게 된다.
request와 paramMap의 차이를 설명하려고 했을뿐인데 Model의 도입과정을 모두 다 설명해버린듯하다..
ViewResolver를 도입하면서 전체코드를 정리해보겠다.
ViewResolver 도입
public ModelView process(Map<String, String> paramMap) {
return new ModelView("/WEB-INF/views/new-form.jsp");
}
ModelView modelView = new ModelView("/WEB-INF/views/save.jsp");
modelView.getModel().put("member", member);
return modelView;
ModelView modelView = new ModelView("/WEB-INF/views/members.jsp");
modelView.getModel().put("members", members);
return modelView;
현재 Controller구현클래스 즉 비즈니스 로직 컨트롤러는 ModelView가 생성될 때 viewPath필드에 경로의 모든 주소를 직접넣어서 반환해주게 된다.
이는 "/WEB-INF/views/"와 ".jsp"같이 중복코드가 발생할 뿐더러 후에 만약 /views의 경로가 바뀔경우에는 위 3개의 비즈니스 컨트롤러의 /views코드를 일일이 바뀐 경로로 수정해주어야하는 유지보수에 불편함이 생기게된다.
이러한 문제점을 해결하기위해 ViewResolver가 사용된다.
먼저 위의 3개 코드들에서 viewPath를 논리 뷰이름만 매개변수로 갖도록 설정해준다. (중복되는 부분을 제거)
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
ModelView modelView = new ModelView("save");
modelView.getModel().put("member", member);
return modelView;
ModelView modelView = new ModelView("members");
modelView.getModel().put("members", members);
return modelView;
위와 같이 수정만 해주더라도 가독성이 향상되는 것을 알 수 있다.
이제 FrontController에서 ViewResolver를 통해 중복된 코드들을 하나로 통합시켜보자
두 설계 방식을 비교해보자면, 두 번째 설계 방식이 조금 더 효율적이라고 볼 수 있습니다. 그 이유는 다음과 같습니다.
첫 번째 설계 방식:
ModelView객체를viewResolver메서드의 파라미터로 전달하고, 이 메서드 내에서ModelView객체로부터 뷰 이름을 추출하여 최종적으로 뷰 경로를 생성합니다.
이 방식은ModelView객체가 뷰 경로 생성에 필요한 모든 정보(이 경우에는 뷰 이름)를 이미 가지고 있기 때문에 유효합니다.
두 번째 설계 방식:
뷰 이름을 먼저ModelView객체로부터 추출한 후, 이 뷰 이름을viewResolver메서드의 파라미터로 전달합니다. 그리고 이 메서드는 뷰 이름을 기반으로 새로운MyView객체를 생성하여 반환합니다.
이 방식은viewResolver메서드가 뷰 이름만을 파라미터로 받기 때문에, 뷰 이름을 기반으로 한 다양한 처리를 더 유연하게 할 수 있습니다. 예를 들어, 특정 뷰 이름에 대해 다른 로직을 적용하거나, 다른 형태의 뷰 객체를 생성하는 등의 확장성이 더 높습니다.
비교 및 결론:
두 번째 방식은 viewResolver 메서드의 역할을 더 명확하게 하고, 이 메서드가 단순히 뷰 이름을 기반으로 MyView 객체를 생성하고 반환하는 역할에 집중하게 합니다. 또한, 뷰 이름을 사용하는 다른 종류의 처리나 확장이 필요할 경우 더 유연하게 대응할 수 있습니다. 이러한 이유로 두 번째 설계 방식이 조금 더 효율적이라고 볼 수 있으며, 코드의 유지보수성과 확장성 측면에서도 이점을 가집니다.
간단히 정리하자면 viewResolver는 함수의 역할을 ViewPath를 논리 뷰 이름과 고정된 경로를 결합하여 View의 주소를 반환하는 역할을 하므로 ViewResolver함수 자체에서 경로를 결합함과 동시에 View를 생성하여 호출영역으로 반환하는 것이다.
이렇게하여 함수의 역할과 목표를 명확히하고 유지보수측면에서 효율적이라는 얘기이다.
위 정리한 내용들은 모두인프런에서김영한님의 강의를 듣고 학습한 내용을 스스로 정리한 것임을 밝힙니다.