还是IOC的知识 – Ribbon是何时塞入RestTemplate中的
ribbon
是一个客户端侧的负载均衡器,如果你使用 restTemplates
和 eureka
, ribbon会将url中的 服务名
通过负载均衡直接转换成请求的ip, 而为了实现这个目的,需要在 restTemplates
内部加一个拦截器(restTemplates
本身有一个 interceptors list
),让 ribbon
有地方可以干预每次请求. 那么在spring启动的时候,是什么时候把 ribbon 塞进 restTemplates
的拦截器中的呢?这部分实现的重点还是 IOC 的知识.
public abstract class InterceptingHttpAccessor extends HttpAccessor {
private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
/**
* Sets the request interceptors that this accessor should use.
*/
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
this.interceptors = interceptors;
}
}
LoadBalancerAutoConfiguration
线索一开始还是从spring的自动装配机制开始说起. LoadBalancerAutoConfiguration 类在 spring.factories 文件中有被引用(spring-cloud-commons),根据 spring 自动装配的原理,LoadBalancerAutoConfiguration 会自动被注册加载. 这里再详细点关于自动装配的原理可以参照以前的文章 spring之我见 - Spring AOP实现原理(上)
spring.factories
# AutoConfiguration
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
在 LoadBalancerAutoConfiguration
代码里第一个要分析的是 List<RestTemplate> restTemplates
变量, 如果想实现负载均衡的功能,我们平常用的时候每个restTemplates 都需要 @LoadBalanced 注解来修饰,这样才会起效果. 有没有想过这是为什么?
因为 @LoadBalanced
里面又有一个 @Qualifier
注解,基本的使用可以百度一下, 简单说当Spring无法判断哪个bean应该被注入时(有可能存在多个类型相同的对象),@Qualifier
注解通过指定 value, 有助于消除歧义bean的自动注入. 在这里如果@LoadBalanced
和 @Bean
一起用,那么就相当于给这个bean打上了标记,然后 @LoadBalanced
和 @Autowired
一起用的时候就会把项目所有被 @LoadBalanced
注解修饰的对象注入到 restTemplates
这个list对象中, 等待后续逻辑处理.
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
下面分别创建了三个对象 SmartInitializingSingleton
,LoadBalancerInterceptor
,RestTemplateCustomizer
。
LoadBalancerInterceptor
- ribbon拦截器的具体处理逻辑。RestTemplateCustomizer
- 负责往 restTemplates 的 interceptors 塞入LoadBalancerInterceptor
(拦截器)SmartInitializingSingleton
- spring的拓展接口,就是它打通了spring与RestTemplateCustomizer
的交互,能让 spring 启动的时候顺便执行了RestTemplateCustomizer
的逻辑.
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
SmartInitializingSingleton
现在只剩下一个问题,spring何时执行了 SmartInitializingSingleton
. 这里又要讲回spring ioc
的知识, spring ioc
中的 refresh()
流程应该有了大致了解,在preInstantiateSingletons
方法中,其实一共有两个for循环。
第一个for循环大家一定都很熟悉, 将所有需要提前实例化的bean生成好, 这里会一遍遍执行 getbean
操作,将对象放到ioc中托管.
第二个for循环对大家有点陌生, 将所有生成好的bean再循环一次, 这里循环的意义就是将所有SmartInitializingSingleton对象的逻辑执行了一遍,接下来的事情就不用多讲了吧, RestTemplateCustomizer
的逻辑得以执行, 所有被 @LoadBalanced
修饰的restTemplates
都有了 ribbon
的拦截器,也就可以正常使用功能了.
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
最后我提一下 restTemplates
的坑,有些人会在afterPropertiesSet()
方法中调用restTemplates
,我们知道 afterPropertiesSet()
是属于 InitializingBean
接口的方法. 这段代码会在哪里执行? 根据上面所说的第一个for循环, 它在 getBean()
的时候就执行完了,但那时候 ribbon
还没有放到 restTemplates
的拦截器中,自然也就会出现失效的情况,所以如果理解了原理,后面出现问题也好明白为什么会这样.