@LoadBalanced註解原理

DiligentCoder發表於2023-01-02

在使用springcloud ribbon客戶端負載均衡的時候,可以給RestTemplate bean 加一個@LoadBalanced註解,就能讓這個RestTemplate在請求時擁有客戶端負載均衡的能力:

@Bean
@LoadBalanced
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 {
    }

唯一不同的地方就是多了一個@Qulifier註解.

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

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

@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);
}
}
}
};
}

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

這段自動裝配的程式碼的含義不難理解,就是利用了RestTempllate的攔截器,使用RestTemplateCustomizer對所有標註了@LoadBalanced的RestTemplate Bean新增了一個LoadBalancerInterceptor攔截器,而這個攔截器的作用就是對請求的URI進行轉換獲取到具體應該請求哪個服務例項ServiceInstance。

那麼為什麼

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

這個restTemplates能夠將所有標註了@LoadBalanced的RestTemplate自動注入進來呢?這就要說說@Autowired註解和@Qualifier這兩個註解了。

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

@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中的user list 和user map分別也打上@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();
  • 1
  • 2
  • 3

訪問http://localhost:8080/list:

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

和@Qualifier註解效果一樣,只有user1被注入進了List,user2沒有修飾符,沒有被注入進去。

另外當spring容器中有多個相同型別的bean的時候,可以透過@Qualifier來進行區分,以便在注入的時候明確表明你要注入具體的哪個bean,消除歧義。

相關文章