티스토리 뷰

🤨스프링은 파일 업로드/다운로드를 어떻게 할까?

 


 스프링에서는 파일 업로드와 다운로드를 위한 방법이 여러 가지 존재한다. 파일 업로드/다운로드만큼은 크게 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 만들기

기본적인 설정은 끝났다. 파일과 함께 업로드 요청이 왔을 때, 파일을 저장하기 위해 준비할 것은 두 가지이다.

  1. Client에서 받은 파일
  2. 해당 파일을 저장할 위치

두 가지가 준비되었다면, 아래의 방식으로 업로드를 구현할 수 있다.

※ 필자는 /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


 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 31
글 보관함