앞서 만든 v3 컨트롤러는 서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 등, 잘설계된 컨트롤러이다. 그런데 실제 컨트롤러 인터페이스를 구현하는 개발자 입장에서 보면, 항상 Model/View 객체를 생성하고 반환해야 하는 부분이 조금은 번거롭다.
좋은 프레임워크는 아키텍처도 중요하지만, 그와 더불어 실제 개발하는 개발자가 단순하고 편리하게 사용할 수 있어야 한다. 소위 실용성이 있어야 한다.
기본적인 구조는 v3와 같다 대신에 컨트롤러가 ModelView
를 반환하지 않고, ViewName
만 반환한다.
경로 : hello.servlet.web.frontcontroller.v4
package hello.servlet1.web.frontcontroller.v4;
import java.util.Map;
public interface ControllerV4 {
/**
* @param paramMap
* @param model
* @return viewName
*/
String process(Map<String,String> paramMap, Map<String,Object> model);
}
이전 버전은 인터페이스에 ModelView가 없다. model 객체는 파라미터로 전달 되기 때문에 그냥 사용하면 되고, 결과로 뷰의 이름만 반환해주면 된다.
경로 : hello.servlet.web.frontcontroller.v4.controller
package hello.servlet1.web.frontcontroller.v4.controller;
import hello.servlet1.web.frontcontroller.v4.ControllerV4;
import java.util.Map;
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
단순하게 new-form
이라는 뷰의 논리 이름만 반환하면 된다.
경로 : hello.servlet.web.frontcontroller.v4.controller
package hello.servlet1.web.frontcontroller.v4.controller;
import hello.servlet1.domain.member.Member;
import hello.servlet1.domain.member.MemberRepository;
import hello.servlet1.web.frontcontroller.v4.ControllerV4;
import java.util.Map;
public class MemberSaveControllerV4 implements ControllerV4 {
private 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 = new Member(username, age);
memberRepository.save(member);
model.put("member",member);
return "save-result";
}
}
model.put("member",member)
모델이 파라미터로 전달 되기 때문에, 모델을 직접 생성하지 않아도 된다.
경로 : hello.servlet.web.frontcontroller.v4.controller
package hello.servlet1.web.frontcontroller.v4.controller;
import hello.servlet1.domain.member.Member;
import hello.servlet1.domain.member.MemberRepository;
import hello.servlet1.web.frontcontroller.v4.ControllerV4;
import java.util.List;
import java.util.Map;
public class MemberListControllerV4 implements ControllerV4 {
private 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";
}
}
경로 : hello.servlet.web.frontcontroller.v4
package hello.servlet1.web.frontcontroller.v4;
import ch.qos.logback.core.pattern.SpacePadder;
import hello.servlet1.web.frontcontroller.MyView;
import hello.servlet1.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet1.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet1.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "frontControllerServletV4",urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private 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 controller = controllerMap.get(requestURI);
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String,String> paramMap = new HashMap<>();
Map<String,Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model,request,response);
}
private Map<String,String> createParamMap(HttpServletRequest request){
Map<String, String> paraMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paraName -> paraMap.put(paraName,request.getParameter(paraName)));
return paraMap;
}
private MyView viewResolver(String viewName){
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
FrontControllerServletV4
는 사실 이전 버전과 거의 동일
Map<String,Object> model = new HashMap<>(); //추가
모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다.
컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨 있게 된다.
String viewName = controller.process(paramMap,model);
MyView view = viewResolver(viewName);
컨트롤러가 직접 뷰의 논리 이름을 반환하므로 이값을 사용해서 실제 물리 뷰를 찾을 수 있다.
실행)
등록 : http://localhost:8080/front-controller/v4/members/new-form
목록 : http://localhost:8080/front-controller/v4/members
이번 버전의 컨트롤러는 매우 단순하고 실용적이다.
기존 구조에서 모델을 파라미터로 넘기고, 뷰의 논리 이름을 반환한다는 작은 아이디어를 적용했을 뿐인데, 컨트롤러를 구현하는 개발자 입장에서 보면 이제 군더더기 없는 코드를 작성할 수 있다.
또한 중요한 사실은 여기까지 한번에 온 것이 아니라는 점. 프레임워크가 점진적으로 발전하는 과정 속에서 이런 방법도 찾을 수 있다.