Life

Sunday, June 14, 2020

Spring Cloud(三):服务消费者

       在上一篇博文中创建了一个服务提供者provider,在注册中心完成注册,对外提供接口服务。此时应有一个服务消费者调用provider相关接口完成某些业务。而在实际环境中,各个微服务的IP地址不同,如何实现微服务之间的跨服务调用呢,我学习了两种方法:RestTemplate和Feign。
  • 什么是RestTemplate?
        要理解什么是RestTemplate需要先了解REST。REST的全称是Representational State Transfer,意为表现层状态转化,是一种互联网软件架构风格,满足这种风格的程序或设计就可认为是REST或Restful的。REST定义一套请求资源的规范:用URI定位资源,用HTTP请求类型(GET,POST,PUT,DELETE)来描述操作。满足REST风格的Server提供Restful API(REST风格的接口),处于不同平台的Client便可以使用一套API来消费Server提供的服务。
        RestTemplate是Spring框架提供的基于REST的服务组件,底层对HTTP请求及响应进行了封装,提供访问远程REST服务的方法,例如封装了GET请求的getForObject(String, Class, String...)方法,封装了DELETE请求的delete(String, String...)方法等等,可以简化代码的开发。
  • 什么是Feign?
Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
        那么,让我们实现一个服务提供者consumer并使用这两种方法实现跨服务调用。
        之前已经提到过,所谓服务提供者provider与服务消费者consumer只是从业务角度对他们进行划分,而从代码角度来看,他们都是Eureka Client。因此创建consumer的过程与provider大致相同。
  • 在父工程下新建一个Module,命名为consumer
  • 在pom.xml中添加Eureka Client依赖。
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
  • 在resources下创建配置文件application.yml,添加EurekaClient相关的配置,并命名为consumer
server:
  port: 8020
spring:
  application:
    name: consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
  • 之后在java路径下创建启动类ConsumerApplication。
@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}
  • 启动注册中心,运行ConsumerApplication,访问http://localhost:8761/,应该看到服务消费者consumer已经完成注册。

使用RestTemplate访问REST服务
  • RestTemplate 实例化
RestTemplate实例最好是由Spring容器管理,而不是用到的时候再new RestTemplate()实例出来。可以在@Controller/@Service/@Configuration类中声明一个RestTemplate bean,其他地方直接注入使用。在此选在consumer启动类中声明。
@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  • 创建ConsumerController类,通过@Autowired将IoC容器中的RestTemplate实例注入进来,在业务方法中可通过RestTemplate访问REST服务。
@RestController
@RequestMapping("consumer4actor")
public class ConsumerController {
    
    @Autowired
    private RestTemplate restTemplate;
    
    private void method(){
        
    }
}
  • RestTemplate 的相关方法
GET
  1. public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)。url 为请求的目标资源,responseType 为响应数据的封装模版,uriVariables 是一个动态参数,可以根据实际请求传入参数。                                    getForEntity 方法的返回值类型为 ResponseEntity,通过调用其 getBody 方法可获取结果对象。
  2. 
        @GetMapping("/findAll")
        public Collection<Actor> findAll(){
            return restTemplate.getForEntity("http://localhost:8010/actor/findAll",Collection.class).getBody();
        }
  3. public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)。getForObject 方法的使用与 getForEntity 很类似,唯一的区别在于 getForObject 的返回值就是目标对象,无需通过调用 getBody 方法来获取.
        @GetMapping("/findAll2")
        public Collection<Actor> findAll2(){
            return restTemplate.getForObject("http://localhost:8010/actor/findAll",Collection.class);
        }
POST
  1. public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) .                                           url 为请求的目标资源,request 为要保存的目标对象,responseType为响应数据的封装模版,uriVariables 是一个动态参数,可以根据实际请求传入参数。postForEntity 方法的返回值类型也是 ResponseEntity,通过调用其 getBody 方法可获取结果对象.         
  2.     @PostMapping("/save")
        public Collection<Actor> save(@RequestBody Actor actor){
            return restTemplate.postForEntity("http://localhost:8010/actor/save",actor,Collection.class).getBody();
        }
    为了获得actor的集合,需要把provider微服务的ActorController略做修改,save方法保存后调用findAll()返回所有actor。
        @PostMapping("/save")
        public Collection<Actor> save(@RequestBody Actor actor){
            actorMapper.save(actor);
            return actorMapper.findAll();
        }
  3. public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables).                                                             
    postForObject 方法的使用与 postForEntity 类似,唯一的区别在于 postForObject 的返回值就是目标对象,无需通过调用 getBody 方法来获取
  4.     @PostMapping("/save2")
        public Collection<Actor> save2(@RequestBody Actor actor){
            return restTemplate.postForObject("http://localhost:8010/actor/save",actor,Collection.class);
        }
PUT
  1. public void put(String url, @Nullable Object request, Object... uriVariables).             url 为请求的目标资源,request 为要修改的目标对象,uriVariables 是一个动态参数,可以根据实际请求传入参数.
DELETE
  1. public void delete(String url, Object... uriVariables).
url 为请求的目标资源,uriVariables 是一个动态参数,可以根据实际请求传入参数.
 	     @DeleteMapping("/deleteById/{id}")
    public void DeleteById(@PathVariable("id") Long id){
        restTemplate.delete("http://localhost:8010/actor/deleteById/{id}",id);
    }
       依次启动 注册中心、ProviderApplication、ConsumerApplication,通过 Postman 工具来分别测试 ConsumerController的业务方法
  • findAll接口
  • findAll2接口
  • save接口
  • save2接口
  • deleteById接口
重新调用findAll接口查看删除结果
Feign跨服务调用
       在 Spring Cloud 中使用 Feign 非常简单,Feign 是一个声明式的 Web Service 客户端,所以只需要创建一个接口,同时在接口上添加相关注解即可完成服务提供方的接口绑定.
  • 在pom.xml中添加Feign依赖
  •         <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
  • 修改启动类ConsumerApplication,添加注解@EnableFeignClients.该注解声明启用Feign.
  • 接下来通过接口的方式调用Provider服务,创建ConsumeByFeignClient接口
@FeignClient(value = "provider")
public interface ConsumeByFeignClient {
    @GetMapping("/actor/findAll")
    public List<Actor> getAllByFeign();
}
@FeignClient 指定 Feign 要调用的微服务,直接指定服务提供者在注册中心的 application name 即可。
  • 修改ConsumerController,注入ConsumeByFeignClient,并调用接口的getAllByFeign方法
    @Autowired
    private ConsumeByFeignClient consumeByFeignClient;
    @GetMapping("findAllByFeign")
    public  Collection<Actor> findAllByFeign(){
        return consumeByFeignClient.getAllByFeign();
    }
  • 重新启动ConsumerApplication,使用PostMan工具调用findAllByFeign接口
调用成功.这里我仅定义了findAll接口,其他方法类似,就不赘述了.

       本节实现了服务消费者,并通过RestTemplate和Feign两种方式实现了跨服务接口调用,可以发现,RestTemplate的方式需要得知服务提供者的具体地址,在url中显式填写。
而Feign只需要指定服务提供者在注册中心的名称即可,通过声明式接口的形式来调用服务,非常方便。同时Spring Cloud Feign是基于Ribbon实现,且使用更简单,同时集成了Hystrix,故具有可插拔,基于注解,负载均衡,服务熔断等一系列便捷功能,在实际开发中更推荐使用Feign.
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者Leslie Tien和本文原始地址:
https://leslietien.blogspot.com/2020/06/spring-cloud_13.html

No comments:

Post a Comment