Servlet과 Servlet의 단점을 보완한 JSP를 배우고,
JSP 단점마저 보완한 MVC 패턴을 배우고, 이 MVC 패턴의 장점을 극한으로 활용한 스프링MVC을 배우는 여정에 있다.
현재 글은 JSP에 대한 글이다.
Servlet의 한계점
Servlet의 한계점: 자바 코드로 HTML을 제공해야 함
예시) 회원 등록 폼
@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");
}
}
이렇게 자바 코드안에 HTML을 만들면 매우 복잡하고 비효율적으로 된다.
이러한 단점을 보완하고자 템플릿 엔진이 나왔다.
템플릿 엔진에는 JSP, Thymeleaf, Freemarker 등이 있다.
JSP
JSP는 뷰를 생성하는 HTML 화면을 깔끔하게 작업할 수 있게 해준다.
그리고 동적 변경이 필요한 부분에는 자바 코드를 적용할 수 있게 해준다.
JSP 시작하기
build.gradle에 다음을 추가한다.
//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
//JSP 추가 끝
JSP로 작성한 회원 등록폼.jsp
<%@ 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>
JSP는 자바 코드를 그대로 사용할 수 있다.
import문 : <%@ page import="hello.servlet.domain.member.MemberRepository" %>
자바 코드 입력 : <% ~~ %>
자바 코드 출력 : <%= ~~ %>
예시) 회원 저장 JSP
<%@ 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>
JSP의 한계점
Servlet : 뷰를 위한 HTML 작성 부분이 자바코드 속에 들어가기 때문에 매우 지저분하고 비효율적이 됨
JSP : HTML 작성을 깔끔하게 작성할 수 있도록 도와주고 동적으로 자바 코드도 넣을 수 있음
그러나 아직 해결하지 못한 문제는,
뷰를 위한 HTML 작성부와 비즈니스 로직을 위한 부분이 같은 곳에 존재한다.
회원 저장 JSP를 보자. 코드의 상위 절반은 회원을 저장하기 위한 비즈니스 로직이고,
나머지 하위 절반만 결과를 HTML로 보여주기 위한 뷰 영역이다.
JAVA 코드, 데이터를 조회하는 리포지토리 등등 다양한 코드가 모두 JSP에 노출되어 있다. JSP가 너무 많은 역할을 한다.
거대 프로젝트의 경우를 생각해본다면 이런 방식은 좋지 않다.
MVC 패턴
하나의 서블릿, JSP로 처리하던 것을 컨트롤러와 뷰라는 영역으로 역할을 나눈다.
컨트롤러: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
모델: 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.
뷰: 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.
MVC패턴 구현
기존의 Servlet과 JSP을 이용해서 MVC 패턴을 구현할 수 있다.
회원 등록 폼 - 컨트롤러 부분
@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파일은 외부에서 직접 호출이 되지만 /WEB-INF 경로 안에 JSP파일이 있으면 외부에서 직접 JSP를 호출할 수 없고 위와 같이 컨트롤러의 forward 등을 통해서만 호출할 수 있다. 즉, url에 localhost:8080/WEB-INF/views/new-form.jsp를 치면 에러가 발생
dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있게 해줌.
* redirect처럼 클라이언트가 redirect 응답을 받아 다시 새롭게 요청하는 방식이랑은 다름. forward는 클라이언트가 인지하지 못하게 일어남.
회원 등록 폼 - 뷰 부분
<%@ 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>
경로에 save와 같이 상대 경로를 입력해주면 현재 URL이 속한 계층 경로 + save가 호출됨
현재 계층 경로 : /servlet-mvc/members/
결과 : /servlet-mvc/members/save
데이터 전달 방법
request가 제공하는 setAttribute()을 사용해서 request 객체에 데이터를 보관하여 뷰에 전달할 수 있다.
request.setAttribute("member", member);
뷰는 request.getAttribute()로 데이터를 꺼내 사용함.
JSP에서는 ${} 문법으로 더욱 편리하게 데이터를 꺼낼 수 있음
예시) 회원 저장 -뷰
<%@ 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>
MVC 패턴의 한계
뷰와 컨트롤러를 나눔으로써 코드가 깔끔해지고 직관적이다.
하지만 여전히 컨트롤러 코드에는 중복이 많다.
포워드 중복
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
ViewPath 중복
String viewPath = "/WEB-INF/views/new-form.jsp";
prefix : /WEB-INF/views/
suffix : .jsp
만약 이들이 바뀐다면 모든 코드를 수정해야 한다....;
즉, 공통 처리가 어렵다는 단점이 있다.
이 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리해야 한다. 소위 수문장 역할을 하는 기능이 필요하다. 프론트 컨트롤러(Front Controller) 패턴을 도입하면 이런 문제를 깔끔하게 해결할 수 있다.
스프링 MVC의 핵심도 바로 이 프론트 컨트롤러에 있다.
다음 글부터는 Spring MVC 카테고리에서 작성합니다!!
----------------------------------------
참고 : 인프런 김영한님 강의(스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술)
댓글