ハードルは下げる。ネガティブなことは書かない。

internalスコープのclassをconstructor injectionする(Kotlin, Spring)

2021.05.06

掲題の件について、若干詰まったのでブログに残す。

やりたいこと

以下のようなinternal classを

@Service
internal class SubService { /* .. */ }

以下のようなclassにconstructor injectionさせたい

@Service
class MainService(private val subService: SubService) { /* .. */ }

SubServiceとMainServiceは同じパッケージとする。

え?普通にできないんだっけ?

上記例はコンパイルエラーになる。
なぜなら、publicなclassのprimaryコンストラクタはpublicスコープであり、 そこにintenalスコープのclassのインスタンスを引数に取れないから。

解決策① constructor injectionを諦める

これが一番簡単。だが、そもそも掲題の前提条件を満たしていないように思う。

@Service
class MainService {
    @Autowired
    private val subService: SubService

    /* .. */ 
}

解決策② MainServiceもinternalスコープにしちゃう

MainServiceもinternalスコープにしてしまえば、primaryコンストラクタもinternalスコープになるので コンパイルエラーにならなくなる。
ただ、パッケージ外のMainServiceに依存するクラスがMainServiceをみれなくなるので、 MainServiceをpublicなinterfaceにして、実装クラスをinternalスコープにする。

interface MainService { /* .. */ }

@Service
internal class MainServiceImpl(private val subService: SubService): MainService { 
/* .. */ 
}

一瞬SpringがちゃんとMainServiceImplをInjectionしてくれるのかなと不安になったけど、問題なく動作した。
Spring以外のフレームワークを使っていて、internalスコープのclassを他パッケージにinjectさせたい場合は、 publicなMainServiceFactoryを作れば解決できそう。

以上