티스토리 뷰
🤨스프링은 파일 업로드/다운로드를 어떻게 할까?
스프링에서는 파일 업로드와 다운로드를 위한 방법이 여러 가지 존재한다. 파일 업로드/다운로드만큼은 크게 JSP에서 프로젝트를 할 때와 차이를 느끼진 못한 것 같다.(단지 방법이 더 다양한 것 같다는 것?) 스프링에서 파일 업로드와 다운로드를 하는 법을 알아보자.
1) 의존성 주입
스프링에서 업로드를 사용하기 위해서는 다음과 같은 의존성을 추가해야 한다. pom.xml을 열어 다음 의존성을 추가하자.
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2) 빈 설정하기
Dispatcher-servlet (servlet-context.xml)에 CommonsMultipartResolver객체를 생성해야 한다.
💡 내부적으로 해당 id를 값을 사용하므로, id는 반드시 multipartResolver로 줘야 한다.
💡 maxUploadSize는 최대 용량이며, 필자는 1mb를 기준으로 작성하였다.
💡 파일을 요청 시 반드시 form enctype 타입을 "multipart/form-data"로 지정해줘야 한다.
<beans:bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="1048576"></beans:property>
</beans:bean>
그 외 속성들에 대한 항목은 아래와 같다.
maxInMemorySize | int | 디스크에 임시 파일을 생성하기 전에 메모리에 보관할수있는 최대 바이트크기. 기본 값은 10240 바이트이다. |
defaultEncoding | String | 디폴트로 HttpServletRequest.setCharacterEncoding()에서 인코딩 타입 사용. 아무 값도 없을 경우 ISO-8859-1을 사용. |
uploadTempDir | Resource | 임시디렉터리 지정 |
3) Controller 만들기
기본적인 설정은 끝났다. 파일과 함께 업로드 요청이 왔을 때, 파일을 저장하기 위해 준비할 것은 두 가지이다.
- Client에서 받은 파일
- 해당 파일을 저장할 위치
두 가지가 준비되었다면, 아래의 방식으로 업로드를 구현할 수 있다.
※ 필자는 /upload라는 폴더가 있기 때문에 오류가 생기지 않지만, 없다면 해당 경로에 폴더를 생성하고 진행하자.
File의 mkdir 메서드를 통해 폴더가 존재하지 않는다면 쉽게 생성할 수 있으니 참고하자.
a) MutipartHttpServletRequest 객체를 이용
해당 객체는 이름처럼 MutipartRequest와 HttpServletRequest의 혼종이다. 아래와 같은 방식으로 저장할 수 있다.
@Controller
public class UploadController{
@RequestMapping("/Upload.do")
public String upload(MultipartHttpServletRequest mhsr) throws IllegalStateException, IOException {
//물리적인 저장 경로 설정
String path = mhsr.getServletContext().getRealPath("/upload");
//업로드할 파일명
MultipartFile upload = mhsr.getFile("upload");
String name = upload.getOriginalFilename();
//파일을 저장할 위치 셋업
File dest = new File(path+File.separator+name);
//업로드 처리!
upload.transferTo(dest);
return "uploadCompletePage";
}
}
b) MultipartFile 이용하기
MultipartFile을 이용해서도 다음과 같이 구현할 수 있다.
@Controller
public class UploadController{
@RequestMapping("/Upload.do")
public String upload(@RequestParam Map map,
@RequestParam MultipartFile upload,
HttpServletRequest req) throws IllegalStateException, IOException {
//서버의 물리적 경로
String path = req.getServletContext().getRealPath("/upload");
//객체생성
File dest = new File(path+File.separator+upload.getOriginalFilename());
//업로드 처리!
upload.transferTo(dest);
return "uploadCompletePage";
}
}
💻실습하기
업로드 로직에 대한 실습을 하고 싶다면 아래의 정보를 참고하자.
필자는 실습을 위해 위 정보를 토대로 아래와 같이 뷰와 페이지를 구성하였다.
일부 클래스는 다운로드와 연동되어있다.
View 페이지 명 | Upload.jsp , UploadComplete.jsp |
Controller 명 | UploadController |
📕Upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 위 3개의 메타 태그는 *반드시* head 태그의 처음에 와야합니다; 어떤 다른 콘텐츠들은 반드시 이 태그들 *다음에* 와야 합니다 -->
<title>upload.jsp</title>
<!-- 부트스트랩 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
<!-- 실제 내용 시작 -->
<div class="container">
<div class="page-header">
<h1>
스프링 <small>핸들러 매핑</small>
</h1>
</div>
<fieldset>
<legend>파일업로드 폼</legend>
<a href="<c:url value='/FileUpDown/List.do'/>">파일 목록</a>
<form action="<c:url value='/FileUpDown/Upload.do'/>" method="post"
enctype="multipart/form-data">
<table cellspacing="1" bgcolor="gray">
<tr bgcolor="white">
<td>올린이</td>
<td><input type="text" name="writer" value="${param.writer }" /></td>
</tr>
<tr bgcolor="white">
<td>제목</td>
<td><input type="text" name="title" size="50" value="${param.title }" /></td>
</tr>
<tr bgcolor="white">
<td>첨부파일</td>
<td><input type="file" name="upload" size="30" /></td>
</tr>
<tr bgcolor="white" align="center">
<td colspan="2"><input type="submit" value="업로드" /></td>
</tr>
</table>
</form>
<span style="color: red; font-size: 1.8em">${maxError} ${empty param.error ?'':'파일 용량 초과'}</span>
</fieldset>
</div>
</body>
</html>
📕UploadComplete.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 위 3개의 메타 태그는 *반드시* head 태그의 처음에 와야합니다; 어떤 다른 콘텐츠들은 반드시 이 태그들 *다음에* 와야 합니다 -->
<title>upload.jsp</title>
<!-- 부트스트랩 -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
<!-- 실제 내용 시작 -->
<div class="container">
<div class="page-header">
<h1>
스프링 <small>핸들러 매핑</small>
</h1>
</div>
<fieldset>
<legend>파일 업로드 결과</legend>
<h2>디폴트(기본) 핸들러 매핑</h2>
<li>올린이 : ${param.writer }</li>
<li>제목 : ${param.title }</li>
<li>원본파일명 : ${original}</li>
<li>실제 서버에 저장된 이름 : ${real}</li>
<li>컨텐츠 타입 :${type }</li>
<li>컨텐츠 크기 :${size} KB</li>
</ul>
</fieldset>
</div>
</body>
</html>
📘UploadController
package com.unkown.test.upload;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
@Controller
public class UploadController {
@RequestMapping("/FileUpDown/Upload.do")
public String upload(UploadCommand cmd,HttpServletRequest req) throws IllegalStateException, IOException {
//서버의 물리적 경로
String path = req.getServletContext().getRealPath("/upload");
//MultipartHttpServletRequest는 통합클래스이므로 getter를 통해서 MultipartFile 타입을 가져오면됨
// 가져올때는 jsp에서 보낸 name명과 같게 파라미터를 주면됨
//jsp에서 받은 name명과 파싱
MultipartFile upload = cmd.getUpload();
//객채 생성전 중복체크
//중복된 이름이있을 시 변경
String rename = FileUpDownUtils.getNewFileName(path, upload.getOriginalFilename());
//객체생성
File dest = new File(path+File.separator+rename);
//업로드 처리!
upload.transferTo(dest);
req.setAttribute("original", upload.getOriginalFilename());
req.setAttribute("real", rename);
req.setAttribute("type", upload.getContentType());
req.setAttribute("size", (int)Math.ceil(upload.getSize()/1024.0));
return "blog/upload/UploadComplete";
}
@RequestMapping("/FileUpDown/List.do")
public String list(HttpServletRequest req, Model model) {
System.out.println(req.getServletContext().getRealPath("/upload"));
File dir = new File(req.getServletContext().getRealPath("/upload"));
List<Map> lists = new Vector();
for(File atom : dir.listFiles()) {
Map map = new HashMap();
map.put("name",atom.getName());
map.put("size",(int)(Math.ceil(atom.length()/1024.0)));
System.out.println("이름 : " + atom.getName());
System.out.println("총 사이즈 : " + atom.length()/1024.0);
lists.add(map);
}
//ways1
model.addAttribute("lists",lists);
//ways 2
model.addAttribute("datas",dir.listFiles());
System.out.println("list files 전송함!");
return "blog/upload/List";
}
}
+다운로드
다운로드는 JSP에서 사용한 방법과 동일하다. 해당 경로의 파일을 보내 주면 된다.
💻실습하기
View 페이지 명 List.jsp
Controller 명 DownloadController, FileUpDownUtils
📘DownloadController
package com.unkown.test.upload;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class DownloadController {
//1] 만들어 쓸때
/*
@RequestMapping("/FileUpDown/Download.do")
public void download(HttpServletRequest req,HttpServletResponse resp) {
FileUpDownUtils.download(req, resp, req.getParameter("filename"),"/upload");
}
*/
//2] Spring API 사용시
//BeanName ViewResolver 가 우선순위가 되야 이게 먹음
//해당 bean이 있으면 이쪽으로 파싱함
public String download(Model model,HttpServletRequest req) {
/*
* 컨트롤러 메소드에서는 다운로드할 파일을 모델 계엘에만 전달하면, 나머지는 Download뷰든 딴데든 다른데서알아서함
* 즉, 얘는 모델계열에 파일만 제대로 박아주면됨
*/
//파일 객체 생성
File file = new File(req.getServletContext().getRealPath("/upload") + File.separator+req.getParameter("filename"));
model.addAttribute("file", file);
return "downloadView";
}
}
📘FileUpDownUtils
package com.unkown.test.upload;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FileUpDownUtils {
// [파일 이름 중복 체크용 메소드]
public static String getNewFileName(String path, String fileName) {
// fileName=원격.txt
File file = new File(path + File.separator + fileName);
if (!file.exists()) {
return fileName;
} else {
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).trim();
String fileNameExcludeExt = fileName.substring(0, fileName.lastIndexOf("."));
String newFileName;
while (true) {
newFileName = "";
if (fileNameExcludeExt.indexOf("_") != -1) {// 파일명에 _가 포함됨
String[] arrFiles = fileNameExcludeExt.split("_");
String lastName = arrFiles[arrFiles.length - 1];
try {
int index = Integer.parseInt(lastName);
for (int i = 0; i < arrFiles.length; i++) {
if (i != arrFiles.length - 1)
newFileName += arrFiles[i] + "_";
else
newFileName += String.valueOf(index + 1);
}
newFileName += "." + ext;
} catch (Exception e) {
newFileName += fileNameExcludeExt + "_1." + ext;
}
} else {// _가 없음
newFileName += fileNameExcludeExt + "_1." + ext;
}
File f = new File(path + File.separator + newFileName);
if (f.exists()) {
fileNameExcludeExt = newFileName.substring(0, newFileName.lastIndexOf("."));
continue;
} else
break;
} //////////// while
return newFileName;
}
}/////////////////////
//파일 다운로드 로직]
public static void download(HttpServletRequest request, HttpServletResponse response, String filename, String uploadDir) {
try {
//2]파일이 저장된 서버의 물리적 경로 얻기]
String saveDirectory= request.getServletContext().getRealPath(uploadDir);
//3]파일 크기를 얻기 위한 파일 객체 생성
// -다운로드시 프로그래스바를 표시하기 위함.
File file = new File(saveDirectory+File.separator+filename);
long length=file.length();
/* 다운로드를 위한 응답 헤더 설정 */
//4-1]웹브라우저가 인식하지 못하는 컨텐츠타입(MIME)타입 설정.
response.setContentType("application/octet-stream");
//4-2]다운로드시 프로그래스바를 표시하기 위한 컨텐츠 길이 설정
response.setContentLength((int) length);
//4-3] 응답헤더명: Content-Disposition
// 응답헤더명에 따른 값:attachment;filename=파일명
// setHeader(응답헤더명,헤더값)으로 추가
//브라우저 종류에 따라 한글 파일명이 깨지는 경우가 있음으로
//브라우저별 인코딩 방식을 달리 하자(파일명을 인코딩)
boolean isIe =request.getHeader("user-agent").toUpperCase().indexOf("MSIE") !=-1 ||
request.getHeader("user-agent").indexOf("11.0") != -1 ||
request.getHeader("user-agent").toUpperCase().indexOf("EDGE") !=-1;
if(isIe){//인터넷 익스플로러 혹은 엣지
filename = URLEncoder.encode(filename,"UTF-8");
}
else{//기타 브라우저
//new String(byte[] bytes, String charset)사용
//1]파일명을 byte형 배열로 변환
//2]String클래스의 생성자에 변환한 배열과
// charset는 8859_1을 지정
filename = new String(filename.getBytes("UTF-8"),"8859_1");
}
response.setHeader("Content-Disposition","attachment;filename="+filename);
/* IO작업을 통해서 서버에 있는 파일을 웹브라우저에 바로 출력*/
/*
데이타 소스:파일 -노드 스트림:FileInputStream
필터 스트림:BufferedInputStream
데이타 목적지:웹브라우저-노드 스트림:response.getOutputStream()
필터 스트림:BufferedOutputStream
*/
//5]서버에 있는 파일에 연결할 입력 스트림 생성
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(file));
//6]웹브라우저에 연결할 출력 스트림 생성
BufferedOutputStream bos =
new BufferedOutputStream(response.getOutputStream());
//7]bis로 읽고 bos로 웹브라우저에 출력
int data;
while((data=bis.read()) !=-1){
bos.write(data);
bos.flush();
}
//8]스트림 닫기
bis.close();
bos.close();
}
catch(Exception e) {e.printStackTrace();}
}/////////////download
}
📕List
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 위 3개의 메타 태그는 *반드시* head 태그의 처음에 와야합니다; 어떤 다른 콘텐츠들은 반드시 이 태그들 *다음에* 와야 합니다 -->
<title>upload.jsp</title>
<!-- 부트스트랩 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
<!-- 실제 내용 시작 -->
<div class="container">
<div class="page-header">
<h1>스프링 <small>핸들러 매핑</small></h1>
</div>
<fieldset>
<legend>업로드된 파일목록</legend>
<h2>컬렉션일때</h2>
<c:forEach var="item" items="${lists}">
<ul style= "list-style:decimal;">
<li>파일명 : <a href='<c:url value = "/FileUpDown/Download.do?filename=${item.name }"/>'> ${item.name } </a></li>
<li>파일 크기 : ${ item.size} KB</li>
</ul>
</c:forEach>
<h2>File[]일때 </h2>
<c:forEach var="item" items="${datas }">
<ul style= "list-style:decimal;">
<li>파일명 : <a href='<c:url value = "/FileUpDown/Download.do?filename=${item.name }"/>'> ${item.name } </a> </li>
<li>파일 크기 : <fmt:parseNumber integerOnly="true" value ="${item.length()/1024 +(1-item.length()/1024%1)%1}" /> KB</li>
</ul>
</c:forEach>
</fieldset>
</div>
</body>
</html>
마치며
파일 업로드와 다운로드는 사실 경로와의 싸움인 것 같다. 경로가 절반 나머지는 해당 메서드 사용이 끝!
📃 References
- https://velog.io/@eesiwoo/Spring-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C%EC%99%80-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C
- https://m.blog.naver.com/javaking75/140203390797
- KOSMO 최철환 강사님
'공부 > Spring' 카테고리의 다른 글
[Spring] 쉽게 적용하는 Mybatis (+ JDNI 연결) (0) | 2021.12.10 |
---|---|
[Spring] 쉽게알아가는 REST API , RESTful API (0) | 2021.12.07 |
[Spring] Annotation - @value (1) | 2021.12.07 |
- Total
- Today
- Yesterday
- DP
- 그래프 탐색
- 아기상어나쁜상어
- looker core
- 프로그래머스
- db
- dml
- 9019
- 브루트포스
- 재귀
- 하루 회고
- Database
- java
- 자바
- value annotation
- 파이썬
- Python
- 아기상어미워
- 유클리드-호제법
- looker instance 접속
- 플루이드 와샬
- JNDI연동
- DFS
- 백준
- 프로그래머스 문제정복
- 카카오
- Spring
- 코딩테스트
- 실패일기
- BFS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |