오늘은 Java와 Kotlin에서 리플렉션을 사용할 때 자주 마주치는 InvocationTargetException
에 대해 이야기해보겠습니다. 이 예외를 효과적으로 처리하는 방법도 함께 알아볼게요.
InvocationTargetException이란?
InvocationTargetException
은 Java의 리플렉션 API를 사용해 메서드를 동적으로 호출할 때 발생할 수 있는 예외입니다. 호출된 메서드 내부에서 발생한 예외를 감싸서 전달하는 역할을 하죠.
주요 특징:
- 원인 예외를 감싸서 전달: 발생한 예외를 그대로 감싸서 전달합니다.
- 원본 예외 접근 가능:
getCause()
나getTargetException()
메서드를 사용해 원본 예외에 접근할 수 있습니다. - 주로 발생하는 메서드:
Method.invoke()
나Constructor.newInstance()
같은 리플렉션 메서드에서 주로 발생합니다.
예시 상황: 결제 시스템
간단한 결제 시스템을 예로 들어보겠습니다. 다양한 결제 방식을 지원하는 PaymentController
가 있고, 이를 동적으로 호출하는 라우터를 구현했습니다.
class PaymentController {
fun processCardPayment(amount: Int) {
if (amount <= 0) {
throw PaymentException("카드", "유효하지 않은 결제 금액입니다.")
}
// 결제 처리 로직
}
}
class PaymentException(val paymentType: String, override val message: String) : Exception()
fun invokePaymentMethod(methodName: String, amount: Int) {
val controller = PaymentController()
val method = controller.javaClass.getMethod(methodName, Int::class.java)
try {
method.invoke(controller, amount)
} catch (e: InvocationTargetException) {
// 여기서 InvocationTargetException이 발생!
println("결제 처리 중 오류 발생: ${e.cause?.message}")
}
}
위 예시에서 invokePaymentMethod
는 리플렉션을 사용해 PaymentController
의 메서드를 동적으로 호출합니다.
문제 상황
다음 코드를 실행하면 PaymentException
이 발생하지만, 이는 InvocationTargetException
으로 래핑되어 전달됩니다. 그래서 우리가 원하는 대로 예외를 처리하기 어려워집니다.
fun main() {
invokePaymentMethod("processCardPayment", -100)
}
해결 방법
InvocationTargetException
을 적절히 처리하기 위해 다음과 같은 방법을 사용할 수 있습니다:
1. 원인 예외 추출하기
fun invokePaymentMethod(methodName: String, amount: Int) {
val controller = PaymentController()
val method = controller.javaClass.getMethod(methodName, Int::class.java)
try {
method.invoke(controller, amount)
} catch (e: InvocationTargetException) {
val cause = e.cause
when (cause) {
is PaymentException -> println("결제 오류: [${cause.paymentType}] ${cause.message}")
else -> println("알 수 없는 오류 발생: ${cause?.message}")
}
}
}
2. 예외 전파하기
때로는 원본 예외를 그대로 전파하고 싶을 때가 있습니다. 이 경우 다음과 같이 처리할 수 있습니다:
fun invokePaymentMethod(methodName: String, amount: Int) {
val controller = PaymentController()
val method = controller.javaClass.getMethod(methodName, Int::class.java)
try {
method.invoke(controller, amount)
} catch (e: InvocationTargetException) {
throw e.cause ?: e
}
}
fun main() {
try {
invokePaymentMethod("processCardPayment", -100)
} catch (e: PaymentException) {
println("결제 오류 발생: [${e.paymentType}] ${e.message}")
} catch (e: Exception) {
println("기타 오류 발생: ${e.message}")
}
}
이렇게 하면 원본 PaymentException
을 직접 처리할 수 있게 됩니다.
결론
리플렉션을 사용할 때 InvocationTargetException
은 피할 수 없는 부분입니다. 하지만 이를 적절히 처리하면 더 견고하고 예측 가능한 코드를 작성할 수 있습니다. 특히 프레임워크나 라이브러리를 개발할 때 이러한 예외 처리는 매우 중요합니다.
리플렉션은 강력한 도구이지만, 동시에 주의 깊게 다뤄야 하는 양날의 검과 같습니다. InvocationTargetException
을 올바르게 처리함으로써, 리플렉션의 유연성을 최대한 활용하면서도 안정적인 코드를 유지할 수 있습니다.
'Language > Kotlin' 카테고리의 다른 글
Java Virtual Machine(JVM) 메모리 옵션 분석 (0) | 2024.04.26 |
---|---|
Sealed Class란 무엇인가? (0) | 2023.10.13 |
Event Sourcing을 통한 함수형 프로그래밍의 적용 (0) | 2023.10.13 |
코틀린에서의 `reduce` (0) | 2023.10.13 |
Kotlin에서의 기본 인자 값과 널 허용성 (0) | 2023.10.12 |