검색결과 리스트
Model에 해당되는 글 3건
- 2021.10.29 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 13
- 2021.10.29 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12
- 2021.10.15 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 10
글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 13
인프런 강의 31일차.
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)
- 서블릿, JSP, MVC 패턴
- 서블릿으로 1차 구현
-> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현
-> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현
---- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12 Model 추가 - v3에 이어서 작성.
2. MVC 프레임워크 만들기
2.6 단순하고 실용적인 컨트롤러 - v4
- 앞서 만든 v3 컨트롤러는 서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 등, 잘 설계된 컨트롤러이다.
- 아키텍처 관점에서는 정말로 잘 설계된 컨트롤러이나 실제 개발자 입장에서는 항상 ModelView 객체를 생성하고 반환해야 하는 부분이 있어서 사용이 불편하다는 단점이 있다.
- 실용성이 조금 떨어지는 것을 개선한 v4 컨트롤러를 만들어보자.
package hello.servlet.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);
}
- web.frontcontroller.v4.ControllerV4.java
- paramMap만 리턴하던 것에서 model도 같이 리턴하도록 수정
- 인터페이스에 modelView가 없다. model객체는 파라미터로 전달되기 때문에 그냥 사용하면 되고, 결과로 뷰의 이름만 반환해주면 된다.
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.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";
}
}
- web.frontcontroller.v4.controller.MemberFormControllerV4.java
- 단순하게 new-form이라는 뷰의 논리 이름만 반환하는 컨트롤러
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.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";
/*
V3과 달라진 점은 이미 컨트롤러에서 model을 바로 세팅하기 때문에 아래 로직이 필요가 없다(이미 세팅되어 있음)
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member); //멤버 모델에 저장
return mv;
*/
}
}
- web.frontcontroller.v4.controller.MemberSaveControllerV4.java
- 모델이 파라미터로 전달되기 때문에 모델을 직접 생성하지 않아도 된다. (model.put()으로 전달됨)
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.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();
/*
form과 마찬가지로 model에 이미 처리되어 있으므로 필요가 없는 로직
ModelView mv = new ModelView("members");
mv.getModel().put("members", members); //members 리스트로 저장
return mv;
*/
model.put("members", members);
return "members";
}
}
- web.frontcontroller.v4.controller.MemberListControllerV4.java
package hello.servlet.web.frontcontroller.v4;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.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;
//v4 하위에 모든 컨트롤러를 호출할 수 있음
@WebServlet(name="frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerV1Map = new HashMap<>();
public FrontControllerServletV4() {
//url과 호출해야하는 컨트롤러를 매핑하여 Map에 저장
controllerV1Map.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerV1Map.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerV1Map.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV4.service");
String requestURI = request.getRequestURI(); //호출되는 URL 주소 얻기
ControllerV4 controllerV4 = controllerV1Map.get(requestURI); //url 주소에 해당하는 Controller생성
if(controllerV4 == null){
//매칭되는 컨트롤러가 없으면 404 에러 리턴
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
HashMap<String, Object> model = new HashMap<>();
//매칭되는 컨트롤러를 찾았으면 해당 컨트롤러 호출
String viewName = controllerV4.process(paramMap, model); //기존에 Model을 받던 로직을 viewName을 바로 받도록 수정 (V4에서 수정된 코드)
//viewResolver 구현 (실제 View를 찾아주는 해결자 역할)
MyView view = viewResolver(viewName);
//myView 반환하면서 렌더링 수행
view.render(model, request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
//ctrl + alt + M 으로 람다 표현식 메소드로 추출
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator() //모든 파라미터 이름을 가져온 뒤 forEach 돌린 뒤 paramMap에 모든 데이터를 put함
.forEachRemaining(paramName->paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
- web.frontcontroller.v4.controller.MemberListControllerV4.java
- FrontControllerV4는 FrontControllerV3과 거의 동일하며 controller.process의 return이 model이 아닌 viewName이 되었다는 것만 변경되었다.
- 모델 객체 전달
> Map<String, Object> Model = new HashMap<>();
> 모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨있게 된다.
- 뷰의 논리 이름을 직접 반환
> String viewName = controller.process(paramMap, model);
> MyView view = viewResolver(viewName);
> 컨트롤러가 직접 뷰의 논리 이름을 반환하므로 이 값을 사용해서 실제 물리 뷰를 찾을 수 있다.
* V4 버전의 컨트롤러는 매우 단순하고 실용적이다. 기존 구조에서 모델을 파라미터로 넘기고, 뷰의 논리 이름을 반환한다는 작은 아이디어를 적용한 것 뿐인데 컨트롤러를 구현하는 개발자 입장에서 보면 이제 군더더기 없는 코드를 작성할 수 있다.
** 프레임워크나 공통 기능이 수고로워야 사용하는 개발자가 편리해진다.
'Spring 정리' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 15 (0) | 2021.12.06 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 14 (0) | 2021.11.22 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12 (0) | 2021.10.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 11 (0) | 2021.10.21 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 10 (0) | 2021.10.15 |
설정
트랙백
댓글
글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12
인프런 강의 30일차.
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)
- 서블릿, JSP, MVC 패턴
- 서블릿으로 1차 구현
-> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현
-> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현
---- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 11 View 분리 - v2에 이어서 작성.
2. MVC 프레임워크 만들기
2.5 Model 추가 - v3
- 서블릿 종속성 제거
> 모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고, 깔끔하지 않다 (viewPath 및 dispatcher 호출 부분)
> Model 추가 시 서블릿의 종속성을 제거한다. 컨트롤러 입장에서 HttpServletRequest, HttpServletResponse가 꼭 필요할까?
> 요청 파마리터 정보는 자바의 Map으로 대신 넘기도록 하면 지금 구조에서는 컨트롤러가 서블릿 구조를 몰라도 동작할 수 있다.
> 그리고 request 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 변환하면 된다.
- 뷰 이름 중복 제거
> 컨트롤러에서 지정하는 뷰 이름에 중복이 있는 것을 확인할 수 있다.
> 컨트롤러에서는 뷰의 논리 이름을 변경하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화
> 향후 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 된다.
> /WEB-INF/views/new-form.jsp -> new-form
> /WEB-INF/views/save-result.jsp -> save-result
> /WEB-INF/views/members.jsp -> members
- ModelView
> 지금까지 컨트롤러에서 서블릿에 종속적인 HttpServletRequest를 사용했다. 그리고 Model도 request.setAttribute()를 통해 데이터를 저장하고 뷰에 전달했다.
> 서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 추가로 View 이름까지 전달되는 객체를 만들어보자.
> 실제 프론트 컨트롤러는 작업량이 훨씬 많아지지만 실제 구현한 컨트롤러 입장에서는 로직이 간단해짐
package hello.servlet.web.frontcontroller;
import java.util.HashMap;
import java.util.Map;
public class ModelView {
private String viewName;
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;
}
}
- web.frontcontroller.ModelView.java
- 뷰의 이름과 뷰를 렌더링할 때 필요한 model 객체를 가지고 있다. model은 단순히 map으로 되어 있으므로 컨트롤러에서 뷰에 필요한 데이터를 key, value로 넣어주면 된다.
package hello.servlet.web.frontcontroller.v3;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
- web.frontcontroller.v3ControllerV3.java
- 이 컨트롤러는 서블릿 기술을 전혀 사용하지 않는다. 따라서 구현이 매우 단순해지고, 테스트 코드 작성시 테스트 하기 쉽다.
- HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러가 paramMap에 담아서 호출해주면 된다.
- 응답 결과로 뷰 이름과 뷰에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환하면 된다
package hello.servlet.web.frontcontroller.v3.controller;
import hello.servlet.web.frontcontroller.v2.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import java.util.Map;
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form"); //논리 이름으로만 ModelView 생성 (논리이름을 물리이름으로 변경하는 것은 ViewResolver에서 수행)
}
}
- web.frontcontroller.v3.controller.MemberFormControllerV3.java
- 논리 이름으로만 modelView 생성(실제 물리적인 이름은 프론트 컨트롤러에서 처리)
package hello.servlet.web.frontcontroller.v3.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v2.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import java.util.Map;
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member); //멤버 모델에 저장
return mv;
}
}
- web.frontcontroller.v3.controller.MemberSaveControllerV3.java
- save-result 논리 이름으로 modelView 생성 후 member 정보 저장
- 파라미터 정보는 전부 map에 저장되어 있다.
package hello.servlet.web.frontcontroller.v3.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v2.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import java.util.List;
import java.util.Map;
public class MemberListControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView mv = new ModelView("members");
mv.getModel().put("members", members); //members 리스트로 저장
return mv;
}
}
- web.frontcontroller.v3.controller.MemberListControllerV3.java
package hello.servlet.web.frontcontroller.v3;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
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;
//v3 하위에 모든 컨트롤러를 호출할 수 있음
@WebServlet(name="frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerV1Map = new HashMap<>();
public FrontControllerServletV3() {
//url과 호출해야하는 컨트롤러를 매핑하여 Map에 저장
controllerV1Map.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerV1Map.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerV1Map.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV3.service");
String requestURI = request.getRequestURI(); //호출되는 URL 주소 얻기
ControllerV3 controllerV3 = controllerV1Map.get(requestURI); //url 주소에 해당하는 Controller생성
if(controllerV3 == null){
//매칭되는 컨트롤러가 없으면 404 에러 리턴
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//V3는 controllerV2.process(request, response)로 호출하였다면 V3에서는 Model로 호출해야함 (Model은 paramMap에 저장되어 있음)
//paramMap 호출
//람다 호출의 경우 너무 디테일한 레벨로 구현되어 있으므로 따로 메소드로 빼는 것이 좋다.
/*Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator() //모든 파라미터 이름을 가져온 뒤 forEach 돌린 뒤 paramMap에 모든 데이터를 put함
.forEachRemaining(paramName->paramMap.put(paramName, request.getParameter(paramName)));*/
Map<String, String> paramMap = createParamMap(request);
//매칭되는 컨트롤러를 찾았으면 해당 컨트롤러 호출
ModelView mv = controllerV3.process(paramMap);
String viewName = mv.getViewName();//논리 이름 얻어오는 메소드 ex)new-form
//viewResolver 구현 (실제 View를 찾아주는 해결자 역할)
MyView view = viewResolver(viewName);
//myView 반환하면서 렌더링 수행
view.render(mv.getModel(), request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
//ctrl + alt + M 으로 람다 표현식 메소드로 추출
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator() //모든 파라미터 이름을 가져온 뒤 forEach 돌린 뒤 paramMap에 모든 데이터를 put함
.forEachRemaining(paramName->paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
- web.frontcontroller.v3.FrontControllerServletV3.java
- view.render(mv.getModel(), request, response)에서 컴파일 오류가 발생할 시 오버로딩 메소드를 생성해서 처리
- createParamMap() : HttpServletRequest에서 파라미터 정보를 꺼내서 Map으로 변환한다. 그리고 해당 Map( paramMap )을 컨트롤러에 전달하면서 호출한다
package hello.servlet.web.frontcontroller;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//MyView를 생성할 때 생성자로 세팅한 viewPath 호출
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//model에 있는 데이터를 forEach로 전부 추출해서 request에 key-value로 값을 전부 적재함
modelToRequestAttribute(model, request);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value) -> request.setAttribute(key, value));
}
}
- web.frontcontroller.MyView.java
- 뷰 리졸버
> MyView view = viewResolver(viewName)
> 컨트롤러가 반환한 논리 뷰 이름을 실제 물리 뷰 경로로 변경한다. 그리고 실제 물리 경로가 있는 MyView 객체를 반환한다.
> 논리 뷰 이름: members 물리 뷰 경로: /WEB-INF/views/members.jsp
> view.render(mv.getModel(), request, response)
> 뷰 객체를 통해서 HTML 화면을 렌더링 한다.
> 뷰 객체의 render() 는 모델 정보도 함께 받는다.
> JSP는 request.getAttribute() 로 데이터를 조회하기 때문에, 모델의 데이터를 꺼내서 request.setAttribute() 로 담아둔다.
> JSP로 포워드 해서 JSP를 렌더링 한다.
'Spring 정리' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 14 (0) | 2021.11.22 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 13 (0) | 2021.10.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 11 (0) | 2021.10.21 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 10 (0) | 2021.10.15 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 9 (0) | 2021.10.14 |
설정
트랙백
댓글
글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 10
인프런 강의 28일차.
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)
- 서블릿, JSP, MVC 패턴
- 서블릿으로 1차 구현
-> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현
-> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현
1. MVC 패턴 - 개요
1.1 너무 많은 역할
- 하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게 되면, 너무 많은 역할을 하게되고, 결과적으로 유지보수가 어려워진다.
- 비즈니스 로직을 호출하는 부분에 변경이 발생해도 해당 코드를 손대야 하고, UI를 변경할 일이 있어도 비즈니스 로직이 함께 있는 해당 파일을 수정해야 한다.
- HTML 코드 하나 수정해야 하는데, 수백줄의 자바 코드가 함께 있거나 비즈니스 로직을 하나 수정해야 하는데 수백 수천줄의 HTML 코드가 함께 있게된다..
1.2 변경의 라이프 사이클
- 제일 중요한 문제는 둘 사이에 변경의 라이프 사이클이 다르다는 점이다.
- 예를 들어서 UI 를 일부 수정하는 일과 비즈니스 로직을 수정하는 일은 각각 다르게 발생할 가능성이 매우 높고 대부분 서로에게 영향을 주지 않는다.
- 이렇게 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다. (물론 UI가 많이 변하면 함께 변경될 가능성도 있다.
1.3 기능 특화
- 특히 JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이 부분의 업무만 담당하는 것이 가장 효과적이다.
- 서블릿은 java 소스를 처리하는데 최적화
1.4 Model View Controller
- MVC 패턴은 지금까지 학습한 것 처럼 하나의 서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것을 말한다. 웹 애플리케이션은 보통 이 MVC 패턴을 사용한다
- Contoller : HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
- Model : 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.
- View : 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다
* 참고
- 컨트롤러에 비즈니스 로직을 둘 수도 있지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다. 그래서 일반적으로 비즈니스 로직은 서비스(Service)라는 계층을 별도로 만들어서 처리한다. 그리고 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 담당한다. 참고로 비즈니스 로직을 변경하면 비즈니스 로직을 호출하는 컨트롤러의 코드도 변경될 수 있다. 앞에서는 이해를 돕기 위해 비즈니스 로직을 호출한다는 표현 보다는, 비즈니스 로직이라 설명했다.
2. MVC 패턴 적용
- 서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해보자.
- Model은 HttpServletRequest 객체를 사용한다. request는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute() , request.getAttribute() 를 사용하면 데이터를 보관하고, 조회할 수 있다
3. 회원 등록
package hello.servlet.web.sevletmvc;
import javax.servlet.RequestDispatcher;
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;
@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response); //고객 요청이 오면 서버 내부에서 호출
}
}
- web.servletmvc.MvcMemberFormServlet.java
- dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능이다. 서버 내부에서 다시 호출이 발생한다.
- WEB-INF : 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 즉 컨트롤러를 통해서만 JSP 호출가능
- redirect : 리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다.
- forward : 포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못한다 (url변경 없음)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] -->
<form action="save" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
- webapp/WEB-INF/views/new-form.jsp
- form의 action를 상대경로로 사용해서 폼 전송시 현재 URL이 속한 계층 경로 + save가 호출된다.
- 현재 계층 경로: /servlet-mvc/members/
- 결과: /servlet-mvc/members/save
package hello.servlet.web.sevletmvc;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.RequestDispatcher;
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;
@WebServlet(name="mvcMemberServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age")); //request.getParam의 결과는 항상 문자이므로 숫자는 따로 형변환을 해주어야함
Member member = new Member(username, age);
memberRepository.save(member);
//Model에 데이터 저장
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
- web.servletmvc.MvcMemberSaveServlet.java
- HttpServletRequest를 Model로 사용한다.
- request가 제공하는 setAttribute()를 사용하면 request 객체에 데이터를 보관해서 뷰에 전달할 수 있다.
- 뷰는 request.getAttribute() 를 사용해서 데이터를 꺼내면 된다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
성공
<ul>
<li>id=${member.id}</li>
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
- webapp/WEB-INF/views/save-result.jsp
- <%= request.getAttribute("member")%> 로 모델에 저장한 member 객체를 꺼낼 수 있지만, 너무 복잡해진다.
- JSP는 ${} 문법을 제공하는데, 이 문법을 사용하면 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있다.
- MVC 덕분에 컨트롤러 로직과 뷰 로직을 확실하게 분리한 것을 확인할 수 있다. 향후 화면에 수정이 발생하면 뷰 로직만 변경하면 된다.
package hello.servlet.web.sevletmvc;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.RequestDispatcher;
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.List;
@WebServlet(name="mvcMemberListServlet", urlPatterns = "/servelet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
- web.servletmvc.MvcMemberListServlet.java
v<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>age</th>
</thead>
<tbody>
<c:forEach var="item" items="${members}">
<tr>
<td>${item.id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
- webapp/WEB-INF/views/members.jsp
- 모델에 담아둔 members를 JSP가 제공하는 taglib기능을 사용해서 반복하면서 출력했다. (JSLT)
- members 리스트에서 member 를 순서대로 꺼내서 item 변수에 담고, 출력하는 과정을 반복한다. 이 기능을 사용하려면 다음과 같이 선언해야 한다.
- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
- JSP와 같은 뷰 템플릿은 이렇게 화면을 렌더링 하는데 특화된 다양한 기능을 제공한다.
- 다만 현재 실무에서 JSP가 사양되고 있는 추세기 떄문에 적당히만 알아두자..
'Spring 정리' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12 (0) | 2021.10.29 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 11 (0) | 2021.10.21 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 9 (0) | 2021.10.14 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 8 (0) | 2021.10.05 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 7 (0) | 2021.10.05 |