이번 포스팅은 kotlin에서 구현의 편의성이나 코드공유를 위한 Mixin을 만들때 유용하게 사용하는 default구현시 주의점에 대한 포스팅입니다
Java의 interface에 default 키워드가 있습니다.
public interface AAA {
public default void doSomething() {
System.out.println("AAA method doSomething");
}
}
kotlin 문법으로는 다음과 같습니다
interface AAA {
fun doSomething(){
println("AAA method doSomething")
}
}
인터페이스에서 함수의 구현을 가지게 되고 실행이 가능합니다
이러한 점을 이용하여 코드공유가 간편해지는 효과가 있습니다.
그러나 kotlin으로 Default구현의 실제 구현을 보면 약간 다릅니다.
interface AAA {
fun doSomething(){
println("AAA method doSomething")
}
}
class AAAImpl : AAA {
override fun doSomething() {
super.doSomething()
println("override AAAImpl method doSomething")
}
}
위 예제 코드를 java로 디컴파일해보면 어떻게 구현이 되어있을까요?
default 키워드를 사용하지 않고 DefaultImpls를 따로 구현하여 사용하는것을 볼 수 있습니다.
이유는 default 키워드는 자바 1.8에서 나온것이고 코틀린은 1.6부터의 호환성을 보장하기 위하여 다음과 같은 구현을 사용하였습니다.
따라서 default method를 오버라이드 할때 어노테이션이 공유될 것이라 생각하면 안됩니다.
doSomething method에 @Transactional를 붙혀보겠습니다.
interface AAA {
@Transactional
fun doSomething(){
println("AAA method doSomething")
}
}
class AAAImpl : AAA {
override fun doSomething() {
super.doSomething()
println("override AAAImpl method doSomething")
}
}
네 예상대로 @Transactional이 override에 잘 붙지않습니다
(override니 어찌보면 당연? 한것 일 수 있겠네요.)
그렇다면 이제 override 에서도 @Transactional를 붙히면 어떻게 될까요?
class AAAImpl : AAA {
@Transactional
override fun doSomething() {
super.doSomething()
println("override AAAImpl method doSomething")
}
}
다음과 같이 AAAImpl에선 default로 구현된 super.doSomething()을 하고 그후 추가구현을 하기위해 다음과 같은 형태로 코드를 짠다면 어떻게 될까요?
눈치채신분도 있겠지만 각 메서드마다 @Transactional이 걸립니다.
네 이런식으로 각 구현마다 퍼지게 되고 aop의 프록시가 원치 않게 동작 할 수 있습니다.
-Xjvm-default 같은 컴파일 옵션을 수정하여 해결할 수 있습니다.
jvmdefault-and-how-add-compiler-option 스택오버플로우에 다음과 같은 답변이 있습니다.
겪은 에러로는 mixin과 jpaRepository를 혼합해서 사용하다가 noSuchMethod error 발생되고 jpa 네임드 쿼리 생성 못하는 에러의 주요한 원인입니다.
잘생각해보면 당연한거 아닌가? 할 수 있지만 놓치지말고 주의하자는 의미에서 포스팅하였습니다.