일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 실무활용
- 검증 애노테이션
- 스프링
- 값 타입 컬렉션
- 스프링 데이터 JPA
- Bean Validation
- 트위터
- JPQL
- Spring Data JPA
- jpa 활용
- 일론머스크
- 타임리프 문법
- 김영한
- API 개발 고급
- 예제 도메인 모델
- QueryDSL
- JPA 활용2
- 기본문법
- 프로젝트 환경설정
- 로그인
- 벌크 연산
- 스프링MVC
- JPA 활용 2
- 스프링 mvc
- 불변 객체
- 페이징
- 임베디드 타입
- 컬렉션 조회 최적화
- 타임리프
- JPA
- Today
- Total
RE-Heat 개발자 일지
스프링 MVC 1편 - [3] 서블릿·JSP·MVC 패턴 본문
출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1
인프런 김영한님의 스프링 MVC 1편 강의를 듣고 정리한 내용입니다.
개관 :
서블릿 -> JSP -> MVC 패턴 순서를 따라가면서 각 단계별 한계점과 발전 방향을 알아보는 내용
[1] 서블릿으로 회원 관리 웹 애플리케이션 만들기
MemberFormServlet
@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter w = response.getWriter();
w.write("<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Title</title>\n" +
"</head>\n" +
"<body>\n" +
"<form action=\"/servlet/members/save\" method=\"post\">\n" +
" username: <input type=\"text\" name=\"username\" />\n" +
" age: <input type=\"text\" name=\"age\" />\n" +
" <button type=\"submit\">전송</button>\n" +
"</form>\n" +
"</body>\n" +
"</html>\n");
}
}
싱글톤 유지를 위해 new MemberRepository()를 막아놔 MemberRepository.getInstance()로 가져옴
자바코드로 HTMl을 제공해야 하므로 어렵다.
MemberSaveServlet.java
@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter w = response.getWriter();
w.write("<html>\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
"</head>\n" +
"<body>\n" +
"성공\n" +
"<ul>\n" +
" <li>id=" + member.getId() + "</li>\n" +
" <li>username=" + member.getUsername() + "</li>\n" +
" <li>age=" + member.getAge() + "</li>\n" +
"</ul>\n" +
"<a href=\"/index.html\">메인</a>\n" +
"</body>\n" +
"</html>");
}
}
서블릿을 사용해 동적으로 원하는 HTML 만들 수 있음. 그러나 HTML 만드는 게 굉장히 복잡함.그래서 HTML 문서에서 필요한 곳만 자바 코드를 적용해 동적으로 변경할 수 있는 템플릿 엔진이 발전
템플릿 엔진은 JSP, Thymeleaf, Freemarker, Velocity 등이 있음.
[2] JSP로 회원 관리 웹 애플리케이션 만들기
서블릿으로만 구성하지 않고 템플릿 엔진은 JSP를 활용해 회원 관리 앱 만들기
JSP는 첫 줄에 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 코드를 작성해 JSP 문서라고 선언해야 함.
<%@ 지시자 속성="값" 속성="값" ... %>
ex) <%@ page import="~~" %> JAVA import문
<% ~~ %> 자바코드 입력
<%= ~~ %> 자바코드 출력
save.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "hello.servlet.domain.member.Member" %>
<%@ page import = "hello.servlet.domain.member.MemberRepository" %>
<%
//request, response 사용 가능
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
%>
<html>
<head>
<title>Title</title>
</head>
<body>
성공
<ul>
<li>id=<%=member.getId()%></li>
<li>username=<%=member.getUsername()%></li>
<li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
Member와 MemberRepository를 import 하고, <%= %>로 출력
Members.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
MemberRepository memberRepository = MemberRepository.getInstance();
List<Member> members = memberRepository.findAll();
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>age</th>
</thead>
<tbody>
<%
for (Member member : members) {
out.write(" <tr>");
out.write(" <td>" + member.getId() + "</td>");
out.write(" <td>" + member.getUsername() + "</td>");
out.write(" <td>" + member.getAge() + "</td>");
out.write(" </tr>");
}
%>
</tbody>
</table>
</body>
</html>
회원 리포지토리 결과를 조회한 후 List에 결과값을 담음 이후 for문으로 <tr><td> HTML 태그 반복해 출력
서블릿 + JSP의 한계
- JSP 한 페이지 내에서 두 가지 일을 같이 함 : 비즈니스 로직과 뷰 영역의 혼재
- 그 결과 비즈니스 로직과 서블릿(컨트롤러), 뷰를 분리하는 MVC(Model, View, Controller) 패턴이 등장
[3] MVC 패턴 - 개요
MVC 패턴 이전 문제점들
- 너무 많은 역할 : 하나의 서블릿 or JSP만으로 비즈니스+뷰 렌더링까지 처리하면 유지보수 어려워짐
- 변경의 라이프 사이클 : UI 수정과 비즈니스 로직은 대체적으로 따로따로 수정. 하지만 같이 바꿔야하는 일이 발생
추가로 JSP는 뷰 렌더링에 최적화 돼 있어 JSP는 뷰만 담당하는 방식의 MVC 패턴 발전
Model View Controller
컨트롤러 : HTTP 요청을 받아 파라미터 검증, 비즈니스 로직 실행
모델 : 뷰에 출력할 데이터를 담아둠. 모델 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 됨.
뷰 : 모델에 담긴 데이터를 바탕으로 화면을 그리는 일에 집중. 대부분 HTML을 생성, xml로 가능은 함
컨트롤러에 비즈니스 로직을 둘 수 있으나 그러면 컨트롤러가 너무 많은 역할을 담당하게 돼 비즈니스 로직을 서비스 계층에 만들어 분리함.
[4] MVC 패턴 - 적용
컨트롤러 : 서블릿
뷰 : JSP
모델 : HttpServletRequest가 제공하는 임시저장소 이용
MvcMemberFormServlet.java
@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); //다른 서블릿이나 JSP로 이동할 수 있는 기능
}
}
dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능.
redirect vs forward
리다이렉트는 실제 클라이언트에 응답이 나가면, 클라이언트가 redirect 경로로 다시 요청하는 방식[url도 변경됨]
포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트는 인지하지 못함
/WEB-INF : 이 경로 안에 있으면 외부에서 직접 호출 불가능. 반드시 컨트롤러를 통해서 JSP를 호출해야 함.
new-form.jsp
<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] -->
<form action="save" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
/로 시작하지 않는 상대경로를 사용하면 현재 URL이 속한 계층 경로 + save가 호출됨
절대경로를 사용하면 써 있는 경로가 그대로 호출
@WebServlet(name = "mvcMemberSaveServlet", 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"));
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);
}
}
save-result.jsp
<ul>
<li>id=${member.id}</li>
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
JSP의 ${}문법을 사용하면 request의 attribute에 담긴 데이터 편리하게 조회 가능
아니면 <%= request.getAttribute("member")%>로 모델에 저장한 member 객체를 꺼낸 후 id, username, age 등을 꺼내야함.
members.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<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>
JSTL core 태그 라이브러리를 선언해야 반복문 대체할 수 있는 <c:forEach> 사용 가능.
[5] MVC 패턴 - 한계
MVC 패턴으로 컨트롤러, 뷰의 구분이 명확해졌으나 컨트롤러에 중복이 많고 필요하지 않은 코드가 보임
MVC 컨트롤러의 단점
- 포워드 중복 : View로 이동하는 코드가 항상 중복 호출돼야 함.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
- View Path 내 중복
- prefix: /WEB-INF/views/
- suffix: .jsp => thymeleaf로 변경 시 전체 코드를 변경해야 함
- 사용하지 않는 코드
- 공통처리가 어려움.
- 공통기능을 메서드로 뽑아도 해당 메서드를 항상 호출해야하는 번거로움 발생
이후 컨트롤러 호출 전 공통기능을 처리하는 프론트 컨트롤러 패턴 도입
'백엔드 > 스프링' 카테고리의 다른 글
스프링 MVC 1편 - [6] 기본 기능(상편) (0) | 2023.06.30 |
---|---|
스프링 MVC 1편 - [5] 스프링 MVC 구조 이해 (0) | 2023.06.29 |
스프링 MVC 1편 - [4] MVC 프레임워크 만들기 (0) | 2023.06.23 |
스프링 MVC 1편 - [2] 서블릿 (0) | 2023.06.21 |
스프링 MVC 1편 - [1] 웹 서버, 웹 애플리케이션 서버 (0) | 2023.06.21 |