Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch8
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch11
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch3
- 코드로 시작하는 자바 첫걸음
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch7
- 스프링 mvc2 - 로그인 처리
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch5
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch6
- jpa 활용2 - api 개발 고급
- 스프링 입문(무료)
- 자바의 정석 기초편 ch4
- @Aspect
- 스프링 고급 - 스프링 aop
- 게시글 목록 api
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch13
- 2024 정보처리기사 수제비 실기
- 스프링 mvc2 - 검증
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch12
- 스프링 mvc2 - 타임리프
Archives
- Today
- Total
나구리의 개발공부기록
서블릿,JSP,MVC패턴, MVC패턴(개요/적용/한계) 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
서블릿,JSP,MVC패턴, MVC패턴(개요/적용/한계)
소소한나구리 2024. 2. 24. 17:07 출처 : 인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. MVC 패턴
1) 개요
(1) 너무 많은 역할
- 하나의 서블릿이나 JSP만으로 비즈니스로직과 뷰 렌더링을 모두 처리하면 너무 많은 역할을 하게되고 결과적으로 유지보수가 어려워짐
- 비즈니스 로직, UI둘중에 하나만 변경할 일이 발생해도 비즈니스로직과 뷰 렌더링을 하는 코드가 함께있는 파일을 수정해야함
- 수백, 수천줄의 java코드와 html 코드가 섞여있는 파일을 유지보수 한다고 생각하면 수정할 부분이 있는 코드를 찾는 것 조차 매우 어려울것임
(2) 변경의 라이프 사이클
- 더 중요한 것은 두가지(비즈니스로직과 뷰)의 변경의 라이프 사이클이 다름
- 매우 큰 변화가 일어날 경우 같이 변경이 일어나기도 하지만 대부분은 UI를 일부 수정하거나, 비즈니스 로직만 일부 수정하는 등 각각 다르게 발생할 가능성이 매우 높음
- 이러한 수정은 서로에게 영향을 주지 않으므로 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않음
(3) 기능 특화
- JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어있고 servlet은 자바 코드를 처리하는데 최적화 되어있음
- 각자 최적화 되어있는 부분의 업무만 담당하는 것이 효과적
(4) Model View Controller
- MVC 패턴은 하나의 서블릿이나 JSP로 처리하던 것을 컨트롤러, 뷰라는 영역으로 역할을 분리한 것
- 웹 애플리케이션은 보통 이런 MVC패턴을 사용
(5) 컨트롤러
- HTTP 요청을 받아 파라미터를 검증하고 비즈니스 로직을 실행
- 뷰에 전달할 결과 데이터를 조회 후 모델에 넘김
(6) 모델
- 뷰에 출력할 데이터를 담아둠
- 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에만 집중 할 수 있음
(7) 뷰
- 모델에 담겨있는 데이터를 사용하여 화면을 그리는 일에 집중
** 참고
- 컨트롤러에 비즈니스 로직을 둘 수 있지만 컨트롤러가 많은 역할을 담당하게 되므로 일반적으로 서비스(service)라는 계층을 별도로 만들어서 비즈니스 로직을 처리함 (물론 작은 프로젝트는 한번에 처리해도 무방함)
- 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당
- 그래서 비즈니스 로직(service)을 변경하면 비즈니스로직을 호출하는 컨트롤러의 코드도 변경될 수 있음
2) MVC패턴 - 적용
(1) 적용 방식
- 서블릿: 컨트롤러
- JSP: 뷰
- Model: HttpServletRequest 객체 내부에 가지고있는 데이터 저장소를 활용, request.setAttribute(),request.getAttribute()를 사용하여 데이터를 보관하고 조회할 수 있음
(2) MvcMemberFormServlet - 회원 등록 폼(컨트롤러)
- servlet하위에 mvc.servletmvc 패키지를 생성 후 작성
- /WEB-INF/.. 경로에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없고 컨트롤러를 통해서 JSP를 호출해야 함(문법)
- dispatcher.forward(): 다른 서블릿이나 JSP로 이동할 수 있는 기능으로 서부 내부에서 다시 호출이 발생함
** 참고 - redirect vs forward
- redirect : 실제 클라이언트(웹 브라우저)에 응답이 나갔다가 클라이언트가 redirect 경로로 다시 요청하므로 클라이언트가 인지할 수 있고 실제 url경로도 변경됨
- forward : 서버 내부에서 일어나는 호출이므로 클라이언트가 전혀 인지하지 못함
package hello.servlet.web.servletmvc;
//컨트롤러 역할
@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
// 컨트롤러에서 뷰로 경로를 이동
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
// 다른 서블릿이나 JSP로 이동할 수 있는 기능(서버 내부에서 다시 호출이 발생)
dispatcher.forward(req, resp);
}
}
(3) new-form.jsp - 회원 등록 폼(뷰)
- main/webapp/WEB-INF/views 경로에 작성
- action의 경로를 절대경로(/로 시작하는 경로)가 아닌 상대경로(/로 시작하지 않음)로 지정하면 폼 전송시 현재 URL이 속한 계층 경로 + save가 호출 됨
- 현재 계층 경로: /servlet-mvc/members/
- 결과: /servlet-mvc/members/save
<%@ 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>
(4) MvcMemberSaveServlet - 회원 저장(컨트롤러)
- HttpServletRequest를 Model로 사용
- request가 제공하는 setAttribute()를 사용하면 request 객체에 데이터를 보관해서 뷰에 전달할 수 있음
- 뷰는 request.getAttribute()를 사용하여 데이터를 꺼냄
package hello.servlet.web.servletmvc;
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
// Model 데이터 보관
req.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
dispatcher.forward(req, resp);
}
}
(5) save-result.jsp - 회원 저장(뷰)
- <%= request.getAttribute("member")%>로 모델에 저장한 member 객체를 꺼낼 수 있지만 너무 복잡해짐
- JSP의 ${} 문법을 사용하면 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있음
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body> 성공
<ul>
<!--자바 코드 사용
<li>id=<%=((Member)request.getAttribute("member")).getId()%></li>
<li>username=<%=((Member)request.getAttribute("member")).getUsername()%></li>
<li>age=<%=((Member)request.getAttribute("member")).getAge()%></li>
-->
<!--JSP 표현식(프로퍼티접근법) -->
<li>id=${member.id}</li>
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
(6) MvcMemberListServlet - 회원 목록 조회(컨트롤러)
- request 객체를 사용하여 List<Member> members를 모델에 보관
package hello.servlet.web.servletmvc;
@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
List<Member> members = memberRepository.findAll();
req.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
dispatcher.forward(req, resp);
}
}
(7) 회원 목록 조회 - 뷰
- 모델에 담아둔 members를 JSP가 제공하는 taglib기능을 사용하여 반복하면서 출력(기존의 for문을 리펙토링)
- members리스트에서 member를 순서대로 꺼내서 item변수에 담고 출력하는 과정을 반복
- <c:forEach> 이 기능을 사용하려면 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 이렇게 선언을 해야함
<%@ 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>
<tr>
<th>id</th>
<th>username</th>
<th>age</th>
</tr>
</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>
** 추가적인 JSP의 기능(강의)는 수많은 자료들이 있으므로 검색하거나 관련된 강의,책등을 참고 -> 단, 실무에서는 거의 안쓰는 추세임
3) MVC패턴 - 한계
(1) 한계
- MVC패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링 하는 역할을 구분할 수 있었음
- 특히 뷰는 화면을 그리는 역할에 충실한 덕분에 코드가 깔끔해지고 직관적이게 됨
- 그러나 컨트롤러는 코드의 중복이 많고 필요하지 않은 코드들도 보임
(2) MVC 컨트롤러의 단점
- 포워드 중복
- view로 이동하는 코드가 항상 중복 호출 되어야 함
- 메서드로 공통화 해도 되지만 해당 메서드도 항상 직접 호출해야 함
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
- viewPath에 중복
- prefix : /WEB-INF/views/
- suffix : .jsp
- 만약 jsp가 아닌 thymeleaf같은 다른 뷰로 변경한다면 전체 코드를 다 변경해야 함
String viewPath = "/WEB-INF/views/members.jsp";
- 사용하지 않는 코드
- response는 현재 코드에서 사용되지 않고 request도 사용할 때가 있고 사용하지 않을 때가 있음
- HttpServletRequest, HttpServletResponse를 사용하는 코드는 테스트 케이스를 작성하기도 어려움
service(HttpServletRequest request, HttpServletResponse response)
- 공통 처리가 어려움
- 기능이 복잡해질수록 컨트롤러에서 공통으로 처리해야 하는 부분이 계속 증가 될 것임
- 공통기능을 메서드로 뽑으면 될 것 같지만 메서드를 항상 호출해야하고 실수로 호출하지 않으면 문제가 되며 호출하는 것 자체도 중복임
(3) 정리하면 공통 처리가 어렵다는 것
- 공통 처리가 어려운 문제를 해결하려면 컨트롤러 호출 전에 공통 기능을 처리해야함
- 수문장의 역할을 하는 무언가가 필요한데 프론트컨트롤러(Front Controller)패턴을 도입하면 문제를 해결할 수 있음
- 스프링 MVC의 핵심도 프론트컨트롤러임
4) 정리
- servlet과 자바코드만으로 개발했을 때 불편했던 점(html을 렌더링 하는 코드들..)을 JSP로 해결
- JSP로 html폼에 자바코드를 삽입하여 문제를 해결했으나 JSP코드 안에 자바코드와 html코드가 섞여있어 유지보수가 어려워짐
- MVC패턴을 적용하여 servlet은 컨트롤러, JSP는 뷰로 각각의 역할에 충실하게 하도록하며 문제를 해결
- 그러나, 아직까지도 컨트롤러의 영역에서 로직의 중복이 많고 공통처리가 어려운 문제가 발생
- 프론트컨트롤러(스프링MVC의 핵심)로 해결