BackEnd/Spring

SpringFileUpload_국비_Day86

Leo.K 2022. 7. 4. 14:16

오늘은 스프링 레거시 프로젝트에서 파일 업로드 기능을 사용하는 방법을 배웠다. 모델2에서 학습한 실습 예제를 스프링 레거시 프로젝트로 변경하는 작업을 진행하고 있는데, 생각보다 어려웠는데 오늘 배움으로써 말끔히 해결되었다.

알다시피 파일 업로드를 사용하기 위해서는 파일 업로드를 처리해주는 전용 객체(MultipartRequest)를 사용해야 한다. 이를 스프링이 사용할 수 있도록 객체를 만들어 주어야 하는데 기본적인 스프링 DB환경설정을 마친 상태에서 DispatcherServlet이 참고하는 파일에 아래의 소스코드를 추가하도록 하자. 

더보기
1
2
3
4
5
6
<!-- multipartResolver -->
<bean name="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"/>
    <property name="maxUploadSize" value="104857600"></property>
</bean>
cs

위의 소스코드를 추가했다면, pom.xml에 CommonsMultipartResolver를 사용하기 위한 라이브러리를 추가해주어야 한다. 

아래의 라이브러리를 pom.xml파일의 dependencies를 사용하여 추가해주자.

 

webapp 하위의 resources폴더에 업로드한 이미지를 저장할 폴더를 생성한다. 이제 파일을 업로드 받을 때, 파일을 저장할 경로를 이 폴더로 지정할 것이다. 

다음으로 업로드 처리를 해줄 컨트롤러를 하나 생성하자. 이는 파일업로드 처리만 할 것이기 때문에 별다른 dao의 인젝션이 필요없기 때문에 DispatcherServlet이 인식할 수 있도록 servlet-context.xml파일에 controller패키지만 설정해주자.

컨트롤러에서 사용할 데이터는 DispatcherServlet에게 메서드의 파라미터로 요청한다고 했었다. 하지만, 모든 데이터를 파라미터로 작성하면 메서드가 보기 불편하게 복잡해질텐데, 이를 해결하기 위해서 스프링에서는  @Auto-wired 어노테이션을 지원한다. 이는 스프링의 신인 DispatcherServlet이 가지고 있는 객체를 미리 자동 인젝션 해주는 것이다. 

이때, 컨트롤러를 자동 생성<auto-detecting>해주는 것이 아니라 <bean>태그를 추가해 수동으로 생성해 주는 경우에는 <context:annotation-config />태그를 수동 생성전에 반드시 추가 해주어야 한다는 것이다. 아래의 소스 코드는 두 가지 방법을 제시하지만 두가지 방법을 한 번에 사용할 수 없고, 반드시 하나의 방법만 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
<!-- 방법 1 -->
<!-- 만약 컨트롤러가 수동생성시 @Autowired를 지원하게 하려면 """생성 전에""" 아래와 같은 태그를 추가해야 한다.-->
<context:annotation-config />
<!-- 수동생성 -->
<beans:bean class="controller.FileUploadController"></beans:bean>
 
 
<!-- 방법2 -->
<!-- auto-detecting -->
<context:component-scan base-package="com.ict.fileupload" />
<context:component-scan base-package="controller" />
cs

 

이제는 직접적으로 controller에 비즈니스 로직을 처리해주어야 한다. 먼저 아래의 이미지를 먼저 확인하자.

아래의 이미지에서 Miscellaneous부분을 보면 Multipart handing이라는 것이 보일것이다. 맞다. 스프링이 요청된 URL에 맞는 Controller에게 작업을 할당하기 전에 파일 업로드에 대한 전처리 작업을 하는 곳이다. 

주의해야 할 점은 이 영역은 계속해서 살아있는 것이 아니라, 일시적으로 존재하는 임시파일이라는 것이다. 이 말은 한 번 요청을 받아서 파일을 업로드 하고, 사용자에게 응답을 해주는 요청에 대한 라이프 사이클이 종료되면 방금 업로드한 파일은 이후에 사용할 수 없도록 삭제 된다는 것이다. 따라서 이를 반드시 내가 지정한 경로에 복사하여 저장해야 한다

 

[ FileUploadController ]

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package controller;
 
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
 
import vo.PhotoVo;
 
@Controller
public class FileUploadController {
    
    // 스프링의 절대자 DispatcherServlet에게 어플리케이션을 달라고 한다. 
    // 해당 컨트롤러가 자동 생성(auto-detecting)시에는 Auto-wired가 된다. 
    // 단, 수동 생성시에는 안 된다.(지원하려면 <context:annotation-config />생성 전에 설치해야 한다.)
    // 자동 연결 = 자동 인젝션
    @Autowired
    ServletContext application;
    
    @Autowired
    HttpServletRequest request;
    
    @Autowired
    HttpSession session;
 
    // /upload1.do?title=제목&photo=a.jpg
    // 파일을 낱개로 따로 받는 경우 반드시 requestParam어노테이션을 사용해야 한다.
    // DispatcherServlet이 paremeter받아서 넘겨준다.
    // -> 업로드 파일 정보를 받아서 MultipartFile객체로 넘겨준다.(주의 : 임시파일임(응답하고 나중에 접근하려고 하면 삭제되는 임시 객체에 저장된다.) -> 나중에도 사용하기 위해 내가 지정한 위치에 파일을 복사해야 한다.)
    @RequestMapping("/upload1.do")
    public String upload1(String title,@RequestParam MultipartFile photo, Model model) throws Exception {
        
        //웹 경로 -> 절대 경로
        String web_path = "/resources/upload/";
        String abs_path = application.getRealPath(web_path);
        
        //업로드된 파일명을 얻어오기
        String filename = photo.getOriginalFilename();
        
        File f = new File(abs_path, filename);
        
        if(f.exists()) {//abs_path 경로에 동일 파일명이 존재하냐?
            
            //존재한다면 현재 시간을 기준으로 파일명을 붙여서 정리한다.
            
            long time = System.currentTimeMillis();//현재시간을 1/1000초 단위로 구한다. 
            
            filename = String.format("%d_%s", time, filename);
                    
            f = new File(abs_path, filename);
        }
        
        //임시파일 정보를 내가 원하는 위치로 복사한다. 
        photo.transferTo(f);
        
        //model을 통해서 데이터를 전달한다. => 결과적으로 request binding
        model.addAttribute("title", title);
        model.addAttribute("filename", filename);
        
        return "result_photo1";
    }
    
    
    // /upload2.do?title=제목&photo=a.jpg
    // 파일을 객체로 묶어서 받는 경우
    // DispatcherServlet이 paremeter받아서 넘겨준다.
    @RequestMapping("/upload2.do")
    public String upload2(PhotoVo vo, Model model) throws Exception{
        //웹 경로 -> 절대 경로
        String web_path = "/resources/upload/";
        String abs_path = application.getRealPath(web_path);
        
        MultipartFile photo = vo.getPhoto();
        
        //업로드된 파일명을 얻어오기
        String filename = photo.getOriginalFilename();
        
        File f = new File(abs_path, filename);
        
        if(f.exists()) {//abs_path 경로에 동일 파일명이 존재하냐?
            
            //존재한다면 현재 시간을 기준으로 파일명을 붙여서 정리한다.
            
            long time = System.currentTimeMillis();//현재시간을 1/1000초 단위로 구한다. 
            
            filename = String.format("%d_%s", time, filename);
                    
            f = new File(abs_path, filename);
        }
        
        //임시파일 정보를 내가 원하는 위치로 복사한다. 
        photo.transferTo(f);
        
        //model을 통해서 데이터를 전달한다. => 결과적으로 request binding
        vo.setFilename(filename);
        
        model.addAttribute("vo", vo);
        
        return "result_photo2";
    }
    
    // /upload2.do?title=제목&photo=a.jpg&photo=b.jpg
    // 복수개의 파일을 배열로 받기
    @RequestMapping("/upload3.do")
    public String upload3(String title, @RequestParam("photo") MultipartFile [] photo_array, Model model) {
        
        //웹 경로 -> 절대 경로
        String web_path = "/resources/upload/";
        String abs_path = application.getRealPath(web_path);
        
        int i=0;
        String filename1="no_file";
        String filename2="no_file";
        
        for(MultipartFile photo : photo_array) {
            //업로드된 파일명을 얻어오기
            String filename = photo.getOriginalFilename();
            
            File f = new File(abs_path, filename);
            
            if(f.exists()) {//abs_path 경로에 동일 파일명이 존재하냐?
                
                //존재한다면 현재 시간을 기준으로 파일명을 붙여서 정리한다.
                
                long time = System.currentTimeMillis();//현재시간을 1/1000초 단위로 구한다. 
                
                filename = String.format("%d_%s", time, filename);
                        
                f = new File(abs_path, filename);
            }
            
            //임시파일 정보를 내가 원하는 위치로 복사한다. 
            try {
                photo.transferTo(f);
                
                if(i==0) filename1 = filename;
                if(i==1) filename2 = filename;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            i++;
        }
        
        model.addAttribute("title", title);
        model.addAttribute("filename1", filename1);
        model.addAttribute("filename2", filename2);
        
        return "result_photo3";
    }
    
 
    // 클라이언트 측에서 multiple옵션으로 전송
    // 복수개의 파일을 리스트로 받기
    // @ModelAttribute -> 포워드 되는 뷰에 전송할 수 있는 속성 -> 결과적으로 model을 통해서 DS에게 데이터를 전달, 즉 포워딩 한 것이다.
    @RequestMapping("/upload4.do")
    public String upload4(@ModelAttribute("title"String title, @RequestParam("photo") List<MultipartFile> photo_list, Model model) {
        
        //웹 경로 -> 절대 경로
        String web_path = "/resources/upload/";
        String abs_path = application.getRealPath(web_path);
        
        List<String> filename_list = new ArrayList<String>();
        
        
        for(MultipartFile photo : photo_list) {
            //업로드된 파일명을 얻어오기
            String filename = photo.getOriginalFilename();
            
            File f = new File(abs_path, filename);
            
            if(f.exists()) {//abs_path 경로에 동일 파일명이 존재하냐?
                
                //존재한다면 현재 시간을 기준으로 파일명을 붙여서 정리한다.
                
                long time = System.currentTimeMillis();//현재시간을 1/1000초 단위로 구한다. 
                
                filename = String.format("%d_%s", time, filename);
                        
                f = new File(abs_path, filename);
            }
            
            //임시파일 정보를 내가 원하는 위치로 복사한다. 
            try {
                photo.transferTo(f);
                
                //실패한 것은 제외하고, transfer가 성공한 경우만 추가된다.
                filename_list.add(filename);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
            
        model.addAttribute("filename_list", filename_list);
        
        return "result_photo4";
    }
}
 
cs
 

실제로 내가 복사한 파일이 저장되는 경로는 아래와 같다. 여기에다가 저장하고 참고하도록 하자.

C:\Work\WebStudy\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\2022_0704_SpringFileUpload\resources\upload

입력과 출력의 코드는 기본 비즈니스 코드에서 크게 차이가 없기 때문에 주석을 참고하도록 하자.

[ input_fileupload1.jsp ] - 사진을 따로 받기 (따로 받는 경우에 메서드의 파라미터에 반드시 어노테이션!!)

혹시라도 파라미터이름과 메서드에서 요구하는 인자의 이름이 다르다면 이 또한 반드시 어노테이션을 사용해야 한다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
    function send1(f){
        var title = f.title.value.trim();
        var photo = f.photo.value;
        
        if(title==''){
            alert('제목을 입력하세요!');
            f.title.value = '';
            f.title.focus();
            return;
        }
        
        if(photo==''){
            alert('사진을 등록해주세요!!');
            return;
        }
        
        f.action = "upload1.do";
        f.submit();
    }
    
    function send2(f){
        var title = f.title.value.trim();
        var photo = f.photo.value;
        
        if(title==''){
            alert('제목을 입력하세요!');
            f.title.value = '';
            f.title.focus();
            return;
        }
        
        if(photo==''){
            alert('사진을 등록해주세요!!');
            return;
        }
        
        f.action = "upload2.do";
        f.submit();
    }
</script>
</head>
<body>
<form method="POST" enctype="multipart/form-data">
    제목:<input name="title"><br>
    사진:<input type="file" name="photo"><br>
    <input type="button" value="전송1(따로받기)" onclick="send1(this.form);">
    <input type="button" value="전송2(같이받기)" onclick="send2(this.form);">
</form>
</body>
</html>
cs

[ result_photo1.jsp ] - 사진을 따로 받아서 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<hr>
    제목 : ${title }
<hr>
<img src="resources/upload/${filename }" width="200"><br>
 
<!-- 경로는 URL경로를 기준으로 한다. 현재 jsp파일을 기준으로 하면 안된다. -->
<a href="input_fileupload1.jsp">다시해보기</a>
</body>
</html>
cs

[ input_fileupload2.jsp ] - 사진을 객체로 받기 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
    function send1(f){
        var title = f.title.value.trim();
        var photo1 = f.photo[0].value;
        var photo2 = f.photo[1].value;
        
        if(title==''){
            alert('제목을 입력하세요!');
            f.title.value = '';
            f.title.focus();
            return;
        }
        
        if(photo1==''){
            alert('사진1을 등록해주세요!!');
            return;
        }
        if(photo2==''){
            alert('사진2를 등록해주세요!!');
            return;
        }
        
        f.action = "upload3.do";
        f.submit();
    }
</script>
</head>
<body>
<form method="POST" enctype="multipart/form-data">
    제목:<input name="title"><br>
    <!-- spring에서는 복수개의 파일업로드시 반드시 이름(name)을 같게 줘야 한다. -->
    <!-- multiple속성을 사용하면 복수개의 파일을 업로드할 수 있지만, 업로드 된 파일의 순서를 제어할 수 없다. -->
    사진1:<input type="file" name="photo"><br>
    사진2:<input type="file" name="photo"><br>
    <input type="button" value="전송" onclick="send1(this.form);">
</form>
</body>
</html>
cs

[ result_photo2.jsp ] - 사진을 객체로 받아서 출력하기

원래는 라이프사이클이 종료되면 삭제되었어야 할 임시 파일 정보를 vo에 저장했다. 이는 편리한 방법이긴 하지만 생각보다 엄청난 메모리의 낭비에 기인한다고 하니 유의하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<hr>
    제목 : ${vo.title }
<hr>
<img src="resources/upload/${vo.filename }" width="200"><br>
 
<!-- 경로는 URL경로를 기준으로 한다. 현재 jsp파일을 기준으로 하면 안된다. -->
<a href="input_fileupload1.jsp">다시해보기</a>
</body>
</html>
cs

[ result_photo3.jsp ] - 복수개의 사진을 배열로 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<hr>
    제목 : ${title }
<hr>
<img src="resources/upload/${filename1 }" width="200">
<img src="resources/upload/${filename2 }" width="200"><br>
 
<!-- 경로는 URL경로를 기준으로 한다. 현재 jsp파일을 기준으로 하면 안된다. -->
<a href="input_fileupload2.jsp">다시해보기</a>
</body>
</html>
cs

 

 

[ input_fileupload3.jsp ] - multiple속성 사용하기

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
32
33
34
35
36
37
38
39
40
41
42
43
44
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
    function send1(f){
        
        //alert(f.photo.files.length);
        
        var title = f.title.value.trim();
        
        if(title==''){
            alert('제목을 입력하세요!');
            f.title.value = '';
            f.title.focus();
            return;
        }
        
        //multiple옵션은 files라는 배열에 저장된다.
        if(f.photo.files.length == 0){
            
            alert("업로드할 파일을 선택하세요.");
            
            return;
        }
        
        f.action = "upload4.do";
        f.submit();
    }
</script>
</head>
<body>
<form method="POST" enctype="multipart/form-data">
    제목:<input name="title"><br>
    <!-- spring에서는 복수개의 파일업로드시 반드시 이름(name)을 같게 줘야 한다. -->
    <!-- multiple속성을 사용하면 복수개의 파일을 업로드할 수 있지만, 업로드 된 파일의 순서를 제어할 수 없다. -->
    사진1:<input type="file" name="photo" multiple="multiple"><br>
    <input type="button" value="전송" onclick="send1(this.form);">
</form>
</body>
</html>
cs

[ result_photo4.jsp ] - 복수개의 사진을 리스트로 출력하기

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
32
33
34
<%@ 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="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
    #box{
        width: 700px;
        height: auto;
        padding:  10px;
        border: 2px solid blue;
    }
</style>
</head>
<body>
<hr>
    제목 : ${title }
<hr>
 
<div id="box">
    <c:forEach var="filename" items="${filename_list }">
        <img src="resources/upload/${filename }" width="200"><br>
    </c:forEach>
</div>
 
<!-- 경로는 URL경로를 기준으로 한다. 현재 jsp파일을 기준으로 하면 안된다. -->
<a href="input_fileupload3.jsp">다시해보기</a>
</body>
</html>
cs

'BackEnd > Spring' 카테고리의 다른 글

Spring_AOP_국비_Day87  (0) 2022.07.06
Spring_국비_Day86  (0) 2022.07.05
SpringMVC_DB_국비Day84  (0) 2022.06.30
SpringMVC_Parameter_국비_Day83  (0) 2022.06.29
SpringCollection_국비Day82  (0) 2022.06.28