优质博文:IT-BLOG-CN我们这次项目主要从RestTemplate 和 Feign 进行选型分析。 ### 一、Spring Cloud Feign分析> Feign是另外一种客户端负载均衡实现。我在该模块写了Feign Client的示例代码。 【1】spring-cloud-web-demo-api为服务的sdk模块 【2】spring-cloud-web-demo-service为提供接口服务的模块 【3】spring-cloud-web-demo-client为模拟调用服务的模块首先在spring-cloud-web-demo-api模块,定义Feign API。spring-cloud-web-demo为spring-cloud-web-demo-service暴露的服务名。 @FeignClient(value = "spring-cloud-web-demo") public interface UserFeign { @GetMapping(value = "/user/getUserById", produces = "application/json;charset=utf-8") Object getUserById(@RequestParam(value = "id", required = false) Long id); //省略 } 然后通过ClientAutoConfiguration自动装配。(client直接引入api包就可以使用,不需要再EnableFeignClients) @Configuration @EnableFeignClients("net.teaho.demo.spring.cloud.web.api") public class ClientAutoConfiguration { } org.springframework.boot.autoconfigure.EnableAutoConfiguration=
net.teaho.demo.spring.cloud.web.api.config.ClientAutoConfiguration 在service模块如以往Spring MVC般实现api模块接口即可。 @RestController public class UserController implements UserFeign { private static final Logger logger = LoggerFactory.getLogger(UserController.class); @Override public Object getUserById(Long id) { return "{"id":1, "name": "test"}"; } //省略 } 在Client模块,注入bean后直接调用。 @Component @Slf4j public class TestService { @Autowired private RestTemplate restTemplate; public Object getOneUser(){ return userController.getUserById(1L); } } ### 二、RestTemplate分析写了具有客户端负载均衡能力的RestTemplate的请求代码。类似这样定义: @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } RestTemplate究竟是如何利用注册中心实现客户端负载均衡的呢?实现方式: 就是将上面所说的LoadBalancerInterceptor负载均衡拦截器加到标注了@LoadBalanced的RestTemplate实例中。 LoadBalancerInterceptor拦截器会在执行过程中获取并设置适合的目标请求实例,重新构造请求URI。 // 将配置中标注了@LoadBalanced的RestTemplate注入到这里 @LoadBalanced @Autowired(required = false) private List restTemplates = Collections.emptyList(); //将注册的RestTemplateCustomizer(RestTemplate自定义器)集合处理上面的restTemplates集合 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } ### 三、技术选型最终选择使用OpenFeign,下面说说原因。和RestTemplate比起来,OpenFeign显得更适合Spring Boot微服务。Open Feign相当于(HTTP)RPC,相比起RestTemplate,它直接显式将API声明以JAVA接口形式标识出来。 并且因为底层用的动态代理,它还可以(无感知地)替换底层实现。比如,github上就有替换底层逻辑的repo -- Open Feign+Dubbo的RPC实现。通过sdk包的形式,方便了调用,不需要像RestTemplate一样,客户端自行拼接上一串请求参数。在代码编写上也清晰。要使用就必须知道OpenFeign是怎么实现的呢?### 四、OpenFeign 初始化分析流程图如下:
看看前面例子里我们引入的OpenFeign的东西 【1】@EnableFeignClients("net.teaho.demo.spring.cloud.web.api") 【2】@FeignClient(value = "spring-cloud-web-demo") 还有自动装配引入的 【3】FeignRibbonClientAutoConfiguration 【4】FeignClientsConfiguration我们就从这两个注解开始分析源码。 【1】首先看@FeignClient注解。 //给接口标注成一个REST调用方 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { //服务名,可以带协议前缀,也可以用${property.name}关联一个配置值。 @AliasFor("name") String value() default ""; @Deprecated String serviceId() default ""; //bean name String contextId() default ""; @AliasFor("value") String name() default ""; / * Sets the @Qualifier value for the feign client. / String qualifier() default ""; //直接指定一个地址,比如http://localhost:12345,一般用于调试 String url() default ""; boolean decode404() default false; / * A custom @Configuration for the feign client. Can contain override * @Bean definition for the pieces that make up the client, for instance * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. * * @see FeignClientsConfiguration for the defaults / //可用于覆盖FeignClient默认设置 Class<?>\[\] configuration() default {}; //回滚类,像我的例子中定义的回滚类必须实现UserFeign接口,看https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback Class<?> fallback() default void.class; //如果需要对异常做诊断可用此属性,https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback Class<?> fallbackFactory() default void.class; //路径前缀 String path() default ""; //标记bean是否为primary boolean primary() default true; } 【2】接下来重点关注@EnableFeignClients注解是如何扫描FeignClient接口的。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { //省略 } 嗯,发现没有,就是FeignClientsRegistrar做处理的。来分析下重点方法registerFeignClients和registerFeignClient class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //classPath扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); //ClassPathScanningCandidateComponentProvider扫描的basePackage集合 Set basePackages; Map