Today
- 성낙현의 JSP 자바 웹 프로그래밍
- CHAP 12 - 서블릿(Servlet)
- 선생님 TIP : 서블릿은 개념을 이해하는게 중요!, 스프링을 쓰기 위해 제대로 공부 필요
- 서블릿 이란?
: 서버 단에서 클라이언트의 요청을 받아 처리 후 응답하는 기술
(Java 문서에 HTML 작성하는 방식)
- 서블릿의 특징
1) 클라이언트 요청에 대해 동적으로 작동하는 WEP API 컨포넌트
2) MVC모델에서 컨트롤러 역할을 함
3) 모든 메서드는 스레드로 동작
4) jakarta.servlet.http 패키지의 HttpServlet 클래스를 상속받음
- 서블릿 컨테이너
서블릿을 관리하는 주체로,
서블릿의 수명주기를 관리하고, 클라이언트 요청을 받아서 응답을 보낼 수 있도록 통신을 지원해 줍니다.
1) 통신지원
: 클라이언트와 통신을 위한 복잡한 과정을 간단히 해주는 API를 제공
2) 수명주기 관리
: 서블릿을 인스턴스화한 후 초기화, 요청에 맞는 메서드를 호출 및 응답 후가비지 컬렉터를 통해 객체 소멸
3) 멀티스레딩 관리
: 멀티스레드 방식으로 여러 요청을 동시에 처리할 수 있도록 관리
4) 선언적인 보안 관리 및 JSP 지원
: 컨테이너가 보안 기능을 지원하므로 별도로 구현 필요 X
* 사용할 컨테이너 -> 톰캣 / 서블릿은 톰캣서버에 맞게 만들어짐 따라서 톰캣 또는 톰캣과 유사한 WAS에서만 돌아감.
- 서블릿의 동작방식
1) 클라이언트의 요청을 받음
2) 요청을 분석 후 처리할 서블릿 찾음
3) 찾은 서블릿을 통해 비즈니스 로직을 호출
4) 모델(서비스+DAO)를 통해 결과값을 받아
5) request 또는 session 영역에 저장후 출력할 적절한 뷰 선택
6) 최종적으로 선택된 뷰(JSP페이지)에 결과값을 출력하여 클라이언트에게 응답
* 관점에 따라
M : 서비스+DAO / V : JSP / C : 서블릿
M : DAO / V : JSP / C : 서블릿+서비스로 보기도 함.
- 서블릿 작성 규칙
1) jakarta.servlet, jakarta.servlet.http, java.io 패키지를 임포트 함
2) 서블릿 클래서는 public으로 선언해야함 + HttpServlet을 상속 받아야 함
3) 사용자 요청 처리용 doGet(), doPost() 메서드를 반드시 오버라이딩 해야함
4) doGet(), doPost() 메서드는 ServletException, IOException 예외를 throws 하도록 선언해야함.
5) doGet(), doPost() 메서드는
HttpServletRequest와 HttpServletResponse를 매개변수로 받아 사용해야 합니다.
[작성 예시]
package 패키지명;
imoprt java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
publec class 서블릿클래스명 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse reps)
throws ServletException, IOException {
//메서드 실행부
}
}
- 서블릿 매핑
클라이언트의 요청을 받은 후 요청을 처리할 서블릿을 컨테이너가 찾을 수 있도록
요청명과 서블릿을 연결해주는 과정을 매핑이라고 합니다.
매핑 방법은 아래의 2가지 방법이 있습니다.
1) web.xml에 기술하는 방법
<servlet>
<servlet-name>서블릿명</servlet-name>
<servlet-class>패키지.서블릿클래스명</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>서블릿명</servlet-name>
<url-pattern>클라이언트 요청 URL</url-pattern>
</servlet-mapping>
* 클라이언트 요청 URL의 경우 컨텍스트르 루트를 제외하고 '/'로 시작하는 경로를 사용
* 클라이언트 요청 URL에서는 *로 와일드 카드를 이용할 수 있습니다.
* <servlet>과 <servlet-mapping>은 나란히 위치하지 않더라도 문서 내 어딘가 둘다 필요
2) @WebServlet 어노테이션으로 매핑 - 서블릿 클래스에 직접 매핑
@WebServlet(클라이언트 요청 URL)
publec class 서블릿클래스명 extends HttpServlet {
@Override
protected void doGet ...생략
* 클라이언트 요청 URL의 경우 컨텍스트르 루트를 제외하고 '/'로 시작하는 경로를 사용
* 클라이언트 요청 URL에서는 *로 와일드 카드를 이용할 수 있습니다.
* web.xml 방식과 @WebServlet 방식이 중복되면, @WebServlet 방식이 우선 적용 됩니다.
- 서블릿의 수명주기 메서드
서블릿 컨테이너는 서블릿 객체 생성 후
각 단계마다 자동으로 특정 메서드를 호출하여 해당 단계에 필요한 기능을 수행하고,
이 때 사용되는 메서드를 서블릿의 수명주기 메서드라 합니다.
1) 컨테이너가 서블릿 객체 생성
2) @PostConstruct 호출
- 전처리를 작업 진행
- 객체 생성 직후, init() 사용 전 호출
- 메서드명은 사용자가 정하고, @PostConstruct 어노테이션 달아주면 됨
3) init()
- 서블릿의 초기화 작업 진행
- 최초 요청 시 딱 한번만 실행 됨
4) service()
- 클라이언트의 요청을 처리하기 위해 호출
- 요청 전송방식이 post면 doPost(), get이면 doGet()메서드를 호출 함
5) destroy()
- 일정 시간이 지난 후 GC가 실행될 때 또는 서버가 종료될 때 호출됩니다.
6) @PreDestroy 호출
- destroy()메서드 실행 후 서블릿 객체를 제거하는 과정에서 호출 됨.
메서드명은 사용자가 정하고, @PreDestroy 어노테이션 달아주면 됨
- 파일 업로드
톰캣9 까지는 cos.jar / commons-fileupload.jar, commons.io.jar 등의 라이브러리를 이용하였으나
톰캣10 버전부터는 javax -> jakarta가 되면서 해당 라이브러리 사용 불가해짐
=> 톰캣에서 제공하는 PartAPI 이용 필요
- 파일 업로드 기능 구현
1) 화면(form)구현
<form method="post" enctype="mulipart/form-data"
action="업로드 파일 처리 경로" name="frm" id="frm">
<input type="file" name="file" id="file">
<input type="submit" value="제출" name="submit" id="submit">
</form>
- form의 method 속성은 반드시 "post"여야 함
- form의 enctype 속성은 반드시 "multipart/form-data"로 설정해야함.
- input의 type 속성은 "file"이어야 함.
2) DB에 관련 테이블을 생성 합니다.
3) 위 만든 테이블에 맞는 DTO를 작성합니다.
4) 파일업로드용 DAO를 작성합니다.(INSERT문 작성용)
5) 파일 업로드용 유틸리티 클래스를 작성합니다.
6) 파일 업로드 서블릿 클래스를 작성합니다.
* 실제 코드는 아래에서 확인
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
let title = frm.querySelector(#title)
if(frm.title.value == "") {
alert("제목을 입력하세요");
frm.title.focus();
return;
}
if(frm.file.value == "") {
alert("파일을 선택하세요");
return;
}
}
</script>
</head>
<body>
<h2>파일 업로드</h2>
<span>${ errMsg }</span>
<form name="frm" id="frm" action="FileUpload.do"
onsubmit="checkForm(this.form)" method="post"
enctype="multipart/form-data">
<label for="title">
제목
<input type="text" name="title" id="title" value="" maxlength="100">
</label><br>
<label>카테고리</label>
<label for="c01">사진 <input type="checkbox" name="category" id="c01" value="사진"></label>
<label for="c02">그림 <input type="checkbox" name="category" id="c02" value="그림"></label>
<label for="c03">음악 <input type="checkbox" name="category" id="c03" value="음악"></label>
<label for="c04">악기 <input type="checkbox" name="category" id="c05" value="악기"></label>
<br>
<label for="file">첨부파일<input type="file" name="file" id="file"></label>
<br><hr><br>
<button type="submit">제출</button>
</form>
</body>
</html>
CREATE OR REPLACE TABLE tbl_files(
idx INT not null AUTO_INCREMENT,
title VARCHAR(200) COMMENT '파일설명',
category VARCHAR(200) NULL COMMENT '카테고리' COLLATE 'utf8mb4_general_ci',
orgFile VARCHAR(200) null COMMENT '파일원본이름' COLLATE 'utf8mb4_general_ci',
saveFile VARCHAR(200) null COMMENT '저장파일이름' COLLATE 'utf8mb4_general_ci',
reg_date DATETIME NULL DEFAULT current_timestamp() COMMENT '등록일',
PRIMARY KEY (idx) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;
package fileupload;
public class FileDTO {
public FileDTO() {}
private String idx;
private String title;
private String category;
private String orgFile;
private String saveFile;
private String reg_date;
public String getIdx() {
return idx;
}
public void setIdx(String idx) {
this.idx = idx;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getOrgFile() {
return orgFile;
}
public void setOrgFile(String orgFile) {
this.orgFile = orgFile;
}
public String getSaveFile() {
return saveFile;
}
public void setSaveFile(String saveFile) {
this.saveFile = saveFile;
}
public String getReg_date() {
return reg_date;
}
public void setReg_date(String reg_date) {
this.reg_date = reg_date;
}
}
package fileupload;
import common.ConnectPool;
public class FileDAO extends ConnectPool {
public FileDAO() {}
public int registFile(FileDTO dto) {
int result = 0;
try {
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO tbl_files(title, category, orgfile, savefile)");
sb.append(" VALUES (?, ?, ?, ?)");
psmt = conn.prepareStatement(sb.toString());
psmt.setString(1, dto.getTitle());
psmt.setString(2, dto.getCategory());
psmt.setString(3, dto.getOrgFile());
psmt.setString(4, dto.getSaveFile());
result = psmt.executeUpdate();
} catch(Exception e) {
System.out.println("FILE 등록 에러 : " + e.getMessage());
e.printStackTrace();
}
return result;
}
}
package fileupload;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;
public class FileUtil {
public static String uploadFile(HttpServletRequest req, String dir)
throws ServletException, IOException {
Part part = req.getPart("file");
String pHeader = part.getHeader("content-disposition");
System.out.println("pHeader : " + pHeader );
String[] attPartHeader = pHeader.split("filename=");
String orgFileName = attPartHeader[1].trim().replace("\"", "");
if(!orgFileName.isEmpty()) {
part.write(dir + File.separator + orgFileName);
}
return orgFileName;
}
public static String renameFile (String dir, String fileName) {
String ext = fileName.substring(fileName.lastIndexOf("."));
String now = new SimpleDateFormat("yyyyMMdd_HmsS").format(new Date());
String newFileName = now+ext;
File oldFile = new File(dir + File.separator + fileName);
File newFile = new File(dir + File.separator + newFileName);
oldFile.renameTo(newFile);
return newFileName;
}
}
package fileupload;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
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("/FileUpload.do")
@MultipartConfig(
maxFileSize = 1024*1024 * 1,
maxRequestSize =1024*1024 * 10
)
public class FileUpload extends HttpServlet {
private static final long serialVersionUID = 1L;
public FileUpload() {
super();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
String directory = getServletContext().getRealPath("/Upload");
String orgFileName = FileUtil.uploadFile(request, directory);
String savedFileName = FileUtil.renameFile(directory, orgFileName);
registFile(request, orgFileName, savedFileName);
response.sendRedirect("./fileList.jsp");
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
request.setAttribute("errMsg", "파일 업로드에 실패");
request.getRequestDispatcher("file.jsp").forward(request, response);
}
}
private void registFile(HttpServletRequest request, String orgFileName, String sFileName) {
String title = request.getParameter("title");
String[] categoriesArr = request.getParameterValues("category");
StringBuffer cb = new StringBuffer();
if (categoriesArr == null) {
cb.append("선택 항목 없음");
} else {
for(String e : categoriesArr) {
cb.append(e + ", ");
}
}
System.out.println("파일 외 폼 값 : " + title + "\n" + cb.toString());
FileDTO dto = new FileDTO();
dto.setTitle(title);
dto.setCategory(cb.toString());
dto.setOrgFile(orgFileName);
dto.setSaveFile(sFileName);
FileDAO dao = new FileDAO();
dao.registFile(dto);
dao.close();
}
}
Review
- 파일업로드 관련하여 DB에 데이터가 올라가긴 하는데 실제 업로드 폴더에 파일은 저장되지 않는 상태임 - 추가 작업 월요일 진행 예정
TO DO