2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Kotlin】自作アノテーションをSpringBootのValidatorで動かすと「Cannot subclass final class ${パッケージ名.Validatorのクラス名}」になる問題への対処【BeanValidation】

Last updated at Posted at 2019-10-17

環境

  • SpringBoot: 2.1.9
  • Kotlin: 1.3.50
  • kotlin-maven-allopen導入済み

問題

以下のようなアノテーションを自作し、AutowireSpringBootValidatorを持ってきてバリデーションしようとしました。

自作アノテーション
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@ReportAsSingleViolation
@Constraint(validatedBy = [MyClassValidator::class, MyFieldValidator::class])
annotation class MyAnnotation(
        val message: String = "",
        val groups: Array<KClass<out Any>> = [],
        val payload: Array<KClass<out Payload>> = []
)

// クラスに対するバリデーション想定
class MyClassValidator: ConstraintValidator<MyAnnotation, Any> {
    override fun isValid(value: Any?, context: ConstraintValidatorContext?): Boolean {
        return false
    }
}

// フィールドに対するバリデーション想定
class MyFieldValidator: ConstraintValidator<MyAnnotation, Boolean> {
    override fun isValid(value: Boolean?, context: ConstraintValidatorContext?): Boolean {
        return false
    }
}

すると、以下のようなエラーになりました(最下層のCause以外省略)。
例ではクラスとフィールドの2つのConstraintValidatorを実装していますが、両方とも同じようなエラーとなりました。

スタックトレース
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class ${パッケージ名.Validatorのクラス名}
	at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:657) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$ClassLoaderAwareUndeclaredThrowableStrategy.generate(CglibAopProxy.java:1008) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:358) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) ~[na:1.8.0_181]
	at java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:1.8.0_181]
	at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:416) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:205) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]

buildDefaultValidatorFactoryを使う場合

Validation.buildDefaultValidatorFactory().validatorで定義されたValidatorを用いる場合はエラーになりませんでした。

対処法

ConstraintValidatorの実装をopenするとエラーが出なくなりました。

@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@ReportAsSingleViolation
@Constraint(validatedBy = [MyClassValidator::class, MyFieldValidator::class])
annotation class MyAnnotation(
        val message: String = "",
        val groups: Array<KClass<out Any>> = [],
        val payload: Array<KClass<out Payload>> = []
)

// クラスに対するバリデーション想定
open class MyClassValidator: ConstraintValidator<MyAnnotation, Any> {
    override fun isValid(value: Any?, context: ConstraintValidatorContext?): Boolean {
        return false
    }
}

// フィールドに対するバリデーション想定
open class MyFieldValidator: ConstraintValidator<MyAnnotation, Boolean> {
    override fun isValid(value: Boolean?, context: ConstraintValidatorContext?): Boolean {
        return false
    }
}

雑感

去年アノテーション自作記事を書いた時はこんなことなかったはずなので、これが起きるのは環境由来のなんかだと思います。
最小環境を作って再現することはできていないので、暇があればやってみようと思います。

関連記事

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?