HackToTech

Hack To Technology

LocalVariableTableParameterNameDiscovererが削除されてAutowireに失敗するようになった話

github.com

SpringBootを3.2.0に上げた際に、
同じ型のBeanを定義していて、名前による(Qualifierではない)Autowireが失敗するようになってしまったので、それの備忘録
とりあえず複数のBeanがマッチするところまでの話は以下の記事に書いてるので端折る

www.hacktotech.net

DependencyDescriptor#getDependencyName が呼ばれる
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1728

MethodParameter#getParameterName が呼ばれる
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java#L356

ここでパラメータ名を解決するわけだが、
参照している parameterNameDiscoverer
KotlinReflectionParameterNameDiscoverer
StandardReflectionParameterNameDiscoverer
LocalVariableTableParameterNameDiscoverer
の順番でパラメータ名の解決を行うようになっていたのが、削除されたことによって失敗するようになっていた
(元のバージョンでも上2つのdiscovererは最終的にnullを返していたので、そのままでは解決できなくなった)
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-core/src/main/java/org/springframework/core/MethodParameter.java#L714

で、困ったことに自分たちのコードはKotlinで書いているが、
外部ライブラリのstarterがJavaで書かれており、wikiの対応がまだされていなかったので、自分たちのSpringBootのバージョンをあげたら壊れてしまったという話だった
(自分たちのコードで、Kotlin内で同じ型のBeanが複数構成されていた場合の名前による解決はうまく動いたので、少なくともこのパターンだけは対応しないとうまく動かないとかな気がする)

↓はPoC

github.com

jmodule は外部ライブラリをイメージして書いていて、
src 配下は自分たちのコードをイメージして書いている

org.springframework.boot3.1.7 であれば
jmodule 側の build.gradle に↓を書かなくても問題なく動くが、

tasks.withType(JavaCompile).configureEach {
    options.compilerArgs.add("-parameters")
}

3.2.1 など spring-core6.1 系になると失敗するようになる
最初出会したときは、自分たちの build.gradle.kts だけ対応すればいけるのかと思っていたが、
そんなことはなかったのでバージョンを上げる際は注意したほうが良さそう

一応コンパイルオプションつけたときの ResolverAutoConfiguration.java のclassファイルの違いもみてみた

無効

public class org.example.resolver.ResolverAutoConfiguration
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #12                         // org/example/resolver/ResolverAutoConfiguration
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // org/example/resolver/Resolver
   #8 = Utf8               org/example/resolver/Resolver
   #9 = Methodref          #7.#10         // org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #10 = NameAndType        #5:#11         // "<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #11 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)V
  #12 = Class              #13            // org/example/resolver/ResolverAutoConfiguration
  #13 = Utf8               org/example/resolver/ResolverAutoConfiguration
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lorg/example/resolver/ResolverAutoConfiguration;
  #19 = Utf8               resolver
  #20 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
  #21 = Utf8               parameterNameResolver
  #22 = Utf8               Lorg/example/resolver/ParameterNameResolver;
  #23 = Utf8               RuntimeVisibleAnnotations
  #24 = Utf8               Lorg/springframework/context/annotation/Bean;
  #25 = Utf8               SourceFile
  #26 = Utf8               ResolverAutoConfiguration.java
  #27 = Utf8               Lorg/springframework/context/annotation/Configuration;
{
  public org.example.resolver.ResolverAutoConfiguration();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/resolver/ResolverAutoConfiguration;

  public org.example.resolver.Resolver resolver(org.example.resolver.ParameterNameResolver);
    descriptor: (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #7                  // class org/example/resolver/Resolver
         3: dup
         4: aload_1
         5: invokespecial #9                  // Method org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
         8: areturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lorg/example/resolver/ResolverAutoConfiguration;
            0       9     1 parameterNameResolver   Lorg/example/resolver/ParameterNameResolver;
    RuntimeVisibleAnnotations:
      0: #24()
        org.springframework.context.annotation.Bean
}
SourceFile: "ResolverAutoConfiguration.java"
RuntimeVisibleAnnotations:
  0: #27()
    org.springframework.context.annotation.Configuration

有効

public class org.example.resolver.ResolverAutoConfiguration
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #12                         // org/example/resolver/ResolverAutoConfiguration
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // org/example/resolver/Resolver
   #8 = Utf8               org/example/resolver/Resolver
   #9 = Methodref          #7.#10         // org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #10 = NameAndType        #5:#11         // "<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #11 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)V
  #12 = Class              #13            // org/example/resolver/ResolverAutoConfiguration
  #13 = Utf8               org/example/resolver/ResolverAutoConfiguration
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lorg/example/resolver/ResolverAutoConfiguration;
  #19 = Utf8               resolver
  #20 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
  #21 = Utf8               parameterNameResolver
  #22 = Utf8               Lorg/example/resolver/ParameterNameResolver;
  #23 = Utf8               MethodParameters
  #24 = Utf8               RuntimeVisibleAnnotations
  #25 = Utf8               Lorg/springframework/context/annotation/Bean;
  #26 = Utf8               SourceFile
  #27 = Utf8               ResolverAutoConfiguration.java
  #28 = Utf8               Lorg/springframework/context/annotation/Configuration;
{
  public org.example.resolver.ResolverAutoConfiguration();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/resolver/ResolverAutoConfiguration;

  public org.example.resolver.Resolver resolver(org.example.resolver.ParameterNameResolver);
    descriptor: (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #7                  // class org/example/resolver/Resolver
         3: dup
         4: aload_1
         5: invokespecial #9                  // Method org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
         8: areturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lorg/example/resolver/ResolverAutoConfiguration;
            0       9     1 parameterNameResolver   Lorg/example/resolver/ParameterNameResolver;
    MethodParameters:
      Name                           Flags
      parameterNameResolver
    RuntimeVisibleAnnotations:
      0: #25()
        org.springframework.context.annotation.Bean
}
SourceFile: "ResolverAutoConfiguration.java"
RuntimeVisibleAnnotations:
  0: #28()
    org.springframework.context.annotation.Configuration

MethodParameters を見れば分かる通り、オプションによって有無の違いが見て取れる

docs.oracle.com