-
Notifications
You must be signed in to change notification settings - Fork 38.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prevent @Bean method overloading by default (avoiding accidental overloading and condition mismatches) #22609
Comments
@rfelgent I was able to replicate this behavior. It appears to be happening because of this in Spring Framework. That check can cause unpredictable behavior because it depends on the order in which the bean definitions were processed. In the example above, if the bean definition corresponding to the second bean is processed first, it will be loaded as I don't think there is anything we can do in Spring Boot about this. We can move it to the Spring Framework issue tracker if the rest of the team think that it's something that can be fixed there. |
Framework's behaviour should be predictable thanks to this logic. I think it's worth moving this to Framework to consider an enhancement to use something more distinct than the method name when determining if a method has already been skipped. The simple workaround, as you have already noted, @rfelgent, is to avoid overloading |
@mbhave and @wilkinsona, thanks for the detective work! I agree that the algorithm in question should take overloaded methods into account and thus track skipped methods based on each method's name plus its formal parameter list. Let's see if the rest of the team agrees. |
The current behavior is more or less by design: There is only one bean definition per bean name, so we're conceptutally merging all relevant metadata into one definition per name. Overloaded The general recommendation is indeed not to use overloading but distinct bean/method names. And in the case of overloading, to declare the same annotations on all overloaded methods because that's closest to the actual semantics at runtime. This is not really obvious, so at the very least we need to document this properly... and we could possibly raise exceptions in case of condition mismatches among overloaded methods for the same bean definition, suggesting unified conditions or distinct bean names. |
@wilkinsona Sorry, I didn't explain myself very well in the previous comment. The behavior would be predictable for a given class. The unpredictability I was referring was across classes, where the |
I'd like to add my two cents - I've just lost several hours trying to debug why one of my beans was not registered (it was declared in a @ConditionalOnWebApplication
@Configuration
public class HttpRequestHelpersConfiguration
{
@Bean
public RequestContextHolderFacade requestContextHolderFacade()
{
return new RequestContextHolderFacade();
}
@Bean
public RequestHandlerProvider requestContextHolderFacade(
final ApplicationContext applicationContext,
final RequestContextHolderFacade requestContextHolderFacade
)
{
return new RequestHandlerProvider(
applicationContext,
requestContextHolderFacade
);
}
} ->
I don't have any strong opinion about the behaviour, as I have no problem renaming the method... but there should be a better error message provided by Spring. Thanks :) |
Just to add my experience. Just like other I made typo in method name. Same return type, same method name, different parameters. 2 beans with 2 different qualifiers. First bean declaration wasn't called at all (System.out.println in it wasn't printed, breakpoint wasn't hit), the other method was called twice. Both bean were injectable using 2 different qualifiers, however bean declaration of first qualifier were ever called. |
I also have the same issue. I wanted to refactor the code below: github/mutual-tls-ssl/SSLConfig.java @Component
public class SSLConfig {
@Bean
@Scope("prototype")
public SSLFactory sslFactory(
@Value("${client.ssl.one-way-authentication-enabled:false}") boolean oneWayAuthenticationEnabled,
@Value("${client.ssl.two-way-authentication-enabled:false}") boolean twoWayAuthenticationEnabled,
@Value("${client.ssl.key-store:}") String keyStorePath,
@Value("${client.ssl.key-store-password:}") char[] keyStorePassword,
@Value("${client.ssl.trust-store:}") String trustStorePath,
@Value("${client.ssl.trust-store-password:}") char[] trustStorePassword) {
SSLFactory sslFactory = null;
if (oneWayAuthenticationEnabled) {
sslFactory = SSLFactory.builder()
.withTrustMaterial(trustStorePath, trustStorePassword)
.withProtocols("TLSv1.3")
.build();
}
if (twoWayAuthenticationEnabled) {
sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyStorePath, keyStorePassword)
.withTrustMaterial(trustStorePath, trustStorePassword)
.withProtocols("TLSv1.3")
.build();
}
return sslFactory;
}
} Into the following snippet: @Component
public class SSLConfig {
@Bean
@Scope("prototype")
@ConditionalOnExpression("${client.ssl.one-way-authentication-enabled} == true and ${client.ssl.two-way-authentication-enabled} == false")
public SSLFactory sslFactory(@Value("${client.ssl.trust-store:}") String trustStorePath,
@Value("${client.ssl.trust-store-password:}") char[] trustStorePassword) {
return SSLFactory.builder()
.withTrustMaterial(trustStorePath, trustStorePassword)
.withProtocols("TLSv1.3")
.build();
}
@Bean
@Scope("prototype")
@ConditionalOnExpression("${client.ssl.two-way-authentication-enabled} == true and ${client.ssl.one-way-authentication-enabled} == false")
public SSLFactory sslFactory(@Value("${client.ssl.key-store:}") String keyStorePath,
@Value("${client.ssl.key-store-password:}") char[] keyStorePassword,
@Value("${client.ssl.trust-store:}") String trustStorePath,
@Value("${client.ssl.trust-store-password:}") char[] trustStorePassword) {
return SSLFactory.builder()
.withIdentityMaterial(keyStorePath, keyStorePassword)
.withTrustMaterial(trustStorePath, trustStorePassword)
.withProtocols("TLSv1.3")
.build();
}
} However it fails. When I set debug on I see that the first is not matched because the expression is evaluated into negative. But the second method should pass, however that one is never evaluated. I don't think this is a must option as there is a workaround such as not using method overloading/ and use different method name or combine it in a single method. However it will give a better DX if this would just work out of the box. Looking forward to have this feature 😄 Any news regarding this issue? |
I am sorry for you @Hakky54 - you walked into the same trap like me and others :-( @jholler, I do understand your hint regarding "more or less" by design and I do love your suggestions regarding |
Simply disallowing to declare more methods with the same name on the same configuration class (and guarding that with an exception) would suffice to prevent others from walking into the same trap 👍 I guess this could be even easily made into an ErrorProne check for example 🤔 |
I have forked the repo and made it working for this specific use case. It was a bit tricky because of some unit tests which still needed to pass. Anyone of the spring-framework team here? Just curious if it is worth to submit a PR as I am wondering if the team is considering to have this kind of capability for allowing of creating a bean conditionally while using method overload. |
I have created a PR to make this feature working, see here for the details: #28019 Would love to get everyones input ❤️ |
After a team discussion this morning, we are leaning towards a more radical step: disallowing overloaded The use cases for factory method overloading (just like with constructor overloading) are mostly related to optional arguments, with the "greediest satisfiable" factory method being calling, e.g. a variant with an optional argument, falling back to the overloaded method without the optional argument otherwise. In a modern-day Spring application, optional factory method arguments can be modeled in various forms, including Enforcing a strict non-overloading rule for |
I ended up introducing an All in all, the default behavior should provide better guidance now. The error message shown when rejecting same-named |
@jhoeller thx
|
Hello poeple,
I lost some hours configuring a bean with same id but different properties in java config, as the raised error message was not very helpful.
The bean in question must be created, as it is required via declarative
@DependsOn
configuration.The error
This config fails:
This config works:
If you name both methods differently (
postgresqlContainer
andpostgresqlContainer2
) then everything works as expected otherwise you get an error.Is this desired behavior ?
I am unsure if my scenario could indicate a bug, too. I do not know if this problem happens only to
@ConditionalOnProperty
or any other condition like@ConditionalOnExpression
.Best regards
The text was updated successfully, but these errors were encountered: