开篇词
该指南将引导你使用 Netflix Zuul 边缘服务库将请求路由和过滤到微服务应用。
你将创建的应用
我们将编写一个简单的微服务应用,然后构建一个使用 Netflix Zuul 将请求转发到该服务应用的反向代理应用。我们还将看到如何使用 Zuul 筛选通过代理服务发送的请求。
你将需要的工具
- 大概 15 分钟左右;
- 你最喜欢的文本编辑器或集成开发环境(IDE)
- JDK 1.8 或更高版本;
- Gradle 4+ 或 Maven 3.2+
- 你还可以将代码直接导入到 IDE 中:
如何完成这个指南
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
- 要从头开始,移步至从 Spring Initializr 开始;
- 要跳过基础,执行以下操作:
待一切就绪后,可以检查一下 gs-routing-and-filtering/complete
目录中的代码。
从 Spring Initializr 开始
对于所有的 Spring 应用来说,你应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来提取应用程序所需的依赖,并为你完成许多设置。该指南需要两个应用。第一个应用(书籍应用)仅需要 Spring Web 依赖。下图显示了此示例项目的 Initializr 设置:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和routing-and-filtering-book
的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。
以下清单显示了选择 Maven 时创建的 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.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>routing-and-filtering-book</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>routing-and-filtering-book</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.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-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
以下清单显示了在选择 Gradle 时创建的 build.gradle
文件:
plugins {
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
第二个应用(路由和过滤应用)需要 Spring Web 和 Zuul 依赖。下图显示了为路由和过滤应用设置的 Initializr:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和routing-and-filtering-gateway
的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。
以下清单显示了选择 Maven 时创建的 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.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>routing-and-filtering-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>routing-and-filtering-gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.M3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
以下清单显示了在选择 Gradle 时创建的 build.gradle
文件:
plugins {
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext {
set('springCloudVersion', "Hoxton.M3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
为方便起见,我们在项目的顶部(
book
和gateway
目录上方的一个目录)提供了构建文件(pom.xml
及build.gradle
文件),我们可以使用它们一次构建两个项目。我们还在那里添加了 Maven 和 Gradle 包装器。
搭建微服务
Book 服务将像 Spring 应用一样简单。编辑 RoutingAndFilteringBookApplicationBookApplication.java
,使其与以下清单匹配(来自 book/src/main/java/com/example/routingandfilteringbook/RoutingAndFilteringBookApplication.java
):
package com.example.routingandfilteringbook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class RoutingAndFilteringBookApplication {
@RequestMapping(value = "/available")
public String available() {
return "Spring in Action";
}
@RequestMapping(value = "/checked-out")
public String checkedOut() {
return "Spring Boot in Action";
}
public static void main(String[] args) {
SpringApplication.run(RoutingAndFilteringBookApplication.class, args);
}
}
RoutingAndFilteringBookApplicationBookApplication
类现在是一个 REST 控制器。@RestController
注解将该类标注为控制器类,并确保该类中 @RequestMapping
方法的返回值被自动、适当地转换并直接写入 HTTP 响应。
说到 @RequestMapping
方法,我们添加了两个:available()
及 checkedOut()
。它们处理对 /available
及 /checked-out
路径的请求,每个路径都返回一本书的 String
名称。
在 book/src/main/resources/application.properties
中设置应用名称(book
),如下清单所示:
spring.application.name=book
server.port=8090
还要在该处设置 server.port
,以使其在本地启动和运行服务时不会与边缘服务冲突。
创建边缘服务
Spring Cloud Netflix 包含一个嵌入式 Zuul 代理,我们可以使用 @EnableZuulProxy
注解启用它。这会将网关应用编程反向代理,该代理将相关的呼叫转发到其他服务,例如我们的图书应用。
打开网关应用的 RoutingAndFilteringGatewayApplicationGatewayApplication
类,并添加 @EnableZuulProxy
注解,如下列表所示(来自 gateway/src/main/java/com/example/routingandfilteringgateway/RoutingAndFilteringGatewayApplication.java
)显示:
package com.example.routingandfilteringgateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.example.routingandfilteringgateway.filters.pre.SimpleFilter;
@EnableZuulProxy
@SpringBootApplication
public class RoutingAndFilteringGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingAndFilteringGatewayApplication.class, args);
}
@Bean
public SimpleFilter simpleFilter() {
return new SimpleFilter();
}
}
要转发来自网关应用的请求,我们需要告诉 Zuul 它应该监视的路由以及这些路由的请求转发到的服务。我们通过在 zuul.routes
下设置属性来指定路由。我们的每个微服务都可以在 zuul.routes.NAME
下又一个条目,其中 NAME
是应用名称(存储在 spring.application.name
属性中)。
将 application.properties
文件添加到网关应用中的新目录(src/main/resources
)。它应与以下列表匹配(来自 gateway/src/main/resources/application.properties
):
zuul.routes.books.url=http://localhost:8090
ribbon.eureka.enabled=false
server.port=8080
Spring Cloud Zuul 自动将路径设置为应用名称。在该示例中,设置 zuul.routes.books.url
,以便 Zuul 将 /books
的请求代理到该 URL。
请注意 application.properties
文件中的第二个属性,Spring Cloud Netflix Zuul 使用 Netflix 的 Ribbon 来执行客户端负载均衡。默认情况下,Ribbon 将使用 Netflix Eureka 进行服务发现。对于该简单示例,我们可以跳过服务发现,因此将 ribbon.eureka.enabled
设置为 false
。由于 Ribbon 现在无法使用 Eureka 查找服务,因此我们必须为图书服务指定一个 url
。
添加过滤器
现在,我们可以看到如何通过代理服务过滤请求。Zuul 具有四种标准过滤器类型:
pre
过滤器在路由请求之前运行;route
过滤器可以处理请求的实际路由;post
过滤器在路由请求后运行;error
过滤器在请求处理过程中发生错误时运行。
我们将要编写一个pre
过滤器。Spring Cloud Netflix 会选择扩展com.netflix.zuul.ZuulFilter
并在应用上下文中可用的任何@Bean
作为过滤器。以下清单(来自gateway/src/main/java/com/example/routingandfilteringgateway/filters/pre/SimpleFilter.java
)显示了我们需要的过滤器:
package com.example.routingandfilteringgateway.filters.pre;
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.ZuulFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SimpleFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
过滤器类实现四种方法:
filterType()
:返回一个表示过滤器类型的String
,在这种情况下为pre
(这将是用于路由筛选器的route
);filterOrder()
:给出运行该过滤器的顺序,相对于其他过滤器;shouldFilter()
:包含确定何时运行该过滤器的逻辑(始终运行该特定过滤器);run()
:包含过滤器的功能。
Zuul 过滤器将请求和状态信息存储在 RequestContext 中(并通过 RequestContext 共享)。我们可以使用它来获取 HttpServletRequest,然后在请求发送之前记录请求的 HTTP 方法和 URL。
GatewayApplication
类使用 @SpringBootApplication
进行注释,其中包括 @Configuration
注解,该注解告诉 Spring 在给定的类中查找 @Bean
定义。将过滤器放入应用类中,如下清单所示(来自 gateway/src/main/java/com/example/routingandfilteringgateway/RoutingAndFilteringGatewayApplication.java
):
package com.example.routingandfilteringgateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.example.routingandfilteringgateway.filters.pre.SimpleFilter;
@EnableZuulProxy
@SpringBootApplication
public class RoutingAndFilteringGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingAndFilteringGatewayApplication.class, args);
}
@Bean
public SimpleFilter simpleFilter() {
return new SimpleFilter();
}
}
测试应用
Book Web 应用和 Zuul 网关应用的启动顺序可以不分先后。
要使用 Maven 运行 Book Web 应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./mvnw spring-boot:run -pl book
要使用 Maven 运行 Zuul 网关应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./mvnw spring-boot:run -pl gateway
要使用 Gradle 运行 Book Web 应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./gradlew :book:bootRun
要使用 Maven 运行 Zuul 网关应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./gradlew :gateway:bootRun
确保两个应用都在运行。在浏览器中,通过网关应用访问图书应用的一个端点。如果我们使用了该指南中显示的配置,则可以直接在 localhost:8090/available 上访问图书应用,也可以通过在 localhost:8080/books/available 上的网关服务访问图书应用。
访问其中一个 Book 服务端点(localhost:8080/books/available 或 localhost:8080/books/checked-out),我们应该先查看网关应用记录的请求方法,然后再将其提交给 Book 应用,如以下样本记录输出显示:
2019-10-02 10:58:34.694 INFO 11608 --- [nio-8080-exec-4] c.e.r.filters.pre.SimpleFilter : GET request to http://localhost:8080/books/available
概述
恭喜你!我们已经使用 Spring 开发了一个边缘服务应用,该应用可以代理和过滤对微服务的请求。
参见
以下指南也可能会有所帮助:
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》