728x90
300x250
[Spring Framework] 41. Spring 방식의 파일 업로드, 다운로드(Commons-io, FileUpload) - (XML, Java)


업로드에 관한 내용이다.

방식이 두 가지가 있다. 셋팅에 있어서 다소 차이가 있을 수 있다.


수 차례 실험을 해본 결과로는 Java 방식을 사용하고자 했을 때는 CGLib를 추가로 넣어줘야 한다.


[참고]

해당 코드는 Spring Framework 내에서만 동작하는 방법이다.

JSP/Servlet 업로드, 다운로드 관련해서는 JSP 카테고리에서 살펴보면 되겠다.


[개발 환경]

* IDE: Eclipse 2020-06

* Framework:

  - Spring Framework 4.2.4 RELEASES

  - commons-fileupload (2.8)

  - commons-io (1.4)
  - CGLIB (3.3.0) - Java 환경 설정 방식만 해당 [의외로 사용 빈도가 있는 jar 파일이다.]

    (RootConfig.java 파일과 Beans 등 사용하면 필요할 때가 있음.)


  - Java Version 1.8 (OpenJDK 15)






1. 프로젝트 구성도


공통적으로 작성한 파일은 한번만 소개하고 변경된 사항들만 분리 요약해서 작성하도록 하겠다.

"FileController.java" / 공통

"HomeController.java" / 공통

"insert.jsp" / 공통


 


 

 그림 1. XML 파일 방식

 그림 2. Java 방식




2. Java Compiler, Build Path, Project Desc (공통)


* Java Compiler - compiler compliance level 1.8
* Build Path
               - Add Jar로 ojdbc.jar 파일 등록해주기

* Project Factes - Java : 1.8



3. pom.xml (XML, Java 방식)


 

 (중략)


    <properties>
         <java-version>1.8</java-version>
         <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
         <org.aspectj-version>1.6.10</org.aspectj-version>
         <org.slf4j-version>1.6.6</org.slf4j-version>
    </properties>

 (중략)
 공통 - pom.xml

 
  <!-- https://search.maven.org/artifact/commons-fileupload/commons-fileupload/1.4/jar -->

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

  <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.8.0</version>
  </dependency>

 
  <!-- https://search.maven.org/artifact/commons-fileupload/commons-fileupload/1.4/jar -->

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

  <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.8.0</version>
  </dependency>
   

  <!-- 자바 방식 - FileUpload에서는 CGLIB 넣어줘야 함 -->
  <!-- https://mvnrepository.com/artifact/cglib/cglib -->
  <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
  </dependency>
  

 XML 방식 - pom.xml

 Java 방식 - pom.xml


하나 차이 밖에 없는데, cglib 라이브러리가 있느냐 없느냐 이런 차이인데, 그냥 다 써도 무방하다.

분리해서 소개한 이유는 최소의 라이브러리로 돌아가는지 태스트 해보려고 그랬다.


디버그 창에 종종 스프링 작업을 하면, 오류로 라이브러리가 필요하다고 올라오는데 그런 예기치 못한 작업들도 생각할 수 있으면 좋겠다.




4. insert.jsp (공통)


스프링을 공부하면서 연습 템플릿이 여의치 않아서 삽질을 다소 많이 하였다.


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page session="false" %>
<html>
<head>
 <meta charset="UTF-8">
 <title>File Upload - Insert 페이지</title>
</head>
<body>
<h1>
 Hello world! 
</h1>
 
<h1>파일 업로드</h1>
<form action="fileupload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadfile" placeholder="파일 선택" /><br/>
    <input type="submit" value="업로드">
</form>

<h2>다중 파일 업로드</h2>
<form action="fileupload2" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadfiles" placeholder="파일 선택" multiple/><br/>
    <input type="submit" value="업로드">
</form>

</body>
</html>


파일명: insert.jsp


[첨부(Attachments)]

insert.zip



[비고]

5, 6번(XML, Java 설정) 방식을 하는데 있어서 두 가지를 동시에 설정하면 충돌날 가능성이 있다.

주의해서 사용할 것을 권장한다.




5. 흔히 많이 알려진 XML 설정 방식


두 가지 셋팅을 해놓으면 간단하게 끝난다.

2가지 설정을 통해서 xml 셋팅이 간단하고 좋아보일 수도 있으나 라이브러리마다 셋팅의 양이 다양하고 복잡해지는 문제도 있다.

(지금 생각할 필요는 없음.)



1. root-context.xml (/src/main/webapp/WEB-INF/spring/root-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <!-- Root Context: defines shared resources visible to all other web components -->
 <!-- MultipartResolver -->
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <property name="maxUploadSize" value="100000000"/>  <!-- 10MB-->
     <property name="maxInMemorySize" value="100000000"/> <!-- 10MB-->
  <!-- <property name="uploadTempDir" value=“0"/> -->
 </bean>
 
 
 <!-- 외부 경로를 찾는 방식은 동작하지 않음. -->
 <!--  외부 경로 찾는 방식으로 다운로드를 구현하려면, 다운로더를 하나 구현하는 것이 빠름. -->
 <!-- 다운로드 경로는 servlet-context.xml에 넣어야 함. 이중 선언이 되어서 동작 안함. -->
 <!--
 <mvc:annotation-driven />
   
    <mvc:resources mapping="/static2/**" location="file:///c:/temp/" />
 <mvc:default-servlet-handler />
 -->
 
</beans>


파일명: root-context.xml


[첨부(Attachments)]

root-context.zip




2. servlet-context.xml (/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
  http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

 <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
 
 <!-- Enables the Spring MVC @Controller programming model -->
 <annotation-driven />

 <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
 <resources mapping="/resources/**" location="/resources/" />
 
 <!-- 다운로드 외부 경로 -->
 <resources mapping="/static2/**" location="file:///c:/temp/" />


 <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
 <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <beans:property name="prefix" value="/WEB-INF/views/" />
  <beans:property name="suffix" value=".jsp" />
 </beans:bean>
 
 <context:component-scan base-package="com.website.example" />
 
 
</beans:beans>


파일명: servlet-context.xml


[첨부(Attachments)]

servlet-context.zip


[Resources Mapping 관련]

밑줄 친 이 코드는 다운로드 위치(자원)를 맵핑해주는 명령어이다.

servlet-context.xml에 넣어야만 동작한다.


<mvn:resource......> 이렇게 해버리면, 돌아가지 않는다.


[유의할 점 - servlet-context.xml 이외의 파일에서 사용하고자 했을 때]

코드에 오류는 안 나는데, 동작이 안되는 당황스러운 일도 있다. 

관리차원에서 파일을 하나 별도로 xml파일을 하나 생성해서, 구현을 해버리면 되어야 하는데 파일 자체에는 오류도 안 나고,

서버에 컴파일해서 올린 후 호출하면 오류가 뜨는 경우가 있다.


servlet-context.xml 파일이 수동 셋팅에서는 중요하다.




6. Java 설정 방식


RootWebConfig.java 파일 하나만 만들어놓으면, 추가 셋팅 할 필요없이 자동으로 인식해준다.

(인식해주는 이유는 "extends WebMvcConfigurerAdapter" 이 한 줄이 추가되서 그렇다.)

별도로 셋팅해줄 필요는 없다.


package com.website.example.common;

import java.io.File;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


@Configuration
@Import({FileConfig.class})
@ComponentScan(basePackages = {"com.website.example"})
@EnableWebMvc
//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})


public class RootWebConfig extends WebMvcConfigurerAdapter {

          

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
              registry.addResourceHandler("/static/**").addResourceLocations("file:c:" + File.separator + "temp/");
        }

}


파일명: RootWebConfig.java


[첨부(Attachments)]

RootWebConfig.zip



package com.website.example.common;

import java.io.File;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;


@Configuration
public class FileConfig {
 
     @Bean
     public MultipartResolver multipartResolver() {

           org.springframework.web.multipart.commons.CommonsMultipartResolver multipartResolver = new                

                  org.springframework.web.multipart.commons.CommonsMultipartResolver();
    
                  multipartResolver.setMaxUploadSize(100000000);
                  multipartResolver.setMaxInMemorySize(100000000); // 10MB
                  return multipartResolver;
      }
 
}


파일명: FileConfig.java


[첨부(Attachments)]

FileConfig.zip



7. FileController.java (/com/website/example)


package com.website.example;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class FileController {
 
         private static final Logger logger = LoggerFactory.getLogger(FileController.class);
 
         private final String UPLOAD_PATH = "c:" + File.separator + "temp" + File.separator;

         
         @RequestMapping(value = "/insert", method = RequestMethod.GET)
         public String insert(Locale locale, Model model) {
                   logger.info("Welcome home! The client locale is {}.", locale);
  
                   return "insert";
         }
 
         @RequestMapping(value = "/fileupload",method = RequestMethod.POST)
         public String upload(MultipartFile uploadfile, Model model) throws IOException{
  
                   logger.info("upload() POST 호출");
                   logger.info("파일 이름: {}", uploadfile.getOriginalFilename());
                   logger.info("파일 크기: {}", uploadfile.getSize());

              
                   String result = saveFile(uploadfile);
    
                   if(result !=null){ // 파일 저장 성공
      
                        model.addAttribute("result", result);
        
                   } else { // 파일 저장 실패
      
                        model.addAttribute("result","fail");
        
                   }
    
                   return "home";

         }
 
         @RequestMapping(value = "/fileupload2", method = RequestMethod.POST)
         public String multiupload(@RequestParam("uploadfiles") MultipartFile[] file, Model model) throws IOException {
  
                  logger.info("Welcome multi file! The client locale is {}.", "Hello");
   
                  String result = "";
    
                  for(MultipartFile f : file){
                       result += saveFile(f);
                  }
    
                  return "home";
         }
 

         private String saveFile(MultipartFile file) throws IOException{
 
                // 파일 이름 변경
                UUID uuid = UUID.randomUUID();
                String saveName = uuid + "_" + file.getOriginalFilename();
 
                logger.info("saveName: {}",saveName);

                String fileName = file.getOriginalFilename();
                String contentType = file.getContentType();
                long filesize = file.getSize();
                // byte[] bytes = file.getBytes();
       
               //System.out.println("파일명:" + fileName);
               //System.out.println("컨텐츠 타입:" + contentType);
               //System.out.println("파일크기:" + filesize);
    
               // 저장할 File 객체를 생성(껍데기 파일)ㄴ
               File saveFile = new File(UPLOAD_PATH, saveName); // 저장할 폴더 이름, 저장할 파일 이름
 
               try {
                     file.transferTo(saveFile); // 업로드 파일에 saveFile이라는 껍데기 입힘
               } catch (IOException e) {
                     e.printStackTrace();
                     return null;
               }
 
               return saveName;
       }
 
}


파일명: FileController.java


[첨부(Attachments)]

FileController.zip



8. 출력 결과


동일한 결과를 볼 수 있다. 동일한 작업이기 때문이다.

차이점은 내부적인 관점에서 셋팅을 어떻게 했느냐 이런 문제라고 보겠다.



그림 3. (결과 1) 단일 업로드 모습



그림 4. (결과 1) 단일 업로드 모습 - 실제 파일 업로드 모습



그림 5. (결과 2) 다중 업로드 모습 - 실제 파일 업로드 모습



그림 6. (결과 2) 다중 업로드 모습 - 실제 파일 업로드 모습 - 폴더 모습


FileController.zip



* 맺음글(Conclusion)


Spring Framework 방식으로 Commons-io, Commons-FileUpload, CGLIB를 활용하여 업로드 프로그램을 구현하였다.

참고로 이 방식은 Spring Framework에서만 동작하는 방법이다.



반응형

+ Recent posts