I understand Spring DI and how it works in general.
But what I cannot understand here is in case of @Bean
method parameter injection, how spring knows about the parameter name so it could inject beans from its bean's factory based on the parameter's name?
For example, In the following example, the methods fernas1
and fernas2
parameters are being wiped at runtime. however, spring still can inject the correct Abbas
bean instance into it.
@SpringBootApplication
public class DemoApplication {
@Autowired
private Abbas abbas1; // this is understandable, hence the field name is available at runtime
@Autowired
private Abbas abbas2; // this is understandable, hence the field name is available at runtime
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
Map<String, Fernas> beansOfType = ctx.getBeansOfType(Fernas.class);
System.out.println(beansOfType);
Arrays.stream(DemoApplication.class.getMethods())
.filter(m -> m.getName().startsWith("fernas"))
.flatMap(m -> Stream.of(m.getParameters()))
.map(Parameter::getName)
.forEach(System.out::println);
System.out.println(ctx.getBean(DemoApplication.class).abbas1);
System.out.println(ctx.getBean(DemoApplication.class).abbas2);
}
class Abbas {
String name;
@Override
public String toString() {
return name;
}
}
class Fernas {
Abbas abbas;
@Override
public String toString() {
return abbas.toString();
}
}
@Bean
public Abbas abbas1() {
Abbas abbas = new Abbas();
abbas.name = "abbas1";
return abbas;
}
@Bean
public Abbas abbas2() {
Abbas abbas = new Abbas();
abbas.name = "abbas2";
return abbas;
}
// this is not understandable, hence the parameter name is NOT available at runtime
@Bean
public Fernas fernas1(Abbas abbas1) {
Fernas fernas1 = new Fernas();
fernas1.abbas = abbas1;
return fernas1;
}
// this is not understandable, hence the parameter name is NOT available at runtime
@Bean
public Fernas fernas2(Abbas abbas2) {
Fernas fernas2 = new Fernas();
fernas2.abbas = abbas2;
return fernas2;
}
}
EDIT: The same problem and solution by @Javier both works on method
and constructor
parameters.
If parameter name reflection is not available, it uses the information in the class file itself. See DefaultParameterNameDiscoverer
Default implementation of the ParameterNameDiscoverer strategy interface, using the Java 8 standard reflection mechanism (if available), and falling back to the ASM-based LocalVariableTableParameterNameDiscoverer for checking debug information in the class file.
For instance, the LocalVariableTable of DemoApplication.fernas2
is
Start Length Slot Name Signature
0 16 0 this Lcom/example/demo/DemoApplication;
0 16 1 abbas2 Lcom/example/demo/DemoApplication$Abbas;
9 7 2 fernas2 Lcom/example/demo/DemoApplication$Fernas;
UPDATE for Spring Framework 6.1
According to Spring Framework 6.1 Release Notes, it no longer relies on debug information to deduce parameter names:
LocalVariableTableParameterNameDiscoverer
has been removed in 6.1. Consequently, code within the Spring Framework and Spring portfolio frameworks no longer attempts to deduce parameter names by parsing bytecode. If you experience issues with dependency injection, property binding, SpEL expressions, or other use cases that depend on the names of parameters, you should compile your Java sources with the common Java 8+-parameters
flag for parameter name retention (instead of relying on the-debug
compiler flag) in order to be compatible withStandardReflectionParameterNameDiscoverer
The original Spring Boot example posted in the question will likely to resolve beans correctly tough because spring-boot-starter-parent includes -parameters
option for javac