김영한 강사님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요
이번강의에는 먼저 서블릿을 이용햇 회원 관리 웹 어플리케이션을 만들어본다. 그 후 서블릿으로 만들었을 때의 불편한점을 JSP를 이용해 해결해 똑같은 회원 관리 어플리케이션을 만들고, 불편한점을 MVC 패턴으로 해결해 본다.
이렇게 실제 기술 발전인 서블릿 -> JSP -> MVC 패턴을 알아보고 코딩을 한 후에, 실제 스프링 MVC 패턴이 얼마나 편리하고 왜 쓰는지를 깨닫는게 이번 강의 목적이다.
이렇게 간단한 웹 애플리케이션을 서블릿, JSP, MVC 패턴으로 만들어 보겠다.
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class Member {
private Long id;
private String username;
private int age;
public Member() {
}
public Member(String username, int age) {
this.username = username;
this.age = age;
}
}
/**
* HashMap은 동시성 문제가 고려되지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
*/
public class MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static Long sequence = 0L;
private static final MemberRepository instance = new MemberRepository(); // 싱글톤 객체 생성
public static MemberRepository getInstance(){
return instance;
}
private MemberRepository(){
}
public Member save(Member member){
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
public Member findbyId(Long id){
return store.get(id);
}
public List<Member> findAll(){
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
@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");
}
}
@Webservlet을 애노테이션을 이용한다@WebServlet(name = "memverSaveServlet", urlPatterns = "/servlet/members/save")
public class MemverSaveServlet extends HttpServlet {
MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MemverSaveServlet.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>");
}
}
@WebServlet(name="memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter w = response.getWriter();
w.write("<html>");
w.write("<head>");
w.write(" <meta charset=\"UTF-8\">");
w.write(" <title>Title</title>");
w.write("</head>");
w.write("<body>");
w.write("<a href=\"/index.html\">메인</a>");
w.write("<table>");
w.write(" <thead>");
w.write(" <th>id</th>");
w.write(" <th>username</th>");
w.write(" <th>age</th>");
w.write(" </thead>");
w.write(" <tbody>");
/*
w.write(" <tr>");
w.write(" <td>1</td>");
w.write(" <td>userA</td>");
w.write(" <td>10</td>");
w.write(" </tr>");
*/
for (Member member : members) {
w.write(" <tr>");
w.write(" <td>" + member.getId() + "</td>");
w.write(" <td>" + member.getUsername() + "</td>");
w.write(" <td>" + member.getAge() + "</td>");
w.write(" </tr>");
}
w.write(" </tbody>");
w.write("</table>");
w.write("</body>");
w.write("</html>");
}
}
이렇게, servlet을 이용해 웹 페이지를 만들었다.. 그런데 http응답 메시지를 보낼 때, 자바 코드로 html 문서를 하나하나 작성시 너무 복잡하고 오타날 확률이 크다. 그래서 우리는 html안에다가 자바 코드를 넣는 템플릿 엔진을 사용한다!!!
JSP
서블릿으로 웹 페이지를 만들면, html 코드를 모두 자바 안에 넣어줘야 하므로 매우 불편했다. 그렇다면 템플릿 엔진을 사용해서 코딩을 하면 어떻까?
javax.servlet:jstl 대신 아래 코드 3개를 build.gradle에 넣어준다implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
JSP는 .java가 아니므로 /main/java안에 넣어주는게 아니라 java와 같은 경로인 webapp에 만들어 준다<%@ page contentType="text/html;charset=UTF-8" language="java" %><% %> 안에 넣어주고 비지니스 로직을 넣어준다.Thymeleaf를 많이 사용하는 추세다!<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/jsp/members/save.jsp" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// request, response 사용 가능
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("save.jsp");
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);
%>
<html>
<head>
<meta charset="UTF-8">
</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>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
MemberRepository memberRepository = MemberRepository.getInstance();
List<Member> members = memberRepository.findAll();
%>
<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>
<%
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>
그런데, JSP 또한 HTML 코드안에 비지니스 로직 코드인 JAVA코드를 넣은 것이다. 그렇게 되면 JSP가 너무 많은 역활을 하게된다... JAVA코드, 데이터 조회코드,HTML, 리포지토리등등 다양한 코드가 JSP에 노출되어 있다..
유지보수가 엄청 힘들어진다...---> 역할의 분리???
이렇게 등장한 것이MVC 패턴!!! JSP의 화면을 그리는 부분과, Servlet의 비지니스 로직 부분을 나누어 보자!
MVC 패턴JSP는 화면 렌더링에 특화되있다Servlet : java코드 실행 최적화(객체,싱글톤,멀티쓰레드)컨트롤러 : HTTP 요청을 받아서 파라미터 검증, 비지니스 로직 실행, 뷰에 전달할 결과 데이터를 모델에 담는다모델: 뷰에 출력할 데이터를 담는다. 뷰가 필요한 데이터를 모두 모델에 담아주기 때문에, 뷰는 비지니스 로직, 데이터 접근에 대해 몰라도 되고 오직 화면 렌더링에 집중할 수 있다뷰 : 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중. HTML을 생성하는 부분을 말한다
request.getRequestDispatcher(): 컨트롤러에서 view로 이동할 때 사용하는 메서드, 파라미터로 view의 경로를 받는다dispatcher.forward(request, response)를 이용해 Surblet에서 JSP를 호출할 수 있다Redirect) 가 아니라forward컨트롤러, 모델, 뷰 -> 웹 MVC 모델도메인 : 비지니스 도메인 객체(DTO,VO ->여기서는 model)서비스 : 핵심 비지니스 로직을 구현리포지토리 : 데이터 베이스 접근, 도메인 객체를 DB에 저장 및 관리HttpServletRequest의 객체 -> request의 내부 저장소,request.setAttribute(key,value), request.getAttribute(key)String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request,response);
Controller에서 View로 이동하는 코드가 필요하다.@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);
}
}
<%@ 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>
WEB-INF${} 문법인 프로퍼티 문법을 제공@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
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);
}
}
<%@ 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>
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MemberRepository memberRepository = MemberRepository.getInstance();
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);
<%@ 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>
Front Controller 패턴을 도입스프링 MVC의 핵심 또한 Front Controller부분에 있다. 다음 시간부터 이부분에 대해 배워보겠다!