검색결과 리스트
controller에 해당되는 글 3건
- 2022.04.03 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 19
- 2021.11.22 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 14
- 2021.10.15 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 10
글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 19
인프런 강의 36일차.
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)
- 서블릿, JSP, MVC 패턴
- 서블릿으로 1차 구현
-> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현
-> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현
3. 스프링 MVC - 구조 이해
3.9 스프링 mvc 컨트롤러 통합
> @RequestMapping 을 잘 보면 클래스 단위가 아니라 메서드 단위에 적용된 것을 확인할 수 있다. 따라서 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.
package hello.servlet.web.springmvc.v2;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@Controller("/springmvc/v2/members")
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//Controller에서 매핑된 주소가 prefix로 붙게된다.
@RequestMapping("/new-form")
public ModelAndView newForm() {
return new ModelAndView("new-form");
}
@RequestMapping("/save")
public ModelAndView save(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members); //mv.getModel().put("members", members); //members 리스트로 저장
return mv;
}
@RequestMapping
public ModelAndView members(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member); //mv.getModel().put("member", member); //멤버 모델에 저장
return mv;
}
}
- web.springmvc.v2.SpringMemberControllerV2
- v1에서 각 클래스로 나누어져 있던 기능을 하나의 Controller 파일에 통합해서 구현하였다.
- 최초 v1 메소드 복사 시 각 메소드의 매핑이 절대경로로 되어 있었으나 "/spring/mvc/v1/members" 부분은 중복이므로 해당 url은 Class단으로 올려서 Contoller에 정의해주면 prefix로 조합된다.
- class url + method url 이 최종 메소드 호출 url이 된다.
'Spring 정리' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 21 (0) | 2022.05.15 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 20 (0) | 2022.04.03 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 18 (0) | 2022.04.03 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 17 (0) | 2022.02.02 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 16 (0) | 2021.12.07 |
설정
트랙백
댓글
글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 14
인프런 강의 32일차.
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)
- 서블릿, JSP, MVC 패턴
- 서블릿으로 1차 구현
-> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현
-> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현
---- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 13 단순하고 실용적인 컨트롤러 - v4에 이어서 작성.
2. MVC 프레임워크 만들기
2.7 유연한 컨트롤러 - v5 (v3 내용 추가)
- 특정 개발자는 ControllerV3으로 개발해야하고, 특정 개발자는 ControllerV4으로 개발해야 할 경우 어떻게 해야할까?
> 어댑터 패턴 사용
- 어댑터 패턴
> 지금까지 개발한 프론트 컨트롤러는 한가지 방식과 컨트롤러 인터페이스만 사용할 수 있다.
> ControllerV3와 ControllerV4는 완전히 다른 인터페이스이므로 호환이 불가능하다.
> 이 때 어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 차지할 수 있도록 변경가능하다.
> 핸들러 어댑터 : 중간에 어댑터 역할을 하는 어댑터가 추가되었는데, 이름이 핸들러 어댑터이다. 여기서 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다.
> 핸들러 : 컨트롤러의 이름을 더 넓은 범위의 핸들러로 변경했다(컨트롤러=핸들러). 그 이유는 이제 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다.
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
- web.frontcontroller.v5.MyHandleAdapter.java (인터페이스)
- boolean supports(Object handler)
> handler는 컨트롤러를 말한다.
> 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메소드이다.
- ModelView handle(HttpServletRequest request, HttpServlerResponse response, Object handler)
> 어댑터는 실제 컨트롤러를 호출하고, 그 결과로 ModelView를 반환해야 한다.
> 실제 컨트롤러가 ModelView를 반환하지 못하면, 어댑터가 ModelView를 직접 생성해서라도 반환해야한다.
> 이전에는 프론트 컨트롤러가 실제 컨트롤러를 호출했지만, 이제는 이 어댑터를 통해서 실제 컨트롤러가 호출된다.
package hello.servlet.web.frontcontroller.v5.adapter;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV3); //handler가 ControllerV3이면 true, 아니면 false 반환
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
//supports에서 ControllerV3인 오브젝트만 true로 반환했기 때문에 handler에서는 ControllerV3로만 처리한다.
ControllerV3 controller = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);//ModelView 반환 완료 //ControllerV4는 ModelView가 아니라 논리적인 이름을 반환하므로 로직이 달라져야 한다.
return mv;
}
//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.v5.adapter.ControllerV3HandlerAdapter.java
- ControllerV3을 처리할 수 있는 어댑터를 생성
- handler를 컨트롤러 V3로 변환한 다음 V3형식에 맞도록 호출
- supports()를 통해 ControllerV3만 지원하기 때문에 타입 변환은 걱정없이 실행해도 된다.
- ControllerV3는 ModelView를 반환하므로 그대로 ModelView를 반환하면 된다.
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
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 hello.servlet.web.frontcontroller.v4.ControllerV4;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
//private Map<String, ControllerV4> controllerMap = new HashMap<>(); //기존 컨트롤러는 변수 선언 때 특정 컨트롤러를 선언한다.
private final Map<String, Object> handlerMappingMap = new HashMap<>(); //핸들러는 특정 컨트롤러가 아닌 오브젝트로 대상을 전달받는다.
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>(); //어댑터가 여러개 담겨있을 리스트 선언
public FrontControllerServletV5() {
//MappingMap 선언 후 V3를 붙일 어댑터까지 선언
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerMappingMap() {
//url과 호출해야하는 컨트롤러를 매핑하여 Map에 저장 (Object이기 때문에 ControllerV3로 선언 가능)
//v5/v3 경로를 호출해야만 한다.(v5경로가 아닐 시 404 에러가 발생)
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV5.service");
//1. handler 호출
Object handler = getHandler(request);
if(handler == null){
//매칭되는 컨트롤러가 없으면 404 에러 리턴
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//2. handler에 맞는 adapter 호출
MyHandlerAdapter adapter = getHandlerAdapter(handler);
//3. handle 메소드 호출 (V3에 해당하는 컨트롤러로 바꾼 뒤 실제 컨트롤러의 process 메소드를 호출 후 ModelView 반환)
ModelView mv = adapter.handle(request, response, handler);
//4. 얻은 view의 이름 얻기ㄹ
String viewName = mv.getViewName();//논리 이름 얻어오는 메소드 ex)new-form
//5. viewResolver 호출 (실제 View를 찾아주는 해결자 역할)
MyView view = viewResolver(viewName);
//myView 반환하면서 렌더링 수행
view.render(mv.getModel(), request, response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)){
//handler가 true이면 사용할 adapter에 세팅
return adapter;
}
}
//adapter가 선언 안되어 있는 경우 throw
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = "+handler);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI(); //호출되는 URL 주소 얻기
return handlerMappingMap.get(requestURI); //MappingMap에서 url 주소에 해당하는 Controller 반환
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
- web.frontcontroller.v5.FrontController.ServletV5.java
> 이제는 어댑터를 사용하기 때문에 컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면, 어떤 것이라도 URL에서 매핑해서 사용할 수 있다. 그래서 이름을 컨트롤러에서 더 넓은 범위인 핸들러로 변경.
- 생성자에서는 핸들러 매핑과 어댑터를 초기화 한다.
- 핸들러 매핑(Object handler = getHandler(request))는 handlerMappingMap에서 URL에 매핑된 핸들러(컨트롤러) 객체를 찾아서 반환한다.
- 핸들러를 처리할 수 있는 어댑터 조회 (getHandlerAdapter(handler))는 handler를 처리할 수 있는 어댑터를 adapter.supports(hanlder)를 통해서 찾는다. handler가 ControllerV3 인터페이스를 구현했다면ControllerV3HandlerAdapter 객체가 반환된다.
- 어댑터 호출(adapter.handle(request, response, handle)) 메소드를 통해 실제 어댑터가 호출된다.
2.8 유연한 컨트롤러 - v5 (v4 내용 추가)
- ControllerV3외에 ControllerV4도 반환 가능하도록 구현해보자.
> ControllerV4Adapter 추가!
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
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 hello.servlet.web.frontcontroller.v4.ControllerV4;
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 hello.servlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV4HandlerAdapter;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
//private Map<String, ControllerV4> controllerMap = new HashMap<>(); //기존 컨트롤러는 변수 선언 때 특정 컨트롤러를 선언한다.
private final Map<String, Object> handlerMappingMap = new HashMap<>(); //핸들러는 특정 컨트롤러가 아닌 오브젝트로 대상을 전달받는다.
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>(); //어댑터가 여러개 담겨있을 리스트 선언
public FrontControllerServletV5() {
//MappingMap 선언 후 V3를 붙일 어댑터까지 선언
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerMappingMap() {
//url과 호출해야하는 컨트롤러를 매핑하여 Map에 저장 (Object이기 때문에 ControllerV3로 선언 가능)
//v5/v3 경로를 호출해야만 한다.(v5경로가 아닐 시 404 에러가 발생)
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
//+++ControllerV4를 반환할 수 있는 리스트도 추가
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter()); //ControllerV4용 어댑터 추가
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV5.service");
//1. handler 호출
Object handler = getHandler(request);
if(handler == null){
//매칭되는 컨트롤러가 없으면 404 에러 리턴
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//2. handler에 맞는 adapter 호출
MyHandlerAdapter adapter = getHandlerAdapter(handler);
//3. handle 메소드 호출 (V3에 해당하는 컨트롤러로 바꾼 뒤 실제 컨트롤러의 process 메소드를 호출 후 ModelView 반환)
ModelView mv = adapter.handle(request, response, handler);
//4. 얻은 view의 이름 얻기ㄹ
String viewName = mv.getViewName();//논리 이름 얻어오는 메소드 ex)new-form
//5. viewResolver 호출 (실제 View를 찾아주는 해결자 역할)
MyView view = viewResolver(viewName);
//myView 반환하면서 렌더링 수행
view.render(mv.getModel(), request, response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)){
//handler가 true이면 사용할 adapter에 세팅
return adapter;
}
}
//adapter가 선언 안되어 있는 경우 throw
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = "+handler);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI(); //호출되는 URL 주소 얻기
return handlerMappingMap.get(requestURI); //MappingMap에서 url 주소에 해당하는 Controller 반환
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
- web.frontcontroller.v5.FrontController.ServletV5.java
> 기존 ControllerV3외에 ControllerV4 어댑터도 추가한다.
package hello.servlet.web.frontcontroller.v5.adapter;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4); //ControllerV4 클래스인 경우 true
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV4 controller = (ControllerV4) handler;
Map<String, String> paramMap = createParamMap(request);
HashMap<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model); //ControllerV4는 ModelView가 아니라 논리적인 이름을 반환하므로 로직이 달라져야 한다.
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
}
//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.v5.adapter.ControllerV4HandlerAdapter.java
- ControllerV4을 처리할 수 있는 어댑터를 생성
- handler를 ControllerV4로 캐스팅하고, paramMap, model을 만들어서 해당 컨트롤러를 호출한다. 그 뒤 viewName을 반환한다.
- ModelView mv = new ModelView(viewName)에서 새로 ModelView를 만들어서 반환해야한다. (어댑터는 뷰의 이름이 아니라 ModelView형식을 맞춰서 처리하기 떄문)
* 정리
- 지금까지 v1 ~ v5로 점진적으로 프레임워크를 발전시켜 왔다. 지금까지 한 작업을 정리해보자.
- v1: 프론트 컨트롤러를 도입
> 기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입
- v2: View 분류
> 단순 반복 되는 뷰 로직 분리
- v3: Model 추가
> 서블릿 종속성 제거 뷰 이름 중복 제거
- v4: 단순하고 실용적인 컨트롤러
> v3와 거의 비슷
> 구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공
- v5: 유연한 컨트롤러
> 어댑터 도입
> 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계
- FrontController 구조를 수정하지 않고 처리가 가능하다.
** 참고
- 여기에 애노테이션을 사용해서 컨트롤러를 더 편리하게 발전시길 수도 있다. 만약 애노테이션을 사용해서 컨트롤러를 편리하게 사용할 수 있게 하려면 어떻게 해야할까? 바로 애노테이션을 지원하는 어댑터를 추가하면 된다! 다형성과 어댑터 덕분에 기존 구조를 유지하면서, 프레임워크의 기능을 확장할 수 있다.
** 스프링 MVC
- 스프링 MVC 여기서 더 발전시키면 좋겠지만, 스프링 MVC의 핵심 구조를 파악하는데 필요한 부분은 모두 만들어보았다. 사실은 여러분이 지금까지 작성한 코드는 스프링 MVC 프레임워크의 핵심 코드의 축약 버전이고, 구조도 거의 같다. 스프링 MVC에는 지금까지 우리가 학습한 내용과 거의 같은 구조를 가지고 있다.(스프링에서 @Controller를 통해 위 구조를 그대로 처리한다.)
'Spring 정리' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 16 (0) | 2021.12.07 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 15 (0) | 2021.12.06 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 13 (0) | 2021.10.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 12 (0) | 2021.10.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 11 (0) | 2021.10.21 |
설정
트랙백
댓글
글
스프링 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 |