Commit 6654d320 by Spencer Gibb

create SpringClientFactory for ribbon to not fail on client creation if…

create SpringClientFactory for ribbon to not fail on client creation if LoadBalancer is already created. SpringClientFactory is not a static factory, but an instance factory. Remove unused hystrix configuration classes.
parent f934b88a
......@@ -27,7 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.EurekaDiscoverClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.servo.ServoMetricReader;
import org.springframework.context.ApplicationListener;
import org.springframework.context.SmartLifecycle;
......@@ -140,7 +140,12 @@ public class EurekaClientConfiguration implements SmartLifecycle, Ordered {
@Bean
public DiscoveryClient discoveryClient() {
return new EurekaDiscoverClient();
return new EurekaDiscoveryClient();
}
@Bean
public SpringClientFactory springClientFactory() {
return new SpringClientFactory();
}
@Bean
......
package org.springframework.cloud.netflix;
package org.springframework.cloud.netflix.eureka;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
......@@ -8,7 +8,7 @@ import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
/**
* @author Spencer Gibb
*/
public class EurekaDiscoverClient implements DiscoveryClient {
public class EurekaDiscoveryClient implements DiscoveryClient {
@Autowired
private EurekaInstanceConfigBean config;
......
......@@ -15,9 +15,7 @@
*/
package org.springframework.cloud.netflix.hystrix;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import java.lang.annotation.*;
......@@ -27,34 +25,6 @@ import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HystrixConfigurationSelector.class)
@Import(HystrixConfiguration.class)
public @interface EnableHystrix {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
* opposed to standard Java interface-based proxies ({@code false}). The default is
* {@code false}. <strong>Applicable only if {@link #mode()} is set to
* {@link org.springframework.context.annotation.AdviceMode#PROXY}</strong>.
* <p>Note that setting this attribute to {@code true} will affect <em>all</em>
* Spring-managed beans requiring proxying, not just those marked with
* {@code @Transactional}. For example, other beans marked with Spring's
* {@code @Async} annotation will be upgraded to subclass proxying at the same
* time. This approach has no negative impact in practice unless one is explicitly
* expecting one type of proxy vs another, e.g. in tests.
*/
boolean proxyTargetClass() default false;
/**
* Indicate how transactional advice should be applied. The default is
* {@link org.springframework.context.annotation.AdviceMode#PROXY}.
* @see org.springframework.context.annotation.AdviceMode
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate the ordering of the execution of the transaction advisor
* when multiple advices are applied at a specific joinpoint.
* The default is {@link org.springframework.core.Ordered#LOWEST_PRECEDENCE}.
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
\ No newline at end of file
......@@ -29,6 +29,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -65,7 +66,7 @@ public class HystrixConfiguration implements ImportAware {
}
@Bean
// TODO: add enable/disable
@ConditionalOnExpression("${hystrix.stream.endpoint.enabled:true}")
// TODO: make it @ConditionalOnWebApp (need a nested class)
public HystrixStreamEndpoint hystrixStreamEndpoint() {
return new HystrixStreamEndpoint();
......@@ -81,20 +82,6 @@ public class HystrixConfiguration implements ImportAware {
+ importMetadata.getClassName());
}
@Autowired(required = false)
void setConfigurers(Collection<HystrixConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException(
"Only one HystrixConfigurer may exist");
}
// TODO: create CircuitBreakerConfigurer API
// CircuitBreakerConfigurer configurer = configurers.iterator().next();
// this.txManager = configurer.annotationDrivenTransactionManager();
}
@Configuration
@ConditionalOnClass(GaugeService.class)
protected static class HystrixMetricsPollerConfiguration implements SmartLifecycle {
......
/*
* Copyright 2013-2014 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.netflix.hystrix;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
import org.springframework.context.annotation.AutoProxyRegistrar;
/**
* @author Spencer Gibb
*/
public class HystrixConfigurationSelector extends AdviceModeImportSelector<EnableHystrix> {
/**
* The name of the AspectJ transaction management @{@code Configuration} class.
*/
private static final String TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration";
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[]{AutoProxyRegistrar.class.getName(), HystrixConfiguration.class.getName()};
case ASPECTJ:
return new String[]{TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
/*
* Copyright 2013-2014 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.netflix.hystrix;
/**
* @author Spencer Gibb
*/
public interface HystrixConfigurer {
}
......@@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import com.netflix.client.ClientFactory;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
......@@ -22,6 +21,9 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Autowired
private ServerListInitializer serverListInitializer;
@Autowired
private SpringClientFactory clientFactory;
private Map<String, ILoadBalancer> balancers = new HashMap<String, ILoadBalancer>();
public RibbonLoadBalancerClient(List<BaseLoadBalancer> balancers) {
......@@ -35,7 +37,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
serverListInitializer.initialize(serviceId);
ILoadBalancer loadBalancer = this.balancers.get(serviceId);
if (loadBalancer == null) {
loadBalancer = ClientFactory.getNamedLoadBalancer(serviceId);
loadBalancer = clientFactory.getNamedLoadBalancer(serviceId);
}
Server server = loadBalancer.chooseServer("default");
if (server == null) {
......
package org.springframework.cloud.netflix.ribbon;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientException;
import com.netflix.client.IClient;
import com.netflix.client.IClientConfigAware;
import lombok.extern.slf4j.Slf4j;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.servo.monitor.Monitors;
/**
* A factory that creates client, load balancer and client configuration instances from properties. It also keeps mappings of client names to
* the created instances.
*
*/
@Slf4j
public class SpringClientFactory {
private Map<String, IClient<?,?>> simpleClientMap = new ConcurrentHashMap<>();
private Map<String, ILoadBalancer> namedLBMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, IClientConfig> namedConfig = new ConcurrentHashMap<>();
/**
* Utility method to create client and load balancer (if enabled in client config) given the name and client config.
* Instances are created using reflection (see {@link #instantiateInstanceWithClientConfig(String, IClientConfig)}
*
* @param restClientName
* @param clientConfig
* @throws ClientException if any errors occurs in the process, or if the client with the same name already exists
*/
public synchronized IClient<?, ?> registerClientFromProperties(String restClientName, IClientConfig clientConfig) throws ClientException {
IClient<?, ?> client;
ILoadBalancer loadBalancer = null;
if (simpleClientMap.get(restClientName) != null) {
throw new ClientException(
ClientException.ErrorType.GENERAL,
"A Rest Client with this name is already registered. Please use a different name");
}
try {
String clientClassName = (String) clientConfig.getProperty(CommonClientConfigKey.ClientClassName);
client = (IClient<?, ?>) instantiateInstanceWithClientConfig(clientClassName, clientConfig);
boolean initializeNFLoadBalancer = Boolean.parseBoolean(clientConfig.getProperty(
CommonClientConfigKey.InitializeNFLoadBalancer, DefaultClientConfigImpl.DEFAULT_ENABLE_LOADBALANCER).toString());
if (initializeNFLoadBalancer) {
loadBalancer = getNamedLoadBalancer(restClientName, clientConfig.getClass());
}
if (client instanceof AbstractLoadBalancerAwareClient) {
((AbstractLoadBalancerAwareClient) client).setLoadBalancer(loadBalancer);
}
} catch (Throwable e) {
String message = "Unable to InitializeAndAssociateNFLoadBalancer set for RestClient:"
+ restClientName;
log.warn(message, e);
throw new ClientException(ClientException.ErrorType.CONFIGURATION,
message, e);
}
simpleClientMap.put(restClientName, client);
Monitors.registerObject("Client_" + restClientName, client);
log.info("Client Registered:" + client.toString());
return client;
}
/**
* Return the named client from map if already created. Otherwise creates the client using the configuration returned by {@link #getNamedConfig(String)}.
*
* @throws RuntimeException if an error occurs in creating the client.
*/
public synchronized IClient getNamedClient(String name) {
return getNamedClient(name, DefaultClientConfigImpl.class);
}
public synchronized <C extends IClient> C namedClient(String name, Class<C> clientClass) {
return clientClass.cast(getNamedClient(name, DefaultClientConfigImpl.class));
}
/**
* Return the named client from map if already created. Otherwise creates the client using the configuration returned by {@link #createNamedClient(String, Class)}.
*
* @throws RuntimeException if an error occurs in creating the client.
*/
public synchronized IClient getNamedClient(String name, Class<? extends IClientConfig> configClass) {
if (simpleClientMap.get(name) != null) {
return simpleClientMap.get(name);
}
try {
return createNamedClient(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create client", e);
}
}
/**
* Creates a named client using a IClientConfig instance created off the configClass class object passed in as the parameter.
*
* @throws ClientException if any error occurs, or if the client with the same name already exists
*/
public synchronized IClient createNamedClient(String name, Class<? extends IClientConfig> configClass) throws ClientException {
IClientConfig config = getNamedConfig(name, configClass);
return registerClientFromProperties(name, config);
}
/**
* Get the load balancer associated with the name, or create one with an instance {@link DefaultClientConfigImpl} if does not exist
*
* @throws RuntimeException if any error occurs
*/
public synchronized ILoadBalancer getNamedLoadBalancer(String name) {
return getNamedLoadBalancer(name, DefaultClientConfigImpl.class);
}
/**
* Get the load balancer associated with the name, or create one with an instance of configClass if does not exist
*
* @throws RuntimeException if any error occurs
* @see #registerNamedLoadBalancerFromProperties(String, Class)
*/
public synchronized ILoadBalancer getNamedLoadBalancer(String name, Class<? extends IClientConfig> configClass) {
ILoadBalancer lb = namedLBMap.get(name);
if (lb != null) {
return lb;
} else {
try {
lb = registerNamedLoadBalancerFromProperties(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create load balancer", e);
}
return lb;
}
}
/**
* Create and register a load balancer with the name and given the class of configClass.
*
* @throws ClientException if load balancer with the same name already exists or any error occurs
* @see #instantiateInstanceWithClientConfig(String, IClientConfig)
*/
public ILoadBalancer registerNamedLoadBalancerFromclientConfig(String name, IClientConfig clientConfig) throws ClientException {
if (namedLBMap.get(name) != null) {
throw new ClientException("LoadBalancer for name " + name + " already exists");
}
ILoadBalancer lb = null;
try {
String loadBalancerClassName = (String) clientConfig.getProperty(CommonClientConfigKey.NFLoadBalancerClassName);
lb = (ILoadBalancer) instantiateInstanceWithClientConfig(loadBalancerClassName, clientConfig);
namedLBMap.put(name, lb);
log.info("Client:" + name
+ " instantiated a LoadBalancer:" + lb.toString());
return lb;
} catch (Exception e) {
throw new ClientException("Unable to instantiate/associate LoadBalancer with Client:" + name, e);
}
}
/**
* Create and register a load balancer with the name and given the class of configClass.
*
* @throws ClientException if load balancer with the same name already exists or any error occurs
* @see #instantiateInstanceWithClientConfig(String, IClientConfig)
*/
public synchronized ILoadBalancer registerNamedLoadBalancerFromProperties(String name, Class<? extends IClientConfig> configClass) throws ClientException {
if (namedLBMap.get(name) != null) {
throw new ClientException("LoadBalancer for name " + name + " already exists");
}
IClientConfig clientConfig = getNamedConfig(name, configClass);
return registerNamedLoadBalancerFromclientConfig(name, clientConfig);
}
/**
* Creates instance related to client framework using reflection. It first checks if the object is an instance of
* {@link IClientConfigAware} and if so invoke {@link IClientConfigAware#initWithNiwsConfig(IClientConfig)}. If that does not
* apply, it tries to find if there is a constructor with {@link IClientConfig} as a parameter and if so invoke that constructor. If neither applies,
* it simply invokes the no-arg constructor and ignores the clientConfig parameter.
*
* @param className Class name of the object
* @param clientConfig IClientConfig object used for initialization.
*/
@SuppressWarnings("unchecked")
public Object instantiateInstanceWithClientConfig(String className, IClientConfig clientConfig)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class clazz = Class.forName(className);
if (IClientConfigAware.class.isAssignableFrom(clazz)) {
IClientConfigAware obj = (IClientConfigAware) clazz.newInstance();
obj.initWithNiwsConfig(clientConfig);
return obj;
} else {
try {
if (clazz.getConstructor(IClientConfig.class) != null) {
return clazz.getConstructor(IClientConfig.class).newInstance(clientConfig);
}
} catch (Throwable e) { // NOPMD
}
}
log.warn("Class " + className + " neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used.");
return clazz.newInstance();
}
/**
* Get the client configuration given the name or create one with {@link DefaultClientConfigImpl} if it does not exist.
*
* @see #getNamedConfig(String, Class)
*/
public IClientConfig getNamedConfig(String name) {
return getNamedConfig(name, DefaultClientConfigImpl.class);
}
/**
* Get the client configuration given the name or create one with clientConfigClass if it does not exist. An instance of IClientConfig
* is created and {@link IClientConfig#loadProperties(String)} will be called.
*/
public IClientConfig getNamedConfig(String name, Class<? extends IClientConfig> clientConfigClass) {
IClientConfig config = namedConfig.get(name);
if (config != null) {
return config;
} else {
try {
config = (IClientConfig) clientConfigClass.newInstance();
config.loadProperties(name);
} catch (Throwable e) {
log.error("Unable to create client config instance", e);
return null;
}
config.loadProperties(name);
IClientConfig old = namedConfig.putIfAbsent(name, config);
if (old != null) {
config = old;
}
return config;
}
}
}
......@@ -6,9 +6,9 @@ import static com.netflix.client.config.CommonClientConfigKey.NFLoadBalancerRule
import static com.netflix.client.config.CommonClientConfigKey.NIWSServerListClassName;
import static com.netflix.client.config.CommonClientConfigKey.NIWSServerListFilterClassName;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.ServerListInitializer;
import com.netflix.client.ClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DeploymentContext.ContextKey;
import com.netflix.discovery.EurekaClientConfig;
......@@ -28,17 +28,19 @@ import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
*/
public class EurekaRibbonInitializer implements ServerListInitializer {
private EurekaClientConfig client;
private EurekaClientConfig clientConfig;
private SpringClientFactory clientFactory;
public EurekaRibbonInitializer(EurekaClientConfig client) {
this.client = client;
public EurekaRibbonInitializer(EurekaClientConfig clientConfig, SpringClientFactory clientFactory) {
this.clientConfig = clientConfig;
this.clientFactory = clientFactory;
}
@Override
public void initialize(String serviceId) {
if (client != null
if (clientConfig != null
&& ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone) == null) {
String[] zones = client.getAvailabilityZones(client.getRegion());
String[] zones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
String zone = zones != null && zones.length > 0 ? zones[0] : null;
if (zone != null) {
// You can set this with archaius.deployment.* (maybe requires
......@@ -58,7 +60,7 @@ public class EurekaRibbonInitializer implements ServerListInitializer {
setProp(serviceId, NIWSServerListFilterClassName.key(),
ZonePreferenceServerListFilter.class.getName());
setProp(serviceId, EnableZoneAffinity.key(), "true");
ILoadBalancer loadBalancer = ClientFactory.getNamedLoadBalancer(serviceId);
ILoadBalancer loadBalancer = clientFactory.getNamedLoadBalancer(serviceId);
wrapServerList(loadBalancer);
}
......@@ -71,6 +73,7 @@ public class EurekaRibbonInitializer implements ServerListInitializer {
// This is optional: you can use the native Eureka AWS features as long as
// the server zone is populated. TODO: find a way to back off if AWS
// metadata *is* available.
// @see com.netflix.appinfo.AmazonInfo.Builder
dynamic.setServerListImpl(new DomainExtractingServerList(list));
}
}
......
......@@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.ServerListInitializer;
import org.springframework.context.annotation.Bean;
......@@ -40,10 +41,10 @@ import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
public class RibbonEurekaAutoConfiguration {
@Autowired(required=false)
private EurekaClientConfig client;
private EurekaClientConfig clientConfig;
@Bean
public ServerListInitializer serverListInitializer() {
return new EurekaRibbonInitializer(client);
public ServerListInitializer serverListInitializer(SpringClientFactory clientFactory) {
return new EurekaRibbonInitializer(clientConfig, clientFactory);
}
}
......@@ -16,13 +16,13 @@ import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.ServerListInitializer;
import org.springframework.cloud.netflix.zuul.RibbonCommand;
import org.springframework.cloud.netflix.zuul.SpringFilter;
import org.springframework.util.StringUtils;
import com.netflix.client.ClientException;
import com.netflix.client.ClientFactory;
import com.netflix.client.http.HttpRequest.Verb;
import com.netflix.client.http.HttpResponse;
import com.netflix.hystrix.exception.HystrixRuntimeException;
......@@ -71,10 +71,9 @@ public class RibbonRoutingFilter extends SpringFilter {
String serviceId = (String) context.get("serviceId");
//TODO: should this be an interface or just config?
getBean(ServerListInitializer.class).initialize(serviceId);
RestClient restClient = (RestClient) ClientFactory.getNamedClient(serviceId);
RestClient restClient = getBean(SpringClientFactory.class).namedClient(serviceId, RestClient.class);
String uri = request.getRequestURI();
if (context.get("requestURI") != null) {
......
......@@ -21,9 +21,9 @@ import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Test;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import com.netflix.client.ClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DeploymentContext.ContextKey;
import com.netflix.loadbalancer.ILoadBalancer;
......@@ -45,10 +45,11 @@ public class EurekaRibbonInitializerTests {
public void basicConfigurationCreatedForLoadBalancer() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
client.getAvailabilityZones().put(client.getRegion(), "foo");
SpringClientFactory clientFactory = new SpringClientFactory();
EurekaRibbonInitializer initializer = new EurekaRibbonInitializer(
client);
client, clientFactory);
initializer.initialize("service");
ILoadBalancer balancer = ClientFactory.getNamedLoadBalancer("service");
ILoadBalancer balancer = clientFactory.getNamedLoadBalancer("service");
assertNotNull(balancer);
@SuppressWarnings("unchecked")
ZoneAwareLoadBalancer<Server> aware = (ZoneAwareLoadBalancer<Server>) balancer;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment