天天看點

【Spring Cloud】 @LoadBalanced注解,看完保證你還懂@Qualifier注解

在使用Spring Cloud Ribbon用戶端進行負載均衡的時候,可以給RestTemplate(Bean) 加一個@LoadBalanced注解,就能讓這個RestTemplate在請求時擁有用戶端負載均衡的能力:

@Component
public class RestTemplateComponent {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}      

是不是很神奇?當我打開@LoadBalanced注解的源碼時,發現連方法都沒有:

package org.springframework.cloud.client.loadbalancer;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {}      

唯一不同的地方,就是使用了注解@Qualifier。

搜尋@LoadBalanced注解的使用地方,發現隻有一處使用了,在LoadBalancerAutoConfiguration這個自動裝配類中:

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = 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);
               }
           }
       });
}

@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
    LoadBalancerClient loadBalancerClient) {
  return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}

@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 restTemplate -> {
               List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                       restTemplate.getInterceptors());
               list.add(loadBalancerInterceptor);
               restTemplate.setInterceptors(list);
           };
  }
}      

這段自動裝配的代碼含義不難了解,就是利用了RestTemplate的攔截器,使用RestTemplateCustomizer對所有标注了注解@LoadBalanced的RestTemplate(Bean)添加一個LoadBalancerInterceptor攔截器,而這個攔截器的作用就是對請求的URI進行轉換,擷取到具體應該請求哪個服務執行個體ServiceInstance。

那麼為什麼這個RestTemplates能夠将所有标注了注解@LoadBalanced的RestTemplate(Bean)自動注入進來呢?這就要說說@Autowired注解和@Qualifier這兩個注解了。

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();      

大家日常使用自動注入,很多都是用注解@Autowired來注入一個Bean,其實注解@Autowired還可以注入List和Map,比如我定義兩個Bean:

@Bean("user1")
User user1() {
    return new User("1", "a");
}

@Bean("user2"))
User user2() {
    return new User("2", "b");
}      

然後我寫一個Controller:

@RestController
public class MyController {
 
    @Autowired(required = false)
    private List<User> users = Collections.emptyList();
 
    @Autowired(required = false)
    private Map<String,User> userMap = new HashMap<>();
 
    @RequestMapping("/list")
    public Object listUsers() {
        return users;
    }
    @RequestMapping("/map")
    public Object mapUsers() {
        return userMap;
    }
}      

在Controller中通過:

@Autowired(required = false)
private List<User> users = Collections.emptyList();

@Autowired(required = false)
private Map<String,User> userMap = new HashMap<>();      

就可以自動将兩個Bean注入進來,當注入Map的時候,Map的key必須是String類型,然後Bean的name将作為Map的key,本例中,Map将有兩個key,分别為user1和user2,value分别為對應的User Bean執行個體。

通路http://localhost:8080/map:

{
    "user1": {
        "id": "1",
        "name": "a"
    },
    "user2": {
        "id": "2",
        "name": "b"
    }
}      

通路http://localhost:8080/list:

[
    {
        "id": "1",
        "name": "a"
    },
    {
        "id": "2",
        "name": "b"
    }
]      

然後我們給user1和user2分别标注上注解@Qualifier:

@Bean("user1")
@Qualifier("valid")
User user1() {
    return new User("1", "a");
}

@Bean("user2")
@Qualifier("invalid")
User user2() {
    return new User("2", "b");
}      

然後将Controller中的users和userMap分别也标注上注解@Qualifier:

@RestController
public class MyController {
 
    @Autowired(required = false)
    @Qualifier("valid")
    private List<User> users = Collections.emptyList();
 
    @Autowired(required = false)
    @Qualifier("invalid")
    private Map<String,User> userMap = new HashMap<>();
 
    @RequestMapping("/list")
    public Object listUsers() {
        return users;
    }
    @RequestMapping("/map")
    public Object mapUsers() {
        return userMap;
    }
}      

那麼所有标注了注解@Qualifier(“valid”)的user(Bean)都會自動注入到List users中去(本例是user1),所有标注了注解@Qualifier(“invalid”)的user(Bean)都會自動注入到Map<String,User> userMap中去(本例是user2),我們再次通路上面兩個url。

通路http://localhost:8080/list:

[
    {
        "id": "1",
        "name": "a"
    }
]      

通路http://localhost:8080/map:

{
    "user2": {
        "id": "2",
        "name": "b"
    }
}      

看到這裡我們可以了解注解@LoadBalanced的用處了,其實就是一個修飾符,和注解@Qualifier一樣,比如我們給user1标注上注解@LoadBalanced:

@Bean("user1")
@LoadBalanced
User user1() {
    return new User("1", "a");
}

@Bean("user2")
User user2() {
    return new User("2", "b");
}      

然後Controller中給List users标注上注解@LoadBalanced:

@Autowired(required = false)
@LoadBalanced
private List<User> users = Collections.emptyList();      

通路http://localhost:8080/list:

[
    {
        "id": "1",
        "name": "a"
    }
]