본문 바로가기
코딩도전기/Spring Boot

Spring Boot - Scheduler / File Upload&Download

by 코도꼬마 2023. 6. 7.

Scheduler 

  • Spring Boot Scheduler는 별도의 라이브러리나 설정없이 사용 가능
  • 사용할 클래스에 @EnabledScheduling을 추가하고 사용할 메서드에 @Scheduled 메서드를 추가해 동작
  • 스케줄러는 프로그램과 함께 생명주기를 가져감(프로그램이 켜지면 실행되고 꺼지면 종료됨)
  • @Scheduled()을 사용하는 메서드는 독립적으로 움직임
@Scheduled(fixedDelay=1000) 이전 작업이 종료된 후 설정 시간 이후에 다시 시작
@Scheduled(fixedRate=1000) 설정된 시간마다 시작을 한다. 즉 이전 작업이 종료되지 않아도 시작
@Scheduled(cron = "* * * * * *") 초(0-59) 분(0-59) 시간(0-23) 일(1-31) 월(1-12) 요일(0-7)
- 각 자리에 입력 / 없으면 *로 표시

 

  • SchedulerModule
package kr.co.gudi.schedule;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class SchedulerModule {
	
	Logger logger = LoggerFactory.getLogger(getClass());
	
	@Scheduled(fixedDelay = 1000)
	public void fixedDelay() {
		logger.info("작업 종료 후 1초 후 실행");
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Scheduled(fixedDelay = 3000)
	public void fixedRate() {
		logger.info("3초 마다 실행");
	}
	
	// cron은 인터넷에 사용법이 많이 나와있음
	// 초,분,시,일,월,요일,년도(생략가능)
	//5초마다 : 5 <- 매분 5초(x) / 0/5 : 5초마다(O)
	@Scheduled(cron = "0/5 * * * * MON-FRI")
	public void cron() {
		logger.info("5초 마다 실행");
	}	
	
}

 

 

File Upload / Download

  • File Upload를 위해서 commons-fileupload & commons-io 라이브러리가 필요함
  • Boot에서 기본으로 해당 라이브러리를 가지고 있으므로 설정만 해주면 됨

 

# File Upload

  • application.properties
server.port=80

spring.mvc.view.prefix=/views/
spring.mvc.view.suffix=.jsp

// 업로드 가능한 하나의 파일 크기
spring.servlet.multipart.max-file-size=50MB
// 업로드 가능한 총 파일 크기
spring.servlet.multipart.max-request-size=500MB
// 파일 저장 경로
spring.servlet.multipart.location=C:/upload

 

  • home.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<style></style>
</head>
<body>
	<a href="fileList.do">파일 리스트 보기</a>

	<h3>파일 업로드</h3>
	<form action="upload.do" method="post" enctype="multipart/form-data">
		<input type="file" name="uploadFile"/>
		<button>전송</button>
	</form>
	<hr/>
	<h3>멀티파일 업로드</h3>
	<form action="multiUpload.do" method="post" enctype="multipart/form-data">
		<input type="file" name="files" multiple="multiple"/>
		<button>전송</button>
	</form>
</body>
<script></script>
</html>

 

  • result.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<style></style>
</head>
<body>
	<c:if test="${list.size()==0}"><p>업로드된 파일이 없습니다.</p></c:if>
	<c:if test="${list.size()>0}">
		<c:forEach items="${list}" var="file">
			<img src="photo.do?path=${file}" width="250">
			<a href="download.do?path=${file}">다운로드</a>
			<a href="delete.do?path=${file}">삭제</a>
			<br/>
		</c:forEach>
	</c:if>
	<p><a href="/">돌아가기</a></p>
</body>
<script></script>
</html>

 

  • FileController
package kr.co.gudi.controller;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import kr.co.gudi.service.FileService;

@Controller
public class FileController {
	
	private final FileService service;
	
	@Value("${spring.servlet.multipart.location}") private String root;

	public FileController(FileService service) {
		this.service = service;
	}
	
	Logger logger = LoggerFactory.getLogger(getClass());

	@GetMapping(value = "/")
	public String home() {
		return "home";
	}
	
	@PostMapping(value = "/upload.do")
	public String upload(MultipartFile uploadFile) {
		service.upload(uploadFile);
		return "redirect:/fileList.do";
	}
	
	@PostMapping(value = "/multiUpload.do")
	public String multiUpload(MultipartFile[] files) {
		service.multiUpload(files);
		return "redirect:/fileList.do";
	}
	
	@GetMapping(value = "/fileList.do")
	public String fileList(Model model) {
		ArrayList<String> list = service.filelist();
		model.addAttribute("list",list);		
		return "result";
	}
	
	@GetMapping(value = "/photo.do")
	public ResponseEntity<Resource> showImg(String path) {
		logger.info("show file : "+root+"/"+path);
		
		//body
		Resource body = new FileSystemResource(root+"/"+path);
		
		//header
		HttpHeaders header = new HttpHeaders();
		
		String type;
		try {
			type = Files.probeContentType(Paths.get(root+"/"+path));
			logger.info("type : "+type);
			header.add("Content-type", type);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//body, headers, status
		return new ResponseEntity<Resource>(body, header, HttpStatus.OK);
	}
	
	@GetMapping(value = "/download.do")
	public ResponseEntity<Resource> download(String path) {
		
		//body
		Resource body = new FileSystemResource(root+"/"+path);
		
		//header
		HttpHeaders header = new HttpHeaders();
		
		
		try {
			String fileName = "이미지"+path.substring(path.lastIndexOf("."));
			//한글 파일명은 깨질 수 있으므로 인코딩을 해줘야함
			fileName = URLEncoder.encode(fileName,"UTF-8");
			
			// text/...은 문자열, image/... 이미지, application/octet-stream은 바이너리 데이터
			header.add("Content-type", "application/octet-stream");
			
			//content-Disposition : 내려보낼 데이터가 문자열(inline)인지 파일(attatchment)인지 알려줌
			header.add("content-Disposition", "attatchment;fileName=\""+fileName+"\"");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//body, headers, status
		return new ResponseEntity<Resource>(body, header, HttpStatus.OK);
	}
	
	@GetMapping(value = "delete.do")
	public String delete(String path) {
		service.delete(path);		
		return "redirect:/fileList.do";
	}

}

 

  • FileService
package kr.co.gudi.service;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class FileService {
	
	Logger logger = LoggerFactory.getLogger(getClass());
	
	@Value("${spring.servlet.multipart.location}") private String root;

	public void upload(MultipartFile uploadFile) {
		//1.파일명 추출
		String fileName = uploadFile.getOriginalFilename();
		
		//2.새파일 생성(현재시간+확장자)
		String ext = fileName.substring(fileName.lastIndexOf("."));
		String newFileName = System.currentTimeMillis()+ext;
		logger.info(fileName+" >> "+newFileName);
		
		//3.파일 저장
		try {
			byte[] bytes = uploadFile.getBytes();
			Path path = Paths.get(root+"/"+newFileName);
			Files.write(path, bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void multiUpload(MultipartFile[] files) {
		
		for (MultipartFile file : files) {
			upload(file);
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

	public ArrayList<String> filelist() {

		ArrayList<String> list = new ArrayList<String>();
		File[] files = new File(root+"/").listFiles();
		logger.info("get file list : "+files);
		for(File file : files) {
			list.add(file.getName());
			logger.info("get file : "+file);
		}
		
		return list;
	}

	public void delete(String path) {
		File f = new File(root+"/"+path);
		
		if(f.exists()) {
			f.delete();
		}
	}	

}

 

 

Tomcat

  • Spring Boot에서 제공하는 내장 Tomcat을 사용할 수도 있고 실제 Tomcat을 설치해서 사용도 가능
Internal Tomcat External Tomcat
Tomcat 을 따로 설치 할 필요가 없음 더 많은 트래픽 처리를 할 수 있음
Run AS > Spring Boot App Run As > Run On Server
별도의 설정이 필요 없음(대신 단순한 형태로 이용) Sever.xml이나 web.xml로 여러 설정이 가능
수정이 거의 일어나지 않는 운영에 유용 수정이 자주 일어나는 운영에 유용
Jar 형태로 빌드 할 때 주로 사용 War 형태로 빌드 할 때 주로 사용

 

Build

  • 개발한 소스가 실행 가능한 프로그램으로 변환 하는 작업
  • Eclipse에서는 plugin을 통해 복사된 Tomcat에 소스를 동작시켜 줌(실제 빌드가 되었다고 보기 어려움)
  • 실제 동작할 수 있는 WAR 파일을 사용해 실제 Tomcat에 적용하여 사용 가능
  • Build 전 확인 사항
    • Window > Preferences 선택
    • java > Installed JREs - JRE로 되어있다면 JDK 를 추가해야함
    • 기존 JRE는 삭제 해도 됨

Written by zer0box

  • 에러로 인해 빌드되지 않는 경우 Build 전 Test 진행 문제 발생 시에도 무시하도록 아래 내용을 pom.xml에 추가
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
   	 <testFailureIgnore>true</testFailureIgnore>
    </configuration>
</plugin>

 

 

WAR & JAR

WAR(Web Archive) JAR(Java Archive)
프로젝트가 Tomcat 안에서 동작 프로젝트가 하나의 자바 프로그램으로 동작
가장 일반적인 형태의 빌드 솔루션 형태가 아닌 일반 서비스에서는 사용하지 않는 편이 좋음
(수용 한계가 적음)
외부의 Tomcat webapp 폴더에 파일을 넣어야 함 Tomcat이 내장되어 있기 때문에 Tomcat에 파일을 넣을 필요가 없음
프로젝트 생성 시 packaging을 war로 선택 프로젝트 생성 시 packaging을 jar로 선택해야 함
(보통 jar 빌드의 경우 view page가 없는 것이 좋음)

 

# WarBuild

  • 프로젝트 생성 후 Run As >> Maven install >> target 폴더의 war 파일을 tomcat의 webapp 폴더에 ROOT로 저장
  • apache-tomcat-9.0.71 >> bin 에서 cmd 켜서 start up으로 프로젝트 실행

 

# JarBuild

  • 프로젝트 생성 후 Run As >> Maven install >> target 폴더의 jar 파일을 원하는 폴더에 ROOT로 저장
  • jar 파일이 저장된 폴더에서 cmd 켜서 java -jar ROOT.jar 로 프로젝트 실행