유튜브 강의 주소


보는 방법

  • 강의가 화면 자료에 내용 설명이 적고, 적당히 설명해서 구체적으로 메모 힘들다. 따라서 키워드 위주나 내용 간단 요약 느낌으로 기록했다.
  • 강의 내용 복습은 메모한 것뿐만 아니라 강의 영상도 봐라. 이는 상기용/테스트용 정도로만 활용해라.
  • 여기서는 다음을 기록.
    • 강의 목차와 키워드나 내용 요약


참고사항

  • 61강 이후부터는 핵심적인 개념이 있으면 기록하고, 아니면 넘기려고 한다.
  • 나는 스프링의 뼈대가 되는 개념과 이론을 원해서 servlet과 jsp 강의를 보았다. 이걸로 개발할 건 아니기 때문에 그렇다.



강의 01 - 학습 안내

  • 서블릿으로 자바 웹 프로그램 만들고, 서블릿의 html 코드 출력 문제를 도와주는 jsp, jsp 코드를 정리하는 jsp mvc.



강의 02 - 웹 서버 프로그램이란

  • CS(클라이언트/서버) 프로그램의 문제점: 클라이언트 프로그램 업데이트의 어려움(재설치)
  • 웹 프로그래밍이라는 것은 웹을 이용해서 cs 프로그램을 만든다는 것.
  • 웹은 클라이언트 프로그램 따로 없이 브라우저 하나만 있으면 가능. 웹 서버에서 문서 받아서 보여주는 역할.
  • 기존에 만들어진 정적 페이지를 전달하는 웹 서버의 환경을 조금 바꿔서, 동적으로 페이지 만들 수 있는 환경을 추가. 그래서 웹 서버 쪽에는 서버 프로그램을 얹을 수 있는 환경을 만들게 됨.
  • 자바스크립트 등장 이후 페이지를 요청하는 것이 아니라, 데이터 요청으로 바뀌게 됨. 그 후 브리우저 단에다가 프로그램 만드는 것처럼 자바스크립트 이용해서 만들게 됐음. 즉 브라우저 기반으로 쿨라이언트 프로그램 만들게 되었음. 따라서 이를 프론트엔드로, 서버 단은 백엔드로 구분



강의 03 - 웹 서버 프로그램과 Servlet

  • 웹 기반의 클라이언트/서버(cs) 프로그램
    • 클라이언트가 웹 문서를 요청하면 웹 서버는 요청한 웹 문서를 찾은 후 돌려줄 것임.
    • 하지만 클라이언트가 회원 목록을 요청하면, 회원 목록이라는 것이 미리 문서로서 만들어질 수 없다. 계속 회원의 정보는 바뀔 수 있기 때문이다.
    • 따라서 이 때는 웹 문서가 아니라 목록을 만들어 내기 위한 코드가 있음. 그럼 웹 서버는 이러한 요청을 수반할 수 있는 코드를 실행해서 db에서 목록을 문서화해서 돌려줘야 함. 따라서 이 코드를 실행할 환경이 따로 필요함
    • 즉 사용자가 요구하는 내용이 동적인 문서를 요구한다면, 웹 서버에다가 추가적으로 관련 코드를 실행할 수 있는 환경이 필요한데, 이 환경을 WAS(웹 어플리케이션 서버)라고 한다.
    • was에서 코드 실행되고 목록 받아서 클라이언트에 돌려주는 형식으로 cs 프로그램을 만들게 되는 것
    • 이 때 was에서 실행되는 코드를 server app(서버 어플리케이션 / 동적으로 문서를 만들기 위한 코드)이라고 한다.
  • 웹 서버 응용 프로그램(server app,서버 어플리케이션)을 servlet 단위로 만듦.
    • 클라이언트가 수정/삭제/회원 목록 등을 요청하면, 각 요청을 수반할 수 있는 서버 어플리케이션이 읽혀질 것임.
    • 이 때 서버 어플리케이션은 조각나 있는데(수정/삭제 등 각 요청에 맞는 서버 앱으로 파편화되어 있음), 이를 server application let(서버 어플리케이션 조각)으로 표현할 때 이를 줄여서 servlet이라고 하지 않았을까 추측한다고 말함.
    • 즉 서버 어플리케이션을 서블릿 단위로 만듦



강의 04 - 톰캣 9 설치하기 #1 of 3

  • 23년 1월 16일인 현재 나는 톰켓 10을 다운받았다.
  • 보면 window.zip, window service installer 두 가지를 설치할 수 있지만, 차이점은 다음과 같다.
    • window.zip: 학습용, 개발용으로 적합하다.
    • window service installer: 컴퓨터는 부팅되자마자 실행되는 서비스들이 있음. 이걸로 설치하면 톰켓이 서비스 목록에 등록됨. 따라서 서비스 목적으로 적합함.
    • 따라서 현재는 학습용으로 다운받으므로 window.zip으로 설치
  • 톰켓 파일 속 bin 폴더에 startup.bat을 실행시키면 콘솔창이 떴다 바로 꺼질 수 있는데, 이는 두 가지 원인일 수 있다.
    • 먼저 환경변수에 JAVA_HOME이라는 변수가 없어서 그럴 수 있다. 톰켓의 경우 jdk가 필요한데, jdk가 어느 디렉토리에 설치되었는지를 알기 위해 JAVA_HOME 변수에 jdk의 디렉토리 경로를 등록해야 한다.
    • 두 번째는 다른 톰켓이 실행 중이여서 포트 번호 충돌로 안 될 수 있다.
  • 다시 startup.bat을 실행한 후 localhost:8080에 접속하면 톰켓 페이지가 나온다.



강의 05 - 톰캣 9 설치하기 #2 of 3 - 웹문서 추가해보기

  • 톰켓은 was이기도 하고, 웹 서버라고 볼 수도 있다. 이 강의에선 톰켓을 웹 서버로서 사용해보자.
  • 문서를 보관하고 있는 곳을 홈 디렉토리라고 한다. 만약 내가 test.txt 파일을 홈 디렉토리에 두고, localhost:8080/test.txt를 치면(test.txt파일을 요청하면) test.txt 파일이 제공됨.
  • 홈 디렉토리는 apache-tomcat-10.0.27\webapps\ROOT 폴더이다.



강의 06 - 톰캣 9 설치하기 #3 of 3 - Context 사이트 추가하기

  • 개요
    • 홈 디렉토리에 폴더가 많아지고 각 폴더를 분업해서 맡고 있다면, Context 사이트를 고려할 수 있다.
    • 컨텍스트 사이트는 가상 사이트라고도 얘기하기도 한다. 기존 웹 사이트 폴더가 네이버라고 하자. 그럼 뉴스라는 폴더를 기존 네이버라는 폴더에서 땐 후에, 전혀 다른 폴더에서 웹 사이트를 개발한다. 하지만 뉴스는 기존 웹 사이트의 하위 디렉토리에서 돌아가는 것처럼 보여지는 방식으로 서비스하는 것을 컨텍스트 사이트라고 한다.
  • 실습

    • 아래 내용은 이해를 위한 거지 실효성이 있는 건 아님. 왜냐면 server.xml파일을 수정해서 컨텍스 사이트를 추가하면, 그 후 다시 서버를 껐다 켜야해서 큰 문제다. 따라서 각 앱마다 메타인포라는 곳에서 따로 컨텍스트를 마련할 수 있는데, 이는 수업 범위가 아니라서 다루지 않는다.
    • conf(config) 폴더에서 server.xml이 있다. 그럼 그 안에 host 태그 안에 다음처럼 넣으면 된다.
    • 아래 코드를 이해하면, test란 폴더는 기존 폴더에 없지만, docBase에 지정한 디렉토리를 test라는 가상 디렉토리와 연결해서 서비스가 될 수 있도록 해라는 뜻.
    <Host name="localhost" appBase="webapps">
      <Context path="test" docBase="C:\어쩌구저쩌구\apache-tomcat-10.0.27\webapps\test" privileged="true">
    </Host>
    



강의 07 - 처음으로 서블릿 프로그램 만들어보기

  • 왜 웹 서버 응용프로그램(서버 어플리케이션)을 servlet이라고 명칭할까?
    • 서버 어플리케이션은 기능별로 코드가 나눠져 있고, 클라이언트가 요청하는 것에 해당하는 코드만 실행됨. 즉 필요에 따라서 로드될 수 있도록 조각나 있는 서버 어플리케이션을 servlet이라고 함
  • 서블릿 코드 작성
    • 모든 서블릿 클래스들은 was에 의해서 실행됨. was는 약속되어있는 인터페이스나 추상 클래스를 구현하거나 상속받은 서블릿 클래스를 참조하게 됨. 이 때 기존의 main() 역할이 servlet에서는 service()라고 보면 됨.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class Test extends HttpServlet{
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOExceptionm ServletException{
        // 코드
    }
}



강의 08 - 서블릿 객체 생성과 실행 방법

  • 톰켓을 통해서 서블릿 코드가 실행되도록 코드 배치
    • apache-tomcat-10.0.27\webapps\ROOT\WEB-INF 안 classes 폴더 속에 서블릿 코드를 배치한다.
    • 이 때 서블릿 코드가 어떤 패키지 속에 있다면 그 패키지명대로 classes 폴더 속에 넣어야 함. 예를 들어 패키지 이름이 com.newlec이라면 classes 폴더 속에 com 폴더 만들고, 그 안에 newlec 폴더 만든 후에, 다시 그 안에다 서블릿 클래스 파일을 둬야 함.
  • 서블릿이 실행되는 방식
    • WEB-INF는 클라이언트가 요청할 수 없고, 오직 서버 쪽에서만 사용할 수 잇는 폴더다.
    • 따라서 서블릿 내용은 톰켓(웹서버+was)만 알면 됨. 서블릿은 톰켓이 실행할 테니 클라이언트는 어떤 거를 원하는지만 말해라는 느낌.
    • 즉 클라이언트가 특정 URL로 요청하면, 그 url에 매핑된 서블릿 코드를 찾아서 실행 후 돌려줌.
  • 서블릿 코드를 url과 매핑하기

    • WEB-INF에 web.xml 파일이 있다. 그 안에 다음 코드를 적당히 넣으면 된다.
    • com.newlec.test의 뜻: test라는 클래스의 패키지명(com.newlec)까지 포함해서 작성
    • localhost:8080/testUrl로 접속하면, 일단 톰켓(여기선 웹서버)가 testUrl이라는 파일을 홈 디렉토리에서 찾아봄. 파일이 없으면 다시 톰켓(여기선 was)이 자신의 매핑 정보를 뒤져서 그에 매핑되는 test 클래스(서블릿 코드)가 실행됨
    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>com.newlec.test</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/testUrl</url-pattern>
    </servlet-mapping>
    



강의 09 - 서블릿(Servlet) 문자열 출력

  • 자바 웹 프로그래밍: 여기서 웹은 UI이다. 사용자 입력을 받거나 출력 결과를 보여주는 역할이 UI다. 이 때 ui는 기존에는 콘솔, 윈도우 등을 떠올릴 수 있다. 하지만 자바 웹 프로그래밍에서는 ui로 웹을 사용하는 프로그래밍이라는 뜻이다.
  • response를 이용한 출력방법 1
    • OutputStream os= response.getOutputStream();: 자바는 입출력을 스트림을 통해 수행한다. 여기서는 네트워크 스트림을 쓴다.
    • PrintStream out = new PrintStream(os, true);
      • OutputStream 출력하려고 보니, 바이너리나 바이트를 출력할 게 아니고 문자열을 출력할 거다. 따라서 이를 더 쉽게 쓸 수 있도록 하는 PrintStream로 OutputStream를 매핑해서 쓰는 것이 편하다.
      • PrintStream은 기본적인 출력 스트림을 가지고 좀 더 쉽게 문자열을 출력할 수 있도록 print계열의 함수들을 제공해 주고 있는 객체다.
      • 일반적으로 네트워크로 출력되는 스트림은 출력 버퍼가 일정 단위(윈도우는 기본적으로 8kb)가 채워지면 보낸다. 따라서 print했을 때 버퍼에 넣지 말고 바로 플러쉬해라는(출력해라는) 옵션이 true에 해당한다.
    • out.println("Hello Servlet!");: 기존의 System.out.println의 경우 콘솔창에 출력되는 거고, 아래 코드의 println은 클라이언트에게 전달되는 거다.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class Test extends HttpServlet{
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOExceptionm ServletException{
        OutputStream os = response.getOutputStream();
        PrintStream out = new PrintStream(os, true);
        out.println("Hello Servlet!");
    }
}
  • response를 이용한 출력방법 2
    • 근데 단지 문자열을 다룰거면 PrintWriter를 쓴다.
    • 근데 전에는 PrintStream이었는데, 여기서는 PrintWriter를 쓰는가? 자바에 stream 계열과 writer 계열이 있는데, 문자열을 쓸거고 그게 다국어라면 무조건 PrintWriter를 쓰는게 기본이다. 우리는 영어가 아니라 한국어니까 writer 계열을 쓴다. 즉 다국어 문자열을 사용할 수 있다는 점
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class Test extends HttpServlet{
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOExceptionm ServletException{
        PrintWriter out = response.getWriter();
        out.println("Hello Servlet!");
    }
}



강의 10 - 웹 개발을 위한 이클립스 IDE 준비하기

vsCode

  • 난 vsCode로 쭉 할 거기 때문에 강의 내용과 별개로 몇개 기록하겠다.
  • 톰켓 서버를 vsCode에서 사용하기 위한 익스텐션은 Community Server Connectors다. 몇년 전 블로그를 보면 Tomcat for Java 익스텐션을 다운받아서 사용하였다. 하지만 이는 deprecated 상태라 다운 받을 수 없게 되었다.
  • 익스텐션 다운 후 explorer 탭 안에서 java projects 탭 밑에 servers 탭이 생긴 것을 알 수 있다. 그럼 거기서 새 서버를 추가할 수 있다.
  • 참고 사이트니까 다시 볼 필요 있을 때 참고할 것

eclipse

  • vsCode로 해당 강의를 실습하려 했으나, servlet 관련해서 마땅한 도구가 없어서 무리라고 판단하고 이클립스로 진행하려고 한다.
  • Eclipse IDE for Enterprise Java and Web Developers를 다운받는다.
  • dynamic web project를 생성한다.



강의 11 - 이클립스를 이용한 서블릿 프로그래밍

이클립스 프로젝트, 서버 관련 오류 해결 기록

  • html 파일을 run on server할 때 목록이 안 보여서 한동안 진행하지 못했다. 그리고 이것저것 찾아보면서 조작해도 안 되서 막막한 참에 다시 해보니까 갑자기 된다?
    • 차이를 떠올려보면, 프로젝트 생성할 때 Target runtime에서 new runtime버튼 클릭해서 설정할 때 밑에 있는 “create a new local server”라는 체크박스에 체크한 후 나머진 동일하게 진행했다.
    • 그 후 다시 시도해 보니까 run on server에서 목록이 보이긴 했다. 그런데 보니까 run on server에서 choose an existing server를 선택할 수 있게 된 것이다. 단 manually define a new server쪽은 선택 안 되는 건 동일한 것 같다.
  • 근데 신난 마음에 실행하려고 하니까 갑자기 vsCode에 test.html 파일이 뜬 것이다… 왜지 싶어서 고생한 결과, 알고보니, window에 web browser 탭이 있는데, 그 안에서 기본 설정은 1. default system ~~인 거였다. 이를 chrome로 변경 후 다시 시도해 보니 정상적으로 실행되었다.
    • 그 이유를 추측하건데, 내 컴퓨터가 기본적으로 html 파일을 열 때 vsCode로 열기로 설정해서 그렇지 않을까 추측해본다.

강의 내용

  • 버전이 업데이트 됨에 따라서 강의와 다르게 작업해야 할 부분들이 있습니다!

    • Java Resources ->src/main/java, WebContent->webapps 로 기본 설정이 변경 됨. (MONE님이 발견 하심)
    • 프로젝트 이름에 우클릭 해야 하며 source folder는 src/main/java이다. (강의: src 폴더에 우클릭)
    • 우클릭 후 New>Servlet으로 해야한다. (강의: New>Class)
    • Nana Servlet을 호출하는 url 변경 시 web.xml에서 url-pattern 태그를 수정한다.
  • 여기서 홈 디렉토리는 webapp이다.
  • 내가 만든 프로젝트 폴더가 루트 프로젝트가 되도록 설정하기.
    • 프로젝트 우클릭->properties->web project settings->context root명을 /로 바꾼다.
  • 서블릿 클래스를 매핑되야 한다. 매핑하기 위해서는 web.xml을 통해 매핑해야 한다. apache-tomcat-10.0.27\webapps\ROOT\WEB-INF 속 web.xml을 복사해서 새로 만든 프로젝트 속 WEB-INF로 붙여넣은 후, 현재 서블릿 클래스 전체 이름(예- com.newlecture.web.test)으로 수정한다.
    • 서블릿 코드 수정하면, 컴파일하고, 배포하고, 톰켓 서버 재시작하고해야 하는 일련의 과정을 이클립스에서 간단히 할 수 있다.



강의 12 - 어노테이션을 이용한 URL 매핑

  • 어노테이션을 이용한 URL 매핑
    • web.xml에서 url 매핑할 필요 없다. 어노테이션이 좋은 이유는 여러 사람이 분업해서 만들때 각자가 분리한 상태에서 수행할 수 있기 때문이다. 왜냐면 각 사람마다 web.xml을 손대려 할 거니까 불편하지.
    • 어노테이션으로 url 매핑하려면, web.xml 속 metadata-complete를 “false”로 수정해야 한다.
    • metadata-complete이 true라면 모든 메타데이터(설정)이 web.xml에 있다는 소리다. 그럼 web.xml 파일만 뒤지고 url 매핑 시도할 것이다. 따라서 이를 false로 해야 한다.
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
  version="5.0"
  metadata-complete="false">

  <!-- <servlet>
      <servlet-name>test</servlet-name>
      <servlet-class>com.newlecture.web.test</servlet-class>
  </servlet>

  <servlet-mapping>
      <servlet-name>test</servlet-name>
      <url-pattern>/testUrl</url-pattern>
  </servlet-mapping> -->
package com.newlecture.web;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.*;

@WebServlet("/testUrl")
public class test extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = response.getWriter();
        out.println("Hello servlet");
	}
}



강의 13 - 서블릿 출력 형식을 지정해야 하는 이유

  • 브라우저에 컨텐츠 형식을 알려주지 않은 경우 각자 자의적인 해석을 한다.
    • 크롬은 이 때 텍스트로 해석해서, <br> 태그가 그대로 출력된다.
    • 반면 엣지의 경우 이를 웹 문서(html)로 해석해서 <br> 태그가 출력되지 않고, 줄내림이 된 것을 알 수 있다.
@WebServlet("/testUrl")
public class test extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = response.getWriter();

		for(int i=0;i<10;i++)
			out.println(i+" Hello servlet<br>");
	}
}



강의 14 - 한글과 콘텐츠 형식 출력하기

  • 한글이 깨지는 이유
    • 서버에서 한글을 지원하지 않는 문자코드로 인코딩한 경우: 기본적으로 웹 서버가 클라이언트에게 보내는 단위가 ISO-8859-1 방식을 이용함. 따라서 한글은 2byte인데, 1byte씩 끊어서 클라이언트에게 전달함.
    • 서버에서는 UTF-8로 인코딩해서 보냈지만, 브라우저가 다른 코드로 잘못 해석한 경우: 클라이언트가 utf-8이 아닌 다른 인코딩 방식을 썼을 경우
  • 코드 이해
    • response.setCharacterEncoding("UTF-8");: 서버가 utf-8 형식으로 보낸다는 뜻
    • response.setContentType("text/html; charset=UTF-8");: 클라이언트에게 컨텐트 타입이 어떤지 알려줌. http 응답 헤더에 컨턴트 타입을 명시해서 넣어줌
@WebServlet("/testUrl")
public class test extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();

		for(int i=0;i<10;i++)
			out.println(i+": 안녕, servlet<br>");
	}
}



강의 15 - GET 요청과 쿼리스트링

  • GET 요청
    • 무엇을 달라고 하는 요청에 추가로 옵션이 붙을 수 있다.
    • 클라이언트는 기본적으로는 웹 문서를 요청한다(localhost:8080/hello). 하지만 localhost:8080/hello?cnt=3으로 hello라는 문서를 요청하면, cnt=3이라는 조건에 맞는 웹 문서를 서버가 돌려줘야 한다(강의에서는 hello라는 문자를 100번 반복해서 출력했는데, cnt=3이라고 하면 3번만 반복해서 출력해라고 뜻). 마치 헴버거를 주문하면서 추가로 양파는 빼달라는 옵션을 요청하는 것이다. -이 때 “?cnt=3”을 쿼리스트링이라고 한다.
  • 코드 이해
    • requset(입력도구) 활용.
    • request.getParameter("cnt"): getParameter()는 “?cnt=3”이라는 쿼리스트링에서 키워드(여기선 cnt)를 읽는 함수. 그럼 이 때 cnt라는 키워드는 서버와 클라이언트가 서로 합의된 키워드를 써야 한다.
@WebServlet("/testUrl")
public class test extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();

		int cnt=Integer.parseInt(request.getParameter("cnt"));

		for(int i=0;i<cnt;i++)
			out.println(i+": 안녕, servlet<br>");
	}
}



강의 16 - 기본값 사용하기

  • 쿼리스트링을 request.getParameter(“cnt”)할 때 전달되는 cnt값은?
    • “/hello?cnt=3” => “3”
    • “/hello?cnt=” => “”
    • “/hello?” => null
    • “/hello” => null
  • 입력 값에 기본 값을 사용하기
    • int cnt=10;: 예제에서는 cnt의 기본값을 10으로 두었다.
    • if(cnt_!=null&&!cnt_.equals("cnt")): getParameter()로 받은 값이 빈 문자열(““)이나 null일 수 있음을 위에서 언급했다. 따라서 이런 경우일 땐 if문이 실행 안 되고 기본값인 10이 될 것이고, 실제 cnt 값이 들어올 경우는 해당 값을 받는 코드
@WebServlet("/testUrl")
public class test extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();

		int cnt=10; // 기본값
		String cnt_=request.getParameter("cnt");
		if(cnt_!=null&&!cnt_.equals(""))
			cnt=Integer.parseInt(cnt_);

		for(int i=0;i<cnt;i++)
			out.println(i+": 안녕, servlet<br>");
	}
}
  • 사용자는 위의 cnt 값을 다음처럼 전달.
    • <a href="testUrl">: 인사하기(기본값) 링크를 누르면 localhost:8080/testUrl로 이동
    • <a href="testUrl?cnt=3">: 인사하기(기본값) 링크를 누르면 localhost:8080/testUrl?cnt=3”로 이동
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
  </head>
  <body>
    환영합니다.<br />
    <a href="testUrl">인사하기(기본값)</a><br />
    <a href="testUrl?cnt=3">인사하기(3번)</a><br />
  </body>
</html>



강의 17 - 사용자 입력을 통한 GET 요청

  • 반복횟수를 사용자로부터 입력받으려면 입력폼을 준비해야 한다.
    • <form action="hello">: 사용자가 채우는(입력하는) 양식을 뜻함. action에다가 서블릿 매핑 주소를 씀. 그럼 submit 버튼 누르면 요청이 /hello에게 요청이 감. 즉 브라우저는 action명을 보고 url을 작성하게 된다.
    • <input type="text" name="cnt" />: 여기에 입력된 값이 있는지 확인 후 있으면, name에 해당하는 키와 입력받은 값으로 쿼리스트링을 만들어 줌.
    • <input type="submit" value="출력" />: 요청을 하게 되는 submit 버튼
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
  </head>
  <body>
    <div>
      <form action="hello">
        <div>
          <label>"안녕하세요"를 몇 번 듣고 싶으신가요?</label>
        </div>
        <div>
          <input type="text" name="cnt" />
          <input type="submit" value="출력" />
        </div>
      </form>
    </div>
  </body>
</html>



강의 18 - 입력할 내용이 많은 경우는 POST 요청

  • POST 요청의 일반적인 방식
    • get 요청으로 바로 끝나는 경우도 있지만, 입력할 내용이 많을 경우 일정한 양식을 통해서 전달받는 경우(post요청)도 있다.
    • 즉 get요청과 post요청을 나눠서, 처음에는 입력폼을 받기 위한 get요청을 하고, 그걸 가지고 내용을 채워서 post함.
  • reg.html파일을 추가로 만든다.
    • <form action="notice-reg" method="post">
      • <form action="notice-reg">
        • 기본적으로 method=”post”가 없는 경우 get방식으로 내용을 전달한다. 이 경우 입력받은 값은 쿼리스트링으로 전달된다. 쿼리스트링은 기본적으로 웹 문서를 요청할 때 추가로 요청하는 사항을 뜻한다. 따라서 그렇게 많이 입력받을 준비가 덜 되어있다.
        • 쿼리스트링으로 붙어서 url이 길어지는데, url은 길이에 제한이 있어 내용이 잘릴 수 있다. 또한 아이디나 패스워드를 입력했을 때 그 내용이 쿼리스트링으로 붙으므로 보안 상의 문제도 있다.
      • method="post": 입력받을 내용이 많기 때문에 url을 통해 전달하지 않고, 요청 바디에 붙어서 전달됨(쿼리스트링으로 전달되지 않음). 요청 바디의 경우 크기가 제한이 없어서 많은 내용을 받을 수 있다.
  • 그럼 noticeReg.java 파일도 추가로 만든다.
    • @WebServlet("/notice-reg"): /notice-reg를 담당하는 servlet 코드를 만든다.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
  </head>
  <body>
    <div>
      <form action="notice-reg" method="post">
        <div><label>제목:</label><input name="title" type="text" /></div>
        <div>
          <label>내용:</label><br />
          <textarea name="content"></textarea>
        </div>
        <div>
          <input type="submit" value="등록" />
        </div>
      </form>
    </div>
  </body>
</html>
@WebServlet("/notice-reg")
public class NoticeReg extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();

		String title=request.getParameter("title");
		String content=request.getParameter("content");

		out.println(title+"<br>");
		out.println(content);
	}
}



강의 19 - 한글 입력 문제

  • 한글이 전달되는 것을 서버에서 받지 못하는 문제
    • 브라우저에서 utf-8로 post하면 2byte가 한 문자로 여겨져서 전송됨.
    • 하지만 톰켓(웹 서버)의 기본적인 인코딩 방식은 ISO-8859-1로, 1byte를 1문자로 읽는다.
    • 따라서 읽을 때 utf-8로 읽어라고 설정해야 한다(request.setCharacterEncoding("UTF-8");)
    • 톰켓의 server.xml에서 인코딩 방식을 바꿀 수 있지만, 톰켓 서버에는 여러 개의 사이트가 있을 수 있다. 따라서 나만 위해서 인코딩 방식을 바꾸면 다른 데서 문제가 될 수 있어서, 기본적으로 서블릿을 통해 설정한다.
@WebServlet("/notice-reg")
public class NoticeReg extends HttpServlet {
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();

		String title=request.getParameter("title");
		String content=request.getParameter("content");

		out.println(title+"<br>");
		out.println(content);
	}
}



강의 20 - 서블릿 필터(Servlet Filter)

  • 필터
    • 요청이 들어오면 was는 서블릿을 실행 후 결과를 돌려줌. 이 때 서블릿을 실행하게 되면 메모리에 존재하게 되는데, 이 공간을 서블릿 컨테이너라고 한다.
    • 요청이 들어오면 서블릿을 실행하려 하는데, 그 중간에 가로채는 게 필터다. 가로채서 먼저 필터가 실행되고, 그 다음에 서블릿이 실행된다(모든 서블릿이 공통적으로 가져야 하는 코드). 혹은 서블릿이 실행할지 말지를 결정할 수도 있다(인증과 권한). 또한 서블릿이 실행된 후에도 필터가 실행될 수 있다.
  • jakarta.servlet.Filter를 구현하는 CharacterEncodingFilter 클래스를 새로 만든다.
    • chain.doFilter(request, response);
      • 필터는 서블릿을 실행할지 말지를 결정할 수 있는데, 이는 FilterChain가 한다.chain.doFilter(request, response);의 경우 요청이 오면 그냥 흐름을 넘겨서 다음 필터 혹은 서블릿이 실행되도록 한다.
      • 이 메소드를 위아래를 기준으로 다음과 같이 실행된다. 이 메서드보다 위에 있는 코드는 요청이 올 때 바로 실행됨. 그 후 doFilter() 메소드 만나면 다음 필터나 서블릿으로 넘기고, 실행된 결과가 오면 아래에 있는 코드가 실행됨.
package com.newlecture.web.filter;

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

public class CharacterEncodingFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request,
			ServletResponse response,
			FilterChain chain)
			throws IOException, ServletException {
		// 위에 있는 코드
		request.setCharacterEncoding("UTF-8");
		chain.doFilter(request, response);
    // 아래에 있는 코드
	}

}
  • web.xml에서 필터 설정을 한다(어노테이션 방식도 당연히 있음)
<filter>
  	<filter-name>characterEncodingFilter</filter-name>
  	<filter-class>com.newlecture.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>characterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
  • 어노테이션으로 필터 설정: @WebFilter("/*")는 모든 url에 대해서 필터를 적용하도록 하겠다는 뜻
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
      // 구현 코드
}



강의 21 - 학습과제(사용자 입력을 통한 계산 요청)

  • 강의 22에 풀이 있으니 기록은 생략



강의 22 - 과제풀이(사용자 입력을 통한 계산 요청)

  • 그냥 지금까지 배운 내용 요약한 느낌

  • add.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
  </head>
  <body>
    <form action="add" method="post">
      <div>
        <label>x: </label>
        <input name="x" type="text" />
      </div>
      <div>
        <label>y: </label>
        <input name="y" type="text" />
      </div>
      <div>
        <input type="submit" value="덧셈" />
      </div>
      <div>결과: 0</div>
    </form>
  </body>
</html>
  • Add.java
    • request.setCharacterEncoding("UTF-8");는 필터에서 처리하고 있음
package com.newlecture.web;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/add")
public class Add extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");

		String x_=request.getParameter("x");
		String y_=request.getParameter("y");

		int x=0; int y=0;

		if(!x_.equals("")) x=Integer.parseInt(x_);
		if(!y_.equals("")) y=Integer.parseInt(y_);

		int result=x+y;
		response.getWriter().printf("result is %d\n", result);
	}

}



강의 23 - 여러 개의 Submit 버튼 사용하기

  • 덧셈 버튼뿐 아니라 뺄셈 버튼을 만들면, 이 둘을 어떻게 구분하지?

    • submit 버튼에도 name 속성을 넣으면 됨.
    • 그럼 value값은 “덧셈” 혹은 “뺄셈”이고 버튼을 누르면, http를 봤을 때 form data 안에 “x=12&y=5&operator=%EB%BA%84%EC%85%88”처럼 전송된다. getParameter(“operator”)해서 값(“덧셈” 혹은 “뺄셈”)을 받은 후 조건문 사용하면 구분해서 동작 가능
  • calc.html

<form action="calc" method="post">
  <div>
    <label>x: </label>
    <input name="x" type="text" />
  </div>
  <div>
    <label>y: </label>
    <input name="y" type="text" />
  </div>
  <div>
    <input type="submit" name="operator" value="덧셈" />
    <input type="submit" name="operator" value="뺄셈" />
  </div>
  <div>결과: 0</div>
</form>
  • Calc.java
@WebServlet("/calc")
public class Calc extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");

		String x_=request.getParameter("x");
		String y_=request.getParameter("y");
		String operator=request.getParameter("operator");

		int x=0; int y=0;
		if(!x_.equals("")) x=Integer.parseInt(x_);
		if(!y_.equals("")) y=Integer.parseInt(y_);

		int result=0;
		if(operator.equals("덧셈"))
			result=x+y;
		else
			result=x-y;

		response.getWriter().printf("result is %d\n", result);
	}

}



강의 24 - 입력 데이터 배열로 받기

  • 가변적인 길이, 개수를 여러 개 보낼 때는 같은 이름으로 다 보낼 수 있다. 같은 이름으로 맞추면 배열로 전달된다.
  • add2.html
<form action="add2" method="post">
  <div>
    <input name="num" type="text" />
    <input name="num" type="text" />
    <input name="num" type="text" />
    <input name="num" type="text" />
  </div>
  <div>
    <input type="submit" value="덧셈" />
  </div>
  <div>결과: 0</div>
</form>
  • String[] num_=request.getParameterValues("num");: 배열로 받으니까 getParameter()가 아니라 getParameterValues()로 받기
  • Add2.java
@WebServlet("/add2")
public class Add2 extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");

		String[] num_=request.getParameterValues("num");

		int result=0;
		for(int i=0;i<num_.length;i++) {
			int num=Integer.parseInt(num_[i]);
			result+=num;
		}

		response.getWriter().printf("result is %d\n", result);
	}

}



강의 25 - 상태 유지를 필요로 하는 경우와 구현의 어려움

  • 사용자로부터 두 개의 값을 하나씩 개별적으로 입력받는 상황
    • 우리가 클라이언트에서 버튼을 누르게 되면, 서버 프로그램은 잠깐 실행되었다가 종료됨. 그럼 전달받은 값을 어딘가에 기록해야 한다. 그래야 다시 입력받아 서블릿이 실행되면 기록한 값을 가지고 처리할 수 있음
  • 상태 유지를 위한 5가지 방법
    • application, session, cookie: 값을 담아놓을 수 있는 곳
    • hidden input, querystring: 나중에 다룰 예정
  • 예제 실습은 다음 강의에서 자세히 다뤄서 생략



강의 26 - Application 객체와 그것을 사용한 상태 값 저장

  • 서블릿을 사용할 때 데이터를 이어갈 수 있는 저장소가 필요한데, 이를 서블릿 컨텍스트(context)라고 한다. 즉 서블릿 간에 문맥(컨텍스트)를 이어갈 수 있는 공간(상태 저장 공간)이라는 뜻. 이곳을 어플리케이션 저장소라고 말하기도 한다.

  • ServletContext application=request.getServletContext();: 어플리케션 저장소를 생성
  • application에는 두 가지를 저장(v, operator)
    • 예시: application.setAttribute("value", v);
    • 키하고 값을 넣는 함수. 맵 컬렉션이라고 생각하면 됨.
  • 그 다음 전달받은 연산자가 +,-,= 중에 뭔지 구분. =이 오면 계산하고, +,-가 오면 값을 저장하고.
  • =의 경우 앞에서 저장한 값과 지금 읽은 값 두 가지가 필요하다.
    • 앞에서 저장한 값과 연산자는 어플리케이션 저장소에서 가져와야 한다.
    • int x=(Integer)application.getAttribute("value");: setAttribute()로 저장한 키워드를 사용해서 들고옴. 반환 타입은 오브젝트라서 (Integer)로 형변환함.
  • 먼저 값을 3을 입력하고 +버튼 누름 -> 해당 정보가 전달되면 서블릿은 값과 연산자를 어플리케이션 저장소에 저장함 -> 이 때 저장만 하고 출력하는 게 없어서 새하얀 화면이 될 것이다. -> 그럼 뒤로가기 버튼 누르고, 이젠 값 2와 =버튼을 누른다. -> 앞에 저장한 값들을 가져오고 계산한 결과가 화면에 출력된다.

  • calc2.html
<form action="calc2" method="post">
  <div>
    <label>입력: </label>
    <input name="v" type="text" />
  </div>
  <div>
    <input type="submit" name="operator" value="+" />
    <input type="submit" name="operator" value="-" />
    <input type="submit" name="operator" value="=" />
  </div>
  <div>결과: 0</div>
</form>
  • Calc2.java
@WebServlet("/calc2")
public class Calc2 extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		ServletContext application=request.getServletContext();

		String v_=request.getParameter("v");
		String op=request.getParameter("operator");

		int v=0;
		if(!v_.equals("")) v=Integer.parseInt(v_);

		// 계산
		if(op.equals("=")) {
			int x=(Integer)application.getAttribute("value");
			int y=v;
			String operator=(String)application.getAttribute("op");

			int result=0;
			if(operator.equals("+"))
				result=x+y;
			else
				result=x-y;

			response.getWriter().printf("result is %d\n", result);
		}
		// 값을 저장
		else {
			application.setAttribute("value", v);
			application.setAttribute("op", op);
		}

	}

}



강의 27 - Session 객체로 상태 값 저장하기(그리고 Application 객체와의 차이점)

  • 일단 강의 26에서 사용한 코드에서 Application 객체에서 Session 객체로 바꿔본 후 차이점을 살펴보도록 하자.
  • 강의 26에서 사용한 Calc2.java에서 HttpSession session= request.getSession();로 세션을 생성한 후, application.get/setAttribute()부분에서 application을 session으로 전부 고치기만 하고 실행. 결과는 전과 똑같음.

  • 차이점: Application 객체는 어플리케이션 전역에서 쓸 수 있다. 반면 Session 객체는 범주 내에서 쓸 수 있다. 이 때 세션은 현재 접속한 사용자를 뜻한다. 그럼 접속자마다 공간이 달라질 수 있다.
  • 다른 브라우저를 쓰면 다른 세션으로 인식한다.
  • 반면 기존에 크롬을 쓰고, 지금도 크롬을 새로 실행해서 접속하면 같은 세션으로 인식된다.
    • 왜냐면 크롬 브라우저를 여러 개 띄워도, 여러 프로세스가 동작하는 게 아니라, 하나의 프로세스에 창을 쓰레드라는 개념(프로세스의 흐름을 나눠 갖는 쓰레드라는 개념)으로 띄우게 된다.
    • 따라서 프로세스가 가지고 있는 자원을 쓰레드들은 같이 공유하기 때문에, 웹 서버에 요청할 경우 같은 프로세스가 요청한 걸로 인식됨. 그래서 같은 세션(사용자)으로 인식하게 된다.



강의 28 - WAS가 현재사용자(Session)을 구분하는 방식

  • 세션 id(SID)가 was에 의해서 발부가 되고, 발급된 번호는 브라우저가 항상 가지고 다님. 그러다가 브라우저를 닫으면 사라짐.
    • was 안에 값을 두고(Application, Session) 나중에 다시 사용함.
    • 세션은 개인별 사물함(캐비닛) 느낌의 공간이라, 개인마다 번호가 있음.
    • 만약 사용자가 서버 상의 프로그램을 실행해라는 요청이 온다면, 서블릿이 실행될 것이다.
    • 이 때 요청이 처음 오는 것이라면, 새로운 사용자가 되고 이 때는 이 사용자를 위한 세션은 존재하지 않음(Application 공간의 경우는 모든 요청에 대해 저장 가능). 세션은 사용자가 세션 아이디를 가지고 있어야 세션에 값을 저장할 수 있음.
    • 요청 처리를 마친 후 새 사용자에게 아이디를 부여해 주고 브라우저는 이를 갖고 있음. 그 후 다시 브라우저가 요청하면 이 땐 아이디를 가지고 있으니 세션 사용 가능.
  • 세션 키 확인
    • f12로 개발자 도구 열고, 네트워크 탭을 연다. 그 후 버튼을 누르고 http 헤더 정보를 보면, Cookie: JSESSIONID=9405B93930922EAF56AFCC941B86628C같은 항목이 있다. 다른 크롬 창을 열고 다시 반복해도 세션 아이디는 같아서 같은 세션으로 인식됨.
  • 세션 객체가 갖고 있는 메서드
    • void setAttribute(String name, Object value): 지정된 이름으로 객체를 설정
    • Object getAttribute(String name): 지정한 이름의 객체를 반환
    • void invalidate(): 세션에서 사용되는 객체를 바로 해제(저장소를 비울 때)
    • void setMaxInactiveInterval(int interval)
      • 세션 타임아웃을 정수(초)로 설정
      • 사용자가 요청했지만, 그 다음에는 요청이 안 올 수 있음. 따라서 시간을 두는데, 기본 타임아웃은 30분임.
      • 만약 타임아웃된 후 전의 아이디를 갖고 있는 요청이 다시 오면, 이는 새 유저로 인식됨. 즉 해당 아이디는 유효하지 않은 아이디가 되서 해당 세션 공간을 메모리에서 수거됨.
    • boolean isNew(): 세션이 새로 생성되었는지를 확인
    • Long getCreationTime(): 세션이 시작된 시간을 반환. 1970년 1월 1일을 시작으로 하는 밀리초
    • Long getLastAccessedTime(): 마지막 요청 시간. 1970년 1월 1일을 시작으로 하는 밀리초



강의 29 - Cookie를 이용해 상태값 유지하기

  • 누구나 쓸 수 있는 저장공간인 Application과, 자기만 쓸 수 있는 공간인 Session이 was 안에 있음을 배웠음.
  • 꼭 서버에다가 값을 보관하지 않고, 상태값을 가지고 다닐 수 있음(브라우저가 상태값을 갖고 들고 다님).
  • 클라이언트에서 저장할 수 있는 공간은 Cookie 저장소가 있음.
  • 클라이언트가 값을 가지고 갈 수 있는데, 이 때 값의 종류는 세 가지가 있음

    • 브라우저가 알아서 담아주는 헤더 정보, 사용자 데이터(내가 보내는 데이터), 쿠기
    • 헤더 정보는 getHeader()로, 사용자 데이터는 getParameter()로, 쿠키는 getCookies()로 꺼내고 씀.
    • 서버가 쿠키를 만들어서 브라우저가 보관하게 하고 싶으면 addCookie()를 씀
  • 쿠키는 문자열만 저장될 수 있음. 따라서 객체 생성할 때 new Cookie("value", String.valueOf(v));에서 String.valueOf(v)처럼 v가 정수면 이를 문자열로 바꿔서 생성해야 함.
    • jakarta.servlet.http.Cookie 사용
    • “value”는 키고, String.valueOf(v)는 값이다.
  • response.addCookie(valueCookie);처럼 쿠키를 보낸다. 쿠키가 어떻게 전달되냐면 response header에 심어진 형태로 전달됨.
  • 브라우저에서 쿠키를 읽는 걸 허용하지 않으면, 이를 통해 상태값을 유지하는 데 쓸 수 없음
  • Cookie[] cookies=request.getCookies();: getCookies로 쿠키를 읽을 수 있다. 쿠키는 여러 개일 수 있으므로 배열로 반환됨.

  • Calc2.java
@WebServlet("/calc2")
public class Calc2 extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");

		ServletContext application=request.getServletContext();
		HttpSession session= request.getSession();
		Cookie[] cookies=request.getCookies();

		String v_=request.getParameter("v");
		String op=request.getParameter("operator");

		int v=0;
		if(!v_.equals("")) v=Integer.parseInt(v_);

		// 계산
		if(op.equals("=")) {
			//int x=(Integer)application.getAttribute("value");
			//int x=(Integer)session.getAttribute("value");
			int x=0;
			for(Cookie c:cookies) {
				if(c.getName().equals("value")) {
					x=Integer.parseInt(c.getValue());
					break;
				}
			}

			int y=v;
			//String operator=(String)application.getAttribute("op");
			//String operator=(String)session.getAttribute("op");
			String operator="";
			for(Cookie c:cookies) {
				if(c.getName().equals("op")) {
					operator=c.getValue();
					break;
				}
			}

			int result=0;
			if(operator.equals("+"))
				result=x+y;
			else
				result=x-y;

			response.getWriter().printf("result is %d\n", result);
		}
		// 값을 저장
		else {
			//application.setAttribute("value", v);
			//application.setAttribute("op", op);

			//session.setAttribute("value", v);
			//session.setAttribute("op", op);

			Cookie valueCookie=new Cookie("value", String.valueOf(v));
			Cookie opCookie=new Cookie("op", op);
			response.addCookie(valueCookie);
			response.addCookie(opCookie);
		}
	}
}



강의 30 - Cookie의 path 옵션

  • 쿠키를 사용할 때 해당 url과 관련된 서블릿에게만 값이 전달될 수 있도록 하기
    • valueCookie.setPath("/");: 모든 페이지를 요청할 때마다 클라이언트가 valueCookie를 가져오도록 설정
    • valueCookie.setPath("/notice/");:notice가 포함된 하위 url을 요청할 경우에만 valueCookie를 가져오도록 설정
    • valueCookie.setPath("/calc2");: calc2에서만 사용되는 거라면 이렇게 설정.
Cookie valueCookie=new Cookie("value", String.valueOf(v));
Cookie opCookie=new Cookie("op", op);
valueCookie.setPath("/");
opCookie.setPath("/");
response.addCookie(valueCookie);
response.addCookie(opCookie);



강의 31 - Cookie의 maxAge 옵션

  • 기본적인 경우 브라우저가 닫히면 쿠키도 없어진다. 하지만 브라우저가 닫혀도 내가 원하는 기간을 설정하면 기간 내에 그 값을 유지할 수 있다.
  • 브라우저가 쿠기를 저장하는 공간: 기본적으로 쿠키는 브라우저의 메모리에 있다가, 기간 설정이 되면 외부 파일로 저장됨.
  • valueCookie.setMaxAge(24*60*60);: 해당 쿠키는 24*60*60초 후에 만료되도록 설정. 이 때 기본 단위는 초이기 때문에 60분(1시간)을 하고 싶으면 60*60, 24시간(하루)로 설정하고 싶으면 24*60*60



강의 32 - Application/Session/Cookie 정리

  • 상태 저장을 위한 저장소 특징
    • application
      • 사용범위: 전역 범위에서 사용하는 저장 공간
      • 생명주기: was가 시작해서 종료할 때까지
      • 저장위치: was 서버의 메모리
    • session
      • 사용범위: 세션 범위(특정 사용자)에서 사용하는 저장 공간
      • 생명주기: 세션이 시작해서 종료할 때까지
      • 저장위치: was 서버의 메모리
    • cookie
      • 사용범위: 웹 브라우저별 지정한 path 범주(특정 url에 대해서만 사용 가능) 공간
      • 생명주기: 브라우저에 전달한 시간부터 만료시간까지
      • 저장위치: web browser의 메모리 또는 파일
    • 값을 저장하려고 하는데, 오래 저장하고 싶은 경우(예로 1년) 어디에 저장해야 하지?
      • 무조건 쿠키에 저장해야 함.
      • 세션은 왜 안됨? 기본적으로 세션 아이디는 쿠키로 전달됨. 따라서 브라우저 닫으면 쿠키 사라짐. 새로 브라우저 열면 그 사용자의 세션 아이디는 그 전과 같을까? 아니다. 또 다른 번호를 부여받고 또 다른 세션 공간을 차지하게 됨. 그럼 서버 자원 낭비.
    • 특정 url(예로 notice)와 관련된 값을 가지고 있고, notice 외에는 이를 쓰는 서블릿이 없다고 치자. 그럼 이를 application,session에 저장하는 것보다 쿠키를 쓰는게 바람직. 어쩌다 한 번 쓰는 값을 session에 넣으면 이것도 서버 자원 낭비



강의 33 - 서버에서 페이지 전환해주기(redirection)

  • 페이지 전환(리다이렉트)
    • 값과 + 버튼을 눌러서 포스트하면 서블릿쪽에서 아무것도 돌려주는 것이 없어서 백지를 돌려주게 됨. 계속 계산 이어가려고 하면 뒤로가기를 눌러야 했다.
    • 이 때 서블릿 쪽에서 백지를 주지 않고 원하는 페이지로 대신 돌려줄 수 있음.
    • response.sendRedirect("calc2.html");:



강의 34 - 동적인 페이지(서버 페이지)의 필요성

  • 동적인 문서
    • 동적인 페이지: 요청이 들어오면 데이터를 결합시켜서 만든 문서(요청이 있을 때 만들어지는 문서)를 동적인 문서라고 함. 또 서버 페이지라고 불리는 까닭도 서버에서 만들어지는 페이지라서 그럼
    • 좀 더 현실적인 계산기는 사용자가 입력한 내용을 서버에서 페이지를 만들 때 끼워 넣어야 함. 하지만 사용자가 어떤 숫자 버튼을 눌렀다면(post했다면), 서버는 그것을 쿠키든 세션이든 어딘가에 저장함. 그 후 다시 계산기 형식의 페이지를 보여주기 위해서 calc.html로 리다이렉트함.
    • 하지만 html은 정적 페이지라서 사용자가 입력한 내용을 끼워 넣을 수 없음. 따라서 동적인 페이지(서버 페이지)가 필요함. 즉 이미 만들어진 페이지를 보내는 것이 아니라, 요청이 오면 출력할 문서를 만들 때 입력한 숫자도 넣어서 출력.
    • 이를 위해 html만이 아니라 서블릿으로 된 동적인 문서를 만드는 프로그램을 만들어야 함
  • 계산기 형식 html 만들기
    • 다음 강의에서 이를 서블릿으로 바꾸는 작업 할 거임
    • 일반 윈도우 계산기에서 좀 간략하게 해서 만들어서, 5행(tr) 4열(td)(결과를 표시하는 행까지 합하면 6행 4열)이다.
    • <td colspan="4">0</td>: 제일 위의 1행은 결과나 사용자가 입력한 문자를 표시하는 행이다. 이 때 colspan은 기본적으로 1로 설정되어 있음. 이 뜻은 열 1칸의 크기를 의미함. 이 때 4라고 설정하면 열의 크기를 4칸만큼 하겠다는 뜻
    • ÷ 만드는 법: ㄷ에 “한자”키 누르면 ÷ 나옴.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
    <style>
      input {
        width: 50px;
        height: 50px;
      }
      .output {
        height: 50px;
        background: #e9e9e9;
        font-size: 24px;
        font-weight: bold;
        text-align: right;
        /*상하는 0px, 좌우는 5px  */
        padding: 0px 5px;
      }
    </style>
  </head>
  <body>
    <form action="calc3" method="post">
      <table>
        <tr>
          <td class="output" colspan="4">0</td>
        </tr>
        <tr>
          <td><input type="submit" name="operator" value="CE" /></td>
          <td><input type="submit" name="operator" value="C" /></td>
          <td><input type="submit" name="operator" value="BS" /></td>
          <td><input type="submit" name="operator" value="÷" /></td>
        </tr>
        <tr>
          <td><input type="submit" name="value" value="7" /></td>
          <td><input type="submit" name="value" value="8" /></td>
          <td><input type="submit" name="value" value="9" /></td>
          <td><input type="submit" name="operator" value="X" /></td>
        </tr>
        <tr>
          <td><input type="submit" name="value" value="4" /></td>
          <td><input type="submit" name="value" value="5" /></td>
          <td><input type="submit" name="value" value="6" /></td>
          <td><input type="submit" name="operator" value="-" /></td>
        </tr>
        <tr>
          <td><input type="submit" name="value" value="1" /></td>
          <td><input type="submit" name="value" value="2" /></td>
          <td><input type="submit" name="value" value="3" /></td>
          <td><input type="submit" name="operator" value="+" /></td>
        </tr>
        <tr>
          <td></td>
          <td><input type="submit" name="value" value="0" /></td>
          <td><input type="submit" name="dot" value="." /></td>
          <td><input type="submit" name="operator" value="=" /></td>
        </tr>
      </table>
    </form>
  </body>
</html>



강의 35 - 처음이자 마지막으로 동적인 페이지 서블릿으로 직접 만들기

  • out.write(): write()는 문자열 출력 전용함수
package com.newlecture.web;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/calcpage")
public class CalcPage extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out=response.getWriter();


		out.write("<!DOCTYPE html>");

		out.write("<html>");
		out.write("<head>");
		out.write(" <meta charset=\"UTF-8\" />");
		out.write(" <title>Insert title here</title>");
		out.write(" <style>");
		out.write("	input{");
		out.write("		width:50px;");
		out.write("		height:50px;");
		out.write("	}");
		out.write("	.output{");
		out.write("		height:50px;");
		out.write("		background: #e9e9e9;");
		out.write("	font-size:24px;");
		out.write("	font-weight:bold;");
		out.write("	text-align:right;");
		out.write("	padding:0px 5px; ");
		out.write("}");
		out.write(" </style>");
		out.write("</head>");
		out.write("<body>");
		out.write(" <form action=\"calc3\" method=\"post\">");
		out.write("	<table>");
		out.write("	<tr>");
		out.printf("		<td class=\"output\" colspan=\"4\">%d</td>",3+4);
		out.write("	</tr>");
		out.write("	<tr>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"CE\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"C\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"BS\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"÷\" /></td>");
		out.write("	</tr>");
		out.write("	<tr>");
		out.write("		<td><input type=\"submit\"  name=\"value\" value=\"7\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"value\" value=\"8\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"value\" value=\"9\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"X\" /></td>");
		out.write("	</tr>");
		out.write("	<tr>");
		out.write("		<td><input type=\"submit\"  name=\"value\" value=\"4\" /></td>");
		out.write("	<td><input type=\"submit\"  name=\"value\" value=\"5\" /></td>");
		out.write("	<td><input type=\"submit\"  name=\"value\" value=\"6\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"-\" /></td>");
		out.write("	</tr>");
		out.write("	<tr>");
		out.write("	<td><input type=\"submit\"  name=\"value\" value=\"1\" /></td>");
		out.write("	<td><input type=\"submit\"  name=\"value\" value=\"2\" /></td>");
		out.write("	<td><input type=\"submit\"  name=\"value\" value=\"3\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"+\" /></td>");
		out.write("	</tr>");
		out.write("	<tr>");
		out.write("		<td></td>");
		out.write("		<td><input type=\"submit\"  name=\"value\" value=\"0\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"dot\" value=\".\" /></td>");
		out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"=\" /></td>");
		out.write("	</tr>");
		out.write("	</table>");
		out.write(" </form>");
		out.write("</body>");
		out.write("</html>");
	}

}



강의 36 - 계산기 서블릿 완성하기

  • 목표: 버튼 눌러서 post하게 되면, 이를 누적해서 쿠키로 저장 후 리다이렉트한다. 그러면 ui에는 저장된 쿠키 정보를 출력하기
  • CalcPage.java
    • exp: 이 문자열은 연산식(숫자와 연산자로 이루어진 식)을 표현. 이를 out.printf(" <td class=\"output\" colspan=\"4\">%s</td>", exp);게 출력
    • 기존에 value="÷", value="X"의 경우 실제적으로는 연산자가 /, *이므로 value="/", value=\"*\"로 수정
  • Calc3.java

    • response.sendRedirect("calcpage");: Calc3(@WebServlet(“/calc3”))에서 CalcPage(@WebServlet(“/calcpage”))으로 갈 때는 경로(/)가 같기 때문에 그냥 주소만(calcpage) 쓸 수 있음.
    • String value=request.getParameter("value");, String operator=request.getParameter("operator");, String dot=request.getParameter("dot");는 이중에 하나만 올 수 있다.
      • 만약에 숫자버튼 하나만 눌렀으면, value만 값이 오고 나머지는 다 null이다.
      • 따라서 exp+=(value==null)?"":value;, exp+=(operator==null)?"":operator;, exp+=(dot==null)?"":dot;가 뜻하는 것은 얻은 값이 null이 아닐 때만 붙인다는 소리다.
    • 자바 스크립트 엔진이 안되서 다음 댓글을 참고해서 진행했는데…도 안된다.
    스크립트 엔진 안되시는분들
    1. 프로젝트 우클릭 -> configure -> convert to maven project 클릭
    2. 생성된 pom.xml 파일에
      <dependencies>
        <dependency>
          <groupId>org.graalvm.js</groupId>
          <artifactId>js</artifactId>
          <version>19.2.0.1</version>
        </dependency>
        <dependency>
          <groupId>org.graalvm.js</groupId>
          <artifactId>js-scriptengine</artifactId>
          <version>19.2.0.1</version>
        </dependency>
      </dependencies>
    삽입 ( <build> 바로 위 )
    3. Calc3.java에 ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js"); 삽입
    
  • 코드
package com.newlecture.web;

import java.io.IOException;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/calc3")
public class Calc3 extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		Cookie[] cookies=request.getCookies();

		String value=request.getParameter("value");
		String operator=request.getParameter("operator");
		String dot=request.getParameter("dot");

		// 기존의 쿠키를 읽어와서, 그걸 가지고 덧붙이는 작업
		String exp="";
		if(cookies!=null)
			for(Cookie c:cookies) {
				if(c.getName().equals("exp")) {
					exp=c.getValue();
					break;
				}
			}

		if(operator!=null&&operator.equals("=")) {
			// 자바스크립트 엔진 에러난다.
//			ScriptEngineManager manager = new ScriptEngineManager();
//			ScriptEngine engine = manager.getEngineByName("graal.js");
//			try {
//			    exp=String.valueOf(engine.eval(exp));
//			} catch (ScriptException e) {
//			    System.err.println(e);
//			}
		} else {
			exp+=(value==null)?"":value;
			exp+=(operator==null)?"":operator;
			exp+=(dot==null)?"":dot;
		}

		Cookie expCookie=new Cookie("exp",exp);
		response.addCookie(expCookie);
		response.sendRedirect("calcpage");
		}
	}



강의 37 - 쿠키 삭제하기

  • 계산기에서 클리어 버튼(C) 눌렀을 때 쿠키 삭제하기

    • expCookie.setMaxAge(0);: 클리어 버튼 누르면 쿠키의 만료시간을 0초 후로 설정하면 쿠키는 삭제됨
  • Calc3.java

@WebServlet("/calc3")
public class Calc3 extends HttpServlet {

	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		Cookie[] cookies=request.getCookies();

		String value=request.getParameter("value");
		String operator=request.getParameter("operator");
		String dot=request.getParameter("dot");

		// 기존의 쿠키를 읽어와서, 그걸 가지고 덧붙이는 작업
		String exp="";
		if(cookies!=null)
			for(Cookie c:cookies) {
				if(c.getName().equals("exp")) {
					exp=c.getValue();
					break;
				}
			}

		if(operator!=null&&operator.equals("=")) {
			// 안된다.
//			ScriptEngineManager manager = new ScriptEngineManager();
//			ScriptEngine engine = manager.getEngineByName("graal.js");
//			try {
//			    exp=String.valueOf(engine.eval(exp));
//			} catch (ScriptException e) {
//			    System.err.println(e);
//			}
		} else if(operator!=null&&operator.equals("C")) {
			exp="";
		}
		else {
			exp+=(value==null)?"":value;
			exp+=(operator==null)?"":operator;
			exp+=(dot==null)?"":dot;
		}

		Cookie expCookie=new Cookie("exp",exp);
		if(operator!=null&&operator.equals("C")) {
			expCookie.setMaxAge(0);
		}
		response.addCookie(expCookie);
		response.sendRedirect("calcpage");
		}
	}



강의 38 - GET과 POST에 특화된 서비스 함수

  • req.getMethod().equals("GET"): form에 method값을 반환하는 함수가 getMethod(). 단 반환은 대문자로 하므로 주의
  • 부모의 service함수(super.service(req, res);)

    • get요청이 오면 doGet()을, post요청일 경우 doPost()를 실행하게 되어 있음.
    • 만약 get요청이 오고 super.service(req, res); 실행되면 doGet()가 호출되는데, doGet()이 오버라이드되지 않았으면 오류 발생
    • 따라서 부모의 service를 오버라이드하고 조건문써서 get과 post를 다 여기서 처리할 건지, 아님 service 함수 오버라이드 하지 않고 get요청만 처리하고 싶으면 doGet()만 오버라이드해서 처리하는 식으로 할건지 고를 수 있음
    • get과 post 요청을 따로 처리할 거면 각각 doGet,doPost메소드를 오버라이드해서 사용. 혹은 get과 post 요청을 한번에 처리할 거면 service를 오버라이드해서 사용
  • calculator.html
<body>
  <form action="calculator" method="get">
    <input type="submit" value="요청" />
  </form>
</body>
  • service를 오버라이드하고 조건문써서 get과 post를 다 여기서 처리할 건지
@WebServlet("/calculator")
public class Calculator extends HttpServlet{
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

		if(req.getMethod().equals("GET")) {
			System.out.println("GET 요청이 왔습니다");
		}else if(req.getMethod().equals("POST")) {
			System.out.println("POST 요청이 왔습니다");
		}
	}
}
  • service 함수 오버라이드 하지 않고 get요청만 처리하고 싶으면 doGet()만 오버라이드해서 처리하는 식으로 할건지
@WebServlet("/calculator")
public class Calculator extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("doGet 메소드가 호출되었습니다");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("doPost 메소드가 호출되었습니다");
	}
}



강의 39 - 계산기 프로그램 하나의 서블릿으로 합치기

  • 전에 만들었던 계산기 서블릿을 보면, /calcpage로 get요청만 하고 있고, /calc3로 post요청만 하고 있었다.
    • 그럼 만약 쿠키.setPath()해서 특정 url에서만 쿠키를 사용하도록 하고 싶다고 치자. 이 때 이 setPath()는 하나의 경로만 지정 가능하다. 또 아무것도 지정하지 않으면 루트경로가 된다.
    • 루트경로가 되면 쿠키를 사용할 필요가 없는 곳에서도 쿠키를 넣게 된다. 이는 싫으니까 경로를 설정하려고 보니 /calcpage로 설정하면 /calc3는 쿠키를 못 받으니까 문제다.
    • 따라서 url을 하나로 합치고 싶다.
  • /calculator이라는 url로 합치자. 이 때 전에 만든 calc3.java는 doPost() 안에, calcpage.java는 doGet() 안에 넣고 약간만 수정하자

    • <form action="calc3" method="post">"에서 action="calc3" 삭제
      • 아래 코드처럼 합치면 action을 쓸 필요가 없다. 왜냐면 기존에는 get요청하는 페이지와 post를 담당하는 url이 달라서 “calc3”라고 지정한 거다.
      • 현재 페이지를 post요청하니까 안 넣어도 됨
    • response.sendRedirect("calculator");: 자기가 자기를 요청하는데, 이는 get요청을 의미함
    • expCookie.setPath("/calculator");: 자기자신(calculator)에서만 해당 쿠키를 사용할 수 있음
  • 기존의 코드와 똑같은 건 주석 처리함.
package com.newlecture.web;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/calculator")
public class Calculator extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// Cookie[] cookies=request.getCookies();

		// String exp="0";
		// if(cookies!=null)
		// 	for(Cookie c:cookies) {
		// 		if(c.getName().equals("exp")) {
		// 			exp=c.getValue();
		// 			break;
		// 		}
		// 	}

		// response.setCharacterEncoding("UTF-8");
		// response.setContentType("text/html; charset=UTF-8");
		// PrintWriter out=response.getWriter();

		// out.write("<!DOCTYPE html>");

		// out.write("<html>");
		// out.write("<head>");
		// out.write(" <meta charset=\"UTF-8\" />");
		// out.write(" <title>Insert title here</title>");
		// out.write(" <style>");
		// out.write("	input{");
		// out.write("		width:50px;");
		// out.write("		height:50px;");
		// out.write("	}");
		// out.write("	.output{");
		// out.write("		height:50px;");
		// out.write("		background: #e9e9e9;");
		// out.write("	font-size:24px;");
		// out.write("	font-weight:bold;");
		// out.write("	text-align:right;");
		// out.write("	padding:0px 5px; ");
		// out.write("}");
		// out.write(" </style>");
		// out.write("</head>");
		// out.write("<body>");
		out.write(" <form method=\"post\">");
		// out.write("	<table>");
		// out.write("	<tr>");
		// out.printf("		<td class=\"output\" colspan=\"4\">%s</td>", exp);
		// out.write("	</tr>");
		// out.write("	<tr>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"CE\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"C\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"BS\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"/\" /></td>");
		// out.write("	</tr>");
		// out.write("	<tr>");
		// out.write("		<td><input type=\"submit\"  name=\"value\" value=\"7\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"value\" value=\"8\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"value\" value=\"9\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"*\" /></td>");
		// out.write("	</tr>");
		// out.write("	<tr>");
		// out.write("		<td><input type=\"submit\"  name=\"value\" value=\"4\" /></td>");
		// out.write("	<td><input type=\"submit\"  name=\"value\" value=\"5\" /></td>");
		// out.write("	<td><input type=\"submit\"  name=\"value\" value=\"6\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"-\" /></td>");
		// out.write("	</tr>");
		// out.write("	<tr>");
		// out.write("	<td><input type=\"submit\"  name=\"value\" value=\"1\" /></td>");
		// out.write("	<td><input type=\"submit\"  name=\"value\" value=\"2\" /></td>");
		// out.write("	<td><input type=\"submit\"  name=\"value\" value=\"3\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"+\" /></td>");
		// out.write("	</tr>");
		// out.write("	<tr>");
		// out.write("		<td></td>");
		// out.write("		<td><input type=\"submit\"  name=\"value\" value=\"0\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"dot\" value=\".\" /></td>");
		// out.write("		<td><input type=\"submit\"  name=\"operator\" value=\"=\" /></td>");
		// out.write("	</tr>");
		// out.write("	</table>");
		// out.write(" </form>");
		// out.write("</body>");
		// out.write("</html>");
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// Cookie[] cookies=request.getCookies();

		// String value=request.getParameter("value");
		// String operator=request.getParameter("operator");
		// String dot=request.getParameter("dot");

		// 기존의 쿠키를 읽어와서, 그걸 가지고 덧붙이는 작업
		// String exp="";
		// if(cookies!=null)
		// 	for(Cookie c:cookies) {
		// 		if(c.getName().equals("exp")) {
		// 			exp=c.getValue();
		// 			break;
		// 		}
		// 	}

		// if(operator!=null&&operator.equals("=")) {
		// 	안된다.
		// 	ScriptEngineManager manager = new ScriptEngineManager();
		// 	ScriptEngine engine = manager.getEngineByName("graal.js");
		// 	try {
		// 	    exp=String.valueOf(engine.eval(exp));
		// 	} catch (ScriptException e) {
		// 	    System.err.println(e);
		// 	}
		// } else if(operator!=null&&operator.equals("C")) {
		// 	exp="";
		// }
		// else {
		// 	exp+=(value==null)?"":value;
		// 	exp+=(operator==null)?"":operator;
		// 	exp+=(dot==null)?"":dot;
		// }

		// Cookie expCookie=new Cookie("exp",exp);
		// if(operator!=null&&operator.equals("C")) {
		// 	expCookie.setMaxAge(0);
		// }

		expCookie.setPath("/calculator");
		// response.addCookie(expCookie);
		response.sendRedirect("calculator");
	}
}



강의 40 - JSP 시작하기 (Jasper를 이용한 서블릿 프로그래밍)

  • Jasper가 만들어주는 서블릿 출력 코드
    • html 코드에 out.write()를 일일히 다 붙여서 서블릿 코드로 바꾼 다음 출력했는데, 이를 Jasper가 대신 해줌.
    • Jasper에게 일을 시키려면 확장자만 .html에서 .jsp로 바꾸면 된다.
  • Jasper를 이용한 코드 작성 방법
    • 기본적으로 Jasper에 적혀 있는 모든 내용은 다 출력해야 하는 걸로 알고 다 write() 안에 넣어버림
    • 하지만 이건 출력할 게 아니고 자바 코드를 넣기 위한 용도로 “<% 안에 자바 코드 %>”(코드블록)를 이용할 수 있다.
    • 만약 add.jsp에 코드를 쓰면 이는 Jasper가 add_jsp.java파일로 변환한다. 코드 블록을 쓰면 write() 안에 넣지 않고 쓴 그대로 넣는다.



강의 41 - JSP의 코드 블록

  • 코드 블록: 예시로 add.jsp파일에서 다음처럼 코드 블록 활용하면, Jasper가 add_jsp.java파일로 변환할 때 어떻게 할까?
    • <% y=x+3; %>: 그대로 y=x+3;를 add_jsp.java 속 _jspService함수에 넣음
    • y의 값은: <% out.print(y)%>: out.write("y의 값은: "); out.print(y);가 add_jsp.java 속 _jspService함수에 심어짐
    • y의 값은: <%=y%>: y의 값은: <% out.print(y)%>와 같이 쓰기 불편하니 간단히 한 버전. 결과는 똑같이out.write("y의 값은: "); out.print(y);가 add_jsp.java 속 _jspService함수에 심어짐
    • <%! public int sum(int a,int b) {return a+b;} %>: 일반적인 코드블록(<%%>)안에 느낌표를 앞에 하나 추가하면 이 코드는 add_jsp.java 속 _jspService함수가 아니라, add_jsp 클래스의 멤버를 선언하는 곳에 심어짐
  • 페이지 지시자
    • 페이지를 어떤 인코딩 방식을 이용할 것인지, 콘텐츠 타입은 무엇인지 지시하는 것
    • 이는 지시 블록(<%@ %>)를 이용한다.



강의 42 - JSP의 내장객체 간단히 알아보기

  • JSP의 내장객체
    • Jasper가 만들어 놓은 객체를 가리키는 변수들을 내장 객체라고 한다.
    • 우리는 이런 변수가 있음을 알고 있어야 하고, 이를 적절히 활용해서 만들 수도 있어야 한다.
    • request, response: 입력, 출력 도구
    • applicaton, session 객체
    • pageContext
      • applicaton, session처럼 페이지 내에서 임시로 데이터를 저장할 수 있는(setAttribute, getAttribute 갖고 있는) 변수
      • ServletContext는 전역에서 사용하는 거라면, PageContext는 파일 내부에서만 쓰는 저장소라고 보면 됨
    • config(ServletConfig 변수), out(출력도구), page(이 페이지의 객체를 참조하는 Object 변수)



강의 43 - JSP로 만드는 Hello 서블릿


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
int cnt=10; // 기본값

String cnt_=request.getParameter("cnt");
if(cnt_!=null&&!cnt_.equals(""))
	cnt=Integer.parseInt(cnt_);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<%for(int i=0;i<cnt;i++) {%>
			안녕, servlet<br>
		<%} %>
</body>
</html>



강의 44 - 스파게티 코드를 만드는 JSP

  • 스파게티 코드: 코드 블록을 나눠서 만들게 되면, 코드 수정할 때 자바 코드만 보기가 힘듦. 내가 관심 있는 코드와 없는 코드가 섞여 있는 코드
  • 한국말로는 실타래 코드라고 한다. 한 번 꼬이면 답이 없는 코드

  • 스파게티 코드 예제(spag.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	int num=0;
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<%
String num_=request.getParameter("n");
if(num_!=null&&!num_.equals(""))
num=Integer.parseInt(num_);
%>
<body>
	<%if(num%2!=0) {%>
	홀수입니다.
	<%}
	else
	{%>
	짝수입니다.
	<%} %>
</body>
</html>



강의 45 - JSP MVC model1

  • jsp 코드 블록을 어떻게 간단하게 만들 수 있을까?(스파게티 코드를 어떻게 피할까?)
  • 이에 대해 나온 해결책이 MVC model1
    • 코드 블록 최소화: 입력 코드는 전부 위쪽에 놓고, 출력 코드는 전부 아래쪽에 놓기
    • 출력을 위해서 만드는 데이터(출력할 데이터)를 모델이라고 함.
    • 이러한 모델은 입력 코드부분에서 만들어서, 출력 코드 부분에 꽂으면 됨.
  • 정리하면

    • Controller: 출력할 데이터(Model)를 만들어 내는 부분(입력과 제어를 담당하는 자바 코드)
    • View: 출력 담당(html 코드)
    • Model: 출력 데이터
  • 스파게티 코드 예제(spag.jsp)에 mvc패턴을 적용 후

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	int num=0;
	String num_=request.getParameter("n");
	if(num_!=null&&!num_.equals(""))
	num=Integer.parseInt(num_);

	String result;
	if(num%2!=0)
		result="홀수";
	else
		result="짝수";
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%=result %>입니다.
</body>
</html>



강의 46 - JSP MVC model1을 model2 방식으로

  • mvc model1은 컨트롤러와 뷰가 물리적으로 구분되지 않았다(한 파일 내에 자바 코드(컨트롤러)와 뷰(html 코드)가 같이 있다)
  • mvw model2는 컨트롤러와 뷰가 물리적으로 구분한 방식(자바 코드와 html 코드를 따로 분리)
  • model2: dispatcher를 집중화하기 전의 모델
    • 컨트롤러와 뷰를 연결하기 위해서 포워딩하는 방법이 포함됨. 즉 컨트롤러에서 뷰로(서블릿에서 jsp(서블릿)로) 흐름을 이어받아서 코드를 진행하는 게 포워딩
    • 이 때 포워딩은 dispatcher로 하게 됨
    • 계속 페이지 만들다보면 컨트롤러에서 공통적으로 가지고 있는 dispatcher 기능이 비효율적
  • model2: dispatcher를 집중화하기 전의 모델
    • dispatcher는 하나만 두고 컨트롤러의 기능만 따로 담는 방법
    • 즉 실질적으로 서블릿은 하나만 만들고, 업무 로직(컨트롤러)는 따로 POJO클래스라고 해서, 서블릿 클래스가 아닌 일반 클래스 형태로 만듦
    • 사용자 요청이 들어오면 dispatcher가 적절한 컨트롤러 찾아서 수행하게 함.
  • forward
    • redirect와 forward
      • forward는 현재 작업하던 내용을 이어갈 수 있도록 할 때 씀
      • redirect는 현재 작업하던 내용과 전혀 관계없이 새로 요청할 때 씀
  • 실습

    • request.getRequestDispatcher("spag.jsp");
      • 현재 spag라는 url이 있는데, 요청이 들어왔을 경우, spag.jsp로 요청을 전달하려고 함
      • 페이지명(spag.jsp)만 넣었지 경로를 넣지 않은 이유는, url 상으로 같은 디렉토리에 있다고 생각하기 때문에 지정하지 않음.
    • dispatcher.forward(request, response);
      • 위에서 얻은 dispatcher로 포워딩할 수 있음.
      • 즉 현재 작업했던 내용들을 request, response에 담고 있다면, 그 내용이 spag.jsp로 이어져서 진행될 것이다.
    • request.setAttribute("result", result);
      • 우리가 result에 출력할 결과를 담았고, 이를 spag.jsp로 넘겨줄 때 쓸 수 있는 저장소가 request다.
      • 즉 포워드 관계에 있는 둘 사이에 공유하는 저장소는 request가 사용됨
  • spag.java(컨트롤러)
package com.newlecture.web;

import java.io.IOException;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/spag")
public class spag extends HttpServlet{
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		int num=0;
		String num_=request.getParameter("n");
		if(num_!=null&&!num_.equals(""))
		num=Integer.parseInt(num_);

		String result;
		if(num%2!=0)
			result="홀수";
		else
			result="짝수";

		request.setAttribute("result", result);
		RequestDispatcher dispatcher
			=request.getRequestDispatcher("spag.jsp");
		dispatcher.forward(request, response);
	}
}
  • spag.jsp(뷰)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%=request.getAttribute("result")%>입니다.
</body>
</html>



강의 47 - EL(Expression Language)

  • EL: 객체에서 값을 추출해서 출력하는 표현식
    • 코드 블록을 쓰지 않고 값을 꺼내 쓸 수 있다.
  • ${result}
    • request.setAttribute("result", result); -> <%=request.getAttribute("result")%>입니다.
    • 이 때 <%=request.getAttribute("result")%> 대신 ${result}입니다로 간단히 쓸 수 있음
  • ${list[0]}
    • List list=new ArrayList(){"1", "test"};request.setAttribute("list", list); -> ((List)request.getAttribute("list")).get(0)
    • 이 때 ((List)request.getAttribute("ist")).get(0)대신 ${list[0]}으로 쓸 수 있음
  • ${n.title}
    • Map n=new HashMap("title","제목");request.setAttribute("n", n); -> ((Map)request.getAttribute("n")).get("title")
    • 이 때 ((Map)request.getAttribute("n")).get("title") 대신 ${n.title}로 쓸 수 있음



강의 48 - EL의 데이터 저장소

  • page객체에 값을 저장하기
    • <%pageContext.setAttribute("test","content")%> 코드 블록을 jsp에 넣은 후 해당 jsp 파일 내에서 ${test}라고 입력하면 저장된 값을 쓸 수 있음.
  • EL이 저장 객체에서 값을 추출하는 순서
    • EL을 이용해서 뽑아낼 수 있는 것은 page 객체, request 객체, session 객체, application 객체 등 어디서든 뽑을 수 있다.
    • 값을 추출하는 우선순위는 page->request->session->application 순이다.
  • 그럼 나는 session의 cnt를 바라고 ${cnt}를 썼는데, page에도 cnt가 저장되어 있다면, 어떻게 session의 cnt를 쓸 수 있을까?
    • 우선순위는 page가 더 높으므로 cnt는 page의 cnt가 출력된다.
    • 따라서 이와 비슷한 경우에는 다음의 키워드를 쓰면 된다.
      • ${pageScope.cnt}
      • ${requestScope.cnt}
      • ${sessionScope.cnt}
      • ${applicationScope.cnt}
  • 클라이언트의 입력 값 추출
    • ${param.cnt}
      • param은 파라미터 값을 저장하고 있는 저장소
      • 파라미터란 “localhost:8080/spag?n=3”에서 n을 의미.
    • ${header.host}
      • header는 header 정보를 저장하고 있는 저장소
    • ${header.["host"]}
      • 쓰려는 이름이 변수 명명 규칙에 맞지 않을 때(예로 대쉬가 들어갈 때), 대괄호와 따옴표로 감싸서 쓸 수 있음.
  • pageContext 객체
    • pageContext: 페이지 범위의 컨텍스트 저장소
    • ${pageContext.request.method}: <%=pageContext.getRequest().getMethod() %>를 EL로 표현한 것



강의 49 - EL 연산자

  • empty
    • ${empty param.n}: “localhost:8080/spag?n=3”처럼 n에 값이 오지 않으면 빈 문자열(“spag?n=”일 때) 혹은 null(“spag”일 때)이 되는데, 이 경우 empty 구문은 참이 된다.
  • +, -, *, (/와 div), (%와 mod)
    • ${3/2}는 1.5로 출력됨(일반적인 것처럼 1로 출력되지 않음)
  • (<와 lt),(>와 gt),(<=와 le),(>=와 ge)
    • html 자체가 꺾음새를 가지고 있는데, html의 엄격한 구문으로 파악하면 오류를 유발할 수도 있음. 따라서 lt(less than), le(less and equal) 등의 키워드도 사용할 수 있음
  • (==와 eq),(!=와 ne)
    • ne(not equal)
  • (&&와 and),(||와 or)
  • ?:
    • ${empty param.n? '값이 비어있습니다': param.n}: 삼항연산자



강의 50 - 수업용 프로젝트 준비하기

  • 별달리 메모할 내용은 없었다.



강의 51 - JSP를 이용해서 자바 웹 프로그램 만들기 시작

  • JSP 프로그래밍: Jasper가 작성할 서블릿 코드에 코드 블록을 적절히 끼워 달라고 지시하는 방식
  • list.html을 list.jsp로 복사하면, 한글이 다 깨져있음. 기본적으로 복사한 파일에 대한 인코딩 방식이 ISO-8859-1이라서 그럼. 이 땐 alt+enter키 누른 후에 파일 인코딩 방식을 utf-8로 설정
  • list.jsp를 실행하면 한글이 다 깨져 있음.
    • 지시 블록 사용: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  • list.jsp에서 공지사항 목록 부분을 약간 손 봄

<%for(int i=0;i<10;i++) {%>
	<tr>
		<td><%=i+1 %></td>
		<td class="title indent text-align-left"><a href="detail.html">스프링 8강까지의 예제 코드</a></td>
		<td>newlec</td>
		<td>
			2019-08-18
		</td>
		<td>146</td>
	</tr>
<%} %>



강의 52 - JDBC를 이용해 글 목록 구현하기

  • 오라클 드라이버 가져오기

    • 웹 개발할 때는 웹에서 사용하는 라이브러리가 어딘가에 배포돼서 실행되기 때문에 build path로 경로 지정해서 사용하면 안 되고, 라이브러리를 프로젝트에 포함시켜야 한다.
    • WEB-INF 속 lib폴더가 있음. 여기에 우리가 사용하는 파일을 담아야 한다.
  • 해당 코드 내용은 jdbc 강의 ex1의 program.java파일에서 복붙한 내용도 포함돼 있다.
  • list.jsp

<%
String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "select * from notice";

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, "newlec", "1234");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);

%>

<!-- 중략 -->

<%while(rs.next()) {%>
	<tr>
		<td><%=rs.getInt("id") %></td>
		<td class="title indent text-align-left"><a href="detail.html"><%=rs.getString("title") %></a></td>
		<td><%=rs.getString("writer_id") %></td>
		<td>
			<%=rs.getDate("regdate") %>
		</td>
		<td><%=rs.getInt("hit") %></td>
	</tr>
<%} %>

<!-- 중략 -->

<%
	rs.close();
	st.close();
	con.close();
%>



강의 53 - 자세한 페이지 구현하기

  • <td class="title indent text-align-left"><a href="detail.jsp?id=<%=rs.getInt("id") %>"><%=rs.getString("title") %></a></td>
    • 전 강의의 list.jsp의 일부분을 다음처럼 수정했다. 목록 페이지에서 게시글을 누르면 detail.jsp?id=<%=rs.getInt(“id”) %>로 이동하는 것이다.
    • 즉 id에 해당하는 게시글로 이동 가능하게 하기 위해 수정했다.
  • detail.jsp를 구현하기
    • list.jsp와 차이나는 부분만 기록하겠다. 전의 list.jsp에서 추가한 내용 중 일부를 복붙한 후 조금만 수정했기 때문이다.

<%

int id=Integer.parseInt(request.getParameter("id"));

String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "select * from notice where id=?";

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, "newlec", "1234");
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, id);

ResultSet rs = st.executeQuery();
rs.next();
%>

<!-- 중략 -->

<div class="margin-top first">
	<h3 class="hidden">공지사항 내용</h3>
	<table class="table">
		<tbody>
			<tr>
				<th>제목</th>
				<td class="text-align-left text-indent text-strong text-orange" colspan="3"><%=rs.getString("title") %></td>
			</tr>
			<tr>
				<th>작성일</th>
				<td class="text-align-left text-indent" colspan="3"><%=rs.getDate("regdate") %>	</td>
			</tr>
			<tr>
				<th>작성자</th>
				<td><%=rs.getString("writer_id") %></td>
				<th>조회수</th>
				<td><%=rs.getString("hit") %></td>
			</tr>
			<tr>
				<th>첨부파일</th>
				<td colspan="3"><%=rs.getString("files") %></td>
			</tr>
			<tr class="content">
				<td colspan="4"><%=rs.getString("content") %></td>
			</tr>
		</tbody>
	</table>
	</div>

<div class="margin-top text-align-center">
	<a class="btn btn-list" href="list.jsp">목록</a>
</div>



강의 54 - 자세한 페이지 MVC model1으로 변경하기

  • detail.jsp에 MVC model1 적용
<%@page import="java.util.Date"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.DriverManager"%>
<%@page import="java.sql.Connection"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
int id=Integer.parseInt(request.getParameter("id"));

String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "select * from notice where id=?";

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, "newlec", "1234");
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, id);

ResultSet rs = st.executeQuery();
rs.next();

String title=rs.getString("title");
String writerId=rs.getString("writer_id");
Date regdate=rs.getDate("regdate");
String hit=rs.getString("hit");
String files=rs.getString("files");
String content=rs.getString("content");

rs.close();
st.close();
con.close();
%>

<!-- 중략 -->

<div class="margin-top first">
	<h3 class="hidden">공지사항 내용</h3>
	<table class="table">
		<tbody>
			<tr>
				<th>제목</th>
				<td class="text-align-left text-indent text-strong text-orange" colspan="3"><%=title %></td>
			</tr>
			<tr>
				<th>작성일</th>
				<td class="text-align-left text-indent" colspan="3"><%=regdate %></td>
			</tr>
			<tr>
				<th>작성자</th>
				<td><%=writerId %></td>
				<th>조회수</th>
				<td><%=hit %></td>
			</tr>
			<tr>
				<th>첨부파일</th>
				<td colspan="3"><%=files %></td>
			</tr>
			<tr class="content">
				<td colspan="4"><%=content %></td>
			</tr>
		</tbody>
	</table>
</div>

<!-- 중략 -->



강의 55 - MVC model2 방식으로 변경하기

  • MVC model2: 뷰와 컨트롤러를 물리적으로 나누는 방식

    • 적용 이유
      • 뷰와 컨트롤러 개별적으로 유지관리 가능. 또 재사용성 증가
      • 실행 면에서도 도움. 요청이 오면 jsp는 서블릿으로 변환되고 컴파일 과정도 거치게 된다. 따라서 컨트롤러(자바 코드) 부분이 뷰와 같이 있으면 두 코드를 합쳐서 요청이 올 때마다 서블릿으로 바꾸고 컴파일해야 한다. 서로 분리하면 컨트롤러 부분을 미리 컴파일할 수 있기에, 실행 성능에서도 유리하다.
  • NoticeDetailController.java

    • 기존의 detail.jsp의 코드 블록 내용을 복붙한 게 바탕이므로 추가로 넣은 것만 주석 처리 안 함.
package com.newlecture.web.controller;
// import부분 생략

@WebServlet("/notice/detail")
public class NoticeDetailController extends HttpServlet{
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// int id=Integer.parseInt(request.getParameter("id"));

		// String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
		// String sql = "select * from notice where id=?";

		try {
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			// Connection con = DriverManager.getConnection(url, "newlec", "1234");
			// PreparedStatement st = con.prepareStatement(sql);
			// st.setInt(1, id);

			// ResultSet rs = st.executeQuery();
			// rs.next();

			// String title=rs.getString("title");
			// String writerId=rs.getString("writer_id");
			// Date regdate=rs.getDate("regdate");
			// String hit=rs.getString("hit");
			// String files=rs.getString("files");
			// String content=rs.getString("content");

			request.setAttribute("title", title);
			request.setAttribute("writerId", writerId);
			request.setAttribute("regdate", regdate);
			request.setAttribute("hit", hit);
			request.setAttribute("files", files);
			request.setAttribute("content", content);

			// rs.close();
			// st.close();
			// con.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		request
		.getRequestDispatcher("/notice/detail.jsp")
		.forward(request, response);
	}
}
  • detail.jsp
    • <%=request.getAttribute("writerId")%>거만 수정했다.
<div class="margin-top first">
	<h3 class="hidden">공지사항 내용</h3>
	<table class="table">
		<tbody>
			<tr>
				<th>제목</th>
				<td class="text-align-left text-indent text-strong text-orange" colspan="3"><%=request.getAttribute("title")%></td>
			</tr>
			<tr>
				<th>작성일</th>
				<td class="text-align-left text-indent" colspan="3"><%=request.getAttribute("regdate")%></td>
			</tr>
			<tr>
				<th>작성자</th>
				<td><%=request.getAttribute("writerId")%></td>
				<th>조회수</th>
				<td><%=request.getAttribute("hit")%></td>
			</tr>
			<tr>
				<th>첨부파일</th>
				<td colspan="3"><%=request.getAttribute("files")%></td>
			</tr>
			<tr class="content">
				<td colspan="4"><%=request.getAttribute("content")%></td>
			</tr>
		</tbody>
	</table>
</div>
  • list.jsp
    • <td class="title indent text-align-left"><a href="detail?id=<%=rs.getInt("id") %>"><%=rs.getString("title") %></a></td>로 수정. 원래는 href=”detail.jsp?~~”였다.



강의 56 - Model 데이터를 구조화하기

  • 엔티티
    • 아래 데이터들은 개념적으로 공지사항으로 묶을 수 있음.
    • 개념화된 데이터(개념적으로 말할 수 있는 데이터 집합).
    • 구조적인 데이터: 아래 데이터들을 묶어서 계층을 만들었다고 해서
    • 아래 데이터를 다음 한 줄로 수정: request.setAttribute("notice", notice);
request.setAttribute("title", title);
request.setAttribute("writerId", writerId);
request.setAttribute("regdate", regdate);
request.setAttribute("hit", hit);
request.setAttribute("files", files);
request.setAttribute("content", content);
  • Notice.java 생성(엔티티)
package com.newlecture.web.entity;

import java.util.Date;

public class Notice {
	private int id;
	private String title;
	private String writerId;
	private Date regdate;
	private String hit;
	private String files;
	private String content;

	public Notice() {
		// TODO Auto-generated constructor stub
	}

	public Notice(int id, String title, String writerId, Date regdate, String hit, String files, String content) {
		this.id = id;
		this.title = title;
		this.writerId = writerId;
		this.regdate = regdate;
		this.hit = hit;
		this.files = files;
		this.content = content;
	}

	// getter와 setter  생략

	@Override
	public String toString() {
		return "Notice [id=" + id + ", title=" + title + ", writerId=" + writerId + ", regdate=" + regdate + ", hit="
				+ hit + ", files=" + files + ", content=" + content + "]";
	}

}
  • NoticeDetailController.java 수정
    • 주석 부분을 대체
Notice notice=new Notice(id,title,writerId,regdate,hit,files,content);
request.setAttribute("n", notice);

// request.setAttribute("title", title);
// request.setAttribute("writerId", writerId);
// request.setAttribute("regdate", regdate);
// request.setAttribute("hit", hit);
// request.setAttribute("files", files);
// request.setAttribute("content", content);
  • detail.jsp 수정
    • 다 ${n.title}느낌으로 수정
<tbody>
<tr>
	<th>제목</th>
	<td class="text-align-left text-indent text-strong text-orange" colspan="3">${n.title}</td>
</tr>
<tr>
	<th>작성일</th>
	<td class="text-align-left text-indent" colspan="3">${n.regdate}</td>
</tr>
<tr>
	<th>작성자</th>
	<td>${n.writerId}</td>
	<th>조회수</th>
	<td>${n.hit}</td>
</tr>
<tr>
	<th>첨부파일</th>
	<td colspan="3">${n.files}</td>
</tr>
<tr class="content">
	<td colspan="4">${n.content}</td>
</tr>
</tbody>



강의 57 - 목록 페이지도 MVC model2로 수정하기

  • 전에 했던 것과 비슷. 복습용 느낌
  • NoticeListController.java 생성
    • Notice 게시글이니까 list에 담아서 전달
@WebServlet("/notice/list")
public class NoticeListController extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		List<Notice> list=new ArrayList<>();

		String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
		String sql = "select * from notice";

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
			Connection con = DriverManager.getConnection(url, "newlec", "1234");
			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery(sql);

			while(rs.next()) {
				int id=rs.getInt("id");
				String title=rs.getString("title");
				String writerId=rs.getString("writer_id");
				Date regdate=rs.getDate("regdate");
				String hit=rs.getString("hit");
				String files=rs.getString("files");
				String content=rs.getString("content");

				Notice notice=new Notice(id,title,writerId,regdate,hit,files,content);
				list.add(notice);
			}


			rs.close();
			st.close();
			con.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		request.setAttribute("list", list);
		request
		.getRequestDispatcher("/notice/list.jsp")
		.forward(request, response);
	}
}
  • list.jsp 수정
    • request.getAttribute("list")해서 request로 전달받은 list를 다시 지역변수에 담는 이유는 list의 각 notice를 꺼내서 쓰는 걸 반복해야 하기 때문이다.
    • pageContext.setAttribute("n", n);하는 이유: EL에서는 자바의 지역변수는 쓸 수 없다. page, request, session, application 저장소에 있는 것만 꺼내올 수 있다.

<%
	List<Notice> list=(List<Notice>) request.getAttribute("list");
	for(Notice n:list) {
		pageContext.setAttribute("n", n);
%>
<tr>
	<td>${n.id }</td>
	<td class="title indent text-align-left"><a href="detail?id=${n.id }">${n.title}</a></td>
	<td>${n.writerId }</td>
	<td>${n.regdate }</td>
	<td>${n.hit }</td>
</tr>
<%} %>



강의 58 - View 페이지 은닉하기

  • 뷰는 더이상 사용자가 직접 요청해서는 안되는 페이지다. 컨트롤러로 출력할 일이 있을 때만, 컨트롤러가 선택적으로 실행시킬 녀석임
    • WEB-INF 폴더는 외부에 서비스되지 않는 파일들(예로 설정 파일, 라이브러리, 코드 파일)이 여기에 있다.
    • 이 예제에서 admin, member, notice, student 폴더는 뷰 페이지를 가지고 있는 폴더다. 사용자가 직접 요청하는 것도 아니고, 컨트롤러에 의해서 선택적으로 사용될 파일들(동적 파일들)이 모여있다. 그래서 이러한 폴더들을 WEB-INF 폴더 속 view라는 폴더를 만들어서 보관해두자
    • 그럼 각 컨트롤러 파일에서 다음 부분을 수정하자.
      • NoticeListController 속 getRequestDispatcher("/WEB-INF/view/notice/list.jsp")로 수정.
      • 또 NoticeDetailController 속 getRequestDispatcher("/WEB-INF/view/notice/detail.jsp")로 수정



강의 59 - View(list.jsp)에서 반복문 제거하기

jstl 관련 오류 문제 해결

  • 강의대로 jstl 1.2버전 다운 받으니 문제였다.
  • 참고 사이트
  • 23년 1월 20일의 나는 톰켓 10.0버전을 쓰고 있다. 따라서 그에 맞게 jstl 2.0버전을 다운받았다.
  • 그 후 문제없이 작동했다.

깅의 내용

  • view에서 사용하는 제어구조
    • 자바의 반복문을 이용한 제어구조에서 태그를 이용한 제어구조로
  • JSTL 다운로드
    • 위의 태그를 사용할 수 있도록 하는 라이브러리 다운
    • 다운 후 WEB-INF 속 lib 폴더에 넣기
    • 무슨 의미인지 모르겠지만 태그 라이브러리를 쓰기 위해서 다음 설정을 해야 함.
      • <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
        • “prefix(접두사) c라는 녀석을 찍어보면 우리가 쓸 수 있는 태그들이 보일 겁니다.”
      • <c:forEach var="n" items="${list}">
        • items라는 속성: 저장소에 담겨있는 녀석을 뽑아서 items에 담으면, 반복할 때마다 items에서 하나하나씩 꺼내서 반복됨
        • var: items에서 꺼낸 녀석을 담는 녀석. n이라는 키워드로 pageContext에 담아주고, forEach가 반복해서 실행함.
<c:forEach var="n" items="${list}">
<tr>
	<td>${n.id }</td>
	<td class="title indent text-align-left"><a href="detail?id=${n.id }">${n.title}</a></td>
	<td>${n.writerId }</td>
	<td>${n.regdate }</td>
	<td>${n.hit }</td>
</tr>
</c:forEach>



강의 60 - Tag 라이브러리와 JSTL

  • jstl(java standard tag library)
    • 5가지 범주의 태그 라이브러리 지원
      • core: 제어의 행위를 담당하는 태그 라이브러리
      • formating: 날짜, 숫자 등을 출력할 때 formating
      • functions: 문자열 조작이 필요할 때 사용하는 함수 모아놓음
      • sql, xml
        • 이 두 개는 쓰지 않는게 바람직함. mvc 나오기 전에 뷰단에 로직들 처리할 때 썼음. 코드의 구성이 깨짐
        • sql문, xml 사용할 수 있게 해줌
  • jstl core
    • <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    • 접두사와 uri를 사용하는 이유: jasper가 jstl 태그를 이해할 수 있어야 하기 때문에
      • 접두사 c를 붙이면 이는 곧 uri가 제공하는 거라고 설명하게 됨. 따라서 uri통해서 이 태그 의미를 이해할 수 있음
  • 태그 라이브러리 원리
    • 태그 라이브러리 만들 때는 TagSupport를 상속받아서 클래스를 만들면 됨
    • 또한 tag library descriptor, 즉 태그 라이브러리를 만들고 쓰기 위해서 필요한 설명자(.tld 파일)가 필요
      • uri: 태그를 구분해주는 식별자(이름이 관리되고 있는 도메인 이름은 중복되지 않음) 역할이다. 만들려고 하는 태그 이름은 다른 사람의 태그와 중복될 수 있기 때문이다.
    • 그럼 태그 쓸 때 중복 피하도록 uri를 붙이자니 너무 기니까 접두사를 정해서 태그 앞에 붙임.
    • 그럼 jasper가 이것이 태그 라이브러러임을 알게 됨.



강의 61 - 중간 정리

  • 서블릿은 자바 웹 프로그램을 만들 때 쓴다. 웹에서 입력(request)과 출력(response)을 적절히 설정한 후 웹 문서를 출력했다.
  • html 문서를 출력하다보니 문제가 많아서 문서 기반(jsp)의 코드블록을 사용했다.
  • 하지만 이 또한 코드가 스파게티처럼 되는 문제가 있어서 MVC 패턴이 도입되었다.
  • mvc 패턴을 쓸 때 뷰에서 코드 블록이 꼭 필요한 경우, EL과 JSTL을 사용한다.



강의 62 - forEach의 속성 사용하기

  • begin과 end
    • 반복되는 forEach에서 범위를 한정할 수 있음
    • forEach로 반복해서 출력할 때, 출력되는 순서에 따라 0,1,2…의 인덱스가 있다고 보면 된다.
    • 이 때 <c:forEach var="n" items="${list}" begin="2" end="3"> 처럼 사용하면 2~3인덱스 내용만 출력된다.
  • varStatus
    • forEach가 반복하면서 index를 사용하는데, 이런 index와 같은 상태값을 얻을 수 있음
    • 상태값을 알기 위한 변수명만 설정하면 된다.
    • <c:forEach var="n" items="${list}" varStatus="st">처럼 쓰면 된다.
    • st가 가지고 있는 상태값(사용은 ${st.index} 느낌)
      • current(현재 사용 중인 객체(var=”n”에서 n))
      • index: 0부터 시작되서 반복할 때마다 느는 값
      • count: 반복되고 있는 횟수
      • first: 현재 반복됙 있는게 첫 번째 아이템이면 true, 아니면 false를 반환
      • last: 현재 반복됙 있는게 마지막 아이템이면 true, 아니면 false를 반환
      • begin, end: 우리가 설정한 begin, end 값
      • step: 만약 이걸 2로 놓으면 2씩 증가해서 반복되게 됨



강의 69 - 기업형으로 레이어를 나누는 이유와 설명

  • 서블릿만으로 웹 서비스 만듦
  • -> 서블릿을 분리(컨트롤러/뷰)
    • 컨트롤러(servlet): 요청을 처리하고 출력할 데이터인 모델을 만듦
    • 뷰(jsp): 모델을 받아서 출력만 함
  • -> 컨트롤러에서 업무 서비스를 분리
    • 컨트롤러: 요청 처리하고, 모델은 넘겨받아서 뷰한테 줌
    • 업무 서비스: 요청 받은 후 모델을 만들어서 줌
    • 미숙한 개발자는 컨트롤러를, 숙련된 개발자는 업무 서비스를
  • -> 업무 서비스에서 DAO(데이터 서비스)를 분리
    • 업무 서비스: entity(개념화된 데이터)를 넘겨받아서 model을 만듦
    • DAO: entity(개념화된 데이터)를 만듦