基于SpringBoot搭建SpringCloud基础测试环境
一、基本组件
- 注册中心:Eureka
- 负载均衡:Ribbon
- 声明式调用远程方法:Feign
- 熔断、降级、监控:Hystrix
- 网关:Zuul
二、基础测试环境搭建
使用 RestTemplate 实现远程方法调用
1、结构
2、具体搭建
2.1、创建 parent 父工程
pro01-spring-cloud-parent,其打包方式为pom
在pom.xml文件中加入依赖
<modelVersion>4.0.0</modelVersion>
<groupId>com.tianfei</groupId>
<artifactId>pro01-spring-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 配置依赖管理 -->
<dependencyManagement>
<dependencies>
<!-- 导入 SpringCloud 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<!-- import 依赖范围表示将 spring-cloud-dependencies 包中的依赖信息导入 -->
<scope>import</scope>
</dependency>
<!-- 导入 SpringBoot 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2、创建 coomon 通用工程
pro02-spring-cloud-common,其打包方式为jar
① pom.xml文件配置如下:
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tianfei</groupId>
<artifactId>pro01-spring-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>pro02-spring-cloud-common</artifactId>
<packaging>jar</packaging>
② 在该工程中创建实体类 Employee
/**
* @author tianfei
*
*/
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
public Employee() {
}
public Employee(Integer empId, String empName, Double empSalary) {
super();
this.empId = empId;
this.empName = empName;
this.empSalary = empSalary;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Double getEmpSalary() {
return empSalary;
}
public void setEmpSalary(Double empSalary) {
this.empSalary = empSalary;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + ", empSalary=" + empSalary + "]";
}
}
2.3、创建 provider 提供者工程
pro03-spring-cloud-provider,其打包方式为jar
① pom.xml配置文件如下
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tianfei</groupId>
<artifactId>pro01-spring-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>pro03-spring-cloud-provider</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.tianfei</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
② 创建启动类 SpringCloudProvider
@SpringBootApplication
public class SpringCloudProvider {
public static void main(String[] args) {
SpringApplication.run(SpringCloudProvider.class, args);
}
}
③ 在 Resource 目录下 创建 application.yml 文件并做如下配置
server:
port: 1000
④ 创建 Controller
由于服务端 响应返回的都是JSON数据,所以使用 @RestController
@RestController
public class EmployeeController {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote() {
return new Employee(555, "tom555", 555.55);
}
}
⑤ 启动并试着访问该请求
2.4、创建 consumer 消费者工程
pro04-spring-cloud-consumer,其打包方式为jar
① pom.xml配置如下
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tianfei</groupId>
<artifactId>pro01-spring-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>pro04-spring-cloud-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.tianfei</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
② 创建启动类 SpringCloudConsumer
@SpringBootApplication
public class SpringCloudConsumer {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConsumer.class, args);
}
}
③ 创建 application.yml 文件,配置端口号
server:
port: 4000
④ 创建 配置类,用来将 RestTemplate 注入到 IOC容器中
@Configuration
public class SpringCloudConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
⑤ 编写 Controller
/**
* @author: Herz
* @date: 2021/7/24 10:30
*/
@RestController
public class HumanResourceController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/get/employee")
public Employee getEmployeeRemote() {
// 1、声明远程微服务的主机地址加端口号
String host = "http://localhost:1000";
// 2、声明具体要调用的功能的URL地址
String url = "/provider/get/employee/remote";
// 3、通过 RestTemplate 调用远程微服务
return restTemplate.getForObject(host + url, Employee.class);
}
}
⑥ 试着从 Consumer 端远程取得 Provider 端返回的数据
2.5、创建 Eureka 注册中心
pro05-spring-cloud-eureka
① pom.xml 配置文件如下
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.tianfei</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro05-spring-cloud-eureka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
如果在启动该工程的启动类时,报错 Caused by: java.lang.IllegalStateException: StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[] failed to start 时,在pom.xml 中加入以下依赖
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
② 创建启动类 SpringCloudEureka
// 启用 Eureka 服务器功能
@EnableEurekaServer
@SpringBootApplication
public class SpringCloudEureka {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEureka.class, args);
}
}
③ 创建 application.yml 文件,并做如下配置
server:
port: 5000
eureka:
instance:
hostname: localhost # 配置当前 Eureka 服务的主机地址
client:
register-with-eureka: false # 当前服务本身就是注册中心,不必“自己注册自己”
fetch-registry: false # 当前服务本身就是注册中心,不必“从注册中心取回信息”
service-url: # 客户端(指的是consumer,provider)访问当前注册中心的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
④ 启动并尝试访问 Eureka 界面
2.6、目标1:将 Provider 注册到 Eureka
① 在 pro03-spring-cloud-provider 工程的 pom.xml文件中加入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
② 在 pro03-spring-cloud-provider 工程的 yml 配置文件中,加入如下配置
server:
port: 1000
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/ # provider 作为客户端访问 Eureka 的访问地址(所以需要加上“/eureka”)
spring:
application:
name: com-provider # 设置当前微服务的应用名称,以便将来通过微服务名称调用当前微服务时能够找到
# 在 SpringCloud 环境下开发,每一个微服务工程都要设置一个应用名称
③ 重新启动 Provider 工程的启动类
④ 刷新 Eureka 页面查看是否注册成功
2.7、目标2:consumer 访问 provider 时使用微 服务名称代替 localhost:1000
① 分析
② 在 Consumer 工程的 pom.xml中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
③ 在 application.yml 配置文件中再次添加:
spring:
application:
name: com-consumer # 指定微服务的应用名
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/ # 使用 eureka 注册中心找到要相应被调用的微服务的信息
④ 在注入 RestTemplate 的 Bean 时,加上 @LoadBalance 支持负载均衡
@Configuration
public class SpringCloudConfig {
// 支持负载均衡
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
⑤ 在 Controller 里将 微服务应用名 代替 主机地址和端口号
/**
* @author: Herz
* @date: 2021/7/24 10:30
*/
@RestController
public class HumanResourceController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/get/employee")
public Employee getEmployeeRemote() {
// 1、声明远程微服务的主机地址加端口号
// String host = "http://localhost:1000";
// 使用 provider 的 “微服务名称” 代替 “主机地址和端口号”
String host = "http://com-provider";
// 2、声明具体要调用的功能的URL地址
String url = "/provider/get/employee/remote";
// 3、通过 RestTemplate 调用远程微服务
return restTemplate.getForObject(host + url, Employee.class);
}
}
2.8、目标3:使用 Ribbon 的客户端负载均衡功能
1、分析
2、引入 Ribbon 依赖,由于 目标2 已经在 Consumer 工程中引入该依赖,可不再引入
3、修改 Provider 工程中得 Controller 方法
@RestController
public class EmployeeController {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote(HttpServletRequest request) {
// 获取当前微服务的端口号
int serverPort = request.getServerPort();
return new Employee(555, "tom555 " + serverPort, 555.55);
}
}
4、Provider 以集群的方式启动
按照端口号 1000 启动第一个实例
按照端口号 2000 启动第二个实例
按照端口号 3000 启动第三个实例
在Provider工程的application.yml中改动端口号即可
server:
port: 3000 # 改动这里即可
5、Consumer 正常访问
每次刷新时,将依此顺序交替,体现了负载均衡的轮询策略。即 Ribbon 的负载均衡功能已起作用。
!!!注意: provider 的微服务名称必须使用同一个名称才能构成一个集群,否则将不会认定为是属于同一个集群。
使用 Feign 实现远程方法调用
1、分析
2、操作
1、在 Common 工程的pom.xml中引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2、 在 Common 工程中创建远程调用方法的接口
!!!注意: 该接口所在的包需要和其他工程的启动类所在的包名一致,或者是其子包。
// @FeignClient 注解表示当前接口和一个Provider对应,注解中得value属性指定要调用的Provider的微服务名称
@FeignClient( value = "com-provider")
public interface EmployeeRemoteService {
// 远程调用的接口方法
// 要求 @RequestMapping 注解中得地址 和 Provider 中得一致
// 要求方法声明一致
// 用来获取请求参数的 @RequestParam,@PathVariable,@RequestBody 不能省略,两边一致
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote();
}
3、修改 Provider 工程中得 Controller
@RestController
public class EmployeeController {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote() {
return new Employee(555, "tom555 ", 555.55);
}
}
4、创建新的 Consumer 工程 pro04-spring-cloud-fegin-consumer
① 在pom.xml中加入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.tianfei</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
由于 Common 工程中已经添加了 Feign 依赖,而 Feign 依赖已经包含 Ribbon 的依赖,所以根据依赖的传递性,在 Consumer 工程中不用再添加 Ribbon 的依赖
② 添加启动类 SpringCloudFeignConsumer
// 启用客户端功能
@EnableFeignClients
@SpringBootApplication
public class SpringCloudFeignConsumer {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignConsumer.class, args);
}
}
③ 创建 新的 Consumer 工程的 Controller
@RestController
public class FeignHumanResourceController {
// 装配调用远程微服务的接口,后面像调用本地方法一样使用
@Autowired
EmployeeRemoteService employeeRemoteService;
@RequestMapping("/feign/consumer/get/employee")
public Employee getRemoteEmployee(){
return employeeRemoteService.getEmployeeRemote();
}
}
④ 在 resource 目录创建 application.yml 配置文件并做以下配置
server:
port: 6000
spring:
application:
name: com-feign-consumer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:5000/eureka/
⑤ 访问页面获取返回的数据
3、传参
1、简单类型
① common 的接口的写法
// @FeignClient 注解表示当前接口和一个Provider对应,注解中得value属性指定要调用的Provider的微服务名称
@FeignClient( value = "com-provider")
public interface EmployeeRemoteService {
@RequestMapping("/provider/get/employee/by/id")
public Employee getEmployeeById(@RequestParam("empId") Integer empId);
}
别忘了写@RequestParam 注解
②provider 的 controller 方法:
@RestController
public class EmployeeController {
@RequestMapping("/provider/get/employee/by/id")
public Employee getEmployeeById(@RequestParam("empId") Integer empId) {
return new Employee(empId, "tom999", 999.99);
}
}
方法声明部分需和接口中一致
2、复杂类型
① common 中得接口的写法
// @FeignClient 注解表示当前接口和一个Provider对应,注解中得value属性指定要调用的Provider的微服务名称
@FeignClient( value = "com-provider")
public interface EmployeeRemoteService {
@RequestMapping("/provider/save/emp")
public Employee saveEmp(@RequestBody Employee employee);
}
别忘了写@RequestBody 注解
②provider 的 controller 方法:
@RestController
public class EmployeeController {
@RequestMapping("/provider/save/emp")
public Employee saveEmp(@RequestBody Employee employee) {
return employee;
}
}
方法声明部分需和接口中一致