在之前文章spring clound负载均衡之Ribbon(二)- 自动装配 中,介绍了ribbon自动装配的原理,这篇文章中将主要介绍在ribbon启动过程中,涉及到的重要类型。这些类型在ribbon中充当比较重要的作用。下面我们就一起来看看吧。
RibbonClientSpecification
该类作为ribbon配置类型存在,主要由spring-cloud提供,我们看下该类型的源码:
/**
* Specification with name and configuration.
*/
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
public class RibbonClientSpecification implements NamedContextFactory.Specification {
....
}
在spring实现中,实现类Speicification接口,该接口中主要包含了两个属性:name与configuration两个。该类型主要用于保存所有的配置类型相关。
在Spring-cloud-netflix默认配置中,是有两个RibbonClientSpecification的指定,这两个指定,也是为后面使用RestTemplate做了默认配置的设定,在开始看这两个配置之前,优先查看下两个比较重要的注解:
@RibbonClients@RibbonClient
@RibbonClients
这个注解可以包含多个@RibbonClient注解,同时当我们默认的configuration配置的时候,则可以通过defaultConfiguration进行设置,具体查看源码如下:
/**
* Convenience annotation that allows user to combine multiple <code>@RibbonClient</code>
* annotations on a single class (including in Java 7).
*
* @author Dave Syer
*/
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {
RibbonClient[] value() default {};
Class<?>[] defaultConfiguration() default {};
}
在这个源码中,通过@Import引入了RibbonClientConfigurationRegistrar类型,对@RibbonClients注解进行单独的处理。
@RibbonClient
该注解主要对单个服务进行ribbon项的设置,具体源码如下:
package org.springframework.cloud.netflix.ribbon;
/**
* Declarative configuration for a ribbon client. Add this annotation to any
* <code>@Configuration</code> and then inject a {@link SpringClientFactory} to access the
* client that is created.
*
* @author Dave Syer
*/
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
/**
* Synonym for name (the name of the client).
*
* @see #name()
* @return name of the Ribbon client
*/
String value() default "";
/**
* The name of the ribbon client, uniquely identifying a set of client resources,
* including a load balancer.
* @return name of the Ribbon client
*/
String name() default "";
/**
* A custom <code>@Configuration</code> for the ribbon client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link ILoadBalancer}, {@link ServerListFilter}, {@link IRule}.
*
* @see RibbonClientConfiguration for the defaults
* @return the custom Ribbon client configuration
*/
Class<?>[] configuration() default {};
}
这个注解主要为ribbon配置指定名称,以及configuration配置信息。同时这个注解在使用的时候,也通过@Import的方式引入了RibbonClientConfigurationRegistrar对注解的处理,下面我查看下该类的源码处理:
RibbonClientConfigurationRegistrar
这个类是ImportBeanDefinitionRegistrar的实现类,用于实现动态bean的实现,下面是该类的实现:
package org.springframework.cloud.netflix.ribbon;
/**
* @author Dave Syer
*/
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取元数据的RibbonClients注解
Map<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);
// 包含了value属性
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
}
}
// 是否包含defaultConfiguration配置
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
// 在配置名称前加入default配置项信息
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 注册客户端配置信息
registerClientConfiguration(registry, name,
attrs.get("defaultConfiguration"));
}
// 获取RibbonClient配置列表
Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);
// 客户端名称
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("value");
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @RibbonClient");
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
// 获取RibbonClientSpecification类的元数据
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册BeanDefinition
registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition());
}
}
SpringClientFactory
该类作为客户端工厂存在,主要用于保存Specification相关配置类信息。同时该类根据名称进行隔离,保证了bean之间不会相互影响。下面为SpringClientFactory的类图.
从类图上可以看出,该类继承了NamedContextFactory类型,我们看下该类的说明文档:
/**
* Creates a set of child contexts that allows a set of Specifications to define the beans
* in each child context.
*
* Ported from spring-cloud-netflix FeignClientFactory and SpringClientFactory
*
* @param <C> specification
* @author Spencer Gibb
* @author Dave Syer
*/
// TODO: add javadoc
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
}
通过注解可以得知,该类主要用于创建关于Specifications的子容器。在每个子容器中,对应的Specification的bean是相互隔离的。
我们在查看SpringClientFactory创建源码:
/**
* A factory that creates client, load balancer and client configuration instances. It
* creates a Spring ApplicationContext per client name, and extracts the beans that it
* needs from there.
*
* @author Spencer Gibb
* @author Dave Syer
*/
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
/**
* 创建SpringClientFactory对象,处理的类型为RibbonClientConfiguration, sourceType为ribbon, propertyName为ribbon.client.name
*/
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
/**
* Get the rest client associated with the name.
* @param name name to search by
* @param clientClass the class of the client bean
* @param <C> {@link IClient} subtype
* @return {@link IClient} instance
* @throws RuntimeException if any error occurs
*/
public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
return getInstance(name, clientClass);
}
/**
* Get the load balancer associated with the name.
* @param name name to search by
* @return {@link ILoadBalancer} instance
* @throws RuntimeException if any error occurs
*/
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
/**
* Get the client config associated with the name.
* @param name name to search by
* @return {@link IClientConfig} instance
* @throws RuntimeException if any error occurs
*/
public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
}
/**
* Get the load balancer context associated with the name.
* @param serviceId id of the service to search by
* @return {@link RibbonLoadBalancerContext} instance
* @throws RuntimeException if any error occurs
*/
public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
return getInstance(serviceId, RibbonLoadBalancerContext.class);
}
static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
return instantiateWithConfig(null, clazz, config);
}
// 实例化IClientConfig对象
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
Class<C> clazz, IClientConfig config) {
C result = null;
try {
Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
result = constructor.newInstance(config);
}
catch (Throwable e) {
// Ignored
}
if (result == null) {
result = BeanUtils.instantiate(clazz);
if (result instanceof IClientConfigAware) {
((IClientConfigAware) result).initWithNiwsConfig(config);
}
if (context != null) {
context.getAutowireCapableBeanFactory().autowireBean(result);
}
}
return result;
}
@Override
public <C> C getInstance(String name, Class<C> type) {
// 通过NameContextFactory获取instance对象
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
// 如果获取失败,则创建instance实例
IClientConfig config = getInstance(name, IClientConfig.class);
// 实例化config对象
return instantiateWithConfig(getContext(name), type, config);
}
@Override
protected AnnotationConfigApplicationContext getContext(String name) {
return super.getContext(name);
}
}
通过以上源码可以看出,该SpringClientFactory对象主要用于创建ribbon客户端必须的对象,其中包括IClient,ILoadBalancer, IClientConfig, RibbonLoadBalancerContext. 因此该类就是一个工厂类型,创建所需要的组件对象。
在该类中,最为重要的一个方法就是getInstance()方法,该方法会根据名称以及type获取对应的instance对象,因此我们主要看下该方法中的执行流程:
NamedContextFactory#getInstance()
我们直接上该类中源码信息:
public <T> T getInstance(String name, Class<T> type) {
// 根据name获取绑定的子context
AnnotationConfigApplicationContext context = getContext(name);
// 判断context中是否包含了需要type的bean, 如果包含,则从context中获取
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
return context.getBean(type);
}
return null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
// 判断contexts缓存中是否包含了指定name的context对象
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// 如果不包含,则创建上下文对象
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
// 创建ApplicaionContext对象
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 判断configurations中是否包含了指定name的配置信息,如果包含,将configuration注册到context上下文中
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
// 如果configurations中包含default的配置信息,则将默认的confuration注入
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));
// 判断是否包含了parent context, 如果包含,则为当前context设置parent
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
// 刷新当前容器
context.refresh();
return context;
}
通过以上源码可以看出,在ribbon中做了LoadBalancerClient的配置隔离的,隔离的方式是通过定义子容器的方式来实现。因此,当我们有负载均衡个性化配置时,则可以通过单独为某一个server单独配置的方式,实现个性化。
SpringClientFactory#getInstance()
上面谈到了在获取父类的时候,实际上父类getInstance()方法只是从parent的ApplicaionContext中获取对应的实例对象,当parent中虎丘实例对象失败时,此时SpringClientFactory则会自己处理特殊情况,具体代码如下:
IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config);
可以看出,这里其实是一个递归的调用,会再从parent的ApplicaiontContext中获取IClientConfig的实现Bean。紧接着就会调用instantiateWithConfig方法,我们看下该方法的源码实现:
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
Class<C> clazz, IClientConfig config) {
C result = null;
try {
// 这里其实有个注意的点,在所有传入需要获取实例的class对象,都要求必须包含IClientConfig构造函数
Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
result = constructor.newInstance(config);
}
catch (Throwable e) {
// Ignored
}
if (result == null) {
// 当不包含IClientConfig构造函数的时候, 则使用默认的构造函数创建对象
result = BeanUtils.instantiate(clazz);
// 如果该class对象为IClientConfigAware的实现, 则调用initWithNiwsConfig(config)方法,做类型的初始化
if (result instanceof IClientConfigAware) {
((IClientConfigAware) result).initWithNiwsConfig(config);
}
// 当上线问存在时, 执行autowireBean对象
if (context != null) {
context.getAutowireCapableBeanFactory().autowireBean(result);
}
}
return result;
}
RibbonBalancerClient
该类主要是ribbon客户端具体实现,首先看下该类的一个设计结构:
在RibbonBalancerClient中,最重要的依赖就是SpringClientFactory对象,上面我们也提到了,SpringClientFactory中负责创建于Ribbon相关的主要对象。下面我们主要看下RibbonBalancerClient中比较重要的几个方法:
/*
* Copyright 2013-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.netflix.ribbon;
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
// 重新组装URI信息,该方法会传入instance信息,以及请求的uri信息, 然后组装成为新的URI并返回
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
// 获取serviceId
String serviceId = instance.getServiceId();
// 通过serviceId创建LoadBalancerContext上下文对象
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
URI uri;
Server server;
// 判断instance是否为RibbonServer
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
// 从ribbon server中获取服务信息
server = ribbonServer.getServer();
// 根据需要将http请求转换为https请求
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
else {
// 不是RibbonServer对象,则走该逻辑,测试创建Server对象
server = new Server(instance.getScheme(), instance.getHost(),instance.getPort());
// 通过SpringClientFactory对象创建ClientConfig对象
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
// 根据serviceId获取ServerIntrospector对象
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
// 根据需要将Http请求转换为https请求
uri = updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
}
// 重新组装URI信息
return context.reconstructURIWithServer(server, uri);
}
// 该方法用于选择合适的ServiceInstance信息
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
/**
* New: Select a server using a 'key'.
* @param serviceId of the service to choose an instance for
* @param hint to specify the service instance
* @return the selected {@link ServiceInstance}
*/
public ServiceInstance choose(String serviceId, Object hint) {
// 根据serviceId获取Server对象,该对象根据LoadBalancer获取
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
// 组装成为RibbonServer对象
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
// 执行请求
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
/**
* New: Execute a request by selecting server using a 'key'. The hint will have to be
* the last parameter to not mess with the ``execute(serviceId, ServiceInstance,
* request)`` method. This somewhat breaks the fluent coding style when using a lambda
* to define the LoadBalancerRequest.
* @param <T> returned request execution result type
* @param serviceId id of the service to execute the request to
* @param request to be executed
* @param hint used to choose appropriate {@link Server} instance
* @return request execution result
* @throws IOException executing the request may result in an {@link IOException}
*/
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 获取LoadBalancer对象
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 获取Server对象
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 组装成为RibbonServer对象
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
// 执行请求
return execute(serviceId, ribbonServer, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if (serviceInstance instanceof RibbonServer) {
server = ((RibbonServer) serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 获取Ribbon LoadBalancer上下文对象
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
// Ribbon 状态记录器
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
// 执行请求
T returnVal = request.apply(serviceInstance);
// 记录状态值
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
// 创建ServerIntrospector对象
private ServerIntrospector serverIntrospector(String serviceId) {
// 通过SpringClientFactory创建ServerIntrospector对象
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
// 判断是否支持安全链接
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
return RibbonUtils.isSecure(config, serverIntrospector, server);
}
// Note: This method could be removed?
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId), null);
}
protected Server getServer(ILoadBalancer loadBalancer) {
return getServer(loadBalancer, null);
}
// 获取需要调用的Server对象,对象通过LoadBalancer进行选择
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
// 获取LoadBalancer对象
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
/**
* Ribbon-server-specific {@link ServiceInstance} implementation.
*/
public static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map<String, String> metadata;
public RibbonServer(String serviceId, Server server) {
this(serviceId, server, false, Collections.emptyMap());
}
public RibbonServer(String serviceId, Server server, boolean secure,
Map<String, String> metadata) {
this.serviceId = serviceId;
this.server = server;
this.secure = secure;
this.metadata = metadata;
}
@Override
public String getInstanceId() {
return this.server.getId();
}
@Override
public String getServiceId() {
return this.serviceId;
}
@Override
public String getHost() {
return this.server.getHost();
}
@Override
public int getPort() {
return this.server.getPort();
}
@Override
public boolean isSecure() {
return this.secure;
}
@Override
public URI getUri() {
return DefaultServiceInstance.getUri(this);
}
@Override
public Map<String, String> getMetadata() {
return this.metadata;
}
public Server getServer() {
return this.server;
}
@Override
public String getScheme() {
return this.server.getScheme();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("RibbonServer{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append(", server=").append(server);
sb.append(", secure=").append(secure);
sb.append(", metadata=").append(metadata);
sb.append('}');
return sb.toString();
}
}
}
通过分析我们可以知道,其实在RibbonLoadBalancerClient中,在执行请求时,主要通过ILoadBalance选择合适的ServerInstance,然后执行远程请求。
PropertiesFactory
该类是作为配置类型的存在,当对单个serviceId有特殊需求的时候,此时我们将会用到这里面对的配置项,首先我们直接上源码:
/*
* Copyright 2016-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.netflix.ribbon;
/**
* @author Spencer Gibb
*/
public class PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
// class到配置之间的映射关系
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
public boolean isSet(Class clazz, String name) {
return StringUtils.hasText(getClassName(clazz, name));
}
public String getClassName(Class clazz, String name) {
if (this.classToProperty.containsKey(clazz)) {
String classNameProperty = this.classToProperty.get(clazz);
String className = environment
.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
return className;
}
return null;
}
@SuppressWarnings("unchecked")
public <C> C get(Class<C> clazz, IClientConfig config, String name) {
String className = getClassName(clazz, name);
if (StringUtils.hasText(className)) {
try {
Class<?> toInstantiate = Class.forName(className);
return (C) SpringClientFactory.instantiateWithConfig(toInstantiate,
config);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load " + className
+ " for class " + clazz + " named " + name);
}
}
return null;
}
}
RibbonClientHttpRequestFactory
该类需要配和RestTemplate来说会用的类,主要用于创建HttpRequest对象,我们查看源码:
public class RibbonClientHttpRequestFactory implements ClientHttpRequestFactory {
private final SpringClientFactory clientFactory;
public RibbonClientHttpRequestFactory(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
@SuppressWarnings("deprecation")
public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod)
throws IOException {
// 此处需要注意,当我们通过LoadBalancer客户端调用的时候,host所代表的其实是对应的serviceId信息
String serviceId = originalUri.getHost();
if (serviceId == null) {
throw new IOException(
"Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
}
// 获取客户端配置信息
IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
// 创建RestClient对象
RestClient client = this.clientFactory.getClient(serviceId, RestClient.class);
// 创建Verb对象, 其实这里的Verb对应的就是http method的枚举值
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
// 创建RibbonHttpRequest对象
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
}
}
因此该类主要是用于创建RibbonHttpRequest对象,处理http请求信息.
RestTemplateCusomizer
该类是作为RestTemplate的重要扩展使用,在Ribbon的默认启动中,会配合RibbonClientHttpRequestFactory来使用。具体可以看下配置方法:
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return restTemplate -> restTemplate
.setRequestFactory(ribbonClientHttpRequestFactory);
}
LoadBalancerAutoConfiguration
这个类是一个配置类型,这里单独拿出来,是因为上面的RestTemplateCustomizer需要在该类中进行使用,具体源码如下:
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
在该源码中,有几个需要注意的点:
- 并不是所有的
RestTemplate都会被方法执行,因为RestTemplate必须要被@LoadBalanced派生注解 SmartInitializingSingleton是一个特殊的类型,该类型会在bean的声明周期中,pre-instantiation之后执行的语义
LoadBalancerRequestFactory
该类主要用于创建LoadBalancerRequest对象,其中设计到对请求参数transformer操作,具体源码如下:
public class LoadBalancerRequestFactory {
private LoadBalancerClient loadBalancer;
private List<LoadBalancerRequestTransformer> transformers;
public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
List<LoadBalancerRequestTransformer> transformers) {
this.loadBalancer = loadBalancer;
this.transformers = transformers;
}
public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
// 通过transformRequest进行处理
serviceRequest = transformer.transformRequest(serviceRequest,instance);
}
}
return execution.execute(serviceRequest, body);
};
}
}
RibbonEurekaClientConfiguration
该类也是很重要的一个类,为什么这么说呢,其实重要的一点,在于该类定义了默认的Ribbon在工作时,所需要的IPing, IRule, IConfig的配置bean信息,具体源码如下:
package org.springframework.cloud.netflix.ribbon.eureka;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
/**
* Spring configuration for configuring Ribbon defaults to be Eureka based if Eureka
* client is enabled.
*
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
s可以看到,该配置类型被@RibbonClients定义,而从上面的解析可以得知,RibbonClients最终会被定义为RibbonClientSpecification类型的bean, 因此,在defaultConfiguration的配置中,会默认加载EurekaRibbonClientConfiguration中的配置到NamedContextFactory的子context中,因此我们看下对应的配置类型。
EurekaRibbonClientConfiguration
package org.springframework.cloud.netflix.ribbon.eureka;
/**
* Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as:
* <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
* NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more
*
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
@Configuration
public class EurekaRibbonClientConfiguration {
private static final Log log = LogFactory.getLog(EurekaRibbonClientConfiguration.class);
@Value("${ribbon.eureka.approximateZoneFromHostname:false}")
private boolean approximateZoneFromHostname = false;
@RibbonClientName
private String serviceId = "client";
@Autowired(required = false)
private EurekaClientConfig clientConfig;
@Autowired(required = false)
private EurekaInstanceConfig eurekaConfig;
@Autowired
private PropertiesFactory propertiesFactory;
public EurekaRibbonClientConfiguration() {
}
public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig,
String serviceId, EurekaInstanceConfig eurekaConfig,
boolean approximateZoneFromHostname) {
this.clientConfig = clientConfig;
this.serviceId = serviceId;
this.eurekaConfig = eurekaConfig;
this.approximateZoneFromHostname = approximateZoneFromHostname;
}
// 当IPing的bean确实的时候,则使用NIWSDiscoveryPing()代替
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
return this.propertiesFactory.get(IPing.class, config, serviceId);
}
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
}
// 当ServerList的bean在缺失的时候,则使用ServerList代替
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config,
Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(
discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
@Bean
public ServerIntrospector serverIntrospector() {
return new EurekaServerIntrospector();
}
@PostConstruct
public void preprocess() {
String zone = ConfigurationManager.getDeploymentContext()
.getValue(ContextKey.zone);
if (this.clientConfig != null && StringUtils.isEmpty(zone)) {
if (this.approximateZoneFromHostname && this.eurekaConfig != null) {
String approxZone = ZoneUtils
.extractApproximateZone(this.eurekaConfig.getHostName(false));
log.debug("Setting Zone To " + approxZone);
ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone,
approxZone);
}
else {
String availabilityZone = this.eurekaConfig == null ? null
: this.eurekaConfig.getMetadataMap().get("zone");
if (availabilityZone == null) {
String[] zones = this.clientConfig
.getAvailabilityZones(this.clientConfig.getRegion());
// Pick the first one from the regions we want to connect to
availabilityZone = zones != null && zones.length > 0 ? zones[0]
: null;
}
if (availabilityZone != null) {
// You can set this with archaius.deployment.* (maybe requires
// custom deployment context)?
ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone,
availabilityZone);
}
}
}
RibbonUtils.initializeRibbonDefaults(serviceId);
}
}
s在以上代码中,我们可以知道对于部分的代码,默认的使用类型:
| 默认值 | 加载位置 | |
|---|---|---|
| IPing | NIWSDiscoveryPing | EurekaRibbonClientConfiguration |
| ServerList | DomainExtractingServerList | EurekaRibbonClientConfiguration |
| ILoadBalancer | ZoneAwareLoadBalancer | RibbonClientAutoConfiguration |
| IRule | ZoneAvoidanceRule | RibbonClientAutoConfiguration |
| ServerListUpdater | PollingServerListUpdater | RibbonClientAutoConfiguration |
| ServerListFileter | ZonePreferenceServerListFilter | RibbonClientAutoConfiguration |
以上就是Ribbon重要类型的工作原理,后面我们将讲述RestTemplate如何配合Ribbon组件进行负载均衡,更好的实现负载均衡功能。

