Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
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
Tags
more
Archives
Today
Total
관리 메뉴

dearbeany

[Spring] SpringMVC 요청처리흐름 | 파일업로드와 다운로드 실습 본문

Spring

[Spring] SpringMVC 요청처리흐름 | 파일업로드와 다운로드 실습

dearbeany 2022. 10. 19. 17:22
더보기

<File Upload>
Commons-fileupload 라이브러리 세팅
commonsFileUpload 객체를 꼭 multipartResolver 란 이름으로 빈등록
jsp 파일(사용자 ui) input type file로 파일 업로드할 수 있도록 ui 제공 (enctype=”multipart/form-data”)
파일 파라미터 키 값에 대응되도록 MultipartFile 을 수신
그 다음 파일 원하는 곳에 저장

<File Download>
resources 태그를 통해서 예외적으로 파일이 있는 자원 위치를 열어주든가
downloadView 응답을 구현

■ 파일 업로드

pom.xml로 들어가보면?

servlet-context.xml : 웹과 관련된 설정들, DispatcherServlet을 등록해서 쓰고 있다.

root-context.xml : 그 외의 기타 설정들

 

파일업로드를 위한 세팅을 먼저 해보자. pom.xml 에 라이브러리 추가

https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/1.4

// pom.xml
<!-- 파일 업로드 -->
<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.4</version>
</dependency>

 

MainContoller 파일 생성

하기 전에... Spring MVC 요청처리 흐름을 파악해볼까?

더보기
  1. 클라이언트의 요청을 DispatcherServlet이 받음
  2. HandlerMapping에 의해 해당 요청을 어떤 Controller가 처리할지 결정
  3. Controller에 의해 요청 처리
  4. 결과를 보여줄 View와 data를 ModelAndView에 담아 반환
  5. ViewResolver에 의해 실제 결과를 보여줄 View를 결정하고 View에 ModelAndView를 반환
  6. DispatcherServlet은 View가 만들어낸 결과로 응답

* Spring MVC 요청 처리 흐름  

Spring MVC - 요청 처리 흐름

(1) 클라이언트가 요청한다.

http://localhost:8080/mvc/

더보기

'mvc'는 컨텍스트 루트 '/' 이후로는 실제 요청

* 컨텍스트 루트: 서버에 프로젝트를 여러 개 올렸을 때 구분자역할

 

(2) 서버에게 요청이 날라가면 DispathcerServlet이 요청을 받아서 Hanlder에게 넘긴다.

 

(3) HandlerMapping이 받아서 어떤 Controller가 처리하면 좋을지 결정한다.

 

(4) MainController가 GET방식으로 들어온 요청을 받아서 요청에 해당하는 @GetMapping("/")된 메소드를 실행한다.

@Controller
public class MainController {
	@GetMapping("/")
	public String showIndex() {
		return "index";
	}
}

(5) MainController가 반환할 땐 ModelAndView의 형태로 돌려주지만, 메소드의 반환형태가 String이기에 리턴된 "index"를 viewname으로 사용한다. (viewname = index)

 

(6) ViewResolver(servlet-context.xml에 존재)가 동작을 하면서 가지고 있는 viewname에 접두사 접미사를 붙여서 경로를 완성하여 돌려준다. 

<!-- servlet-context.xml -->
<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.ssafy.mvc" />

→ 이를 통해 완성된 경로!

/WEB-INF/views/index.jsp

 

(7) 완성된 경로를 호출하여 페이지를 만든 후 클라이언트에게 결과를 반환한다.

 

(8) 결과적으로, 프로젝트를 실행하면 index.jsp 페이지가 등록한다!

 

 


 

 

 

그렇다면 지금부터 파일을 업로드해보자.

 

1. index.jsp 에 파일 입력폼을 만든 후 서버 전송하기 

먼저 파일이라는 것은 입력을 받을 수 있어야 하고, 이 파일을 서버에 전송해주어야 한다.

<form action="" method=""> 태그를 사용하자!

<!-- index.jsp -->
	<h1>파일 업로드 하기 </h1>
	<form action="upload" method="post">
		<input type="file" />
		<input type="submit" />
	</form>

- 파일 전송 시 method는 POST방식을 사용해야 한다.

(GET은 url이 노출되며 용량제한. POST는 key-value형식으로 날아가면서 용량제한이 없다)

 

 

 

파일을 업로드 후(왼) → 제출하면 오류 발생(오) 

왜냐하면?

(1) html, jsp에 올린 데이터를 서버에서 어떻게 구분하나? name 속성을 지정해야한다.

(2) upload에 대한 컨트롤 맵핑을 해주지 않았음

 

 

	<h1>파일 업로드 하기 </h1>
	<form action="upload" method="post" enctype="multipart/form-data">
		<input type="file" name="upload_file"/>
		<input type="submit" />
	</form>

- 인코딩타입 && file에 대한 name 지정 필수!

 

 

2. MainController 에서 upload 액션 매핑 

// MainController.java
	@PostMapping("upload")
	public String upload(MultipartFile upload_file) {
		System.out.println(upload_file);
		return "result";
	}

- upload에 대한 컨트롤러 매핑 완료!

- 파라미터명은 index.jsp에서 입력으로 받은 name명과 동일해야 한다. 다르게 할 경우 별도로 @RequestParam("upload_file") 해주어야 한다.

- viewname은 result로 반환했다. ViewResolver에서 경로완성해서 반환한다.

/WEB-INF/views/result.jsp

 

 

 

 

3. servlet-context.xml에서 MultipartResolver를 bean 세팅 

 

파일 제출 누를 시 결과

- 지금까지의 실행결과. multipart에 대한 설정이 필요하다.

- Commons-fileupload 라이브러리 세팅

<!-- pom./xml -->
		<!-- file upload 처리를 위해 추가한다. -->
		<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.4</version>
		</dependency>

- commonsFileUpload 객체를 꼭 multipartResolver 란 이름으로 빈등록

- 즉, multipartResolver를 bean으로 등록해서 데이터로 넘어온 걸 쪼개서 사용하자

 

<!-- servlet-context.xml -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<beans:property name="maxUploadSize" value="10485760" />
	<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>

- 파일 크기 용량을 10MB로 제한해준다. (performance 향상을 위해)

 

 

 

4. MainController 에서 ModelAndView 반환하기 

// MainController.java
	@Autowired
	private ServletContext servletContext;

	@GetMapping("/")
	public String showIndex() {
		return "index";
	}

	@PostMapping("upload")
	public String upload(MultipartFile upload_file, Model model) {
		String uploadPath = servletContext.getRealPath("/file");
		String fileName = upload_file.getOriginalFilename();
		File target = new File(uploadPath, fileName);

		// 만약 file이라고 하는 경로가 없다면 만들어주는 게 먼저
		if (!new File(uploadPath).exists()) {
			new File(uploadPath).mkdir();
		}

		// 파일을 카피를 떠서 저장한다. 데이터를 바이트배열로 만들어서 어디다 쓸 지 지정해준다
		try {
			FileCopyUtils.copy(upload_file.getBytes(), target);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		model.addAttribute("fileName", fileName);
		return "result";
	}

- @Autowired → new 해서 쓰는 게 아니니까, 의존성 주입해서 쓴다는 의미 (있는 걸 가져다 쓰자!)

- 넘겨받은 파일(upload_file)을 가져다가 서버에 저장해보자.

- ModelAndView 형태가 아니므로, 데이터를 담는 바구니인 Model을 사용하여 결과창에 반환해주자.

 

제출 후 파일명을 출력해보자. 파일이 서버로 정상제출됐음을 확인하였다.

- 그렇다면, 제출된 파일이 /file 경로에 잘 저장되어있는 걸 어디서 확인하면 될까?

- 톰캣(WAS)에서 확인해야한다! 우리 프로젝트는 톰캣에 올라가 있는 상태다.

- 파일을 업로드 한다 → 복사본을 톰캣 서버에 저장한다 → 서버로부터 파일을 불러온다

- 등록해논 파일을 서버로부터 가져와서 보는 방법은 없을까?

 

 

 

5. 업로드한 파일 확인하기 

<!-- servlet-context.xml -->
<resources mapping="/file/**" location="/file/" />
<!-- result.jsp -->
<a href="/mvc/file/${fileName }">${fileName }</a>

 

링크 클릭 시 업로드 한 파일을 확인할 수 있다.

- 결과적으로 파일을 업로드 하는 순간 서버에 저장된다.

- 지금부터는 파일을 다운로드 해보자!

 

 

 


 

■ 파일 다운로드

 

1. FileDownLoadView.java 생성 

더보기
@Component
public class FileDownLoadView extends AbstractView {
	public FileDownLoadView() {
		setContentType("application/download; charset=UTF-8");
	}

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		ServletContext ctx = getServletContext();
		String realPath = ctx.getRealPath("/file");

		Map<String, Object> fileInfo = (Map<String, Object>) model.get("downloadFile"); // 전송받은 모델(파일 정보)

		String filename = (String) fileInfo.get("fileName"); // 파일 경로
		File file = new File(realPath, filename);

		response.setContentType(getContentType());
		response.setContentLength((int) file.length());

		String header = request.getHeader("User-Agent");
		boolean isIE = header.indexOf("MSIE") > -1 || header.indexOf("Trident") > -1;
		String fileName = null;
		// IE는 다르게 처리
		if (isIE) {
			fileName = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
		} else {
			fileName = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
		}
		response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
		response.setHeader("Content-Transfer-Encoding", "binary");
		OutputStream out = response.getOutputStream();
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			FileCopyUtils.copy(fis, out);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		out.flush();
	}
}

 

2. servlet-context.xml 에서 bean 등록 

<!-- servlet-context.java -->
<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<beans:property name="order" value="0"></beans:property>
</beans:bean>

- 생성 후에 servlet-context.xml에서 bean으로 등록해준다. (이전에 context:component-scan 으로 미리 등록돼있음)

 

 

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />
	<resources mapping="/file/**" location="/file/" />

- 사용자가 resources 폴더에 접근할 수 있도록 file폴더 등록해서 열어준다.

 

 

 

 

3. result.jsp 

<a href="download?fileName=${fileName }">${fileName }</a>

- 링크 클릭 시 download라는 요청을 날리는데, 이 때 쿼리스트링으로 fileName이 파일의 이름으로 담겨서 날아간다.

 

 

4. download 요청 컨트롤러에서 매핑 

// MainController.java
	@GetMapping("download")
	public String download(Model model, String fileName) {
		Map<String, Object> fileInfo = new HashMap<>();
		fileInfo.put("fileName", fileName);
		model.addAttribute("downloadFile", fileInfo);
		
		return "fileDownLoadView";
	}

- a태그로 오는 것은 GET방식. fileName으로 요청이 넘어왔다.

- fileDownLoadView로 보내는데, FileDownloadView를 bean으로 등록 후 별도의 id를 설정해주지 않았으므로 알아서 맨 앞글자를 소문자로 바꾼 게 bean이름이 된다. 

- downloadFile이라는 객체를 생성해서 리턴하는 곳에 보내준다.

 

 

4. 파일 다운로드 해보기 

 

링크 클릭 시 파일이 다운로드 된다.