-
Notifications
You must be signed in to change notification settings - Fork 38.4k
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
Fix: type matching for request-scope generic beans #34537
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nicely done
|
||
ResolvableType targetResolvableType = targetMbd.targetType; | ||
if (targetResolvableType == null) { | ||
targetResolvableType = targetMbd.factoryMethodReturnType; | ||
} | ||
if (targetResolvableType == null) { | ||
targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); | ||
} | ||
|
||
if (typeToMatch.isAssignableFrom(targetResolvableType)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two if
statements at the same level look suspicious at first glance, making me think twice and question the logic. Moving the second if
inside the first one would make it clear that it is an intended recheck of the first operation returning null
. Nesting it this way makes the intent clearer—at least to me.
Alternatively, using the defaultIfNull
method—although nested in this case as it’s a double-check method—would achieve the same result in just three lines. This keeps the logic concise, leaving only the essential parts without any redundancy.
ResolvableType targetResolvableType = targetMbd.targetType; | |
if (targetResolvableType == null) { | |
targetResolvableType = targetMbd.factoryMethodReturnType; | |
} | |
if (targetResolvableType == null) { | |
targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); | |
} | |
if (typeToMatch.isAssignableFrom(targetResolvableType)) { | |
ObjectUtils.defaultIfNull(targetMbd.targetType, | |
ObjectUtils.defaultIfNull(targetMbd.factoryMethodReturnType, ResolvableType.forClass(targetMbd.getBeanClass()))) | |
if (typeToMatch.isAssignableFrom(targetResolvableType)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback on the nested if statements! I've refactored the code to make the intent clearer, as you suggested.
Instead of having two if statements at the same level, I've now properly nested them to show the fallback logic more explicitly:
ResolvableType targetResolvableType = targetMbd.targetType;
if (targetResolvableType == null) {
targetResolvableType = targetMbd.factoryMethodReturnType;
if (targetResolvableType == null) {
targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass());
}
}
|
||
RootBeanDefinition targetDef = new RootBeanDefinition(SomeGenericSupplier.class); | ||
targetDef.setScope("request"); | ||
factory.registerBeanDefinition("scopedTarget.wordBean", targetDef); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
considering randomized test data as String targetBeanName = "scopedTarget.wordBean";
would pass the test but fail production.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback! I've addressed your concerns in two commits:
-
I've improved the resolvable type extraction logic in
isTypeMatch
method by using properly nested if statements for better clarity of intent. This makes the fallback logic more explicit while maintaining the same functionality. Unfortunately, I couldn't use thedefaultIfNull
method as it's not available in this version of Spring. -
I've refactored the test to use variables for bean names instead of hardcoded strings. This makes the test more flexible and better simulates real-world scenarios where bean names might vary, addressing your concern about potential production issues. The variables maintain the "scopedTarget." prefix convention that Spring uses, while allowing the actual names to be dynamic.
Let me know if you have any other suggestions!
85b3991
to
94877ce
Compare
ResolvableType targetResolvableType = targetMbd.targetType; | ||
if (targetResolvableType == null) { | ||
targetResolvableType = targetMbd.factoryMethodReturnType; | ||
if (targetResolvableType == null) { | ||
targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); | ||
} | ||
} | ||
|
||
if (typeToMatch.isAssignableFrom(targetResolvableType)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
considering
we should be able to reduce all redundancy by using the internal ternary operator hidden inside ObjectUtils.defaultIfNull
ResolvableType targetResolvableType = targetMbd.targetType; | |
if (targetResolvableType == null) { | |
targetResolvableType = targetMbd.factoryMethodReturnType; | |
if (targetResolvableType == null) { | |
targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); | |
} | |
} | |
if (typeToMatch.isAssignableFrom(targetResolvableType)) { | |
if (typeToMatch.isAssignableFrom(ObjectUtils.getIfNull(targetResolvableType, ObjectUtils.getIfNull(targetMbd.factoryMethodReturnType, ResolvableType.forClass(targetMbd.getBeanClass()))))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but of course this can be done afterward or not at all, as its working an acceptable anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggestion and for pointing me to the ObjectUtils.defaultIfNull from Apache Commons Lang!
I initially wasn't able to use any defaultIfNull method because I was working with org.springframework.util.ObjectUtils, which doesn't have this method in our current version.
Your approach using nested ternary operators looks more concise and elegant. I agree this would be a great improvement, but I think we should leave it for a separate refactoring PR in the future. This way, we can keep the current PR focused on fixing the original issue with clean, minimal changes.
Thanks again for your approval and the valuable feedback throughout this PR!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I just noticed you've added the getIfNull method to ObjectUtils.java - this is fantastic!
Could you guide me on how I can best incorporate this change into my implementation? Should I update my PR to use this new method with the nested ternary approach you suggested, or would you prefer to handle that separately after merging?
I'm excited to use this more elegant solution if that's the right approach at this stage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest merging it as it is and incorporating the refactoring into the Util PR (#34587) so we don’t add empty code and can deliver a practical example right away.
If it’s merged before, then you can, of course, try it here. I would not suggest mixing the PRs now.
Thanks for your heartwarming feedback—teamwork makes the dream work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just some personal inline flavor one considering RSPEC-S1488 and https://docs.openrewrite.org/recipes/staticanalysis/inlinevariable. Now it looks very well structured overall. Good increment. 👍
boolean isMatch = factory.isTypeMatch(proxyBeanName, supplierType); | ||
assertThat(isMatch).isTrue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boolean isMatch = factory.isTypeMatch(proxyBeanName, supplierType); | |
assertThat(isMatch).isTrue(); | |
assertThat(factory.isTypeMatch(proxyBeanName, supplierType)).isTrue(); |
String[] names = factory.getBeanNamesForType(supplierType); | ||
assertThat(names).contains(proxyBeanName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String[] names = factory.getBeanNamesForType(supplierType); | |
assertThat(names).contains(proxyBeanName); | |
assertThat(factory.getBeanNamesForType(supplierType)).contains(proxyBeanName); |
String targetBeanName = "scopedTarget.wordBean"; | ||
String proxyBeanName = "wordBean"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String targetBeanName = "scopedTarget.wordBean"; | |
String proxyBeanName = "wordBean"; | |
String proxyBeanName = "wordBean-" + UUID.randomUUID(); | |
String targetBeanName = "scopedTarget." + proxyBeanName; |
this should still work, right?
Signed-off-by: currenjin <[email protected]>
Signed-off-by: currenjin <[email protected]>
Signed-off-by: currenjin <[email protected]>
Signed-off-by: currenjin <[email protected]>
Is it better to include the Apache library to obtain this syntactic sugar? This utility would reduce a lot of boilerplate code. Or do you prefer the 10 lines of boilerplate code instead of the 1 line that's actually needed? There is obviously a need for this, so when we can't copy it, can we rely on other open-source frameworks providing the feature already? Thanks for the advisory. @bclozel |
This PR fixes an issue where
@MockBean
fails to work with request-scopedSupplier<T>
without explicit bean name.Issue
When using
@MockBean
with a request-scoped generic bean (especiallySupplier<T>
), Spring fails to match the bean by type unless the bean name is explicitly specified. This happens because the type matching algorithm doesn't properly handle the generic type information for scoped proxy beans.Solution
The solution enhances
AbstractBeanFactory.isTypeMatch
method to check for beans with a scope and look up their corresponding target bean definitions. When a scoped proxy bean is found, the method tries to match the type against the target bean's resolvable type.Testing
Added a test case that verifies type matching works correctly for scoped proxy beans with generic types.
Fixes gh-30043