Java: try-with-resource

Buddy·2022년 12월 30일
1

Java

목록 보기
2/2

EffectiveJava아이템 9. try-finally 보다는 try-with-resource를 사용하라 를 읽고 내가 작성한 코드에 문제점이 있음을 인지했다.

자원 회수

InputStream, OutputStream, java.sql.Connection 과 같이 사용이 끝나면 close 메서드를 호출해 자원을 회수해야 하는 자원들이 있다. 현재 내가 진행중인 프로젝트에서는 apache POI API를 사용해 DB의 데이터들을 핸들링해 excel 파일을 만들고 있고, 이 때 다루는 Workbook 인터페이스를 구현한 클래스들은 마찬가지로 사용이 끝나면, close 메서드를 통해 자원을 회수해야 한다. 자원을 회수하지 않는다면 프로그램의 성능에 문제가 생길 것이다.

만약, 클라이언트가 명시적으로 자원을 회수하지 않는다면 이 자원들은 finalizer 로 자원이 회수 될 수도 있다. 하지만 첫 번째로 finalizer 가 구현이 되어 있어야 하고 두 번째로 말 그대로 회수 '될 수도' 있는 것이지 회수 되는 것이 '보장'되지는 않는다.

때문에 close 메서드를 명시적으로 호출하는 방법으로 자원을 회수해야 하고, try-finally 와 함께 close 메서드를 호출하는 것이 일반적이였으나, 자바7 이후로는 try-with-resource 를 통해 위와 같은 문제를 해결할 수 있다.

try-finally

먼저 EffectiveJava 서적에서 제시하는 전통적인 방법의 try-finally 예시 코드다.

static String readLineOfFile(String path) throws IOException {
	BufferedReader br = new BufferedReader(new FileReader(path));
	try {
		return br.readLine();
	} finally {
		br.close();
	}
}

분명히 나쁜 코드라는 느낌은 들지 않지만, close 가 필요한 자원이 두개 이상이 된다면,

static void copy(String src, String dst) throws IOException {
	InputStream in = new FileInputStream(src);
	try {
		OutputStream out = new FileOutputStream(dst);
		try {
			byte[] buf = new byte[BUFFER_SIZE];
			int n;
			while ((n = in.read(buf)) >= 0) {
				out.write(buf, 0, n);
			}
		} finally {
			out.close();
		}
	} finally {
		in.close();
	}
}

위와 같이 복잡한 코드가 작성된다. 또한 try, finally 블록 모두에서 예외가 발생할 가능성이 있는데 물리적인 문제로 두 블록에서 모두 예외가 발생한다면 두 번째 예외가 첫 번째 예외를 집어삼켜버려 스택을 추척해도 첫 번째 예외에 관한 정보가 남지 않는다는 문제점이 있다.

try-with-resource

try-with-resourcetry-finally 의 문제점을 모두 해소한다. 심지어 코드가 훨씬 간결해진다. 하지만 그 전에 AutoCloseable 인터페이스를 implements 하는 것이 조건으로 주어진다.

AutoCloseable

public interface AutoCloseable {
	void close() throws Exception;
}

AutoCloseable 인터페이스는 위의 코드가 전부다. InputStream, Workbook 등의 클래스에서 모두 이 인터페이스를 구현하고 있다. (CloseableAutoCloseable 을 상속한 인터페이스로 close 메서드의 throws Exception 부분이 throws IOException 인 부분을 제외하면 동일하다.)

public abstract class InputStream implements Closeable {
	...
}
public interface Workbook extends Closeable, Iterable<Sheet> {
	...
}

예시

마찬가지로 서적에서 제시하는 try-with-resource 방식의 코드이다. close 메서드의 호출을 코드에서 명시적으로 하고 있지는 않지만, try 블록의 return 이후에 () 안의 자원이 AutoCloseable 을 구현했다면 자동적으로 close 가 호출되는 것이다.

static String readLineOfFile(String path) throws IOException {
	try (BufferedReader br = new BufferedReader(new FileReader(path))) {
		return br.readLine();
	}
}

자원 회수가 필요한 자원이 두개 이상인 경우에는

static void copy(String src, String dst) throws IOException {
	try (InputStream in = new FileInputStream(src);
		OutputStream out = new FileOutputStream(dst)){
		byte[] buf = new byte[BUFFER_SIZE];
		int n;
		while ((n = in.read(buf)) >= 0) {
			out.write(buf, 0, n);
		}
	}
}

이와 같이 자원의 선언 사이에 ; 으로 구분한다.

try-with-resource 적용하기

public class ExcelClient {
	public File createMonthlyDocumentFile(int year, int month) {
        Workbook workbook = new SXSSFWorkbook();

        CellStyle style = createCellStyle(workbook);

       	...
        }
        return exportWorkbook(workbook);
	}
    
    private File exportWorkbook(Workbook workbook) {
        try {
            File file = File.createTempFile(UUID.randomUUID().toString(), "xlsx");
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            workbook.write(fileOutputStream);

            workbook.close();

            return file;
        } catch (IOException exception) {
            throw new RuntimeException("파일 생성 실패");
        }
    }
}

프로젝트 코드에서 workbook 자원은 exportWorkbook 메서드에서 명시적으로 호출을 하여 자원을 회수하고 있지만, 지금 보니 exportWorkbook 메서드에서는 fileOutputStream 의 자원을 회수하지 않고 있다.

workbook 의 자원은 createMonthlyDocumentFile 메서드에서 회수를 하도록 하고, 마찬가지로 fileOutputStream 의 자원은 해당 자원이 사용되는 exportWorkbook 메서드에서 회수를 하도록 코드를 변경해야한다.

public class ExcelClient {
	public File createMonthlyDocumentFile(int year, int month) {
        try (Workbook workbook = new SXSSFWorkbook()) {
	        CellStyle style = createCellStyle(workbook);
	
    	   	...
            
	        return exportWorkbook(workbook);
        }
	}
    
    private File exportWorkbook(Workbook workbook) throws IOException {
    	File file = File.createTempFile(UUID.randomUUID().toString(), "xlsx");
		
        try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            workbook.write(fileOutputStream);

            return file;
        }
    }
}

workbookfileOutputStreamtry-with-resource 방식을 통해 자원이 회수되도록 코드를 변경했다. (두 객체 모두 AutoCloseable 인터페이스를 구현한 클래스로 생성됐다.) 이 외에도 코드에 수정된 부분이 있지만 주제와 관련된 부분만 작성했다.

profile
가볍게 쓰는 내용들이라 주관과 틀린 내용이 많습니다. 비판적인 시각으로 봐주시고 지적은 환영 :)

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN