1. 모델과 3티어

  • 웹 MVC 구조에서 모델은 Controller에 필요한 기능이나 데이터를 처리해주는 존재
  • 모델은 두 영역으로 세분화 가능
    • 서비스(로직 처리) 계층: 비즈니스 로직 처리
    • 영속(데이터 처리) 계층: 데이터베이스 담당

  • 웹 MVC 구조를 제외하고 전체 시스템 구조를 정리하면 다음과 같은 계층 3티어 구조가 됨

 

 1) DTO(Data Transfer Object)

  • 3티어로 계층을 분리하는 경우 반드시 계층이나 객체 간 데이터 교환이 이루어짐
  • 한 개 이상의 데이터를 전달할 때가 많으므로 여러 데이터를 묶어 하나의 객체로 전달하는 것을 DTO라고 함

  • DTO에는 특별한 규격이나 제약은 없지만, 대부분 Java Beans 형태로 구성
  • Java Beans
    • 생성자가 없거나 반드시 파라미터가 없는 생성자 함수를 가지는 형태
    • 속성(멤버 변수)은 private로 작성
    • getter/ setter를 제공할 것
  • Controller는 DTO를 구성해서 서비스 계층을 호출하기도 하고, 서비스 계층에서 DTO를 받기도 함
  • 따라서, 서비스 계층 구성 전 DTO를 위한 클래스 구성
package org.zerock.w1.todo.dto;

import java.time.LocalDate;

public class TodoDTO {

  // 멤버 변수
  // 실수, 번호
  private Long tno;
  // 문자열, 제목
  private String title;
  // 날짜, 기간
  private LocalDate dueDate;
  // 참/ 거짓, 완료여부
  private boolean finished;

  // getter / setter
  public Long getTno() {
    return tno;
  }
  public void setTno(Long tno) {
    this.tno = tno;
  }
  public String getTitle() {
     return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public LocalDate getDueDate() {
    return dueDate;
  }
  public void setDueDate(LocalDate dueDate) {
    this.dueDate = dueDate;
  }
  public boolean isFinished() {
    return finished;
  }
  public void setFinished(boolean finished) {
    this.finished = finished;
  }
  
  @Override
  public String toString() {
     return "TodoDTO{" +
            "tno=" + tno +
            ", title='" + title + "\'" +
            ", dueDate=" + dueDate +
            ", finished=" + finished +
            "}";
  }
}

 

 2) 서비스 객체

  • DTO는 여러 개의 데이터를 묶어서 하나의 객체를 구성하며 서비스 객체 메서드의 파라미터나 return 타입으로 사용
  • 서비스 객체는 기능(로직)들의 묶음, 프로그램이 구현해야 하는 기능들의 실제 처리를 담당
  • 예를 들어, CRUD(Create, Read, Update, Delete) 기능들은 모두 서비스 객체에 모아서 구현됨

 


실습

 1) TodoService 클래스

package org.zerock.w1.todo.service;

import org.zerock.w1.todo.dto.TodoDTO;

import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

// enum 타입의 클래스는 정해진 수만큼의 객체를 생성할 수 있는 장점이 있음
public enum TodoService {
  // INSTANCE는 enum에서의 객체 개수를 결정하는 부분, 한 개만 지정되어 있으므로 하나의 객체만 생성해서 사용
  // 객체 사용시에는 TodoService.INSTANCE를 입력하여 사용
  /// 예제에서는 여러 Controller들이 TodoService 객체를 통해 원하는 데이터를 주고받는 구조로 구성하기 때문에 TodoService 객체를 하나만 생성하는 '싱글톤 패턴(Singleton Pattern)' 사용
  INSTANCE;

  // 등록 메서드(아직 구현x)
  public void register(TodoDTO todoDTO) {
    System.out.println("DEBUG.........." + todoDTO);
  }

  // 목록 메서드(10개의 TodoDTO 객체를 만들어서 반환)
  public List<TodoDTO> getList() {
    List<TodoDTO> todoDTOS = IntStream.range(0, 10).mapToObj(i -> {
      TodoDTO dto = new TodoDTO();
      dto.setTno((long)i);
      dto.setTitle("Todo.." + i);
      dto.setDueDate(LocalDate.now());

      return dto;
    }).collect(Collectors.toList());

    return todoDTOS;
  }
}

 

2. Controller에서 모델 처리

  • 웹 MVC 구조에서 Controller는 화면에 필요한 데이터를 서비스 객체를 통해 처리
  • 예를 들어, 화면에 목록 데이터가 필요하다면 TodoServie의 getList()의 결과를 받아서 JSP에 전달하고, JSP에서 보여주는 방식
  • 이 과정에서 HttpServletRequest의 setAttribure() 메서드 사용(키(key)-값(value) 형식으로 HttpServletRequest에 데이터 보관, 보관된 데이터를 JSP에서 꺼내어 사용)

 


실습

 2) TodoListController의 처리

  - TodoListController의 TodoService에서 제공하는 List<TodoDTO>를 가져와서 JSP로 전달

package org.zerock.w1.todo;

import org.zerock.w1.todo.dto.TodoDTO;
import org.zerock.w1.todo.service.TodoService;

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 = "todoListController", urlPatterns = "/todo/list")
public class TodoListController extends HttpServlet{

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("/todo/list");

    List<TodoDTO> dtoList = TodoService.INSTANCE.getList();

    // setAttribute()를 이용햐서 "list"라는 이름으로 List<TodoDTO> 객체를 보관
    // list.jsp에서 EL(${}안에 작성한 Expression Language)로 확인 가능
    req.setAttribute("list", dtoList);

    req.getRequestDispatcher("/WEB-INF/todo/list.jsp").forward(req, resp);
  }
}
// list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>List Page</h1>

${list}

</body>
</html>

 

3. JSP - EL(Expression Language)

  • JSP 코드에서 '${}'안에 작성한 코드는 EL 표현식
  • JSP는 주로 HTML, 자바스크립트, CSS 등으로 코드 작성하므로, 자바를 모르는 사람도 간단히 사용 가능한 출력용 언어로 EL 개발
  • EL를 통해 자바 코드를 몰라도 getter / setter 호출, 연산도 가능

 

 1) EL을 이용한 출력

  • EL을 이용하면 자동으로 getter 호출
  • 예시
<!-- 첫번째 TodoDTO의 tno와 title을 출력하고 싶다면 -->
${list[0].tno} --- ${list[0].title}

<!-- 출력 결과 -->
0 --- Todo..0
    • TodoDTO 클래스에서 tno, title등 변수는 private 처리되어 있어 자바 코드 외부에서 바로 접근 불가능
    • EL은  getTno(), getTitle()을 자동으로 호출

 

  • EL은 '표현식'이기 때문에, '${}' 내부에서 표현식이 결과를 만들어 낼 수 있다면 얼마든지 사용 가능
<h3>${ 1 + 2 + 3 }</h3>
<!-- 출력 결과 -->
6

<h3>${ "AAA" += "BBB" }</h3>
<!-- 출력 결과 -->
AAABBB

<h3>${ "AAA".equals("AAA") }</h3>
<!-- 출력 결과 -->
true

 

  • EL에서 자바 코드를 그대로 이용하는 것도 가능
  • 아래의 두 줄은 같은 결과를 출력
<h4>${list[0].title}</h4>
<h4>${list[0].getTitle()}</h4>

 

  • EL은 JSP에서 간단한 표현식을 출력만 하는 용도로, 반복문이나 제어문 같은 '식'이 아닌 '문'을 처리하려면 JSTL 라이브러리 필요

 

 

4. JSTL(JavaServer Pages Standard Tag Library)

  • JSP에서 동작하는 새로운 태그들의 묶음
  • 자바 문법보다 조금 더 간결하게 제어문이나 반복문, 선언문 등 처리 가능, 확장 가능
  • JSTL 이용을 위해 build.gradle 파일에 의존성 라이브러리 추가(추가하고 톰캣 중지하고 gradle 새로고침)
dependencies {
    compileOnly('javax.servlet:javax.servlet-api:4.0.1')

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
    
    // ---------이 부분 추가---------
    implementation group: 'jstl', name : 'jstl', version:' 1.2'
    // ---------이 부분 추가---------
}

 

 1) JSP 파일에서 JSTL 사용하기

  • JSP에서 JSTL을 사용하기 위해 다음 태그 관련 설정 추가
    <%@ taglib uri = "http://java.sun.com/jsp/jstl/core" prefix = "c" %>
  • <%@ %>로 시작하는 코드는 JSP에서 '지시자'의 역할, 위의 추가한 설정은 '태그 라이브러리 지시자'

 

  • <c:forEach>: 배열이나 리스트의 반복문 처리
    • var: EL에서 사용될 변수 이름
    • items: List, Set, Map, Enumeration, Iterator 등의 컬렉션
    • begin / end: 반복의 시작 / 끝 값
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri ="http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>List Page</h1>

<ul>
  <c:forEach var = "dto" items = "${list}">
    <li>${dto}</li>
  </c:forEach>
</ul>

</body>
</html>

 

  • <c:if>: test라는 속성값에 true / false로 나올 수 있는 식이나 변수 등이 들어갈 수 있음
<c:if test = "${list.size() % 2 == 0}">
  짝수
</c:if>
<c:if test = "${list.size() % 2 != 0}">
  홀수
</c:if>
  • <c:choose>: 자바의 switch와 비슷, <c:when test=..>과 <c:otherwise>를 이용하여 if~else 처리 가능
<c:choose>
  <c:when test = "${list.size() % 2 == 0}">
    짝수
  </c when>
  <c:otherwise>
    홀수
  </c:otherwise>
</c:choose>
  • <c:set>: 새로운 변수 생성, var 속성으로 변수명 지정, value 속성으로 값 지정
<c:set var = "target" value = "5"></c:set>

<ul>
  <c:forEach var = "num" begin = "1" end = "10">
    <c:if test = "${num == target}">
      num is target
    </c:if>
  </c:forEach>
</ul>

 


실습

 3) Todo 조회

  - TodoService에 특정 번호의 조회 기능 추가

// TodoService

...

  // 특정 번호 조회 기능
  public TodoDTO get(Long tno) {
  
    TodoDTO dto = new TodoDTO();
    dto.setTno(tno);
    dto.setTitle("Sample Todo");
    dto.setDueDate(LocalDate.now());
    dto.setFinished(true);
    
    return dto;
  }
}

 

  - GET 방식으로 조회 기능 동작하도록 TodoReadController 작성

// TodoReadController
package org.zerock.w1.todo;

import org.zerock.w1.todo.dto.TodoDTO;
import org.zerock.w1.todo.service.TodoService;

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 = "todoReadController", urlPatterns = "/todo/read")
public class TodoReadController extends HttpServlet {
  
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    System.out.println("/todo/read");
    
    // 브라우저의 주소창에 전달되는 tno(게시물 번호)라는 이름의 파라미터를 처리하도록 작성
    // HttpServletRequest의 getParameter()는 항상 문자열로 결과가 나오므로 Long 타입으로 처리하기 위해 변환 필요
    Long tno = Long.parseLong(req.getParameter("tno"));
    
    // TodoService의 get()을 통해 나온 TodoDTO 객체는 dto라는 이름으로 JSP에 전달
    TodoDTO dto = TodoService.INSTANCE.get(tno);
    
    req.setAttribute("dto", dto);
    
    req.getRequestDispatcher("/WEB-INF/todo/read.jsp").forward(req, resp);
  }
}

 

  - TodoReadController의 결과를 받을 WEB-INF/todo/read.jsp 파일 작성

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <div>${dto.tno}</div>
    <div>${dto.title}</div>
    <div>${dto.dueDate}</div>
    <div>${dto.finished}</div>
</body>
</html>

 

  - 그 결과, 다음과 같이 주소창에서 /todo/read 경로에 쿼리 스트링을 통해 ?tno=번호를 파라미터로 전달하면 TodoDTO 객체의 내용 확인 가능

1. HttpServlet

  - HttpServlet의 특징

  • HttpServlet은 GET / POST 등에 맞게 doGet(), dePost() 등을 제공, 개발자는 필요한 메서드를 오버라이드 하는 것 만으로 GET / POST 방식을 나누어 처리 가능
  • HttpServlet을 상속받은 클래스 객체는 톰캣 같은 WAS 내부에서 자동으로 객체를 생성·관리, 개발자가 생성·관리 신경쓸 필요 x
  • HttpServlet은 멀티 스레드에 의해 동시에 실행될 수 있도록 처리, 개발자는 동시에 많은 사용자를 처리하는 방법 고민할 필요 x

 

  - Servlet의 상속 구조

  • GenericServlet은 HttpServlet가 상속받는 상위 클래스이고, 추상 클래스
  • GenericServlet은 HttpServlet과 다르게 HTTP 프로토콜에 특화되지 않는 요청과 응답에 대한 기능 정의
  • GenericServlet은 Http가 아닌 응답과 요청을 의미하는 ServletRequest / ServletResponse 이용
  • HttpServlet은 GenericServlet을 상속받아 HttpServletRequest / HttpServletResponse 이용

 

 1) HttpServlet 라이프사이클

  - Servlet은 기본적으로 요청(Request)을 처리해서 응답(Response)하는 것이 목적

  - Servlet 클래스 처리 과정

  1. 브라우저가 톰캣에 Servlet이 처리해야 하는 특정 경로 호출
  2. 톰캣은 해당 경로에 맞는 Servlet 클래서 로딩, 객체 생성(init() 메서드  실행하여 서블릿 객체 동작 전 수행해야 하는 일 처리)
  3. 생성된 Servlet 객체는 브라우저 요청에 대한 정보를 분석하여 GET / POST 등의 정보와 함께 전달되는 파라미터(쿼리 스트링의 내용)들을 HttpServletRequest라는 타입의 파라미터로 전달받고, 응답을 처리하는 데 필요한 기능들은 HttpServletResponse라는 타입의 객체로 전달받음
  4. Servlet 내부에서 GET / POST에 맞게 doGet(), doPost() 등의 메서드를 실행, 동일 주소의 호출이 있을 때 Servlet은 동일 객체 하나만을 이용해 처리
  5. 톰캣 종료 시 Servlet의 destroy() 메서드 실행

 

  - 위의 과정에서 중요한 점

  • Servlet 객체는 경로에 맞게 하나만 만들어짐
  • 매번 호출 시 자동으로 doGet() / doPost()를 이용해 처리됨

 

  - 동작 예시

package org.zerock.w1;

import javax.servlet.ServletConfig;
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;

// "/sample" 경로에서의 동작
@WebServlet(name = "sampleServlet", urlPatterns = "/sample")
public class SampleServlet extends HttpServlet{

  // doGet() 메서드를 HttpServlet에서 상속받아와서 Override
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("doGet..." + this);
  }

  // destroy() 메서드를 HttpServlet에서 상속받아와서 Override
  @Override
  public void destroy(){
    System.out.println("destroy....................");
  }

  // init() 메서드를 HttpServlet에서 상속받아와서 Override
  @Override
  public void init(ServletConfig config) throws ServletException {
    System.out.println("init(ServletConfig).........");
  }
}
  • 처음 '/sample'경로에 접속했을 때, init() 메서드가 실행되어 해당 메세지 출력
  • 이후 doGet() 메서드도 실행되어 doGet......과 함께 Servlet 객체(this) 출력

  • '/sample' 경로에서 새로고침 등으로 여러 번 호출하면 doGet() 메서드만 반복적으로 실행되고 init() 메서드의 메세지는 다시 출력되지 않음
  • 또한, doGet() 메서드의 출력 메세지 중 Servlet 객체를 나타내는 this부분이 전부 동일한 것으로 보아, Servlet의 객체는 경로 당 하나만 생성됨을 확인

  • 마지막으로 톰캣을 종료하면 destroy() 메서드가 실행되어 해당 메세지가 출력됨

  • 위의 동작 예시로 보아, init()과 destroy()는 한 번씩만 호출되고, doGet() / doPost()는 동일한 객체를 이용해 여러 번 호출됨

 

 

2. HttpServletRequest의 주요 기능

  - HTTP 메세지 형태로 들어오는 요청에 대한 정보를 파악

기능 메서드 설명
HTTP 헤더 관련 getHeaderNames()
getHeader(이름)
HTTP 헤더 내용들을 찾아내는 기능
사용자 관련 getRemoteAddress() 접속한 사용자의 IP주소
요청 관련 getMethod()
getRequestURL()
getRequestURI()
getServletPath()
GET / POST 정보, 사용자가 호출에 사용한 URL 정보 등
쿼리 스트링 관련 getParameter()
getParameterValues()
getParameterNames()
쿼리 스트링 등으로 전달되는 데이터를 추출하는 용도
쿠키 관련 getCookies() 브라우저가 전송한 쿠키 정보
전달 관련 getRequestDispatcher()  
데이터 저장 setAttribute() 전달하기 전에 필요한 데이터를 저장하는 경우에 사용

 1) getParameter()

  • '?name=AAA&age=20'과 같은 쿼리 스트링에서 'name'이나 'age'라는 키(key)를 이용해 값(value)을 얻는 역할
  • getParameter()의 결과는 항상 String, 해당 파라미터가 존재하지 않으면 null 반환
  • 숫자 처리할 때는 예외가 발생할 수 있어 주의

 

 2) getParameterValues()

  • 동일한 이름의 파라미터가 여러 개 있는 경우 사용
  • 여러 개 존재하는 이름에 대해 String[] 타입으로 반환

 

 3) setAttribute()

  • JSP로 전달할 데이터를 추가할 때 사용
  • 키(key)와 값(value) 형태로 저장, 키(key)는 문자열, 값(value)는 모든 객체 타입
  • JSP에는 Servlet에서 setAttribute()로 전달된 데이터를 화면에 출력

 

 4) RequestDispatcher

  • 웹 MVC 구조에서 getRequestDispatcher()를 이용해서 RequestDispatcher 타입의 객체 생성
  • 현재의 요청을 다른 서버의 자원(Servlet 또는 JSP)에 전달하는 용도
  • RequestDispatcher의 메서드
    • forward(): 현재까지의 모든 응답 내용 무시, JSP가 작성하는 내용만 브라우저로 전달
    • include(): 지금까지 만들어진 응답 내용 + JSP가 만든 내용을 브라우저로 전달
  • 실제 개발에서는 forward()만 거의 이용

 

 

3. HttpServletResponse의 주요 기능

  - HttpServletRequest가 '읽는' 기능, HttpServletReponse는 '쓰는' 기능 제공

기능 메서드 설명
MIME 타입 setContentType() 응답 데이터의 종류를 지정(이미지 / hyml / xml 등)
헤더 관련 setHeader() 특정 이름의 HTTP 헤더 지정
상태 관련 setStatus() 404, 200, 500 등 응답 상태 코드 지정
출력 관련 getWriter() PrintWriter를 이용해서 응답 메세지 작성
쿠키 관련 getCookie() 응답 시에 특정 쿠키 추가
전달 관련 sendRedirect() 브라우저에 이동을 지시

 1) sendRedirect()

  • 웹 MVC 구조에서 HttpServletResponse 메서드들 중 가장 많이 사용
  • '다른 곳으로 가라'는 응답 메세지 전달
  • 지정한 경로를 이름으로 갖는 HTTP 헤더로 전달되어 브라우저는 경로가 인자로 있는 응답을 받으면 화면을 처리하는 대신 주소창에 지정된 주소로 이동하고, 다시 호출
  • sendRedirect()를 이용하면 주소가 아예 변경되어 새로고침 등의 요청 방지, 특정 작업 끝내고 새로 시작하는 흐름 만들 수 있음

 


실습

 1) 와이어 프레임 구현

  - 앞서 Todo 웹 애플리케이션을 만들기 위해 정리한 구현 목록에서 브라우저가 호출할 때 사용하는 URL 결정하여 추가

기능 동작 방식Controller(org.zerock.w1.todo) 컨트롤러 URL JSP
목록 GET TodoListController /todo/list WEB-INF/todo/list.jsp
등록(입력) GET TodoRegisterController /todo/register WEB-INF/todo/register.jsp
등록(처리) POST TodoRegisterController /todo/register Redirect
조회 GET TodoReadController /todo/read WEB-INF/todo/read.jap
수정(입력) GET TodoModifyController /todo/modify WEB-INF/todo/modify.jsp
수정(처리) POST TodoModifyController /todo/modify Redirect
삭제(처리) POST TodoRemoveController /todo/remove Redirect

  - 구현할 때는 GET / POST에 따라 doGet() / doPost()를 오버라이드

 

 2) TodoListController 구현

  - GET 방식만 처리하므로 doGet()만 오버라이드

// TodoListcontroller
package org.zerock.w1.todo;

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 = "todoListController", urlPatterns = "/todo/list")
public class TodoListController extends HttpServlet{

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("/todo/list");

    req.getRequestDispatcher("/WEB-INF/todo/list.jsp").forward(req, resp);
  }
}

  - "/WEB-INF/todo/list.jsp"의 경로에 List Page임을 명시하는 내용 작성

// list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>List Page</h1>
</body>
</html>

  - 경로에 접속하면 JSP로 작성한 페이지가 출력되고, doGet() 메서드도 잘 실행되었다는 증거로 로그에 'Todo/list'가 출력됨

 

 3) TodoRegisterController 구현

  - GET 방식으로 호출되면 입력 화면, POST 방식으로 호출되면 등록이 처리된 후에 다시 목록 페이지('/todo/list') 호출(sendRedirect())

// TodoRegisterController
package org.zerock.w1.todo;

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 = "todoRegisterController", urlPatterns = "/todo/register")
public class TodoRegisterController extends HttpServlet{

  // GET 방식에 대한 동작
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("Compose to view the input screen");

    // GET 방식의 호출은 등록 화면을 보게 하는 것이므로 RequestDispatcher를 사용하여 JSP를 보도록 작성
    RequestDispatcher dispatcher =req.getRequestDispatcher("/WEB-INF/todo/register.jsp");
    
    // forward는 지금까지 응답 무시하고 JSP가 작성하는 내용만 브라우저로 전달
    dispatcher.forward(req, resp);
  }

  // POST 방식에 대한 동작
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("Move to list page after processing input");
  }
}
// register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action = "/todo/register" method = "post">
  <button type = "submit">등록처리</button>
</form>
</body>
</html>

  - 'http://localhost:8080/todo/register' 경로로 접속하면 'register.jsp'에서 작성한 화면이 나오며, 로그에는 doGet() 메서드의 실행 결과인 'Compose to view the input screen'이 출력됨

  - '등록처리' 버튼을 누르면 POST 동작이 실행되고 doPost() 메서드의 실행 결과인 'Move to list page after processing input'이 출력됨

  - doPost가 실행되었지만 이후 갈 곳이 정해지지 않아 화면은 빈 화면이고, 새로고침을 하면 경고창이 뜨고, 로그에는 doPost() 메서드의 메세지가 반복해서 출력됨

  - 이를 막기 위해 PRG 패턴을 적용하며 이 때 sendRedirect 필요

// TodoRegisterController의 doPost() 수정

...

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("Move to list page after processing input");

    // 브라우저가 호출해야 하는 주소
    resp.sendRedirect("/todo/list");
  }

...

  - '/todo/register' 경로에서 '등록처리' 버튼 눌렀을 때 목록 페이지('/todo/list')로 이동

  - 로그를 살펴보면

  1. Compose to view the input screen: 처음 '/todo/register' 경로에 접속하면 TodoRegisterController의 doGet() 메서드가 실행되어 출력
  2. Move to list page after processing input: '/todo/register' 경로에서 버튼 클릭을 통해 POST 동작이 실행되면 TodoRegisterController의 doPost() 메서드가 실행되어 출력
  3. /todo/list: TodoRegisterController의 doPost()에서 sendRedirect된 경로인 '/todo/list'로 이동하여 TodoListController의 doGet() 메서드가 실행되어 출력

1. MVC 구조와 Servlet / JSP

  - Servlet: 자바의 코드 그대로 이용 가능, 상속이나 인터페이스 처리도 가능, HTTP로 전달된 HTML 처리 시 많은 코드 필요

  - JSP: HTML 코드를 바로 사용할 수 있어 HTTP 메세지 작성에 적합, 그 안의 자바 코드를 재사용하고 자바 코드와 HTML이 혼재 하는 등의 문제 존재

  - 절충안: 브라우저 요청은 해당 주소를 처리하는 Servlet에 전달 → Servlet 내부에서 응답에 필요한 재료 데이터 준비 → 준비한 데이터를 JSP에 전달 → JSP는 EL을 이용해 최종 결과 데이터 생성

 

  - MVC 구조: Model - View - Controller의 역할을 분리하여 처리, 데이터는 Controller(Servlet), 결과는 View(JSP)에서 처리

  - Servlet(Controller)가 JSP(View)에 필요한 데이터를 가공, 이때 필요한 데이터를 제공하는 객체가 Model

 

 1) MVC 구조로 계산기 설계

  • MVC 구조의 원칙
    • 브라우저의 호출은 반드시 Controller의 역할을 하는 Servlet을 호출하도록 구성
    • JSP는 브라우저에서 직접 호출 x, Controller 통해서만 JSP에 접근하도록 구성

 

  • GET 입력 화면 설계
    1. 브라우저는 /input과 같이 특정한 주소 호출
    2. /input에 맞는 Servlet을 InputController로 작성(GET 방식일 때만 동작하도록 함)
    3. InputController의 화면처리는 input.jsp를 이용하도록 지정
    4. input.jsp에는 HTML 코드를 이용해 브라우저에서 볼 수 있는 결과 생성
    • 컨트롤러에서 View 호출
package org.zerock.w1.calc;

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으로 urlPatterns 속성을 지정해서 처리해야 하는 경로 지정("/calc/input" 경로에서 데이터를 받아 처리하려고 함)
@WebServlet(name = "inputController", urlPatterns = "/calc/input")
public class InputController extends HttpServlet{

  // 부모 클래스에 있던 doGet을 Override하여 다시 정의
  // GET 방식으로 들어오는 요청에 대해서만 처리하도록 구성
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    System.out.println("InputController...doGet...");

    // RequestDispatcher는 Servlet에서 전달된 요청을 다른 쪽으로 전달 또는 배포하는 역할의 객체
    // InputController는 RequestDispatcher를 거쳐서 '/WEB-INF/calc/input.jsp'라는 목적지에 도착 가능
    // WEB-INF는 브라우저에서 직접 접근이 불가능한 경로, jsp 파일을 브라우저에서 직접 호출할 수 없도록 하기 위함
    RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/calc/input.jsp");

    dispatcher.forward(req, resp);
  }
}

 

    • RequestDispatcher를 이용하여 InputController가 '/WEB-INF/calc/input.jsp'로 접근하는 과정

 

    • 결과
    • 주소창에 'localhost:8080/calc/input.jsp'가 아닌 'localhost:8080/calc/input'으로 접속함
      • 이는 InputController 내부의 RequestDispatcher에 의해 'calc/input' 경로로 접속하면 자동으로 '/WEB-INF/calc/input.jsp'로 접근하도록 설정되었기 때문
      • 이로 인해, 브라우저에서 직접 jsp 파일에 접근할 필요가 없어짐
    • 또한, doGet이 실행됐을 때 "InputController...doGet..."이 print되도록 하여 로그에 "InputController...doGet..." 출력됨

 

  • POST 처리 설계
    1. input.jsp의 <form>태그의 action을 '/calcResult'와 같이 변경, 해당하는 CalcResultServlet 을 Controller로 작성
    2. calcResultServlet은 <form>으로 전달되는 num1, num2 데이터를 읽고 결과 데이터를 만듦
    3. 만들어진 결과를 calcResult.jsp와 같이 JSP로 전달해야 하고, JSP에서는 결과 데이터 출력
    • CalcController 생성(InputController에 doPost를 doGet과 같이 Override하여 작성해도됨)
package org.zerock.w1.calc;

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 = "calcController", urlPatterns = "/calc/makeResult")
public class calcController extends HttpServlet{

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{

    // req.getParameter라는 메서드를 사용하여 쿼리 스트링으로 전달되는 num1과 num2 파라미터를 처리
    // 이때, 숫자가 아닌 문자열로 처리
    // JSP에서는 ${param.num1}과 같이 단순하게 사용가능하지만, Servlet에서는 HttpServletRequest aPI를 이용해야함
    String num1 = req.getParameter("num1");
    String num2 = req.getParameter("num2");

    System.out.printf(" num1: %s", num1);
    System.out.printf(" num2: %s", num2);
  }
}

 

    • 'WEB-INF/calc/input.jsp' 파일에서 SEND 버튼을 통해 form 태그를 'calcResult.jsp'로 전송하고 있음
    • calcController에서는 urlPatterns 속성을 '/calc/makeResult'로 지정하였으므로, 'calcResult.jsp' 경로를 '/calc/makeResult'로 수정해야함
    • 이제 SEND 버튼을 클릭하면 'localhost:8080/calc/makeResult' 경로로 이동함
    • 또한, 화면은 비어있지만, System.out.printf 명령어를 통해 num1과 num2의 값을 로그에 정상적으로 출력함
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<form action='/calc/makeResult' method='post'>

    <input type="number" name="num1">
    <input type="number" name="num2">
    <button type="submit">SEND</button>
    
</form>

</body>
</html>

 

  • sendRedirect()
    • POST 방식으로 처리하고 JSP로 결과를 보여준 이후에는 브라우저 창에서 반복적인 호출을 막기 위해 처리가 끝남과 동시에 다른 경로로 이동하게 하는 것이 일반적(로그인창에서 아이디, 비밀번호 입력 후 로그인 처리 끝나면 로그인을 반복적으로 시도하지 못하게 다른 화면으로 넘어감)
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{

String num1 = req.getParameter("num1");
String num2 = req.getParameter("num2");

System.out.printf(" num1: %s", num1);
System.out.printf(" num2: %s", num2);

resp.sendRedirect("/index");

    • sendRedirect를 통해 처리가 끝난 후 '/index' 경로를 호출하여 해당 페이지가 나오며, 아직 '/index'의 Controller를 생성하지 않아서 위의 에러가 발생

 

 

2. PRG 패턴(POST - Redirect - GET)

  - MVC 구조에서 가장 흔하게 사용되는 패턴

  - PRG 패턴의 흐름

  • 사용자는 Controller에 원하는 작업을 POST 방식으로 처리하기를 요청
  • POST 방식을 Constroller에서 처리하고 브라우저는 다른 경로로 이동(GET)하라는 응답(Redirecct)
  • 브라우저는 GET 방식으로 이동

 

 1) PRG 패턴을 이용한 와이어 프레임 작성

  • 간단한 Todo List 작성 
  • 와이어 프레임: 웹 개발 전, 여러 페이지를 만들때 이동이 많기 때문에, 우선 설계를 통해 동작의 흐름을 미리 구성
  • 와이어 프레임 요령
    • 화면에는 해당 페이지를 볼 수 있는 경로(URL) 명시
    • GET 방식으로 동작하고 눈에 보이는 것 먼저 구성
    • POST 방식으로 처리되는 Controller는 다른 그림으로 표현
    • redirect 되어 보영지는 경우 다른 선으로 표현
  • Todo List 웹 애플리케이션의 와이어 프레임 그리기
    • 목록 화면(GET): GET 방식으로 조회, 등록 / 수정 / 삭제 후 결과 확인할 수 있는 화면
    • 등록 화면(GET)
    • 등록 화면(POST): 등록 화면에서 입력한 내용들 을 POST 방식으로 전소오디기 때문에 별도의 Controller 추가, 처리 후에는 Redirect로 다시 목록 화면 보여지도록 설계
    • 조회 화면(GET): 목록 화면에서 특정 Todo 글을 클릭하면 동작하도록 설계
    • 수정 / 삭제 화면(GET): 조회 화면에서 '수정/삭제'를 선택하면 GET 방식으로 이동하도록 설계
    • 수정(POST): Controller에서 수정이 POST 방식으로 처리되고 다시 목록 화면으로 Redirect
    • 삭제(POST): 수정과 동일
  • 구현 목록 정리
기능 동작 방식 Controller(org.zerock.w1.todo) JSP
 목록 GET TodoListController WEB-INF/todo/list.jsp
등록(입력) GET TodoRegisterController WEB-INF/todo/register.jsp
등록(처리) POST TodoRegisterController Redirect
조회 GET TodoReadController WEB-INF/todo/read.jap
수정(입력) GET TodoModifyController WEB-INF/todo/modify.jsp
수정(처리) POST TodoModifyController Redirect
삭제(처리) POST TodoRemoveController Redirect

1. Request(요청) / Response(응답)

  - 브라우저가 서버로 '요청'할 때

  • GET 방식: 주소창에 직접 원하는 데이터를 적거나 링크를 클릭해서 호출
    • 단순 링크로 주소와 필요한 정보를 동시에 보냄, 주로 특정 정보 조회 목적
  • POST 방식: 입력 화면에서 필요한 내용을 작성한 수 '전송'과 같은 버튼 등을 클릭해서 호출
    • 주소와 데이터를 따로 보내며, 웹 화면에서 데이터를 주고 받는 동작을 처리할 때 사용

 

  - 서버가 브라우저의 요청에 '응답'할 때

  • 정적 데이터: 항상 동일하게 고정된 데이터 전송, 파일로 고정된 html, css, 이미지 등
    • 웹 서버(Web Server): 항상 같은 정적 데이터만 보내는 역할 수행
  • 동적 데이터: 매번 필요한 다른 데이터를 동적으로 구성하여 전송, 메일함 등 상황에 따라 다른 데이터로 응답(서버 사이드 프로그래밍), Servlet / JSP 등
    • 웹 어플리케이션 서버(Web Application Server, WAS): 동적 데이터를 만들어 보내는 서버

 

 

 1) HTTP

  • 브라우저의 요청과 서버의 응답 사이의 데이터 교환 약속(프로토콜)
    • HTTPS는 HTTP에서 보안이 강화된 프로토콜

 

  • 메세지의 구성: Header와 Body로 구성되며 Request와 Response가 하나의 쌍으로 묶여서 처리

 

  • 비연결성 : 하나의 요청과 응답을 처리한 후에 연결을 종료함을 의미
    • 여러 사용자가 브라우저를 통해 서버를 호출하는 웹의 특성 상 최대한 많은 사용자에게 서비스를 제공하기 위해 비연결성 채택
    • 특정 웹페이지의 최신 결과를 확인하려면 새로 고침을 해야함
    • 이는 한번 요청과 응답이 오고 간 이후에 서버와 연결이 끊겨, 그 이후의 데이터를 받아오지 않았기 때문
    • 하지만, 요청과 응답 이후 연결을 끊음으로써 다른 사람에게 바로바로 응답을 할 수 있게 해줌

 

 2) 서버 사이드 프로그래밍

  • 서버 쪽에서 프로그래밍을 통해 데이터를 처리할 수 있도록 구성
  • 고려 사항
    • 동시에 여러 요청이 들어온다면 어떻게 처리?
    • 서버에 문제가 생기면 어떻게 처리?
    • 데이터 전송 방법 최적화
    • 분산 환경, 분산 처리 문제
  • JavaEE라는 기술 스펙에 위의 고려 사항들이 정리되어 있음(Servlet과 JSP도 JavaEE의 기술 중 하나)

 

    • Servlet 기술: 서버에서 동적으로 요청과 응답을 처리할 수 있는 API들을 정의한 것(Java 코드로 HTML 문자열 작성)
      • 톰캣 등의 Servlet의 실행이 가능한 환경(Servlet 컨테이너) 필요
      • 객체 생성, 호출의 주체는 사용자가 아닌 Servlet 컨테이너
      • Servlet 클래스에서 생성하는 객체의 관리는 Servlet 컨테이너가 함
      • Servlet / JSP 코드 개발은 Java API와 Servlet API 같이 사용
// java로 시작하는 Java의 API
// javax로 시작하는 Servlet의 API
import java.io.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
// HttpServlet이라는 부모 클래스가 지정된 HelloServlet 클래스
public class HelloServlet extends HttpServlet {
  private String message;
  
  
  // 아래의 init(), doGet(), destroy() 메서드는 Servlet API에서 지정된 메서드
  // init(), doGet(), destroy()의 호출 주체는 개발자가 아닌 Servlet 컨테이너(톰캣 등)
  // 이런 메서드들을 Servlet의 라이프 사이클이라고 함
  public void init() { message = "Hello World!"; }

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
    response.setContentType("text/html");
    // Hello
    PrintWriter out = response.getWriter();
    out.println("<html><body>");
    out.println("<h1>" + message + "</h1>");
    out.println("</body></html>");
  }

  public void destroy() {}
}

 

    • JSP(Java Server Page): Servlet과 같은 원리이지만 HTML을 쉽게 이용할 수 있는 방식(HTML 코드 이용, 약간의 Java 코드)
      • JSP 코드는 Java 코드가 아니지만, 컴파일될 때, Java 코드로 변환되어 실행
// Servlet 코드
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
    response.setContentType("text/html");

    // Hello
    PrintWriter out = response.getWriter();
    out.println("<html><body>");
    out.println("<h1>" + message + "</h1>");
    out.println("</body></html>");
}

// JSP 코드
<head>
    <title>JSP - Hello World</title>
</head>
<body>

	<h1><%= "Hello World!" %></h1>
    
<br/>
<a href="hello-servlet">Hello Servlet</a>

// JSP 코드는 컴파일되고 실행될 때 Java코드로 변환됨
out.write("\n");
out.write("<DOCTYPE html>\n");
out.write("<html>\n");
out.write("<head>\n");
out.write("	<title>JSP - Hello World</title>\n");
out.write("<head>\n");
out.write("<body>\n");
out.write("\n");
out.write("<h1>");
out.write("Hello World!");
out.write("</h1>\n");
out.write("\n");
out.write("<br/>\n");
out.write("<a href=\"hello-servlet\">Hello Servlet</a>\n");
out.write("</body>\n");
out.write("</html>\n");
    • Servlet / JSP 정리
      • Servlet / JSP 모두 JavaEE의 일부
      • Servlet / JSP를 실행하기 위해서는 Servlet 컨테이너가 필요
      • Servlet 컨테이너는 Servlet / JSP 객체를 생성하고 라이프 사이클을 관리
      • JSP는 내부적으로 Servlet과 동일 방식의 코드(Java 코드)로 변환됨
      • JSP는 HTML 내에 Java 코드 추가, Servlet은 Java 코드 내에 HTML 코드 추가

 

 

2. JSP를 이용해서 GET / POST 처리

 1) GET 방식

  • 입력과 조회
    • 원하는 데이터 조회
    • 사용자가 입력할 수 있는 화면
  • 예시
<!-- <form> 태그와 GET 방식 -->

<!-- page contentType="text/html;charset=UTF-8" language="java" -->
<html>
<head>
    <title>Title</title>
</head>
<body>

<!-- <form>은 입력 양식을 의미 -->
<form>

    <!-- <input>은 문자나 숫자 입력의 용도 -->
    <input type="number" name="num1">
    <input type="number" name="num2">

        <!-- <button>은 화면에 버튼을 보여줌, type="submit"이라면, <form>태그 전송 -->
    <button type="submit">SEND</button>
</form>

</body>
</html>

서버를 열고 localhost:8080/calc/input.jsp를 들어갔을 때 화면
SEND 버튼을 클릭하면 주소창이 바뀜
각 칸에 숫자를 입력하고 SEND 버튼을 누르면 주소창에 해당 숫자가 출력

    • 버튼이 'type=submit'으로 지정되어 버튼을 클릭하면 <form> 태그 내부에 있는 <input> 태그의 속성값이 name 태그와 함께 전송됨
    • 'localhost:8080/calc/input.jsp?num1=1&num2=2'에서 ?로 시작하는 부분이 '쿼리 스트링'
    • 쿼리 스트링은 무언가를 요구하거나 물어보는 용도로 데이터를 전달하기 위해 사용
    • 쿼리 스트링은 키, 값 쌍의 형태로 데이터 전달, 여러 개의 데이터가 필요하면 '&'로 연결

 

 2) POST 방식

  • 처리를 위한 용도
    • action과 method라는 속성을 이용해 양식을 어디에 어떤 방식으로 전송할 것인지 결정
  • 예시
<!-- <form>양식에 action과 method 속성 추가-->
<form action='calcResult.jsp' method='post'>

    <input type="number" name="num1">
    <input type="number" name="num2">
    <button type="submit">SEND</button>
</form>

 

    • http://localhost:8080/calc/input.jsp에서 SEND를 눌렀을 때 화면
    • POST 방식은 주소와 전달하고자 하는 데이터를 분리하여 전송하므로 브라우저를 통해 확인 불가
    • 개발자 도구를 통해 확인 가능, 코드에서 데이터를 calcResult로 보내려고 했으나 calcResult가 존재하지 않아 에러

    • num1과 num2에 입력한 내용도 개발자 도구를 통해 확인 가능

 

  GET POST
주용도 조회 등록,  수정, 삭제 등의 쿼리
구성 URL 뒤의 "?"와 쿼리 스트링 URL 전달 후, HTTP 몸체(body)로 쿼리 스트링
효과 사용자가 손쉽게 사용할 수 있는 링크 제공 단순 조회가 아닌 원하는 작업 처리
한계
  • 브라우저에 따라 길이 제한
  • URL 뒤의 쿼리 스트링으로 모든 정보가 전달됨
  • 쿼리 스트링의 길이에 대한 제한
    (일반적으로 2kb, 브라우저마다 다름)
  • GET 방식에 비해 많은 양의 데이터 전송
  • 주소창만으로는 테스트가 어려움

 

  • input.jsp에서 데이터를 받아 처리하는 calcResult.jsp 작성
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <!-- ${}는 JSP에서 사용하는 Expression Language로 서버에서 데이터를 출력하는 용도(웹에서 System.out.println()과 유사한 역할 -->
    <h1>NUM1 ${param.num1}</h1>
    <h1>NUM2 ${param.num2}</h1>
</body>
</html>

calc/input.jsp에서 num1에 10, num2에 100을 입력한 후 SEND 버튼 클릭
calc/calcResult.jsp가 input.jsp의 num1, num2 데이터를 받아와 명령을 처리

 

  • 웹의 파라미터는 모두 문자열로, 위와 같은 숫자를 처리할 때 Integer.parseInt() 사용
<h1>NUM1 ${param.num1}</h1>
<h1>NUM2 ${param.num2}</h1>

<h1> SUM ${Integer.parseInt(param.num1) + Interger.parseInt(param.num2)}</h1>

 

 3) JSP의 올바른 사용법

  • JSP는 기본적으로 GET / POST 호출을 구분하지 않아서 POST 방식으로 접근해야하는 calcResult를 GET 방식으로도 호출 가능

주소창에 localhost:8080/calc/calcResult.jsp를 입력, calcResult.jsp에 GET 방식으로 접근

  • 유지보수 측면에서도 input.jsp가 input2.jsp로 변경되었다면, 모든 사용자들이 input2.jsp로 바꿔야함
  • 따라서, 다음의 용도로만 제한적으로 JSP 사용
    • JSP에서 쿼리 스트링이나 파라미터 처리 x, JSP 대신 Servlet에서 처리
    • JSP는 입력 화면을 구성하거나 처리 결과를 보여주는 용도로만 사용
    • 브라우저는 직접 JSP 경로로 호출 x, Servlet 경로를 통해 JSP를 보는 방식으로 사용
  • 이러한 처리를 위해 웹 MVC 방식 사용(JSP는 결과만 출력, 처리는 Servlet에서)

+ Recent posts