在之前文章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组件进行负载均衡,更好的实现负载均衡功能。