前面已经介绍了 Netflix Zuul 和服务网关的基础知识,下面将搭建学习 Zuul 的学习环境。

上图中,提供了一个 Eureka 服务,一个订单服务、一个产品服务。项目结构如下图:

基础环境服务启动后的端口占用如下图:

下面是搭建这些服务的详细步骤:
添加 eureka server 依赖信息,pom.xml 内容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxstrive</groupId>
<artifactId>zuul-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuul-eureka-server</name>
<description>zuul-eureka-server</description>
<properties>
<java.version>8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>netflix-eureka-server</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>application.yml 配置文件内容如下:
server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost client: # 向注册中心注册自己,false 表示禁用eureka客户端的注册功能 register-with-eureka: false # 取消检索服务 fetch-registry: false server: # 开启注册中心的保护机制,默认是开启 enable-self-preservation: true # 设置保护机制的阈值,默认是0.85 renewal-percent-threshold: 0.5
编写启动类,如下:
package com.hxstrive.netflixeurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 启动 Eureka Server 注册中心
* @author hxstrive.com
*/
@SpringBootApplication
@EnableEurekaServer
public class ZuulEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulEurekaServerApplication.class, args);
}
}运行启动类,启动 Eureka 服务,如下图:

使用浏览器访问 http://localhost:8761/ 地址,效果如下图:

准备产品服务,产品服务根据产品 ID 获取产品信息,项目结构如下图:

搭建步骤如下:
(1)项目 pom.xml 文件内容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxstrive</groupId>
<artifactId>zuul-service-product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuul-service-product</name>
<description>zuul-service-product</description>
<properties>
<java.version>8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>(2)application.yml 配置文件内容如下:
server: port: 8091 compression: # 启用压缩功能 enabled: true # 配置要压缩的 mime-type 列表 mime-types: application/json,text/html,text/xml,text/plain # 设置最小响应大小,低于该大小则不开启压缩,单位字节 min-response-size: 1 spring: application: name: service-product ## 服务地址 eureka: client: enabled: true service-url: # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔 defaultZone: http://localhost:8761/eureka instance: hostname: localhost # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端, # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活 lease-renewal-interval-in-seconds: 5 # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后, # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除 lease-expiration-duration-in-seconds: 10
(3)通用返回类和实体类如下:
CommonReturn.java
package com.hxstrive.service_demo.dto;
import lombok.Data;
/**
* 通用返回对象
* @author hxstrive.com
*/
@Data
public class CommonReturn<T> {
private int code;
private String message;
private T data;
private String appName;
private String port;
public static <T> CommonReturn<T> success(T data) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(1);
commonReturn.setData(data);
return commonReturn;
}
public static <T> CommonReturn<T> success() {
return success(null);
}
public static <T> CommonReturn<T> fail(String message) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(0);
commonReturn.setMessage(message);
return commonReturn;
}
public CommonReturn<T> ext(String appName, String port) {
this.setAppName(appName);
this.setPort(port);
return this;
}
}Product.java
package com.hxstrive.service_demo.entity;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
/**
* 商品实体
* @author hxstrive.com
*/
@Data
@Builder
@ToString
public class Product {
private Long id;
private String name;
private Float price;
}(4)控制类,提供根据 ID 获取产品的接口:
package com.hxstrive.service_demo.controller;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* 商品控制器
* @author hxstrive.com
*/
@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController {
private final static List<Product> PRODUCTS = new ArrayList<>();
static {
PRODUCTS.add(Product.builder().id(1L).name("罗技品牌无线键盘").price(180.5f).build());
PRODUCTS.add(Product.builder().id(2L).name("VOC品牌28寸显示器").price(745.0f).build());
PRODUCTS.add(Product.builder().id(3L).name("ThinkPad无线鼠标").price(35.3f).build());
}
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
// GET请求,根据 ID 查询商品信息
@GetMapping("/get")
public CommonReturn<Product> get(@RequestParam("id") Long id) {
log.info("商品 get() id={}", id);
return PRODUCTS.stream()
.filter(product -> product.getId().equals(id))
.findFirst()
.map(u -> CommonReturn.success(u).ext(appName, appPort))
.orElse(CommonReturn.fail("商品不存在"));
}
// GET请求,Restful 风格,根据 ID 查询商品信息
@GetMapping("/{id}")
public CommonReturn<Product> getRestful(@PathVariable("id") Long id) {
log.info("商品 getRestful() id={}", id);
return PRODUCTS.stream()
.filter(product -> product.getId().equals(id))
.findFirst()
.map(u -> CommonReturn.success(u).ext(appName, appPort))
.orElse(CommonReturn.fail("商品不存在"));
}
}(5)启动类,如下:
package com.hxstrive.service_demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 入口
* @author hxstrive.com
*/
@RestController
@SpringBootApplication
public class ZuulServiceProductApplication {
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
public static void main(String[] args) {
SpringApplication.run(ZuulServiceProductApplication.class, args);
}
@GetMapping("/")
public String index() {
return "appName=" + appName + ", appPort=" + appPort;
}
}运行启动类,启动项目,项目成功启动后会在 Nacos 中注册,如下图:
使用浏览器访问 http://localhost:8091/product/1 地址,返回结果如下图:

使用浏览器访问 http://localhost:8091/product/get?id=1 地址,返回结果如下图:

准备订单服务,订单服务根据 ID 获取订单信息,项目结构如下图:

搭建步骤如下:
(1)项目 pom.xml 文件内容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxstrive</groupId>
<artifactId>zuul-service-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuul-service-order</name>
<description>zuul-service-order</description>
<properties>
<java.version>8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>(2)application.yml 配置文件内容如下:
server: port: 8092 compression: # 启用压缩功能 enabled: true # 配置要压缩的 mime-type 列表 mime-types: application/json,text/html,text/xml,text/plain # 设置最小响应大小,低于该大小则不开启压缩,单位字节 min-response-size: 1 spring: application: name: service-order ## 服务地址 eureka: client: enabled: true service-url: # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔 defaultZone: http://localhost:8761/eureka instance: hostname: localhost # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端, # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活 lease-renewal-interval-in-seconds: 5 # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后, # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除 lease-expiration-duration-in-seconds: 10
(3)创建实体类和通用返回对象,如下:
CommonReturn.java
package com.hxstrive.service_demo.dto;
import lombok.Data;
/**
* 通用返回对象
* @author hxstrive.com
*/
@Data
public class CommonReturn<T> {
private int code;
private String message;
private T data;
private String appName;
private String port;
public static <T> CommonReturn<T> success(T data) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(1);
commonReturn.setData(data);
return commonReturn;
}
public static <T> CommonReturn<T> success() {
return success(null);
}
public static <T> CommonReturn<T> fail(String message) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(0);
commonReturn.setMessage(message);
return commonReturn;
}
public CommonReturn<T> ext(String appName, String port) {
this.setAppName(appName);
this.setPort(port);
return this;
}
}Order.java
package com.hxstrive.service_demo.entity;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* 订单实体
* @author hxstrive.com
*/
@Data
@Builder
@ToString
public class Order {
private Long id;
private String name;
private Float amount;
private List<Long> productIds;
private List<Product> products;
}Product.java
package com.hxstrive.service_demo.entity;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
/**
* 商品实体
* @author hxstrive.com
*/
@Data
@Builder
@ToString
public class Product {
private Long id;
private String name;
private Float price;
}(4)定义 Feign 客户端,如下:
package com.hxstrive.service_demo.feign;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 商品服务Feign客户端
* @author hxstrive.com
*/
@FeignClient(value = "SERVICE-PRODUCT", path = "/product")
public interface ProductFeign {
@GetMapping("/get")
CommonReturn<Product> get(@RequestParam("id") Long id);
// GET请求,Restful 风格,根据 ID 查询商品信息
@GetMapping("/{id}")
CommonReturn<Product> getRestful(@PathVariable("id") Long id);
}(5)控制器,如下图:
package com.hxstrive.service_demo.controller;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.entity.Order;
import com.hxstrive.service_demo.entity.Product;
import com.hxstrive.service_demo.feign.ProductFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 订单控制器
* @author hxstrive.com
*/
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
private final static List<Order> ORDERS = new ArrayList<>();
static {
ORDERS.add(Order.builder().id(1L).name("张三").amount(925.5f).productIds(Arrays.asList(1L,2L)).build());
ORDERS.add(Order.builder().id(2L).name("李四").amount(745.0f).productIds(Collections.singletonList(2L)).build());
ORDERS.add(Order.builder().id(3L).name("王五").amount(780.3f).productIds(Arrays.asList(2L,3L)).build());
}
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
@Autowired
private ProductFeign productFeign;
// 根据ID获取商品信息
private Product getProductById(Long id) {
CommonReturn<Product> commonReturn = productFeign.get(id);
if(commonReturn.getCode() != 1) {
throw new RuntimeException("查询商品信息失败! msg=" + commonReturn.getMessage());
}
return commonReturn.getData();
}
// 根据ID获取订单信息
private CommonReturn<Order> getOrderById(Long id) {
return ORDERS.stream()
.filter(order -> order.getId().equals(id))
.findFirst()
.map(u -> {
final List<Product> products = new ArrayList<>();
u.setProducts(products);
u.getProductIds().forEach(productId -> {
products.add(getProductById(productId));
});
return CommonReturn.success(u).ext(appName, appPort);
})
.orElse(CommonReturn.fail("订单不存在"));
}
// GET请求,根据ID查询订单信息
@GetMapping("/get")
public CommonReturn<Order> get(@RequestParam("id") Long id) {
log.info("订单 get() id={}", id);
return getOrderById(id);
}
// GET请求,Restful风格,根据ID查询订单信息
@GetMapping("/{id}")
public CommonReturn<Order> getRestful(@PathVariable("id") Long id) {
log.info("订单 getRestful() id={}", id);
return getOrderById(id);
}
}(6)启动类,如下:
package com.hxstrive.service_demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 入口
* @author hxstrive.com
*/
@RestController
@SpringBootApplication
// 开启 Feign
@EnableFeignClients
public class ZuulServiceOrderApplication {
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
public static void main(String[] args) {
SpringApplication.run(ZuulServiceOrderApplication.class, args);
}
@GetMapping("/")
public String index() {
return "appName=" + appName + ", appPort=" + appPort;
}
}运行启动类,启动项目,项目成功启动后会在 Nacos 中注册,如下图:

使用浏览器访问 http://localhost:8092/order/1 地址,返回结果如下图:

使用浏览器访问 http://localhost:8092/order/get?id=1 地址,返回结果如下图:

注意,上述输出结果中,products 中的产品列表是使用 Feign 调用目标“product-service”服务获取的。
到这里,学习 Zuul 的准备工作就做完了,后续将在此基础上学习 Zuul 服务网关各方面的知识。
点击下载/查看本教程相关资料或者源代码。