Merge branch '2.0.x'
This commit is contained in:
@@ -313,6 +313,15 @@ To disable that behavior, you can set:
|
||||
* `@EnableDiscoveryClient(autoRegister=false)` to permanently disable auto-registration.
|
||||
* `spring.cloud.service-registry.auto-registration.enabled=false` to disable the behavior through configuration.
|
||||
|
||||
===== ServiceRegistry Auto-Registration Events
|
||||
|
||||
There are two events that will be fired when a service auto-registers. The first event, called
|
||||
`InstancePreRegisteredEvent`, is fired before the service is registered. The second
|
||||
event, called `InstanceRegisteredEvent`, is fired after the service is registered. You can register an
|
||||
`ApplicationListener`(s) to listen to and react to these events.
|
||||
|
||||
NOTE: These events will not be fired if `spring.cloud.service-registry.auto-registration.enabled` is set to `false`.
|
||||
|
||||
==== Service Registry Actuator Endpoint
|
||||
|
||||
Spring Cloud Commons provides a `/service-registry` actuator endpoint.
|
||||
@@ -399,15 +408,15 @@ You can use `client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextSer
|
||||
If you would like to disable the retry logic with Spring Retry on the classpath, you can set `spring.cloud.loadbalancer.retry.enabled=false`.
|
||||
See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation] for a description of what these properties do.
|
||||
|
||||
If you would like to implement a `BackOffPolicy` in your retries, you need to create a bean of type `LoadBalancedBackOffPolicyFactory` and return the `BackOffPolicy` you would like to use for a given service, as shown in the following example:
|
||||
If you would like to implement a `BackOffPolicy` in your retries, you need to create a bean of type `LoadBalancedRetryFactory` and override the `createBackOffPolicy` method:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
@Bean
|
||||
LoadBalancedBackOffPolicyFactory backOffPolciyFactory() {
|
||||
return new LoadBalancedBackOffPolicyFactory() {
|
||||
LoadBalancedRetryFactory retryFactory() {
|
||||
return new LoadBalancedRetryFactory() {
|
||||
@Override
|
||||
public BackOffPolicy createBackOffPolicy(String service) {
|
||||
return new ExponentialBackOffPolicy();
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2013-2018 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
|
||||
*
|
||||
* http://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.discovery.event;
|
||||
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* An event to fire before a service is registered.
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class InstancePreRegisteredEvent extends ApplicationEvent {
|
||||
|
||||
private Registration registration;
|
||||
|
||||
/**
|
||||
* Create a new pre registration event.
|
||||
*
|
||||
* @param source the object on which the event initially occurred (never {@code null})
|
||||
*/
|
||||
public InstancePreRegisteredEvent(Object source, Registration registration) {
|
||||
super(source);
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registration data.
|
||||
* @return the registration data
|
||||
*/
|
||||
public Registration getRegistration() {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
@@ -14,9 +16,13 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class LoadBalancerExchangeFilterFunction implements ExchangeFilterFunction {
|
||||
|
||||
private static Log logger = LogFactory
|
||||
.getLog(LoadBalancerExchangeFilterFunction.class);
|
||||
|
||||
private final LoadBalancerClient loadBalancerClient;
|
||||
|
||||
public LoadBalancerExchangeFilterFunction(LoadBalancerClient loadBalancerClient) {
|
||||
@@ -27,10 +33,18 @@ public class LoadBalancerExchangeFilterFunction implements ExchangeFilterFunctio
|
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
||||
URI originalUrl = request.url();
|
||||
String serviceId = originalUrl.getHost();
|
||||
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUrl);
|
||||
if(serviceId == null) {
|
||||
String msg = String.format("Request URI does not contain a valid hostname: %s", originalUrl.toString());
|
||||
logger.warn(msg);
|
||||
return Mono.just(ClientResponse.create(HttpStatus.BAD_REQUEST).body(msg).build());
|
||||
}
|
||||
//TODO: reactive lb client
|
||||
|
||||
ServiceInstance instance = this.loadBalancerClient.choose(serviceId);
|
||||
if(instance == null) {
|
||||
String msg = String.format("Load balancer does not contain an instance for the service %s", serviceId);
|
||||
logger.warn(msg);
|
||||
return Mono.just(ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE).body(msg).build());
|
||||
}
|
||||
URI uri = this.loadBalancerClient.reconstructURI(instance, originalUrl);
|
||||
ClientRequest newRequest = ClientRequest.method(request.method(), uri)
|
||||
.headers(headers -> headers.addAll(request.headers()))
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2018 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
|
||||
*
|
||||
* http://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.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a
|
||||
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
|
||||
* WebClient.Builder}.
|
||||
*
|
||||
* See original {@link org.springframework.boot.web.reactive.function.client.WebClientCustomizer}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface WebClientCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a
|
||||
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
|
||||
* WebClient.Builder} instance.
|
||||
* @param webClientBuilder the client builder to customize
|
||||
*/
|
||||
void customize(WebClient.Builder webClientBuilder);
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
|
||||
import org.springframework.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.cloud.client.discovery.ManagementServerPortUtils;
|
||||
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -113,6 +114,7 @@ public abstract class AbstractAutoServiceRegistration<R extends Registration>
|
||||
// only initialize if nonSecurePort is greater than 0 and it isn't already running
|
||||
// because of containerPortInitializer below
|
||||
if (!this.running.get()) {
|
||||
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
|
||||
register();
|
||||
if (shouldRegisterManagement()) {
|
||||
registerManagement();
|
||||
|
||||
@@ -22,9 +22,11 @@ import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperti
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -33,6 +35,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@@ -68,6 +71,26 @@ public class LoadBalancerExchangeFilterFunctionTests {
|
||||
assertThat(value).isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoInstance() {
|
||||
ClientResponse clientResponse = WebClient.builder()
|
||||
.baseUrl("http://foobar")
|
||||
.filter(lbFunction)
|
||||
.build()
|
||||
.get().exchange().block();
|
||||
assertThat(clientResponse.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHostName() {
|
||||
ClientResponse clientResponse = WebClient.builder()
|
||||
.baseUrl("http:///foobar")
|
||||
.filter(lbFunction)
|
||||
.build()
|
||||
.get().exchange().block();
|
||||
assertThat(clientResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@EnableDiscoveryClient
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@@ -106,6 +129,9 @@ public class LoadBalancerExchangeFilterFunctionTests {
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
if(instances.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
int instanceIdx = random.nextInt(instances.size());
|
||||
return instances.get(instanceIdx);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
@@ -34,12 +42,6 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
@@ -90,20 +92,29 @@ public class ReactiveLoadBalancerAutoConfigurationTests {
|
||||
assertThat(getFilters(two.nonLoadBalanced)).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noCustomWebClientBuilders() {
|
||||
ConfigurableApplicationContext context = init(NoWebClientBuilder.class);
|
||||
final Map<String, WebClient.Builder> webClientBuilders = context
|
||||
.getBeansOfType(WebClient.Builder.class);
|
||||
|
||||
assertThat(webClientBuilders).hasSize(1);
|
||||
|
||||
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
|
||||
|
||||
assertThat(builder).isNotNull();
|
||||
assertThat(getFilters(builder)).isNullOrEmpty();
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext init(Class<?> config) {
|
||||
return new SpringApplicationBuilder().web(WebApplicationType.NONE)
|
||||
// .properties("spring.aop.proxyTargetClass=true")
|
||||
.sources(config, ReactiveLoadBalancerAutoConfiguration.class).run();
|
||||
.sources(config, WebClientAutoConfiguration.class,
|
||||
ReactiveLoadBalancerAutoConfiguration.class).run();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class OneWebClientBuilder {
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
WebClient.Builder loadBalancedWebClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
protected static class NoWebClientBuilder {
|
||||
|
||||
@Bean
|
||||
LoadBalancerClient loadBalancerClient() {
|
||||
@@ -116,7 +127,18 @@ public class ReactiveLoadBalancerAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TwoWebClientBuilders {
|
||||
protected static class OneWebClientBuilder extends NoWebClientBuilder {
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
WebClient.Builder loadBalancedWebClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TwoWebClientBuilders extends OneWebClientBuilder {
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
@@ -124,17 +146,6 @@ public class ReactiveLoadBalancerAutoConfigurationTests {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
WebClient.Builder loadBalancedWebClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerClient loadBalancerClient() {
|
||||
return new NoopLoadBalancerClient();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class Two {
|
||||
@Autowired
|
||||
|
||||
@@ -11,8 +11,12 @@ import org.springframework.boot.actuate.autoconfigure.web.server.LocalManagement
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
@@ -34,6 +38,12 @@ public class AbstractAutoServiceRegistrationTests {
|
||||
@Autowired
|
||||
private TestAutoServiceRegistration autoRegistration;
|
||||
|
||||
@Autowired
|
||||
private PreEventListener preEventListener;
|
||||
|
||||
@Autowired
|
||||
public PostEventListener postEventListener;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@@ -52,6 +62,14 @@ public class AbstractAutoServiceRegistrationTests {
|
||||
assertEquals("Lifecycle appName is wrong", "application", autoRegistration.getAppName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventsFireTest() {
|
||||
assertTrue(preEventListener.wasFired);
|
||||
assertEquals("testRegistration2", preEventListener.registration.getServiceId());
|
||||
assertTrue(postEventListener.wasFired);
|
||||
assertEquals("testRegistration2", postEventListener.config.getServiceId());
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration
|
||||
public static class Config {
|
||||
@@ -59,6 +77,36 @@ public class AbstractAutoServiceRegistrationTests {
|
||||
public TestAutoServiceRegistration testAutoServiceRegistration() {
|
||||
return new TestAutoServiceRegistration();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PreEventListener preRegisterListener() {
|
||||
return new PreEventListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PostEventListener postEventListener() {
|
||||
return new PostEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PreEventListener implements ApplicationListener<InstancePreRegisteredEvent> {
|
||||
public boolean wasFired = false;
|
||||
public Registration registration;
|
||||
@Override
|
||||
public void onApplicationEvent(InstancePreRegisteredEvent event) {
|
||||
this.registration = event.getRegistration();
|
||||
this.wasFired = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PostEventListener implements ApplicationListener<InstanceRegisteredEvent> {
|
||||
public boolean wasFired = false;
|
||||
public Registration config;
|
||||
@Override
|
||||
public void onApplicationEvent(InstanceRegisteredEvent event) {
|
||||
this.config = (Registration)event.getConfig();
|
||||
this.wasFired = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestRegistration implements Registration {
|
||||
@@ -188,7 +236,7 @@ public class AbstractAutoServiceRegistrationTests {
|
||||
|
||||
@Override
|
||||
protected Object getConfiguration() {
|
||||
return null;
|
||||
return getRegistration();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -173,13 +173,24 @@ public class BootstrapApplicationListener
|
||||
// Don't use the default properties in this builder
|
||||
.registerShutdownHook(false).logStartupInfo(false)
|
||||
.web(WebApplicationType.NONE);
|
||||
final SpringApplication builderApplication = builder.application();
|
||||
if(builderApplication.getMainApplicationClass() == null){
|
||||
// gh_425:
|
||||
// SpringApplication cannot deduce the MainApplicationClass here
|
||||
// if it is booted from SpringBootServletInitializer due to the
|
||||
// absense of the "main" method in stackTraces.
|
||||
// But luckily this method's second parameter "application" here
|
||||
// carries the real MainApplicationClass which has been explicitly
|
||||
// set by SpringBootServletInitializer itself already.
|
||||
builder.main(application.getMainApplicationClass());
|
||||
}
|
||||
if (environment.getPropertySources().contains("refreshArgs")) {
|
||||
// If we are doing a context refresh, really we only want to refresh the
|
||||
// Environment, and there are some toxic listeners (like the
|
||||
// LoggingApplicationListener) that affect global static state, so we need a
|
||||
// way to switch those off.
|
||||
builder.application()
|
||||
.setListeners(filterListeners(builder.application().getListeners()));
|
||||
builderApplication
|
||||
.setListeners(filterListeners(builderApplication.getListeners()));
|
||||
}
|
||||
List<Class<?>> sources = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
|
||||
Reference in New Issue
Block a user