Browse Source
* Switch to passing customisers in BeanPostProcessor instead of the SmartInitializingSingleton. * Do not instantiate ReactorLoadBalancerExchangeFilterFunction while registering LB BeanPostProcessor. * Do not instantiate ReactorLoadBalancerExchangeFilterFunction while registering LB BeanPostProcessor. * Extract BeanPostProcessor instantiation to a separate configuration. * Fix tests. * Switch to using generics for DeferringLoadBalancerExchangeFilterFunction and reusing BPP configuration for Ribbon. Add more test assertions. * Add javadocs. * Add test verifying LB filters added when WebClient built in constructor.pull/643/head
Olga Maciaszek-Sharma
5 years ago
committed by
GitHub
10 changed files with 324 additions and 89 deletions
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.web.reactive.function.client.ClientRequest; |
||||
import org.springframework.web.reactive.function.client.ClientResponse; |
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; |
||||
import org.springframework.web.reactive.function.client.ExchangeFunction; |
||||
|
||||
/** |
||||
* An {@link ExchangeFilterFunction} implementation that uses {@link ObjectProvider} to |
||||
* resolve appropriate load-balancing {@link ExchangeFilterFunction} delegate when the |
||||
* {@link ExchangeFilterFunction#filter(ClientRequest, ExchangeFunction)} method is first |
||||
* called. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 2.2.0 |
||||
*/ |
||||
public class DeferringLoadBalancerExchangeFilterFunction<T extends ExchangeFilterFunction> |
||||
implements ExchangeFilterFunction { |
||||
|
||||
private final ObjectProvider<T> exchangeFilterFunctionProvider; |
||||
|
||||
private T delegate; |
||||
|
||||
public DeferringLoadBalancerExchangeFilterFunction( |
||||
ObjectProvider<T> exchangeFilterFunctionProvider) { |
||||
this.exchangeFilterFunctionProvider = exchangeFilterFunctionProvider; |
||||
} |
||||
|
||||
@Override |
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { |
||||
tryResolveDelegate(); |
||||
return delegate.filter(request, next); |
||||
} |
||||
|
||||
// Visible for tests
|
||||
void tryResolveDelegate() { |
||||
if (delegate == null) { |
||||
delegate = exchangeFilterFunctionProvider.getIfAvailable(); |
||||
if (delegate == null) { |
||||
throw new IllegalStateException( |
||||
"ReactorLoadBalancerExchangeFilterFunction not available."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Visible for tests
|
||||
T getDelegate() { |
||||
return delegate; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive; |
||||
|
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter; |
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Conditional; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Primary; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
|
||||
/** |
||||
* An auto-configuration that provides a {@link BeanPostProcessor} that allows the use of |
||||
* a {@link LoadBalanced} {@link WebClient.Builder} with |
||||
* {@link ReactorLoadBalancerExchangeFilterFunction} and {@link ReactiveLoadBalancer} used |
||||
* under the hood. NOTE: This has been extracted to a separate configuration in order to |
||||
* not impact instantiation and post-processing of other Reactor-LoadBalancer-related |
||||
* beans. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 2.2.0 |
||||
*/ |
||||
@Configuration |
||||
@ConditionalOnClass(WebClient.class) |
||||
@Conditional(LoadBalancerBeanPostProcessorAutoConfiguration.OnAnyLoadBalancerImplementationPresentCondition.class) |
||||
public class LoadBalancerBeanPostProcessorAutoConfiguration { |
||||
|
||||
@Bean |
||||
public LoadBalancerWebClientBuilderBeanPostProcessor loadBalancerWebClientBuilderBeanPostProcessor( |
||||
DeferringLoadBalancerExchangeFilterFunction deferringExchangeFilterFunction, |
||||
ApplicationContext context) { |
||||
return new LoadBalancerWebClientBuilderBeanPostProcessor( |
||||
deferringExchangeFilterFunction, context); |
||||
} |
||||
|
||||
@Configuration |
||||
@Conditional(ReactorLoadBalancerClientAutoConfiguration.OnNoRibbonDefaultCondition.class) |
||||
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class) |
||||
protected static class ReactorDeferringLoadBalancerFilterConfig { |
||||
|
||||
@Bean |
||||
@Primary |
||||
DeferringLoadBalancerExchangeFilterFunction<ReactorLoadBalancerExchangeFilterFunction> reactorDeferringLoadBalancerExchangeFilterFunction( |
||||
ObjectProvider<ReactorLoadBalancerExchangeFilterFunction> exchangeFilterFunctionProvider) { |
||||
return new DeferringLoadBalancerExchangeFilterFunction<>( |
||||
exchangeFilterFunctionProvider); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@ConditionalOnBean(LoadBalancerClient.class) |
||||
@AutoConfigureAfter(ReactorDeferringLoadBalancerFilterConfig.class) |
||||
@Deprecated |
||||
protected static class ReactiveLoadBalancerConfig { |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean |
||||
DeferringLoadBalancerExchangeFilterFunction<LoadBalancerExchangeFilterFunction> deferringLoadBalancerExchangeFilterFunction( |
||||
ObjectProvider<LoadBalancerExchangeFilterFunction> exchangeFilterFunctionProvider) { |
||||
return new DeferringLoadBalancerExchangeFilterFunction<>( |
||||
exchangeFilterFunctionProvider); |
||||
} |
||||
|
||||
} |
||||
|
||||
static final class OnAnyLoadBalancerImplementationPresentCondition |
||||
extends AnyNestedCondition { |
||||
|
||||
private OnAnyLoadBalancerImplementationPresentCondition() { |
||||
super(ConfigurationPhase.REGISTER_BEAN); |
||||
} |
||||
|
||||
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class) |
||||
static class ReactiveLoadBalancerFactoryPresent { |
||||
|
||||
} |
||||
|
||||
@ConditionalOnBean(LoadBalancerClient.class) |
||||
static class LoadBalancerClientPresent { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
|
||||
/** |
||||
* A {@link BeanPostProcessor} that applies |
||||
* {@link DeferringLoadBalancerExchangeFilterFunction} filter to all |
||||
* {@link WebClient.Builder} instances annotated with {@link LoadBalanced}. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 2.2.0 |
||||
*/ |
||||
public class LoadBalancerWebClientBuilderBeanPostProcessor implements BeanPostProcessor { |
||||
|
||||
private final DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction; |
||||
|
||||
private final ApplicationContext context; |
||||
|
||||
public LoadBalancerWebClientBuilderBeanPostProcessor( |
||||
DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction, |
||||
ApplicationContext context) { |
||||
this.exchangeFilterFunction = exchangeFilterFunction; |
||||
this.context = context; |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) |
||||
throws BeansException { |
||||
if (bean instanceof WebClient.Builder) { |
||||
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) { |
||||
return bean; |
||||
} |
||||
((WebClient.Builder) bean).filter(exchangeFilterFunction); |
||||
} |
||||
return bean; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue