일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- biblecash
- fcm데이터구조
- java enum
- 프로세스 생성
- rest api 검증
- 공유기작동방식
- Wrapper class
- HttpServlet
- equals
- HTTP프로토콜
- multiparfile데이터
- httpservlet기술
- 옵티마저
- fcm성능비교
- 데이터베이스파서
- 옵티마이저
- 디스패처서블릿
- 검증 실패 예외처리
- 동등성동일성
- DispatcherServlet
- 클라이언트요청반응
- 스프링요청반응
- 불변객체
- multipart바인딩
- 래퍼클래스
- 왜불변객체인가
- fcmv1
- rest api
- 동기비동기블로킹논블로킹
- 중첩클래스
- Today
- Total
개발은 아름다워
[ Spring ] 고대의 서블릿을 찾아서(6) - ModelView의 등판 본문
'작성된 코드를 보면서 이게 과연 반드시 필요한 것일까?' 라는 의문이 든적이 있지 않는가? ModelView도 같은 질문에서 시작한다.
ModelView의 필요성
이전 controller들의 코드를 봐보자
public class MemberFormControllerV2 implements ControllerV2{
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
return new MyView("/WEB-INF/views/new-form.jsp");
}
}
public class MemberListControllerV2 implements ControllerV2{
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
return new MyView("/WEB-INF/views/members.jsp");
}
}
public class MemberSaveControllerV2 implements ControllerV2{
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
// Model에 데이터를 보관
request.setAttribute("member", member);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
request를 쓰는 곳도 있고 안쓰는 곳도 있다. 심지어 response는 아예 쓰지 않는다. 여기서 request의 무슨 역할을 하고 있을까? http 요청으로부터 받은 데이터를 파싱할 수 있게 해주기도하고 controller에서 발생한 데이터를 jsp 서블릿에 전달해주기도 한다. 하지만 데이터를 주고 받는 역할로 서블릿 기술을 컨트롤러에서 쓰기에는 코드 자체가 복잡해보인다. 또한 테스트 코드 작성도 어려울 수 있다. 따라서 데이터를을 주고 받을 수 있는 model을 만들 것이다.
그리고 각 controller에서 MyView에 들어가는 viewPath를 봐보자
"/WEB-INF/views/new-form.jsp"
"/WEB-INF/views/save-result.jsp"
"/WEB-INF/views/members.jsp"
공통으로 부분들이 보이며 다른 부분들도 보인다. 따라서 다른 부분들은 논리 이름으로 만든 후 추후에 공통으로 들어가는 부분에 넣으면 된다. 예) 논리부분인 String viewName = new-form 을 추후에 String viewPath = "/WEB-INF/views/" + viewName + ".jsp" 이런식으로 말이다.
위의 두가지 문제를 해결할 ModelView 객체는 다음과 같다.
public class ModelView {
// viewPath의 논리이름
private String viewName;
// controller에서 발생한 데이터를 담는 model
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName){
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
서블릿 종속성을 제거 후 확연히 달라진 controller 코드
자, 코드를 보기 전 이것을 염두해두자. request를 통해 http 요청 데이터를 가져왔었고, controller에서 발생한 데이터를 request에 넣어서 jsp서블릿에 데이터를 보내도록 했었다.
이 부분에 변화가 있다. http요청 데이터는 Map<String,String> paramMap 넣을 것이고,
controller로 부터 발생한 데이터는 Map<String,Object> model에 넣을 것이다.
따라서 Controller 인터페이스는 서블릿을 인자로 받는 것이 아닌 paramMap을 받는다.
참고로 request에 있는 http요청 데이터는 FrontController에서 paramMap에 넣을 것이다.
public interface ControllerV3 {
// MyView process(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException;
ModelView process(Map<String,String> paramMap);
}
MyView를 반환했던 것과 달리 ModelView를 반환한다. 또한 더 이상 컨트롤러는 서블릿에 종속되지 않으므로 코드가 훨씬 간결해진 것을 확인할 수 있다.
다음은 Controller 인터페이스를 상속 받는 controller들을 확인해보자.
public class MemberFormControllerV3 implements ControllerV3 {
// ModelView 사용 전
// @Override
// public MyView process(HttpServletRequest request, HttpServletResponse response)
// throws ServletException, IOException {
// return new MyView("/WEB-INF/views/new-form.jsp");
// }
// ModelView 사용 후
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
public class MemberSaveControllerV3 implements ControllerV3{
private MemberRepository memberRepository = MemberRepository.getInstance();
// ModelView 사용 전
// @Override
// public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// String username = request.getParameter("username");
// int age = Integer.parseInt(request.getParameter("age"));
// Member member = new Member(username, age);
// System.out.println("member = " + member);
// memberRepository.save(member);
// // Model에 데이터를 보관
// request.setAttribute("member", member);
// return new MyView("/WEB-INF/views/save-result.jsp");
// }
@Override
public ModelView process(Map<String, String> paramMap) {
//paramMap은 Http 요청 데이터를 주고 받는 용도
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
// ModelView에 데이터를 넣을 수 있음
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
public class MemberListControllerV3 implements ControllerV3{
private MemberRepository memberRepository = MemberRepository.getInstance();
// ModelView 사용 전
// @Override
// public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("MvcMemberListServlet.service");
// List<Member> members = memberRepository.findAll();
// request.setAttribute("members", members);
// return new MyView("/WEB-INF/views/members.jsp");
// }
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView mv = new ModelView("members");
mv.getModel().put("members", members);
return mv;
}
}
서블릿에 종속되지 않으니 훨씬 간결해진 코드들을 볼 수 있다. 각 Controller는 ModelView 객체에 논리 viewName과 controller에서 발생한 데이터를 담아서 ModelView 객체를 반환한다.
그렇다면 이제 FrontController는 뭘하면 될까? 컨트롤러에서 받은 ModelView 객체에서 논리 viewName을 viewPath로 바꾸면 되고 view 렌더링 시 컨트롤러에서 받은 데이터를 넘겨주면 된다.
FrontController에 viewResolver 추가요~
controller에서는 ModelView 객체를 반환한다. 반환된 ModelView 객체에는 두가지가 들어가 있다.
1) viewName 2)controller에서 발생한 데이터가 담긴 model
따라서 FrontController에서 할 일은 viewName을 viewPath로 바꾸는 일과 model에 담긴 데이터를 view에 넘겨줘서 render가 되도록하면 된다.
코드를 봐보자
@WebServlet(name = "frontControllerServletV3",urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet{
private Map<String,ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save",new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV2.service");
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
if(controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
// http요청에 있는 데이터를 paramMap에 넣어줘야함
Map<String,String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
// 실제 뷰 경로를 가져와야함
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
// 뷰 렌더링시 컨트롤러에서 받은 데이터들이 담긴 modeld을 넘겨줘야함
view.render(mv.getModel(),request,response);
}
private 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;
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/"+viewName+".jsp");
}
}
createParamMap은 뭘까?
controller가 서블릿에 종속일때는 HttpServletRequest request가 가진 데이터를 가져왔다. 하지만 이제 controller는 서블릿 종속이 아니므로 request가 가진 데이터를 paramMap에 넣어줘야한다. 그 역할을 하는 것이 createParamMap 메서드이다.
viewResolver를 통해 viewPath를 가진 MyView 객체를 반환
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/"+viewName+".jsp");
}
// 실제 뷰 경로를 가져와야함
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
controller에서 발생한 viewName을 viewResolver를 통해 viewPath를 가진 MyView view 객체를 만든다. MyView view 객체는 이제 jsp 서블릿에 데이터를 전달해주면 된다.
view.render를 위해 model 데이터를 request에 전달하기
public class MyView {
private String viewPath;
public MyView(String viewPath){
this.viewPath = viewPath;
}
public void render(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException{
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
public void render(Map<String,Object> model,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException{
modelToRequestAttribute(model, request);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
// 컨트롤러를 거치고 나온 model 데이터를 request에 전달해야함
private void modelToRequestAttribute(Map<String,Object> model,HttpServletRequest request){
model.forEach((key,value)->request.setAttribute(key,value));
}
}
controller에서 받은 model 데이터를 request에 전달해야한다. 왜냐하면 jsp서블릿에 데이터를 전달하기 위해서는 request를 통해 전달해야 되기 때문이다. 이는 MyView 내의 modelToRequestAttribute를 통해 작업이 진행된다.
정리
전체적으로 코드가 한결 간결해진 것이 보인다. 특히 controller가 서블릿에 종속 되지 않으니 코드가 더욱 직관적으로 보인다. 때로는 서두에서 말한것처럼 '작성된 코드를 보면서 이게 과연 반드시 필요한 것일까?'에 대한 질문을 던져보고 코드를 수정해보는 노력도 필요한 것 같다. 또한 이번 frontController를 만들면서 마치 기계와 같다는 생각이 들었다. 부품 하나 하나가 연쇄적으로 작동하여 기계가 작동하듯이 코드 하나 하나가 연쇄적으로 작동하여
참고
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/
'스프링' 카테고리의 다른 글
[ Spring ] 고대의 서블릿을 찾아서(8) - 어댑터패턴과 트레이드 오프 (0) | 2024.10.14 |
---|---|
[ Spring ] 고대의 서블릿을 찾아서(7) - ModelView를 없애고 더욱 단순하게 (0) | 2024.10.14 |
[ Spring ] 고대의 서블릿을 찾아서(5) FrontController에 View 추가 (0) | 2024.10.14 |
[ Spring ] 고대의 서블릿을 찾아서(3) 템플릿 엔진을 너머 MVC패턴으로 (0) | 2024.10.12 |
[ Spring ] 고대의 서블릿을 찾아서(2) - 서블릿을 넘어 템플릿 엔진으로 (0) | 2024.10.12 |