ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] SpringBoot 프로젝트 : 프로필 사진 기능 구현(1) - 이미지 byte[] 처리(FileInputStream, ByteArrayOutputStream)
    프로젝트/기능 정리 2023. 6. 8. 14:34

    목적

    • 회원제 웹사이트 개발시 거의 필수적으로 사용되는 프로필 사진 등록 및 수정 기능 구현
    • 프로필 이미지 등록관련 DB, Java, JS 처리 총 정리
    • 일부 받아서 쓴 코드 리뷰

     

    진행했던 프로젝트들의 코드를 다시 보며 시간을 들였던 기능들을 정리하고 있는데, 생각보다 이미지 처리하는 부분들이 어렵게 느껴졌다(기초가 없으니까..).

     

    파일 처리나 이미지 처리 등을 담당해주는 코드를 받아서 적용만 했던터라 세부 내용에 대해 깊이 탐색해보지 않았던 것도 큰 요인으로 생각된다.

     

    지금이라도 좀 더 자세히 기록을 남겨두기 위해 프로필 이미지 처리 관련 작업들을 몇 부분으로 나눠 작성해보겠다.

     

     

    [데이터 흐름도]

    우선 큰 틀에서 어떤 과정을 거쳐 데이터가 변환되고 흘러가는지 그려보면 다음과 같다.

     

    프로필이미지 데이터 흐름도

     

    간략히 설명하자면,

    1. 마이페이지jsp가 호출되면 jsp에서 컨트롤러로 이미지 요청을 보낸다.
    2. 요청받은 컨트롤러는 회원DB에서 등록된 이미지(BLOB)와 파일 이름을 받아오고 Abstract View에 넘겨준다.
    3. 추상View에서는 DB에서 꺼낸 이미지 파일을 읽고 jsp로 뿌려준다.
    4. 이미지 등록 또는 변경시 JS에서 upload된 파일을 받아 Ajax방식으로 컨트롤러에 전송한다.
    5. 전송 받은 컨트롤러는 memberVO에 이미지 바이트 배열, 파일명, 회원번호를 담아 Mapper에 전달한다.
    6. Mapper는 전달 받은 데이터를 sql문에 따라 DB에 저장한다.

     

    이번 글에서는 jsp로부터 이미지를 요청받은 컨트롤러에서 일어나는 default 이미지 처리 과정을 알아보자.

     

    [JSP]

    마이페이지jsp는 호출될 시 이미지를 불러오기 위해  매핑된 경로로 컨트롤러에 요청을 보낸다. 코드는 다음과 같다.

    <a href="#profile-change" data-bs-toggle="modal">
    	<img src = "${pageContext.request.contextPath}/mypage/photoView.do" class = "profile-photo">
    </a>

    여기서 img태그의 src속성을 보면 이미지 경로를 직접 지정하지 않고 매핑된 주소를 통해 컨트롤러에서 처리한다.

     

    이유는 단순히 웹서버가 실행되고 있는 물리적 경로 어딘가에 저장되어 있는 이미지 파일을 불러오는 것(ex. /profileImages/mem_no1.jpg)이 아니라 DB에 저장되어 있는 이미지 바이트 배열을 꺼내와야 하기 때문이다.

     

    DB와 연동한 작업을 하기 위해서는 Java단에서의 처리가 필요하다.

     

    리뷰하면서 보니 이런 정적 데이터는 그냥 JSTL로 로그인 여부나 프로필 이미지 등록 여부 체크하는게 더 간단할 것 같다는 생각이 든다.. 아님말고

     

    이제 컨트롤러에서 어떤 처리를 하는지 보겠다.

     

     

    [Controller - photoView]

    호출된 컨트롤러의 코드는 다음과 같다.

    @RequestMapping("/mypage/photoView.do")
    public String getProfile(HttpSession session, HttpServletRequest request, Model model) {
        // 로그인한 회원 정보 세션에서 가져오기
        MemberVO user = (MemberVO)session.getAttribute("user");
        if(user == null) { //로그인하지 않은 경우
        	//현재 서버가 돌아가고 있는 디렉토리에서 기본 이미지 불러와서 FileUtil처리
            byte[] readbyte = FileUtil.getBytes(request.getServletContext().getRealPath("/image_bundle/default_img.png"));
            //처리된 이미지 바이트 배열, 파일명 모델에 담기
            model.addAttribute("imageFile", readbyte);
            model.addAttribute("filename", "default_img.png");
        }else { //로그인한 경우
        	//주입 받은 서비스를 통해 DB에 회원번호를 넘겨주고 회원정보 VO에 담기
            MemberVO memberVO = mypageService.selectMember(user.getMem_num());
            //viewProfile 메소드에 전달
            viewProfile(memberVO,request,model);
        }
        //imageView라는 AbstractView로 리턴
        return "imageView";
    }

    전체 코드 흐름은 다음과 같다.

    • 회원 로그인 여부 체크
    • 로그인 안한 경우 : 기본이미지 모델에 담아서 imageView 리턴
    • 로그인 한 경우 : DB에서 회원이 등록한 이미지 조회 후 등록된 이미지(없으면 기본) 모델에 담아서 imageView 리턴

     

    로그인을 한 경우 DB에서 회원정보를 받아와서 viewProfile이라는 메소드로 넘겨주었다.

     

    viewProfile 메소드는 회원이 프로필 사진을 등록했는지에 따라 모델에 담아주는 이미지 데이터를 달리하는 역할을 한다.

     

    프로필 관련 처리를 하는 매핑이 또 있어서 따로 메소드로 정리해 둔 것이나 필요 없으면 viewProfile 위치에 아래 조건문 코드를 바로 적어도 상관 없다.

     

    일단 viewProfile의 코드부터 살펴본 후 이미지 처리에 직접 관여하는 FileUtil 클래스와 imageView 클래스를 보겠다.

     

    [viewProfile 메소드]

    public void viewProfile(MemberVO member, HttpServletRequest request, Model model) {
        if(member == null || member.getMem_photo_name() == null) {
            byte[] readbyte = FileUtil.getBytes(request.getServletContext().getRealPath("/image_bundle/default_img.png"));
            model.addAttribute("imageFile", readbyte);
            model.addAttribute("filename", "default_img.png");
        }else {
            model.addAttribute("imageFile", member.getMem_photo());
            model.addAttribute("filename", member.getMem_photo_name());
        }
    }

    viewProfile은 회원의 프로필 사진 등록 여부에 따라 모델에 다른 이미지를 담아주는 역할을 한다.

     

    전달받은 VO에 DB에 등록되어있던 이미지 파일이 있는지 보고 없으면 기본이미지, 있으면 DB에 저장되어 있던 이미지를 모델에 담아주는 것으로 끝이다.

     

    [FileUtil 클래스]

    FileUtil 클래스는 로그인이 안되어 있거나, 회원의 DB에 등록된 프로필 이미지가 없는 조건에서 기본 이미지(파일이 위치한 path를 통해 불러옴)를 처리하는데 사용된다.

     

    FileUtil 클래스는 원래 받아서 쓴 코드인데 이것저것 찾아보다가 아래 포스트를 보고 좀 변형했다. 코드는 다음과 같다.

     

    참고: http://egloos.zum.com/innocentev/v/2011621

     

    public class FileUtil {
    	public static byte[] getBytes(String path) {
    		FileInputStream fis = null;
    		ByteArrayOutputStream byteOs = null;
    		try {
    			//FileIS 생성
    			fis = new FileInputStream(path);
    			//byteArrayOS 생성
    			byteOs = new ByteArrayOutputStream();
    			//바이트 하나씩 가져올 변수
    			int readFile = 0;
    			//하나씩 꺼내서 읽는데 다 읽으면 -1을 리턴
    			while((readFile = fis.read()) != -1) {
    				//생성한 OS에 읽은 데이터 쓰기
    				byteOs.write(readFile);
    			}
    		}catch(IOException e){
    			System.out.println(e.toString());
    		}finally {
    			if(fis != null) {
    				try {fis.close();}catch(IOException e) {}
    			}
    		}
    		//OS에 쓴 데이터 byte[]로 리턴
    		return byteOs.toByteArray();
    	}
    }

    하나하나 간단히 설명해보겠다.

     

    일단 FileInputStream클래스를 보면 다음과 같이 나와있다.

     

    FileInputStream.class 설명

     

    대충 보면 파일에서 byte를 가져오는데 이미지 데이터 같은 raw byte의 스트림을 읽기 위한 것이라고 적혀있다.

     

    이미지 파일을 바이트단위로 읽을 때 쓰는 클래스 정도로 생각하면 될 것 같다.

     

    암튼 FileInputStream을 생성하고 인자로 getBytes 메소드에 전달된 이미지 파일의 경로(path)를 넣어줬다.

     

    다음으로 ByteArrayOutputStream클래스는 다음과 같이 설명이 나와있다.

     

    ByteArrayOutputStream.class 설명

     

    OutputStream을 상속 받은 클래스인데, 바이트 배열로 쓰인 출력 스트림을 구현해준다고 한다. 

     

    기존에 가지고 있던 FileUtil에서는 정적 할당을 했다.

     

    풀어 말하면, 들어오는 이미지 파일의 사이즈를 알아내서 그 크기만큼 바이트 배열의 크기를 할당해줬다는 것이다.

     

    근데 이 ByteArrayOutputStream은 위 설명에 나와있듯, 버퍼가 데이터 크기에 따라 필요하면 자동으로 증가한다.

     

    그리고 보통 Input/Output Stream은 사용 후 close 해줘야하는데 이건 안해줘도 되나보다(여러모로 좋아보임..).

     

    설명은 이렇고, 정리하면 fis.read()로 바이트 하나씩 꺼내서 readFile에 담아주고, 그걸 byteOS.write()에 인자로 넘겨주어 OutputStream 버퍼에 하나씩 써준거라고 할 수 있다. 

     

    모두 처리가 되면 toByteArray() 메소드를 통해 버퍼에 쓰여있던 바이트 데이터들을 배열로 바꿔서 리턴한다.

     

     

    지금까지 작성한 내용을 정리하면 다음과 같다.

     

    ∴ 로그인이 안되어 있거나 DB에 등록한 프로필 이미지가 없는 경우, FileUtil 클래스를 통해 기본으로 설정해둔 이미지 파일을 byte 배열로 변환하여 모델에 담아준다.

     

     

    쓰다보니 내용이 길어 여기까지 작성해야겠다.

     

    다음 내용은 회원이 등록한 이미지가 DB에 있는 경우와 최종적으로 return되는 imageView 클래스에 대해 정리하도록 하겠다.

Designed by Tistory.