들어가며
- 저번 포스트에서 Model의 추가를 통해 서블릿 코드 종속을 제거하고, ViewResolver와 MyView의 render()를 조금 손봄으로서 컨트롤러에선 View의 논리적 이름과 모델만을 반환하면 되도록 개선하였다.
- 이번 포스트에서는 약간의 아이디어를 통해 더욱 편리하게 컨트롤러를 만들 수 있도록 한다.
- 구현 코드의 결과는 여기에서 볼 수 있다.
아이디어
- 지금 우리가 만든 이 FrontController와 프레임워크도 매우 탄탄한 구조를 가지고 있다. 여기서 조금의 아이디어를 통해 좋은 구조와 더불어 더 좋은 편리성까지 얻을 수 있도록 해보자.
- 다음의 코드를 보자.
public class MemberListControllerV3 implements ControllerV3 {
private static final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView modelView = new ModelView("members");
modelView.getModel().put("members", members);
return modelView;
}
}
- ModelView에 우리가 정의한 모델을 가져와 해당 모델에 데이터를 넣고 있다.
- 근데 생각해보면 이 모델은 결국 FrontController에서 빼내진 뒤에 다시 render()에 들어가야 한다. 즉 FrontController에서 다시 가공되게 된다.
- 그렇다면 다음과 같은 생각도 해봄직하다.
- FrontController에서 Model을 만들어서 파라미터로 넘겨도 되지 않을까?
- ModelView 객체 내부에 있는 모델을 결국 빼내서 사용해야하기 때문에 ModelView를 만드는 번거로운 과정을 거치지 않고, FrontController에서 모델을 만들어 보내준다면 결과적으로 컨트롤러는 View의 논리적 이름만을 반환해도 괜찮을 것이다.
- 즉 기존에 하나로 묶여있던 ModelView를 분리시켜 Model은 FrontController에서 만들어 공급하고, View 논리적 이름만을 반환하도록 만들면 된다.
구현
- 추가적인 컴포넌트들이 없기 때문에 컨트롤러만 새로 구현하면 된다.
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
- 기존 ModelView에 있던 model을 매개변수로 받게 하였다. 그리고 뷰의 논리적 이름만을 반환하면 되기 때문에 String으로 반환 타입을 잡았다.
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
public class MemberListControllerV4 implements ControllerV4 {
private static final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
}
public class MemberSaveControllerV4 implements ControllerV4 {
private static final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
String userName = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = memberRepository.save(new Member(userName, age));
model.put("member", member);
return "save-result";
}
}
- 더 이상 ModelView를 생성하지 않아도 된다. 컨트롤러는 단지 뷰의 논리적 이름만을 반환한다. 모델은 FrontController에서 만들고 객체를 넘기기 때문에 컨트롤러에서 넣은 데이터들이 FrontController에도 정상적으로 반환될 것이다.
FrontController 구현
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private final Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4(){
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV4 controllerV4 = controllerMap.get(requestURI);
if (controllerV4 == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>();
String viewName = controllerV4.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model, request, response);
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
private static Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
- 코드를 보면 변경된 점이 매우 적다. 새로운 Map인 모델을 만들어 컨트롤러를 호출하는데 넣고, View의 논리적 이름을 반환받은 뒤, ViewResolver를 통해 MyView를 받도록 한 것 이외에는 변경된 점이 없다.
- 이는 기존의 설계가 좋은 설계라는 것을 의미한다.
결과
- 해당 아이디어를 적용함으로서 컨트롤러 개발자는 메서드 파라미터로 받은 모델에 데이터를 넣고, 뷰의 논리적 이름만을 그대로 반환하면 된다.
- 기존의 ModelView를 만들어 내보낸 것보다 더 편리하게 변하였다. 이는 기존 컨트롤러가 해야 했던 일들을 프론트 컨트롤러에서 계속해서 가져가고 있기 떄문이다. 이렇게 프레임워크가 하는 일들이 많아질수록 이를 사용하는 유저는 점점 편리해진다.
- 또한 기존의 설계가 매우 잘 되어 있었기 때문에 해당 아이디어를 적용하는데에도 그렇게 많은 변경점이 발생하지 않았다. 즉 이러한 편리함은 좋은 설계를 토대로 하여 달성될 수 있다고 할 수 있겠다.
- 다음 포스트에서는 반환 타입이 다른 컨트롤러 등을 효과적으로 호출할 수 있도록 개선한다. 즉 확장성에 초점을 두고 개선해보도록 할 것이다.
'Spring & JPA > SpringMVC' 카테고리의 다른 글
Spring MVC - MVC 구조 - 8. 확장성 적용(2) (0) | 2023.06.02 |
---|---|
Spring MVC - MVC 구조 - 7. 확장성 적용(1) (0) | 2023.06.02 |
Spring MVC - MVC 구조 - 5. Model의 추가 (0) | 2023.06.01 |
Spring MVC - MVC 구조 - 4. View의 분리 (1) | 2023.05.31 |
Spring MVC - MVC 구조 - 3. 프론트 컨트롤러 구현(2) (0) | 2023.05.30 |