[Spring] Kotiln + Spring 에서 AOP 적용 시 Caused by: java.lang.IllegalArgumentException: Cannot subclass final class 이슈 해결

2023-05-07


사진: Unsplash 의 Karthik Sreenivas


1. 이슈

 

토이 프로젝트에서 AOP 적용 중 Caused by: java.lang.IllegalArgumentException: Cannot subclass final class . . . 와 같은 에러가 발생했다. 기존에 java 클래스 파일에서는 문제가 없으나 Kotiln 클래스 파일은 모두 에러가 발생했다. 해당 에러는 표시된 것과 같이 Final Class의 경우 AOP가 CGLIB(Code Generator Library)를 이용해 런타임에 동적으로 자바 클래스에 따른 프록시를 생성해주지 못하기 때문이다. 즉 Kotiln 클래스 파일의 경우 default로 final 키워드가 선언되기 때문에, 동적 생성이 불가해서 생기는 이슈인 것으로 확인 되었다. 그렇다면 아래와 같이 지금까지 생성해 둔 모든 코틀린 파일은 open 처리를 해야 하는 걸까? 이를 해결하는 방법을 알아보자.

 

class test(){
...
}


/*
	아래와 같이 open 키워드를 주어야 하는걸까?
*/

open class test(){
...
}

2. 해결방법

 

jetbrains 사에서도 해당 case에 대한 공식으로 지원하는 플러그인이 존재한다. 

 

Kotlin은 클래스와 그 멤버들이 기본적으로 final로 지정되어 있어, Spring AOP와 같은 프레임워크 및 라이브러리를 사용하는 데 불편함을 초래합니다. all-open 컴파일러 플러그인은 특정 주석이 달린 클래스와 해당 멤버들을 명시적인 open 키워드 없이 open으로 만들어주어 이러한 프레임워크의 요구사항에 Kotlin을 적합하게 만들어줍니다.

 

바로 아래와 같이 allopen은 코틀린 버전에 맞게 부여하는 것이다. 

    dependencies {
	...
		classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
	...
    }

buildGradle 중요한 부분은  3가지이다.

 

classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
apply plugin: "kotlin-allopen"
allOpen {
   annotation("org.springframework.stereotype.Controller")
   annotation("org.springframework.stereotype.Service")
   annotation("org.springframework.stereotype.Repository")
   annotation("org.springframework.boot.autoconfigure.SpringBootApplication")
   annotation("org.springframework.context.annotation.Configuration")
}

아래는 전체 buildGradle 예시이니 참고하자.

 

buildscript {
    ext.kotlin_version = '1.8.20-Beta'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
		classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}
plugins {
	...
}

group = 'com.source'
...
apply plugin: 'kotlin'
apply plugin: "kotlin-allopen"
...

allOpen {
	annotation("org.springframework.stereotype.Controller")
	annotation("org.springframework.stereotype.Service")
	annotation("org.springframework.stereotype.Repository")
	annotation("org.springframework.boot.autoconfigure.SpringBootApplication")
	annotation("org.springframework.context.annotation.Configuration")
}

configurations {
	compileOnly {
		...
	}
}

repositories {
	...
}


dependencies {
	...
}

tasks.named('test') {
	...
}

tasks.named('bootBuildImage') {
	...
}
compileKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

 

공식 문서 : https://kotlinlang.org/docs/all-open-plugin.html

 

All-open compiler plugin | Kotlin

 

kotlinlang.org


3. 왜 코틀린은 default 로 모든 class에 final 키워드를 선언하는 것일까?

 

이는 Kotlin 개발자들이 코드를 더 안전하고 예측 가능하게 유지하고, 오류를 줄이는 것을 목적으로 한다.

 


예를 들어, Java에서는 클래스와 메서드가 상속 가능하도록 선언되어 있으면, 다른 개발자가 해당 클래스나 메서드를 오버라이드하거나 변경할 수 있다. 이러한 상황에서는 예상치 못한 결과가 발생할 가능성이 높아지며, 디버깅이 어려워지기 때문이다. 하지만 Kotlin에서는 클래스와 메서드가 상속 불가능하도록 final로 선언되어 있으므로, 다른 개발자가 이를 변경할 수 없게 되며 결과적으로 이러한 접근 방식은 코드를 더욱 예측 가능하고 안정적으로 만들어주며, 디버깅이 용이해진다.

 

https://stackoverflow.com/questions/51680006/why-are-kotlin-classes-final-by-default-instead-of-open

 

Why are Kotlin classes final by default instead of open?

The documentation tells us the following about open annotation: The open annotation on a class is the opposite of Java's final: it allows others to inherit from this class. By default, all classes...

stackoverflow.com


메인 이미지 출처 : 사진: UnsplashKarthik Sreenivas