참으로 어렵다.
파일 업로드 구현을 위해서 찾아본 자료도 참 많은데, 실상 구현을 하려니 너무 다른 방법들로 구현된 경우가 많아서 어디를 참고해야 할지 고민이 되었다.
허나, 어쩌랴... 뭐 방법이야 다양하겠지만, 우선적으로 고려대상은 Spring의 레퍼런스로 잡았다.
이번에도 시나리오를 작성해 보자.(마음이 답답하다. 어찌 풀어가야 할지...)
1. 설정파일에서 크게 2가지를 먼저 추가해본다.
가. 첨부파일용 CommonsMultipartResolver 설정
(org.springframework.web.multipart.commons.CommonsMultipartResolver)
나. 업로드 디렉토리 지정용 FileSystemResource 설정
(org.springframework.core.io.FileSystemResource)
2. MultipartFile을 담아 둘 빈 작성
3. 업로드를 처리할 컨트롤러 작성
4. 실제 업로드를 수행할 업로드용 빈 작성
5. 설정 파일에서 빈 묶기
가. 첨부파일용 CommonsMultipartResolver 설정
(org.springframework.web.multipart.commons.CommonsMultipartResolver)
나. 업로드 디렉토리 지정용 FileSystemResource 설정
(org.springframework.core.io.FileSystemResource)
2. MultipartFile을 담아 둘 빈 작성
3. 업로드를 처리할 컨트롤러 작성
4. 실제 업로드를 수행할 업로드용 빈 작성
5. 설정 파일에서 빈 묶기
아... 눈 앞이 아른해진다.
1번부터 차근차근 진행해 보겠다. 물론 순서는 대략적으로 잡아보았다.
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000" />
<property name="uploadTempDir" ref="uploadDirResource" />
</bean>
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000" />
<property name="uploadTempDir" ref="uploadDirResource" />
</bean>
최대 10메가 바이트(정확히 10메가는 아니지만)까지 업로드가 가능하도록 설정하고 있다.
그리고, 업로드용 디렉토리를 지정하는데 이건 API를 보면 알겠지만 Resource를 받게 되어 있다.
uploadTempDir로 받는 uploadDirResource를 작성해 보자.
<bean id="uploadDirResource"
class="org.springframework.core.io.FileSystemResource">
<constructor-arg>
<value>디렉토리를 지정합니다.</value>
</constructor-arg>
</bean>
class="org.springframework.core.io.FileSystemResource">
<constructor-arg>
<value>디렉토리를 지정합니다.</value>
</constructor-arg>
</bean>
일단 빈을 이렇게 설정해 두자.
이제, MultipartFile을 담을 빈을 작성해 보자. 이 녀석은 이후에 컨트롤러에 commandClass로 넣어줄 거다.
public class FileUploadBean {
// Constructor
public FileUploadBean() {}
private MultipartFile file;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
간단하다. 그냥 MultipartFile을 setter/getter로 공개하고 있다.// Constructor
public FileUploadBean() {}
private MultipartFile file;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
MultipartFile로 만든 이유는, 이게 아무래도 제일 접근하기 쉬웠다. 다른 녀석은 뭐 잡다하게 더 코딩이 늘어나서... 따로 캐스팅이 필요없어져서 편해서 사용했다.(더 고민이 필요한 부분이겠다. 아직도 이게 왜 필요한지 감감할 뿐이다.)
컨트롤러를 만들어보자.
SimpleFormController를 확장해서 만들건데, 이건 개발자가 어떻게 구성하느냐에 따라 많은 차이를 보인 부분이기도 하다.(여러 소스를 뒤져보니, 이 부분에서 너무 다양한 구현 방법이 있어서 혼란 그 자체였다.)
가장 심플하게 만들어 보는게 주 목적이니, 간단하게 onSubmit 메소드만 구현해야겠다.
protected ModelAndView onSubmit(
HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws ... {
MultipartHttpServletRequest multipartRequest =
(MultipartHttpServletRequest) request; // request를 casting 한다.
MultipartFile file = multipartRequest.getFile("filex"); // 여기서 쓰인 filex는 html 폼에서
// input type=file name=filex
// 이거랑 맞아야 한다.
// 이래 놨더니, file로 업로드되는 파일에 대한 갖가지 정보를
// 뽑아 볼 수 있었다. API 참고
uploadFile.setMultipartFile( file ); // uploadFile은 DI 된 것.
// 실제 업로드를 수행하는 빈이다.
uploadFile.setRealUploadPath( realUploadPath ); // realUploadPath도
// 빈설정 파일에서 DI 했다.
int result = uploadFile.upload(); // uploadFile에게 업로드하라고 위임
....
....
// result에 따른 에러 처리
return new ModelAndView .... // 처리가 끝났으니 논리적인 뷰 이름을 지정해서 이동
}
HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws ... {
MultipartHttpServletRequest multipartRequest =
(MultipartHttpServletRequest) request; // request를 casting 한다.
MultipartFile file = multipartRequest.getFile("filex"); // 여기서 쓰인 filex는 html 폼에서
// input type=file name=filex
// 이거랑 맞아야 한다.
// 이래 놨더니, file로 업로드되는 파일에 대한 갖가지 정보를
// 뽑아 볼 수 있었다. API 참고
uploadFile.setMultipartFile( file ); // uploadFile은 DI 된 것.
// 실제 업로드를 수행하는 빈이다.
uploadFile.setRealUploadPath( realUploadPath ); // realUploadPath도
// 빈설정 파일에서 DI 했다.
int result = uploadFile.upload(); // uploadFile에게 업로드하라고 위임
....
....
// result에 따른 에러 처리
return new ModelAndView .... // 처리가 끝났으니 논리적인 뷰 이름을 지정해서 이동
}
uploadFile을 어떤 이는 static으로 구성하기도 했는데, 난 그냥 외부에서 주입하도록
빈으로 선언했다. 잘 못한건가? -_-; 그렇게 하고파서...
uploadFile에게 일을 시켰으니, 어떻게 일을 처리할지 알아보자.
uploadFile의 upload 메소드만 보자.
public int upload() throws ... {
// 업로드 파일에 대한 기초정보를 얻는다.
// MultipartFile에 대한 API를 보면 얻을 수 있는 정보를 알 수 있다.
String fileName = multipartFile.getName(); // multipartFile은 컨트롤러에서 전해줬다.
String originalFileName = multipartFile.getOriginalFilename();
String contentType = multipartFile.getContentType(); // 별걸 다 알아다 준다.
long fileSize = multipartFile.getSize();
// 업로드되는 파일 이름이 중복되면 안되므로 UUID를 써서 엄청 긴 이름을 주겠다.
UUID randomUUID = UUID.randomUUID();
// 파일 용량 체크
....
// 파일 확장자 체크
...
// 스트림을 설정한다. 결국은 스트림으로 파일을 전송한다.
InputStream inputStream = null;
OutputStream outputStream = null;
try {
if( fileSize > 0 ) {
inputStream = multipartFile.getInputStream(); // 스트림을 얻어올 수 있다.
File realUploadDir = new File(업로드될 디렉토리); // 업로드 디렉토리도 받아뒀다.
if( !realUploadDir.exists() )
realUploadDir.mkdirs(); // 업로드 되는 디렉토리가 없다면, 만들어준다.
String organizedFilePath = 디렉토리 + "/" + randomUUID + "_" + originalFileName;
outputStream = new FileOutputStream( organizedFilePath );
// 버퍼를 지정해서 올린다.
int readBytes = 0;
byte[] buffer = new byte[8192];
while((readBytes = inputStream.read(buffer, 0, 8192)) != -1 ) {
outputStream.writer(buffer, 0, readBytes);
}
}
} catch( ... ) {
} finally {
// 스트림을 반드시 닫는다.
outputStream.close();
inputStream.close();
}
return 결과값; // 이 결과값이 위의 컨트롤러에 전달된다.
}
// 업로드 파일에 대한 기초정보를 얻는다.
// MultipartFile에 대한 API를 보면 얻을 수 있는 정보를 알 수 있다.
String fileName = multipartFile.getName(); // multipartFile은 컨트롤러에서 전해줬다.
String originalFileName = multipartFile.getOriginalFilename();
String contentType = multipartFile.getContentType(); // 별걸 다 알아다 준다.
long fileSize = multipartFile.getSize();
// 업로드되는 파일 이름이 중복되면 안되므로 UUID를 써서 엄청 긴 이름을 주겠다.
UUID randomUUID = UUID.randomUUID();
// 파일 용량 체크
....
// 파일 확장자 체크
...
// 스트림을 설정한다. 결국은 스트림으로 파일을 전송한다.
InputStream inputStream = null;
OutputStream outputStream = null;
try {
if( fileSize > 0 ) {
inputStream = multipartFile.getInputStream(); // 스트림을 얻어올 수 있다.
File realUploadDir = new File(업로드될 디렉토리); // 업로드 디렉토리도 받아뒀다.
if( !realUploadDir.exists() )
realUploadDir.mkdirs(); // 업로드 되는 디렉토리가 없다면, 만들어준다.
String organizedFilePath = 디렉토리 + "/" + randomUUID + "_" + originalFileName;
outputStream = new FileOutputStream( organizedFilePath );
// 버퍼를 지정해서 올린다.
int readBytes = 0;
byte[] buffer = new byte[8192];
while((readBytes = inputStream.read(buffer, 0, 8192)) != -1 ) {
outputStream.writer(buffer, 0, readBytes);
}
}
} catch( ... ) {
} finally {
// 스트림을 반드시 닫는다.
outputStream.close();
inputStream.close();
}
return 결과값; // 이 결과값이 위의 컨트롤러에 전달된다.
}
길었지만, 다 되었다.
이 녀석을 설정 파일에서 빈으로 선언해주고, 컨트롤러에 DI해주면 되겠다.
그럼 이제, 지금까지 만든 컨트롤러와 파일업로드 처리 빈을 설정파일에 기록해 보자.
<bean id="uploadBean" class="company.project.mvc.common.util.UploadFile" />
<bean id="fileUpladController" class="company.project......아까만든 컨트롤러">
<property name="commandClass">
<value>company.project....FileUploadBean</value>
</property>
<property name="realUploadPath">
<value>업로드 될 위치</value>
</property>
<property name="uploadFile" ref="uploadBean" />
<property name="formView" value="폼의 논리적인 이름" />
<property name="successView" value="성공시 갈 논리적인 뷰 이름" />
</bean>
<bean id="fileUpladController" class="company.project......아까만든 컨트롤러">
<property name="commandClass">
<value>company.project....FileUploadBean</value>
</property>
<property name="realUploadPath">
<value>업로드 될 위치</value>
</property>
<property name="uploadFile" ref="uploadBean" />
<property name="formView" value="폼의 논리적인 이름" />
<property name="successView" value="성공시 갈 논리적인 뷰 이름" />
</bean>
이제 다 되었다. 묶는거 까지 다 되었다.
테스트를 해보는 일만 남았다.
less..
테스트 결과는?
너무 잘 된다. 다행이다. 더 생각해보고 싶은건, Commons에서 제공하는 MultipartUpload와 Spring과 어떻게 잘 엮어지는지 그게 궁금하다.
솔직히 원리를 100%다 이해는 못했다.
그래서, 챙피하다. 된걸로 만족한다면, 챙피한 일이다.
너무 잘 된다. 다행이다. 더 생각해보고 싶은건, Commons에서 제공하는 MultipartUpload와 Spring과 어떻게 잘 엮어지는지 그게 궁금하다.
솔직히 원리를 100%다 이해는 못했다.
그래서, 챙피하다. 된걸로 만족한다면, 챙피한 일이다.
less..
더 생각하고, 더 이해해 보길 기대해 본다.
반응형
'Spring framework > Spring' 카테고리의 다른 글
Spring Validation 간략 소개 (0) | 2010.05.07 |
---|---|
CAS 인증(Central Authentication Service) (0) | 2010.05.07 |
Spring IDE 2.0 final 기사 간단 요약 (0) | 2010.05.07 |
목록에서 페이징에 도전 [3부] (0) | 2010.05.07 |
목록에서 페이징에 도전 [2부] (0) | 2010.05.07 |
최근댓글