Repository navigation
Conversation
Eureka registry calls must not use load-balanced RestClient or WebClient builders. Filter @LoadBalanced beans when resolving builders and fall back to plain factory methods when needed. Fixes spring-cloudgh-4591 Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
|
Thanks for the review @ryanjbaxter — addressed all three points in eb419ef:
Tests: |
Resolve the non-@LoadBalanced RestClient/WebClient builder once at configuration time using DefaultListableBeanFactory order metadata. Add WebClient plain-over-load-balanced configuration test. Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
eb419ef to
3f39a85
Compare
|
Force-pushed amended commit (removed erroneous trailer): tip is now |
| } | ||
|
|
||
| private static int orderFor(ConfigurableListableBeanFactory beanFactory, String beanName) { | ||
| if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory) { |
There was a problem hiding this comment.
This will cause the bean to be instantiated. IMO we can just use beanFactory.findAnnotationOnBean here and if there isn't an annotation then treat it as Ordered.LOWEST_PRECEDENCE
Use findAnnotationOnBean for @Order/@priority and require @primary or unique precedence when multiple non-@LoadBalanced builders exist, instead of DefaultListableBeanFactory.getOrder(). Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
| } | ||
|
|
||
| static Supplier<RestClient.Builder> restClientBuilder(ConfigurableListableBeanFactory beanFactory) { | ||
| RestClient.Builder builder = resolveNonLoadBalancedBuilder(beanFactory, RestClient.Builder.class, |
There was a problem hiding this comment.
This should be wrapped in a SingletonSupplier.of to prevent early initialization
| } | ||
|
|
||
| static Supplier<WebClient.Builder> webClientBuilder(ConfigurableListableBeanFactory beanFactory) { | ||
| WebClient.Builder builder = resolveNonLoadBalancedBuilder(beanFactory, WebClient.Builder.class, |
There was a problem hiding this comment.
This should be wrapped in a SingletonSupplier.of to prevent early initialization
kdelay
left a comment
There was a problem hiding this comment.
One regression to flag: getBeanNamesForType only looks at the current factory, while the ObjectProvider this replaces also resolved beans from ancestor contexts. With a plain RestClient.Builder defined only in a parent context, main hands that bean to the transport; on this branch it falls back to RestClient.builder(). I checked it with a WebApplicationContextRunner().withParent(...) test (passes on main, fails here).
Using BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type) restores it; with that change the new test and the 7 tests here all pass.
Parent contexts were invisible to getBeanNamesForType, so a plain RestClient.Builder defined only there was skipped. Look up ancestor bean names and read @LoadBalanced, @order, and @primary on the factory that defines the bean. Defer that resolution until the supplier is first used. Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
|
Thanks @kdelay — Both suppliers are also
|
| throw new NoUniqueBeanDefinitionException(type, primary.size(), | ||
| "more than one @Primary non-@LoadBalanced " + type.getSimpleName() + " bean: " + primary); | ||
| } | ||
| candidates.sort( |
There was a problem hiding this comment.
It seems like you are calling orderFor on the candidates here and in the for loop below. That doesn't seem necessary
|
|
Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
97a156b to
a16d518
Compare
|
DCO sign-off is on tip |
| private static <T> T resolveNonLoadBalancedBuilder(ConfigurableListableBeanFactory beanFactory, Class<T> type, | ||
| Supplier<T> fallback) { | ||
| List<String> candidates = new ArrayList<>(); | ||
| for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) { |
There was a problem hiding this comment.
Thanks for adding the ancestor-factory handling. However I think these is a behavior change caused by this. beanNamesForTypeIncludingAncestors pools the parent and child candidates, but the previous ObjectProvider.getIfAvailable() resolved locally first and only fell back to the parent when the child had no match.
Before this PR, and with normal Spring autowiring, the child’s bean would win.
Could we add a test for this?
Summary
RestClient.Builder/WebClient.Buildercandidates by excluding beans annotated with@LoadBalanced, so calls to the Eureka server URL are not routed through the load balancer (fixesNo instances available for localhostwhen only a load-balanced builder is registered).RestClient.builder()/WebClient.builder()when every registered builder is load-balanced.Fixes gh-4591 (same root cause as gh-4524).
Test plan
./mvnw -pl spring-cloud-netflix-eureka-client -Dtest=LoadBalancedEurekaHttpClientBuilderConfigurationTests test(JDK 21)./mvnw -pl spring-cloud-netflix-eureka-client -am test— module suite passes except pre-existingEurekaConfigServerBootstrapperIntegrationTests(Docker not available locally)