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

# 環境
- `SpringBoot`: 2.1.9
- `Kotlin`: 1.3.50
- `kotlin-maven-allopen`導入済み

# 問題
以下のようなアノテーションを自作し、[`Autowire`で`SpringBoot`の`Validator`を持ってきて](https://qiita.com/wrongwrong/items/4f1901b2f5fa6ef135bb#validator%E3%82%92autowire%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%97%E3%81%A6%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)バリデーションしようとしました。

```kotlin:自作アノテーション
@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`を実装していますが、両方とも同じようなエラーとなりました。

<details><summary>スタックトレース</summary><div>

```bash
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]
```

</div></details>

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

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

```kotlin
@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
    }
}
```

# 雑感
[去年アノテーション自作記事を書いた](https://qiita.com/wrongwrong/items/8abf3b1b09654da3cb36)時はこんなことなかったはずなので、これが起きるのは環境由来のなんかだと思います。
最小環境を作って再現することはできていないので、暇があればやってみようと思います。

# 関連記事
- [【SpringBoot】バリデーション（@Validated / @Valid）を任意のタイミングで動かす \- Qiita](https://qiita.com/wrongwrong/items/4f1901b2f5fa6ef135bb)
- [【Kotlin】アノテーションを自作する【SpringBoot】 \- Qiita](https://qiita.com/wrongwrong/items/8abf3b1b09654da3cb36)