spring之我见 - Ribbon如何在RestTemplate起作用(SmartInitializingSingleton)

还是IOC的知识 – Ribbon是何时塞入RestTemplate中的

ribbon 是一个客户端侧的负载均衡器,如果你使用 restTemplateseureka, 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();

下面分别创建了三个对象 SmartInitializingSingletonLoadBalancerInterceptorRestTemplateCustomizer

  • 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 的拦截器中,自然也就会出现失效的情况,所以如果理解了原理,后面出现问题也好明白为什么会这样.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值