ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 학원 day93. View를 이용한 파일 다운로드
    기록 2023. 1. 13. 15:55

    제목, 내용, 첨부파일, 태그를 하나의 화면에 담는다.

    하나의 폼에서 입력되는 값이 여러개의 테이블에 저장되는 경우가 많다.

    모든 입력값을 수용할 수 있는 클래스를 만든다.

    왼쪽은 요청메세지에 최적화된 클래스

    오른쪽은 테이블에 최적화된 클래스

     

    입력폼의 값들이 하나의 테이블에 담기는게 아니라 여러 테이블에 저장될 경우,

    입력폼의 값들을 다 받을 수 있는 Form을 만들고 그 Form을 서비스한테 전해주고, 서비스에서 필요한 값만 꺼내서 각각의 vo로 만든다음에 그 vo를 전해줘서 db에 저장시킨다. 

    테이블을 가장 잘 표현하는 게 vo이기 때문에

     

    tags는 동적으로 추가되는 거라서 하나도 없으면 null이 올 수 있다.

    반면, title, content는 name이 있기 때문에 입력을 안해도 null이 올 수 없고, 빈문자열이라도 온다.


    db에서 가져와서 dto에 담아서 전해주는 작업을 하기도 하고, (출력화면을 닮은 Dto를 만든다.)

    폼에 있는 것을 꺼내서 vo에 담아서 db안에 넣는 일을 하기도 한다. (입력폼을 닮은 Form클래스를 만든다.)

    값을 표현할 때는 출력화면을 표현하는 Dto객체를 정의하고, 필요한 정보를 전부 조회해서 DTO에 저장한다.

     

    입력할 때는 입력폼에 표현하는 Form 객체를 정의하고, Form 객체에서 각 테이블에 저장할 값을 VO객체로 복사해서 테이블에 저장시키자. 

     

    View가 하나 만들어졌다는 것은 새로운 컨텐츠 타입의 응답을 클라이언트로 보낼 수 있게 되는 것이다.

    스프링 View는 기본적으로 RedirectView와 JstlView를 제공해준다.

    redirect: 라고 적으면 DispatcherServlet은 RediectView의 render를 실행하고, jsp경로를 적으면 JstlView의 render를 실행한다.

    DispatcherServlet은 자기가 전달받은 View의 render만 실행한다.

    컨트롤러메소드 안에는 요청핸들러메소드가 존재하는데 2가지 경우가 있다.

    String을 반환하는 경우(view이름만 담아서 반환하는 경우)와, ModelAndView객체에 view를 담아서 반환하는 경우이다.

    String을 반환하는 경우, return "redirect:list"는 재요청URL을 응답으로 보내는 것이고, return "post/detail"은 jsp로 이동하는것이다.

    요청핸들러메소드를 실행하는 주체는 항상 HandleAdapter이다. 

    HandleAdapter는 최종적인 결과물로 항상 ModelAndView라는걸 DispatcherServlet에게 반환되야 한다.

    ModelAndView에는 Model, viewName, View가 들어있다.

    View가 전달되었을 때에는 View객체의 render를 실행하고, 

    viewName만 들어있을 경우에는 DispatcherServlet은 ViewResolver에게 질의를 한다. 

    ViewResolver는 RedirectView객체와 JstlView객체를 갖고 있다. RedirectView일 때는 재요청 URL을 응답으로 가는 것이고, JstlView가 반환되면 JSP로 내부이동하는 것이다. 

    지금까지는 viewResolver가 반환하는 RedirectView 또는 JstlView를 사용했지만,

    파일을 다운로드해야하므로 DownloadView를 만들어야 한다.

     

    파일다운로드뷰 클래스 만들기

    자동으로 스프링 빈으로 등록되도록 FileDownloadView클래스에 @Component 어노테이션을 붙인다.

    (스프링의 빈으로 등록되면 컨트롤러에서 @Autowired private FileDownloadView fileDownloadView로 의존성 주입받을 수 있다.)

    view를 직접 구현해도되지만 보통 AbstractView를 상속받아서 구현한다.

    renderMergedOutputModel 메소드를 구현한다.

    @Controller
    @RequestMapping("/post")
    @SessionAttributes({"modifyPost"})
    public class PostController {
    	
    	private final String directory = "c:/files";
    	
    	@Autowired
    	private PostService postService;
    	@Autowired
    	private FileDownloadView fileDownloadView;
    
        @GetMapping("/download")
    	public ModelAndView fileDownload(@RequestParam("filename") String filename) {
    		// 지정된 파일정보를 표현하는 File객체 생성, 파일이 존재하지 않더라도 File객체는 생성할 수 있다.
    		File file = new File(directory, filename);
    		// 파일이 존재하지 않으면 예외를 던진다.
    		if (!file.exists()) {
    			throw new ApplicationException("["+filename+"] 파일이 존재하지 않습니다.");
    		}
    		
    		ModelAndView mav = new ModelAndView();
    		
    		// ModelAndView의 Model에 값 저장
    		mav.addObject("file", file);
    		
    		// ModelAndView의 View에 fileDownloadView 저장
    		mav.setView(fileDownloadView);
    		
    		return mav;
    	}
        
    }
    @Component   // 스프링의 빈으로 자동 등록 되게 함
    public class FileDownloadView extends AbstractView {    // View라는 인터페이스를 직접구현해도 되지만 보통은 AbstractView라는 추상클래스를 상속받아 구현한다.
    
    	@Override
    	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
    			HttpServletResponse response) throws Exception {
    		
    		File file = (File) model.get("file");
    		
    		// application/octet-stream - 일반적인 바이너리 데이터에 대한 컨텐츠 타입이다. (메모장으로 읽어들일 수 없는 파일)
    		setContentType("application/octet-stream");
    		// 응답메세지의 헤더부에 다운로드되는 첨부파일을 이름으로 설정한다.
    		// attachment; 는 브라우저에서 파일을 열지 않고, 항상 다운로드되게 한다.
    		// URLEncoder.encode(text,encoding)은 텍스트를 지정된 인코딩 방식으로 변환시킨다.
    		// 텍스트에 한글이 포함되어 있는 경우 utf-8방식으로 인코딩(변환)하지 않으면 한글이 전부 깨진다.
    		response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "utf-8"));
    		
    		// 파일을 읽어오는 입력스트림 객체를 생성한다.
    		InputStream in = new FileInputStream(file);
    		// 브라우저와 연결된 출력스트림을 획득한다.
    		OutputStream out = response.getOutputStream();
    		
    		// 입력스트림으로 읽은 데이터를 출력스트림으로 복사해서 출력시킨다.
    		IOUtils.copy(in, out);
    	}
    }

    현재 웹서버가 내 컴퓨터 안에 있어서 헷갈릴 수 있으나, 원래는 웹서버가 멀리 떨어져 있다.

    서버컴퓨터의 디렉토리에 저장되어 있는 파일을 읽어오기 위해 InputStream을 사용한다. (View입장에서 생각)

    그리고 파일을 응답객체로 OutputStream을 사용하여 브라우저에 내려보내야 한다.

    내 컴퓨터의 다운로드 폴더에 저장시켜주는 것은 크롬 등 브라우저가 해주는 것이다. 브라우저에 보내주기만 하는 것이다.

    요청객체는 Reader와 InputStram을 획득할 수 있고, 응답객체는 Writer와 OutputStream을 획득할 수 있다.

    Reader는 텍스트를 읽어오는 스트림이고, InputStream은 바이너리 데이터를 읽어오는 스트림이다. 

    Writer는 텍스트를 출력하는 스트림이고, OuputStream은 바이너리 데이터를 출력하는 스트림이다. 


    가까이 대는 순간 가져오기, 화면은 가만히 있고 데이터를 가져오는 건 ajax를 써야하는데 ajax로 데이터를 내려보낼때는  json으로 내려보내준다. 

    스프링부트에서는 별도의 jsonView를 만들 필요없이 RestController를 사용하여 응답을 json으로 보낼 수 있다. 

     

    댓글

Designed by Tistory.