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에서만 동작하는 방법이다.



반응형
728x90
300x250

[JSP] 13. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(2)


이전의 글에 이어서 작성하도록 하겠다.


이전의 글

- [JSP] 12. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(1), 2020-09-24

   https://yyman.tistory.com/1414



14. Controller - BoardInsertMultiResultController 


고민을 나름대로 하여 만든 것이다. 인터넷 자료 등도 열심히 참고하고, 삽질을 많이 하였다.


package com.fileWeb.controller;


import java.io.File;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.HashMap;

import java.util.Iterator;

import java.util.List;

import java.util.Map;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;


public class BoardInsertMultiResultController implements Controller {


private Map<String, Object> reqMap = null; // req 정보(MultiRequest)

private Map<Integer, Map<String, Object>> fileMap = null; // 다중 파일 지원

private static int num = 0; 

@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws

ServletException, IOException {

fileMap = new HashMap<Integer, Map<String, Object>>(); 

reqMap = new HashMap<>();

num = 1;

        

        PrintWriter out = res.getWriter();

        out.println("<HTML><HEAD><TITLE>Multipart Test</TITLE></HEAD><BODY>");

try {

            

            //디스크상의 프로젝트 실제 경로얻기

            //String contextRootPath = "c:" + File.separator + "upload";

String dirName = "upload" ; 

String contextRootPath = req.getSession().getServletContext().getRealPath("/") + dirName;

            System.out.println("실제경로:" + contextRootPath);

            

            //1. 메모리나 파일로 업로드 파일 보관하는 FileItem의 Factory 설정

            DiskFileItemFactory diskFactory = new DiskFileItemFactory(); //디스크 파일 아이템 공장

            diskFactory.setSizeThreshold(4096); //업로드시 사용할 임시 메모리

            diskFactory.setRepository(new File(contextRootPath + "/WEB-INF/temp")); //임시저장폴더

            

            //2. 업로드 요청을 처리하는 ServletFileUpload생성

            ServletFileUpload upload = new ServletFileUpload(diskFactory);

            upload.setSizeMax(3 * 1024 * 1024); //3MB : 전체 최대 업로드 파일 크기

            

            //3. 업로드 요청파싱해서 FileItem 목록구함​​

            List<FileItem> items = upload.parseRequest(req); 


            Iterator<FileItem> iter = items.iterator(); //반복자(Iterator)로 받기​            

            while(iter.hasNext()) { //반목문으로 처리​    

                FileItem item = (FileItem) iter.next(); //아이템 얻기

                 //4. FileItem이 폼 입력 항목인지 여부에 따라 알맞은 처리

                

                if(item.isFormField()){ 

                //파일이 아닌경우

                    processFormField(out, item);

                    

                } else {

                //파일인 경우

                // System.out.println("오류:");

                    processUploadFile(out, item, contextRootPath);

                // System.out.println("오류2:");

                }

            }

            

        } catch(Exception e) {

            out.println("<PRE>");

            e.printStackTrace(out);

            out.println("</PRE>");

        }

out.println( "usrID(Map): " + reqMap.get("usrID") );

out.println( "usrPasswd(Map):" + reqMap.get("usrPasswd") );

        

        out.println("</BODY></HTML>");

// req.setAttribute("usrID", reqMap.get("usrID"));

// req.setAttribute("login", 1);//Object Type으로 넘어감

        req.setAttribute("reqMap", reqMap);

        req.setAttribute("fileMap", fileMap);

        

        // 방법3

        for( Integer key : fileMap.keySet() ){


            Map<String, Object> fileMapNode = fileMap.get(key);

            System.out.println( String.format("키 : %s, 값: %s", key, fileMapNode.get("fileName") ));

            

        }

        

// System.out.println("오류3:" + reqMap.get("usrID"));

HttpUtil.forward(req, res, "/WEB-INF/view/board/insertResult.jsp");

}


//업로드한 정보가 파일인경우 처리

private void processUploadFile( PrintWriter out, FileItem item, String contextRootPath)

throws Exception {

Map<String, Object> fileNode = new HashMap<String, Object>();

String name = item.getFieldName(); // 파일의 필드 이름 얻기

String fileName = item.getName(); // 파일명 얻기

// 임시 - 실제 원본 이름 추출

File originalFile = new File(fileName);

String originalFileName = originalFile.getName();

// System.out.println("임시:" + originalFileName );

String contentType = item.getContentType(); // 컨텐츠 타입 얻기

long fileSize = item.getSize(); // 파일의 크기 얻기

// 업로드 파일명을 현재시간으로 변경후 저장

String fileExt = fileName.substring(fileName.lastIndexOf("."));

String uploadedFileName = System.currentTimeMillis() + ""; 

System.out.println(fileExt);

System.out.println(uploadedFileName);

// 저장할 절대 경로로 파일 객체 생성

File uploadedFile = new File(contextRootPath + "/upload/" + uploadedFileName);

item.write(uploadedFile); //파일 저장

//========== 뷰단에 출력 =========//

out.println("<P>");

out.println("파라미터 이름:" + name + "<BR>");

out.println("파일 이름:" + fileName + "<BR>");

out.println("콘텐츠 타입:" + contentType + "<BR>");

out.println("파일 사이즈:" + fileSize + "<BR>");

//확장자가 이미지인겨우 이미지 출력

if(".jpg.jpeg.bmp.png.gif".contains(fileExt.toLowerCase())) {

out.println("<IMG SRC='upload/" 

+ uploadedFileName 

+ "' width='300'><BR>");

}

out.println("</P>");

out.println("<HR>");

out.println("실제저장경로 : "+uploadedFile.getPath()+"<BR>");

out.println("<HR>");

// 파일 정보

fileNode.put("name", name);

fileNode.put("fileName", originalFileName);

fileNode.put("contentType", contentType);

fileNode.put("fileSize", fileSize);

fileNode.put("fileExt", fileExt);

fileNode.put("uploadedFileName", uploadedFileName);

fileNode.put("realName", uploadedFile.getName());

fileNode.put("realPath", uploadedFile.getPath());

fileMap.put(num, fileNode);

num++;

}

private void processFormField(PrintWriter out, FileItem item) 

throws Exception{

String name = item.getFieldName(); //필드명 얻기

Object value = item.getString("UTF-8"); //UTF-8형식으로 필드에 대한 값읽기

// out.println(name + ":" + value + "<BR>"); //출력

reqMap.put(name, value);

}


}



파일명: BoardInsertMultiResultController.java


[첨부(Attachments)]

BoardInsertMultiResultController.zip



15. View - insertResult.jsp


사소해보이지만, jsp파일 내에서도 자료구조를 사용할 수 있다.


JSP: <%@ page import="java.util.*" %>

Java: import java.util.*;


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<%@ page import="java.util.*" %>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>업로드 - 결과</title>

</head>

<body>

<h3>업로드 결과</h3>

<%


//Object name = request.getAttribute("usrID");

//Object login = request.getAttribute("login");

Object obj = request.getAttribute("reqMap");

Map<String, Object> map = null;

if(obj != null){

map = (HashMap<String, Object>)obj;

}


%>


<table style="width:700px;border:1px solid #e2e2e2;">

<tr>

<td style="width:20%;">

<%

        out.println("name : " + map.get("usrID") + "<br />");

%>

</td>

<td style="width:20%;border-left:1px solid #e2e2e2;">

<%

out.println("login : " + map.get("usrPasswd") + "<br />");

%>

</td>

</tr>

<tr>

<td>

</td>

<td>

</td>

</tr>

</table>


</body>

</html>


파일명: insertResult.jsp


[첨부(Attachments)]

insertResult.zip



16. 라이브러리(Libraries)


pom.xml으로 자동 생성된 라이브러리이긴 한데 수동으로 추출해보았다.





17. 맺음글(Conclusion)


실질적으로 다중 파일 업로드와 다운로드 구현에 대해서 자세히 살펴보았다.



* 참고자료(References)


1. [서블릿/JSP] Apache Commons FileUpload를 이용한 파일업로드 구현하기, https://dololak.tistory.com/720?category=636501, Accessed by 2020-09-24, Last Modified 2019-07-24.


2. FrontController & Command Pattern - 프론트 컨트롤러와 커맨드 패턴, https://dailyworker.github.io/servlet-advened/, Accessed by 2020-09-24, Last Modified 2019-07-09.


3. 파일전송/업로드(Multipart) , https://gunbin91.github.io/jsp/2019/05/28/jsp_11_file.html, Accessed by 2020-09-24, Last Modified 2019-05-28.

-> Multipart 방식으로 소개되어 있는데, 현재 서블릿 버전에서는 충돌 발생함. (www.servlets.com)


4. Servlets.com | com.oreilly.servlet, http://www.servlets.com/cos/, Accessed by 2020-09-24, Last Modified 2002-03-01.

-> 비추천: 2002년에서 멈춰버린 프로젝트


5. .[jsp] form 타입이 enctype="multipart/form-data" 일때 request.getParameter() 불가 값이 null로 오는 문제, http://blog.naver.com/PostView.nhn?blogId=software705&logNo=220551397421&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView, Accessed by 2020-09-24, Last Modified 2015-11-27.

-> 비추천: oreilly.servlet jar 파일로 구현한 MultipartRequest인데, 서블릿 충돌을 경험하게 된다.


6. [jsp/servlet] commons-fileupload 를 이용한 파일업로드 (서블릿), https://m.blog.naver.com/javaking75/220056175936, Accessed by 2020-09-24, Last Modified 2014-07-10.

-> 추천(70점): 오래된 것처럼 보이지만, 현실적으로 돌아가고 commons-io, commons-fileupload에 대해서 자세히 잘 소개하고 있음.


7. [Day49][JSP] HashMap을 사용하여 전체 회원 목록 조회 / 상세 회원 목록 조회 / JSTL, https://clapdev.tistory.com/49, Accessed by 2020-09-24, Last Modified 2019-11-05.

-> 추천(30점): c태그(JSTL)에 대해서 잘 소개하고 있음.


8. JSP 기본 실습 : 데이터 전송 받기2 [개인정보입력폼] -request-, https://whdvy777.tistory.com/entry/JSP-%EA%B8%B0%EB%B3%B8-%EC%8B%A4%EC%8A%B5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%86%A1-%EB%B0%9B%EA%B8%B02-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%9E%85%EB%A0%A5%ED%8F%BC-request, Accessed by 2020-09-24, Last Modified 2013-03-08.

-> 추천(35점): 오래되어 보이지만, 기본에 충실하고 있음.


9. Java Map 반복(Iteration)시키는 3가지 방법, https://stove99.tistory.com/96, Accessed by 2020-09-24, Last Modified 2011-11-13.


-> 자주 까먹을 수 있는 방법이라고 본다. 다중 프로그래밍을 다루고 있어서 까먹는 일이 많아서 적어본다.


Map<String, String> map = new HashMap<String, String>();

         

map.put("키1", "값1");

map.put("키2", "값2");

map.put("키3", "값3");

map.put("키4", "값4");

map.put("키5", "값5");

map.put("키6", "값6");

         

         

        // 방법1

        Iterator<String> keys = map.keySet().iterator();

        while( keys.hasNext() ){

            String key = keys.next();

            System.out.println( String.format("키 : %s, 값 : %s", key, map.get(key)) );

        }

         

        // 방법2

        for( Map.Entry<String, String> elem : map.entrySet() ){

            System.out.println( String.format("키 : %s, 값 : %s", elem.getKey(), elem.getValue()) );

        }

         

        // 방법3

        for( String key : map.keySet() ){

            System.out.println( String.format("키 : %s, 값 : %s", key, map.get(key)) );

        }


- https://stove99.tistory.com/96 [스토브 훌로구]


반응형
728x90
300x250

[JSP] 12. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(1)


조금 실질적으로 도움이 되는 프로젝트라고 주장한다.

다중 파일 업로드, 다운로드 프로젝트를 조금 실용적으로 유용하게 사용할 수 있도록 특화하여 따로 만들어보게 되었다.


사용하는 것은 쉽지만, 구현하는 것은 다소 많은 시간이 소요된다고 본다.

현재 시중에 판매되는 교제 등의 내용이 공개 비판하면 그렇지만, 품질이 매우 낮다는 점이다.


최신 버전에 가까운 프로젝트를 위주로 직접 태스트를 엄선해서 수차례 검증하고, 작성하였다.


* apache-tomcat-9.0.37-windows-x64

* Maven - 3.6.3/1.16.0.20200610-1735   - http://maven.apache.org/download.cgi

  (pom.xml)

   - javax.servlet-api 4.0.1

   - commons-io 2.8.0 (apache project)   -  http://commons.apache.org/

   - commons-fileupload 1.4 (apache project)   - http://commons.apache.org/


적용한 모델은 MVC2 모델을 적용하였으니 참고하면 도움이 되겠다.



1. 결과


다운로드, 업로드 기능에 많은 시간이 소요된 프로젝트였다.



그림 1. 결과 



그림 2. 결과 




그림 3. 결과


그림 4. 결과




그림 5. 결과




2. 프로젝트 구성


작업을 해야하는 양이 조금 된다. 하나 잘 만들어놓으면 활용하거나 개선을 해서 사용해보는 것도 좋은 방법인 거 같다.



그림 6. 프로젝트 구성도



3. 프로젝트 초기 설정


새 프로젝트 만들기를 누른다.



그림 7. 새 프로젝트 만들기


Maven의 Maven Project를 클릭한다.

Next를 누른다.



그림 8. New Maven Project


"org.apache.maven.archetypes   | maven-archetype-webapp"를 선택하고 Next를 누른다.




그림 9. New Maven Project(2)


패키지명과 Artifact Id를 입력하고, Finish를 누른다.



그림 10. New Maven Project(3)


톰캣 서버 설정 등을 완료한다. 그리고 빌드를 시도하면, 이런 화면을 볼 수 있다.



4. 프로젝트 속성 - Java Build Path, Project Facets


14버전으로 변경해준다.



그림 11. Java Build Path -> Libraries 탭의 JRE System Library 버전


버전을 깔맞춤해줘야 한다.

14버전이면, 14로 일관성있게 설정 해준다.



그림 12. Project Facets -> Java 버전확인


마찬가지로 버전을 14로 맞춰준다.


비고: 대략 초기 Maven Project를 생성하면, 1.6? 1.7 버전으로 셋팅되어 있음.



5. Controller 생성하기(Servlet 만들기)


프로젝트에서 오른쪽 버튼을 클릭해서 New->Servlet을 클릭해서 하나 만들어준다.



그림 13. Servlet 만들기


패키지: com.fileWeb.controller

클래스명: FrontController(java)



6. pom.xml 설정하기(MvnRepository.com)


pom.xml 설정으로 필요한 라이브러리를 구비해준다.



<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>


  <groupId>com.mavenUpload</groupId>

  <artifactId>web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>


  <name>File - Multi Web Upload Project</name>

  <!-- FIXME change it to the project's website -->

  <url>http://www.example.com</url>


  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>1.7</maven.compiler.source>

    <maven.compiler.target>1.7</maven.compiler.target>

  </properties>


  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.11</version>

      <scope>test</scope>

    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</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://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->

<dependency>

    <groupId>commons-fileupload</groupId>

    <artifactId>commons-fileupload</artifactId>

    <version>1.4</version>

</dependency>

  </dependencies>


  <build>

    <finalName>web</finalName>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

      <plugins>

        <plugin>

          <artifactId>maven-clean-plugin</artifactId>

          <version>3.1.0</version>

        </plugin>

        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->

        <plugin>

          <artifactId>maven-resources-plugin</artifactId>

          <version>3.0.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-compiler-plugin</artifactId>

          <version>3.8.0</version>

        </plugin>

        <plugin>

          <artifactId>maven-surefire-plugin</artifactId>

          <version>2.22.1</version>

        </plugin>

        <plugin>

          <artifactId>maven-war-plugin</artifactId>

          <version>3.2.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-install-plugin</artifactId>

          <version>2.5.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-deploy-plugin</artifactId>

          <version>2.8.2</version>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip



7. web.xml 설정하기


web.xml 설정에 관한 것이다.

jsp가 아닌 *.do로 처리하는 프로젝트에 대한 내용으로 작성되었다.

UTF-8 한글 언어셋도 정의하였다.


<?xml version="1.0" encoding="UTF-8"?>


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 

id="WebApp_ID" version="3.0">


  <display-name>Archetype Created Web Application</display-name>

  

  <welcome-file-list>


    <welcome-file>index.html</welcome-file>

    <welcome-file>index.htm</welcome-file>

    <welcome-file>index.do</welcome-file>

    <welcome-file>default.html</welcome-file>

    <welcome-file>default.htm</welcome-file>

    <welcome-file>default.do</welcome-file>

  </welcome-file-list>

  

  <servlet>

  <servlet-name>FrontController</servlet-name>

  <servlet-class>com.fileWeb.controller.FrontController</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet-mapping>

  <servlet-name>FrontController</servlet-name>

  <url-pattern>*.do</url-pattern>

  </servlet-mapping>

</web-app>



파일명: web.xml


[첨부(Attachments)]

web.zip



8. Controller - HttpUtil.java


패키지명: com.fileWeb.controller

클래스명: HttpUtil (java)



package com.fileWeb.controller;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.PrintWriter;


import javax.servlet.RequestDispatcher;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



public class HttpUtil extends HttpServlet {

private static final long serialVersionUID = 1L;

private static String charset = null;


public static void forward(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

try {

RequestDispatcher dispatcher = req.getRequestDispatcher(path);

dispatcher.forward(req, res);

}catch(Exception e) {

e.printStackTrace();

}

}

public static void fileUpload(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {


charset = (String) req.getAttribute("charset");

System.out.println(charset);

PrintWriter out = res.getWriter();

// 파일 업로드된 경로

String root = req.getSession().getServletContext().getRealPath("/");

String savePath = root + "upload" + File.separator + "upload";

// 서버에 실제 저장된 파일명

String filename = "1600955663095" ;

System.out.println("파일 실제 폴더경로:" + savePath);

// 실제 내보낼 파일명

String orgfilename = "license한글.txt" ;

req.setCharacterEncoding(charset);

res.setCharacterEncoding(charset);

InputStream in = null;

OutputStream os = null;

File file = null;

boolean skip = false;

String client = "";

try{

    // 파일을 읽어 스트림에 담기

    try{

        file = new File(savePath, filename);

        in = new FileInputStream(file);

    }catch(FileNotFoundException fe){

        skip = true;

    }

    client = req.getHeader("User-Agent");

    // 파일 다운로드 헤더 지정

    res.reset() ;

    res.setContentType("application/octet-stream");

    res.setHeader("Content-Description", "JSP Generated Data");

    if(!skip){

        // IE

        if(client.indexOf("MSIE") != -1){

            res.setHeader ("Content-Disposition", "attachment; filename="+new String(orgfilename.getBytes("KSC5601"),"ISO8859_1"));

        }else{

            // 한글 파일명 처리

            orgfilename = new String(orgfilename.getBytes("KSC5601"),"iso-8859-1");

            res.setHeader("Content-Disposition", "attachment; filename=\"" + orgfilename + "\"");

            res.setHeader("Content-Type", "application/octet-stream; charset=utf-8");

        }  

        res.setHeader ("Content-Length", ""+file.length() );

        os = res.getOutputStream();

        

        byte b[] = new byte[(int)file.length()];

        int leng = 0;

        while( (leng = in.read(b)) > 0 ){

            os.write(b,0,leng);

        }

    }else{

    // 한글 깨짐 - 해결

    res.setContentType("text/html;charset=" + charset);

        out.println("<html><head>");

        out.println("<script language='javascript'>alert('파일을 찾을 수 없습니다.');history.back();</script>");

        out.println("</head><body></body></html>");

    }

    in.close();

    os.close();

}catch(Exception e){

e.printStackTrace();

}

    

}


}



파일명: HttpUtil.java


[첨부(Attachments)]

HttpUtil.zip



9. Controller의 인터페이스 (Controller.java)


Controller.java 파일이다.

인터페이스로 설계되었다.


package com.fileWeb.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public interface Controller {

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;

}



파일명: Controller.java


[첨부(Attachments)]

Controller.zip




10. Controller - FrontController와 Command 패턴 적용으로 구성함. 


설명도 중요하지만, 코드로 살펴보도록 하겠다.


package com.fileWeb.controller;


import java.io.IOException;

import java.util.HashMap;

import java.util.Map;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


/**

 * Servlet implementation class HomeController

 */

public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

       

private String charset = null;

    public FrontController() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

// FrontController 패턴 & Command 패턴

protected void doAction(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

charset = sc.getInitParameter("charset");

req.setAttribute("charset", charset);

req.setCharacterEncoding(charset);

res.setContentType("text/html; charset=" + charset);

System.out.println(charset);

String uri = req.getRequestURI();

System.out.println("uri : " + uri);

String conPath = req.getContextPath();

System.out.println("conPath : " + conPath);

String command = uri.substring(conPath.length());

System.out.println("command : " + command);

Controller subController = null;

System.out.println("reqMapSize : " + req.getParameterMap().size());


if(command.equals("/board/insert.do")){

System.out.println("insert");

System.out.println("----------------");


    subController = new BoardInsertController();

    subController.execute(req, res);

   

}else if (command.equals("/board/insertResult.do")) {

System.out.println("insertResult");

System.out.println("----------------");


    subController = new BoardInsertResultController();

subController.execute(req, res);

}

else if(command.equals("/board/insertMultiResult.do")){

System.out.println("insertResult");

System.out.println("----------------");


    subController = new BoardInsertMultiResultController();

subController.execute(req, res);

}else if(command.equals("/board/download.do")) {

System.out.println("download");

System.out.println("----------------");

    HttpUtil.fileUpload(req, res, null);

}else if(command.equals("/board/update.do")){

System.out.println("update");

System.out.println("----------------");

}else if(command.equals("/board/select.do")){

System.out.println("select");

System.out.println("----------------");

}else if(command.equals("/board/delete.do")){

System.out.println("delete");

System.out.println("----------------");

}

}

}



파일명: FrontController.java


[첨부(Attachments)]

FrontController.zip



11. Controller - BoardInsertController.java


BoardInsertController의 핵심 내용은 "insert.jsp" 파일을 불러오는 데 있다.

없어도 된다고 생각했는데, 기능이 많아질 경우를 대비한다면, 있는 것이 낫다고 생각했다.


package com.fileWeb.controller;


import java.io.IOException;

import java.util.HashMap;

import java.util.Map;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class BoardInsertController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws

ServletException, IOException {

HttpUtil.forward(req, res, "/WEB-INF/view/board/insert.jsp");

}


}



파일명: BoardInsertController.java


[첨부(Attachments)]

BoardInsertController.zip



12. View - board/insert.jsp


삽입 페이지에 대한 것이다.

구현 목표는 두 가지에 대한 실험이다.


하나는 순수한 POST 처리 방식이고, 하나는 POST기반의 multipart/form-data 방식에 관한 것이다.


실험 결과를 먼저 소개하면, 

1. multipart/form-data를 정의해버리면, 기본 정의된 request로는 parameter를 전송받을 수가 없다.


* com.oreilly.servlet   (결과: 오래된 프로젝트 / 서블릿 버전 충돌 발생)

  - http://www.servlets.com/cos/


다양한 검색을 통해서 MultipartRequest 정의를 활용해서 이 문제를 oreilly.Servlet으로 해결할 수 있다는 글을 봐서 적용하였으나 
서블릿 충돌 문제가 발생하였다. (사용할 수 없는 오래된 라이브러리)

2. Apache Common-io, Apache Common-FileUpload를 적용하면, 해결할 수 있다. (2020-09-24일 기준)



<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>삽입</title>

</head>

<body>

<h3>삽입</h3>


<!-- 파일 업로드 화면 -->

<!-- 일반적으로 Multipart form으로 전송된 데이터는

      일반 request 메서드로 받아올 수 없습니다.  enctype="multipart/form-data" -->

<form method="post" action="insertResult.do" >

<table style="width:700px;border:1px solid #e2e2e2;">

<tr>

<td style="width:20%">

파일명

</td>

<td>

<input type="text" name="usrID" size="10">

<input type="file" name="uploadFile" multiple>

</td>

</tr>

<tr>

<td colspan="2">

<input type="submit" value="전송">

</td>

</tr>

</table>

</form>



<!-- 멀티파트/데이터 전송 -->

<form method="post" action="insertMultiResult.do" enctype="multipart/form-data" >

<table style="width:700px;border:1px solid #e2e2e2;">

<tr>

<td style="width:20%">

파일명

</td>

<td>

<input type="text" name="usrID" size="10">

<input type="password" name="usrPasswd" size="10">

<input type="file" name="uploadFile" multiple>

<input type="file" name="uploadFile" multiple>

<input type="file" name="uploadFile" multiple>

</td>

</tr>

<tr>

<td colspan="2">

<input type="submit" value="전송">

</td>

</tr>

</table>

</form>


</body>

</html>


파일명: insert.jsp


[첨부(Attachments)]

insert.zip




13. Controller - BoardInsertResultController


multipart/form-data를 미지원하는 타입으로 결과를 출력하는 방법에 대하여 작성하였다.


package com.fileWeb.controller;


import java.io.IOException;

import java.util.HashMap;

import java.util.Map;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class BoardInsertResultController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws

ServletException, IOException {


Map<String, Object> reqMap = new HashMap<String, Object>();

reqMap.put("usrID", req.getParameter("usrID"));

reqMap.put("usrPasswd", req.getParameter("filename"));


            req.setAttribute("reqMap", reqMap);

        

HttpUtil.forward(req, res, "/WEB-INF/view/board/insertResult.jsp");

}


}


파일명: BoardInsertResultController.jsp


[첨부(Attachments)]

BoardInsertResultController.zip



2부에서 만나요.


양이 조금 많아서 2부에서 글을 이어서 소개하려고 한다.


- [JSP] 12. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(2), 2020-09-24

   https://yyman.tistory.com/1415


반응형

+ Recent posts