관리 메뉴

거니의 velog

(7) 스프링에서 지원하는 여러 가지 기능 1 본문

Java_Spring Framework part2

(7) 스프링에서 지원하는 여러 가지 기능 1

Unlimited00 2023. 11. 15. 20:03

* 스프링 프레임워크는 오픈 소스이기 때문에 상당히 많은 기능을 지원한다. 이번에는 여러 오픈 소스 라이브러리를 사용해 다중 파일 업로드, 썸네일 이미지, 이메일, 인터셉터 등 여러 가지 기능을 구현해 보자.


1. 다중 파일 업로드하기

* 스프링의 CommonsMultipartResolver 클래스를 이용하면 여러 개의 파일을 한꺼번에 업로드할 수 있다. 다음 표에 CommonsMultipartResolver 클래스의 여러 가지 속성을 정리했다.

< CommonsMultipartResolver 클래스 속성 >

속성 설명
maxUploadSize 최대로 업로드가 가능한 파일의 크기를 설정한다.
maxInMemorySize 디스크에 임시 파일을 생성하기 전 메모리에 보관할 수 있는 최대 바이트 크기를 설정한다.
defaultEncoding 전달되는 매개변수의 인코딩을 설정한다.

1. 다음과 같이 파일을 준비한다.

2. 파일 업로드에 필요한 라이브러리를 설치하도록 pom.xml을 작성한다.

        <!-- 다중 파일 업로드  -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.1</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.4</version>
        </dependency>

3. servlet-context.xml 파일에서 CommonsMultipartResolver 클래스를 multipartResolver 빈으로 설정한다.

    <!-- 멀티파트 리졸버 -->
    <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <beans:property name="maxUploadSize" value="52428800" />
        <beans:property name="maxInMemorySize" value="1000000" />
        <beans:property name="defaultEncoding" value="utf-8" />
    </beans:bean>

4. 파일 업로드 및 다운로드 기능 컨트롤러를 구현하기 위한 자바 파일들을 준비한다.

5. 먼저 파일 다운로드 컨트롤러인 FileDownloadController 클래스를 다음과 같이 작성한다. 버퍼 기능을 이용해 빠르게 브라우저로 이미지 파일을 전송한다.

package com.myspring.pro28.ex01;

import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class FileDownloadController {
	
	// 파일 저장 위치를 지정한다.
	private static String CURR_IMAGE_REPO_PATH = "c:\\spring\\image_repo";

	@RequestMapping("/download")
	public void download(@RequestParam("imageFileName") String imageFileName, // 다운로드 할 이미지 파일 이름을 전달한다.
						HttpServletResponse response) throws Exception {
		OutputStream out = response.getOutputStream();
		String downFile = CURR_IMAGE_REPO_PATH + "\\" + imageFileName;
		File file = new File(downFile); // 다운로드 할 파일 객체를 생성한다.

		response.setHeader("Cache-Control", "no-cache");
		response.addHeader("Content-disposition", "attachment; fileName=" + imageFileName); // 헤더에 파일 이름을 설정한다.
		FileInputStream in = new FileInputStream(file);
		byte[] buffer = new byte[1024 * 8];
		while (true) {
			int count = in.read(buffer); // 버퍼에 읽어들인 문자개수
			if (count == -1) // 버퍼의 마지막에 도달했는지 체크
				break;
			out.write(buffer, 0, count);
		} // 버퍼를 이용해 한 번에 8Kbyte씩 브라우저로 전송한다.
		in.close();
		out.close();
	}

}

6. 파일 업로드 컨트롤러를 구현한다. 파일과 매개변수를 같이 전송하므로 Map을 사용한다. 매개변수의 name/value를 다시 map의 key/value로 저장한다. 파일 이름을 ArrayList에 저장한 후 다시 map에 key/value로 저장한다.

package com.myspring.pro28.ex01;

import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class FileUploadController {
	
	private static final String CURR_IMAGE_REPO_PATH = "c:\\spring\\image_repo";

	@RequestMapping(value = "/form")
	public String form() {
		return "uploadForm";
	} // 업로드 창인 uploadForm.jsp를 반환한다.

	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	public ModelAndView upload(MultipartHttpServletRequest multipartRequest, HttpServletResponse response)
			throws Exception {
		multipartRequest.setCharacterEncoding("utf-8");
		Map map = new HashMap(); // 매개변수 정보와 파일 정보를 저장할 Map을 생성한다.
		Enumeration enu = multipartRequest.getParameterNames();
		while (enu.hasMoreElements()) {
			String name = (String) enu.nextElement();
			String value = multipartRequest.getParameter(name);
			// System.out.println(name+", "+value);
			map.put(name, value);
		} // 전송된 매개변수 값을 key/value로 map에 저장한다.

		List fileList = fileProcess(multipartRequest);
		map.put("fileList", fileList); // 파일을 업로드한 후 반환된 파일 이름이 저장된 fileList를 다시 map에 저장한다.
		ModelAndView mav = new ModelAndView();
		mav.addObject("map", map);
		mav.setViewName("result"); // map을 결과창으로 포워딩한다.
		return mav;
	}

	private List<String> fileProcess(MultipartHttpServletRequest multipartRequest) throws Exception {
		List<String> fileList = new ArrayList<String>();
		Iterator<String> fileNames = multipartRequest.getFileNames(); // 첨부된 파일 이름을 가져온다.
		while (fileNames.hasNext()) {
			String fileName = fileNames.next();
			MultipartFile mFile = multipartRequest.getFile(fileName); // 파일 이름에 대한 MultipartFile 객체를 가져온다.
			String originalFileName = mFile.getOriginalFilename(); // 실제 파일 이름을 가져온다.
			fileList.add(originalFileName); // 파일 이름을 하나씩 fileList에 저장한다.
			File file = new File(CURR_IMAGE_REPO_PATH + "\\" + fileName);
			if (mFile.getSize() != 0) { // File Null Check ==> 첨부된 파일이 있는지 체크한다.
				if (!file.exists()) { // 경로상에 파일이 존재하지 않을 경우
					if (file.getParentFile().mkdirs()) { // 경로에 해당하는 디렉토리들을 생성
						file.createNewFile(); // 이후 파일 생성
					}
				}
				mFile.transferTo(new File(CURR_IMAGE_REPO_PATH + "\\" + originalFileName)); // 임시로 저장된 multipartFile을 실제 파일로 전송한다.
			}
		}
		return fileList; // 첨부한 파일 이름이 저장된 fileList를 반환한다.
	}
}

7. 파일 업로드창과 업로드한 파일을 표시해 주는 결과창을 나타낼 JSP 파일을 다음과 같이 준비한다.

8. 파일 업로드창인 uploadForm.jsp를 다음과 같이 작성한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
  request.setCharacterEncoding("UTF-8");
%>

<!DOCTYPE html>
<html lang="ko">

    <head>
        <meta charset="utf-8">
        <title>파일업로드 하기</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <script>
            var cnt = 1; // 파일 업로드 name 값을 다르게 하는 변수이다.
            function fn_addFile() {
                $("#d_file").append("<br>" + "<input  type='file' name='file" + cnt + "' />");
                cnt++;
            } // 파일 추가를 클릭하면 동적으로 파일 업로드를 추가한다. name 속성의 값으로 'file'+cnt를 설정함으로써 값을 다르게 해준다.
        </script>
    </head>

    <body>
        <h1>파일 업로드 하기</h1>
        <form method="post" action="${contextPath}/upload" enctype="multipart/form-data"> <!-- 파일 업로드 시 enctype은 반드시 multipart/form-data로 설정해야 한다. -->
            <label>아이디:</label>
            <input type="text" name="id"><br> <!-- 텍스트 박스에 ID를 입력받아 전송한다. -->
            <label>이름:</label>
            <input type="text" name="name"><br> <!-- 텍스트 박스에 이름을 입력받아 전송한다. -->
            <input type="button" value="파일추가" onclick="fn_addFile()" /><br> <!-- 파일 추가를 클릭하면 동적으로 파일 업로드를 추가한다. -->
            <div id="d_file">
            </div> <!-- 자바스크립트를 이용해 div 태그 안에 파일 업로드를 추가한다. -->
            <input type="submit" value="업로드" />
        </form>
    </body>

</html>

9. 결과창을 나타내는 result.jsp를 다음과 같이 작성한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>

<!DOCTYPE html>
<html lang="ko">

    <head>
        <meta charset="UTF-8">
        <title>결과창</title>
    </head>

    <body>
        <h1>업로드가 완료되었습니다.</h1>
        
        <label>아이디:</label>
        <input type="text" name="id" value="${map.id }" readonly><br> <!-- map으로 넘어온 매개변수 값을 표시한다. -->
        <label>이름:</label>
        <input type="text" name="name" value="${map.name }" readonly><br> <!-- map으로 넘어온 매개변수 값을 표시한다. -->
        
        <div class="result-images">
            <c:forEach var="imageFileName" items="${ map.fileList}">
                <%-- <img src="${pageContext.request.contextPath }/download?imageFileName=${imageFileName }" style="width:150px"> --%>
                <img src="${pageContext.request.contextPath }/download?imageFileName=${imageFileName }">
                <br><br><br>
            </c:forEach> <%-- 업로드한 파일을 forEach 문을 이용해 img 태그에 표시한다. --%>
        </div>
        
        <p>
            <a href='${pageContext.request.contextPath }/form'> 다시 업로드 하기 </a>
        </p>
    </body>

</html>

10. 다음 주소로 요청하여 ID와 이름을 입력하고 파일추가를 클릭하여 아무 이미지나 세 개의 파일을 첨부한다. 그리고 업로드를 클릭한다.

- http://localhost/pro28/form

11. 결과창으로 넘어가면서 전송된 매개변수 값들과 업로드된 이미지 세 개가 표시된다.

12. 지정한 경로의 폴더를 보면 업로드된 파일들을 볼 수 있다.


2. 썸네일 이미지 사용하기

* 웹 애플리케이션에서 이미지를 표시할 때는 다음과 같이 썸네일(thumbnail) 이미지를 사용하는 경우가 많다.

상품 이미지 목록에 표시되는 썸네일 이미지

* 만약 브라우저가 원본 이미지를 모두 전달받는 경우라면 어떨까? 표시할 이미지 개수가 많을 경우에는 시간이 꽤 오래 걸릴 것이다. 이처럼 상품 목록에 이미지를 축소해서 표시하는 경우에는 썸네일 기능을 사용하면 신속하게 표시할 수 있다.

1. 다음과 같이 pom.xml에 썸네일 라이브러리를 설정하면 thumbnailator-0.4.8.jar 가 설치된다.

        <!-- 썸네일 이미지 -->
        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>

썸네일 라이브러리 위치

2. 컨트롤러에 요청해 썸네일 이미지를 생성한 후 다운로드해 보자. 다음 위치에 FileDownloadController 클래스 파일을 준비한다.

3. 원본 이미지에 대해 썸네일 이미지 파일을 생성한 후 다운로드할 수 있도록 다음과 같이 작성한다. 
    Thumbnails.of(image).size(50, 50).outputFormat("png").toFile(thumbnail) 은 가로세로 크기가 50 픽셀인 png 썸네일 이미지를 생성한 후 파일로 저장하는 구문이다.

package com.myspring.pro28.ex02;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import net.coobird.thumbnailator.Thumbnails;

@Controller
public class FileDownloadController {
	private static String CURR_IMAGE_REPO_PATH = "c:\\spring\\image_repo";

	@RequestMapping("/download")
	protected void download(@RequestParam("imageFileName") String imageFileName, HttpServletResponse response)
			throws Exception {
		OutputStream out = response.getOutputStream();
		String filePath = CURR_IMAGE_REPO_PATH + "\\" + imageFileName;
		File image = new File(filePath);
		int lastIndex = imageFileName.lastIndexOf(".");
		String fileName = imageFileName.substring(0, lastIndex); // 확장자를 제외한 원본 이미지 파일의 이름을 가져온다.
		File thumbnail = new File(CURR_IMAGE_REPO_PATH + "\\" + "thumbnail" + "\\" + fileName + ".png"); // 원본 이미지 파일 이름과 같은 이름의 썸네일 파일에 대한 File 객체를 생성한다.
		if (image.exists()) {
			thumbnail.getParentFile().mkdirs();
			Thumbnails.of(image).size(50, 50).outputFormat("png").toFile(thumbnail);
		} // 원본 이미지 파일을 가로세로가 50픽셀인 png 형식의 썸네일 이미지 파일로 생성한다.

		FileInputStream in = new FileInputStream(thumbnail);
		byte[] buffer = new byte[1024 * 8];
		while (true) {
			int count = in.read(buffer); // 버퍼에 읽어들인 문자개수
			if (count == -1) // 버퍼의 마지막에 도달했는지 체크
				break;
			out.write(buffer, 0, count);
		} // 생성된 썸네일 파일을 브라우저로 전송한다.
		in.close();
		out.close();
	}

}

4. 다음은 실행 결과이다. /form으로 요청한 후 세 개의 이미지 파일을 첨부하고 업로드를 클릭한다.

- http://localhost/pro28/form

5. 결과창에 각 이미지에 대한 썸네일 이미지가 표시된다.

6. 이미지 저장 폴더 하위에 있는 thumbnail 폴더를 보면 다음과 같이 썸네일 이미지들이 있다.


(1) 썸네일 이미지 바로 출력하기

* 쇼핑몰의 상품 목록 이미지 같은 경우 썸네일 이미지 파일을 따로 생성할 필요 없이 썸네일 이미지를 바로 다운로드 하면 훨씬 빨리 표시할 수 있다.

1. 원본 이미지를 썸네일 이미지로 바로 출력하는 방법은 다음과 같다.

	@RequestMapping("/download")
	protected void download(@RequestParam("imageFileName") String imageFileName, HttpServletResponse response)
			throws Exception {
		OutputStream out = response.getOutputStream();
		String filePath = CURR_IMAGE_REPO_PATH + "\\" + imageFileName;
		File image = new File(filePath);
		int lastIndex = imageFileName.lastIndexOf(".");
		String fileName = imageFileName.substring(0, lastIndex);
		File thumbnail = new File(CURR_IMAGE_REPO_PATH + "\\" + "thumbnail" + "\\" + fileName + ".png");
		if (image.exists()) {
			Thumbnails.of(image).size(50, 50).outputFormat("png").toOutputStream(out); // 원본 이미지에 대한 썸네일 이미지를 생성한 후 OutputStream 객체에 할당한다.
		} else {
			return;
		}
		byte[] buffer = new byte[1024 * 8];
		out.write(buffer); // 썸네일 이미지를 OutputStream 객체를 이용해 브라우저로 전송한다.
		out.close();
	}

2. 썸네일 이미지 저장 폴더들의 이미지들을 삭제한 후 다시 실행해 보자.

- http://localhost/pro28/form

3. 브라우저에 표시되는 결과는 앞에서와 같지만 해당 경로의 폴더를 보면 썸네일 이미지 파일은 따로 생성되지 않았다.