我们已经把Consumer的环境搭建好了,我们也把UserService写好了,我们再来看一下,如何在Consumer的UserService
当中,去调用Provider当中的接口,那么我们要调服务的接口,肯定得知道服务的信息,那么我们在SpringCloud的微服务
当中,如何知道调用服务的基本信息呢,比如服务的IP地址是多少,服务的端口是什么,那么在SpringCloud当中呢,那给我们
提供了一个对象,这个对象叫什么呢,叫LoadBalancerClient,这个对象是什么呢,直接把他定义出来,这个对象是SpringCloud
提供的一个复杂均衡的对象,Ribbon,在SpringCloud当中,是通过Ribbon来完成负载均衡的,那么loadBalancerClient,就是
Ribbon技术下的一个对象,那么至于负载均衡器,所以称之为负载均衡器
private LoadBalancerClient loadBalancerClient;//ribbon负载均衡器
我们在后续中呢,会去讲解他的,那么我们为什么要拿到LoadBalancerClient呢,因为我们通过这个对象,可以取到我们要调用
服务的IP地址和端口,那么他是怎么去找IP地址和端口的呢,就是通过服务的名称,我们的服务不都有一个名称吗,我们provider的,
是不是叫
springcloud-eureka-provider
那么我们现在Consumer的服务,就不能叫跟他相同的名字了,我们应该叫
springcloud-eureka-consumer
我们改过来,也就是说,SpringCloud,在帮我们去查找信息的时候呢,就是通过我们给服务起的名称,然后他会去注册中心里,根据名称
找到这个服务,并且把服务的基本信息,封装到LoadBalancerClient对象当中,然后我们通过这个对象去获取服务的基本信息,我们就可以
拿到服务的IP和端口了,那么在这里我们如何使用loadBalancerClient去获取服务的信息呢,这里我们需要用到一个对象,这个对象
是谁呢,这个对象叫ServiceInstance,然后咱们叫si,然后this.loadBalancerClient,这是咱们上面定义的对象,通过这个对象,
他下面有choose方法,这个choose的方法呢,就是通过我们要查找的接口的名称去做接口的查找,并且返回一个接口的封装对象,
ServiceInstance,那么我们要查找的接口的名称,是不是Provider里面的
springcloud-eureka-provider
所以我们现在给服务其名称的时候不能随便乱起
ServiceInstance si = this.loadBalancerClient.choose("springcloud-eureka-provider");
然后拿到接口的实例,ServiceInstance以后,我们该做什么事呢,其实在这个对象当中,封装了服务的基本信息,如IP,端口,那么
也就意味着我们通过这个对象,可以取到我们服务的IP和端口了,然后大家注意,在SpringCloud当中呢,他对于服务与服务之间
的通信,采用的是URL的形式,所以说我们在这儿,还得拿着服务返回回来的实例,这个对象,去获取我们的服务的IP和端口,
拼成一个URL,所以这里我们还得URL的拼接,拼接访问服务的URL
sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
其实我们说个题外话,我感觉SpringCloud对于服务的调用这一块,相比我们的Dubbo来讲,要复杂一些,因为我们的Dubbo把对象
直接注入进来,@Reference就可以,但是在SpringCloud当中呢,他都是通过URL的方式来访问接口,有点像什么呢,他的这种形式
有点像之前,用过一个Apache的工具,叫HttpClient,跟他有点相似,然后通过StringBuffer去拼接我们的URL,我肯定要拼接
一个HTTP,要通过http协议,
sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
然后接着往后追加,追加什么呢,我们拼的URL是不是应该这样的
http://localhost:9090/user
我们在Provider当中,我们的Provider应该是启动的,我们的启动器改一下,最好叫ConsumerApplication,然后我们来运行这个
Consumer的实例,然后我们刷新,然后注意看Provider9090,这个是不是我们要访问的服务,然后我们看一下里面的Controller,
原来我们通过浏览器地址栏访问,是不是这么访问的,是不是一个叫user的URI
10.40.8.152:9090/user
是不是拿到这个信息了,那么我们现在拼接的URL,应该是这样的,在我们的Consumer当中,要去访问Provider的地址,最后调用接口的
地址是不是应该是这样的,我们是不是要根据这个地址去拼接出来,所以第一个http,第二个我们就不能localhost,因为你这个服务的
IP,它是不确定的,比如我们使用动态IP,我们服务多了,我们总不能一个一个去记IP地址是多少,每一个服务的IP是什么,端口是什么,
所以这块也不用去记住,怎么办呢,我们通过loadBalancerClient返回的ServiceInstance对象里,其实就包含了我们要调用服务的
IP和端口,所以我们之前也说过,我们做微服务开发的时候,不需要你去记住服务的IP和端口的,因为第一个我们服务太多了,
我们也记不住,第二个我们有现成的对象,通过LoadBalancerClient,返回的ServiceInstance,我们就可以去获取服务的基本信息,
关键我们要知道你调用的服务名称是什么,那么这块怎么办呢,正常来讲要拼IP了,我们可以通过si.getHost(),这个Host方法呢,
返回的就是要调用的接口来定义,然后我们还要拼什么,是不是还得拼一个冒号,然后后面是不是还得拼端口,端口si.getPort(),
然后端口拼完了以后,我们的URL就拼完了吗,很显然不是的,后面是不是还有这个信息,这样我们一个访问服务的URL就拼接完毕了,
第一步就已经搞定了,再来看,我们拼完URL以后,是不是要像URL发请求,然后需要一些数据,那么怎么向URL发请求呢,
我们会使用SpringMVC提供的一个对象,RestTemplate,这个大家应该有用过,应该并不陌生,只是封装了一个基于Rest模式的
一个对象,SpringMVC下,那么我们首先要去创建RestTemplate对象
RestTemplate rt = new RestTemplate();
然后你去用它发送请求的时候,是不是要调用Provider的这个Controller,也是会返回一个List,也就是我们现在调用的接口,是有
返回值的,那么我们怎么来处理这个返回值呢,我们来看一下这个代码怎么来编写,首先这里我们要定义一个返回值,有一个叫
ParameterizedTypeReference,我们返回的是什么呢,是一个List,List里面放的是什么,是User,然后这个对象要定义一个名称,
比如叫type,我们这里要去new这个对象,这块我们要注意,这个对象它是一个抽象类,可以看看这个源码
public abstract class ParameterizedTypeReference<T> {
看到了吗,它是一个抽象类,那么抽象类能new吗,跟接口是一样的,你看后面会自动添加一个匿名内部类,作为这个抽象类的一个子类,
那我们把它加上,我们这里也不用加什么方法了,用他里面自带的方法也够用了,然后接下来我们再去定义一个对象,这个对象叫什么呢,
叫ResponseEntity,就是他,这个对象的作用是什么呢,这个对象封装的就是一个返回值的二次封装,这个对象当中,封装了返回值信息,
然后我们返回的是不是一个List,然后这块我们给User,如果用过RestTemplate的同学,应该很熟,这块我们就不细说了,
然后呢response,等于new谁呢,用我们的restTemplate,因为它是发送URL请求的模板对象,有一个exchange方法
ResponseEntity<List<User>> response = rt.exchange(sb.toString(),HttpMethod.GET, null, type);
然后你看exchange方法有这么多,我们随便写一个,这里我们用的是四个参数的,我们来看一下,第一个参数是什么呢,就是你要调用服务的
URL,这个URL我们是不是已经拼接到buffer里面了,所以我们直接sb.toString()就可以了,他要的是一个字符串,第二个是一个method,
method表示什么含义呢,你现在要用什么请求方式去请求他,用get还是post,那我们可以用GET,GET怎么写呢,有一个常量类,是一个枚举,
叫HttpMethod,在这个枚举下呢,有很多在HTTP协议下发送的很多类型,那我们在这里选择GET,第三个参数,你请求有没有参数传递,
那我们现在是没有任何参数的,所以我们这块为空,第三个参数表示请求参数的,然后第四个,responseType,这个 表示什么意思呢,
你返回值的这个对象,用什么类型来包装,在这里我们专门定义了一个返回值类型的对象,ParameterizedTypeReference,这个时候
我们就把这个对象放到里面,然后这个对象,他返回的response,就是封装了我们调用接口后,返回的一个返回值的二次封装的对象,
那我们这里怎么去取呢,很简单,我们现在,我们看一下,我们调用Provider的UserController,是不是返回一个List,那么这样的话,
在我的Consumer当中,他返回的也得是一个List,我这里泛型定义的时候是List,那我们就可以这么写
List<User> list =response.getBody();
然后在这里把list返回,这样我们通过Consumer中的Service去调用Provider服务的一个代码,就写完了,所以相比之下这个代码呢,
这个代码片段还是比较多的,当然你也可以自己封装,比如你可以把这些代码做一个封装,然后只要根据给定的参数,直接在你
封装的工具类里直接做拼接了,或者做一些发送就可以了,你自己回球定义一个工具类,进行封装就可以了,我们现在把
Consumer的Service代码copy过来,先粘到笔记当中,这一块大家要认真的看一看,因为我们服务与服务之间的通信,可以说在
微服务开发当中,是非常重要的,因为我们服务和服务之间肯定是要做调用的,然后我们再来看,这一部分改完了,
但是Consumer的Controller还没有改完
我们直接写个consumer,我们是不是先得把业务层注入进来
@Autowired
private UserService userService;
然后我们这里要做的就是调用userService下的getUsers方法,这个是他的Controller,那么我们的Consumer和Provider写完
以后呢,就可以进行测试了,现在没有任何服务,我们先把Provider启动,观察控制台,然后我们再去启动Consumer,那么
Consumer也启动好了,接下来我们是不是就可以访问Consumer的Controller了,他的端口是多少,他的端口是9091,这个是
9091下面的一个consumer
localhost:9091/consumer
这里出了一个异常,我们来看一下
是不是拿到了,刚才应该是我们的服务还没有启动完毕,那么我们看,现在我们看到的数据就是从Consumer当中拿到的,
那么Consumer又是从哪里拿到的呢,在他的Service当中,去调用我们Provider服务当中的接口,在这个接口当中,做了
一个数据的定义,然后通过Provider将数据返回给我们的Consumer,所以我们看到的数据就是Provider返回回来的,
那么在服务与服务之间调用的当中呢,大家需要重点了解的,重点注意的就是,LoadBalancerClient这个对象,我们通过
这个对象,根据服务的名称返回一个实例,我们通过这个实例,是可以拿到服务的IP和端口,然后我们对服务的IP和端口
进行一个拼接,变成一个完整的访问服务的URL,然后我们再通过SpringMVC提供的RestTemplate,就可以去请求我们的服务了,
Consumer去请求Provider的案例的一个讲解
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learn.cloud</groupId>
<artifactId>springcloud-eureka-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server.port=9091
eureka.client.serviceUrl.defaultZone=http://10.40.8.152:8761/eureka
spring.application.name=springcloud-eureka-consumer
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
package com.learn.pojo;
public class User {
private int userid;
private String username;
private int userage;
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
public User(int userid, String username, int userage) {
super();
this.userid = userid;
this.username = username;
this.userage = userage;
}
public User() {
super();
// TODO Auto-generated constructor stub
}
}
package com.learn.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.learn.pojo.User;
@Service
public class UserService {
@Autowired
private LoadBalancerClient loadBalancerClient;//ribbon负载均衡器
public List<User> getUsers(){
//选择调用的服务的名称
//ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("springcloud-eureka-provider");
//拼接访问服务的URL
StringBuffer sb = new StringBuffer();
//http://localhost:9090/user
sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
//springMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {};
//ResponseEntity:封装了返回值信息
ResponseEntity<List<User>> response = rt.exchange(sb.toString(),HttpMethod.GET, null, type);
List<User> list =response.getBody();
return list;
}
}
package com.learn.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.learn.pojo.User;
import com.learn.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/consumer")
public List<User> getUsers(){
return this.userService.getUsers();
}
}
package com.learn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}