开篇词
该指南将引导你使用 Spring 创建 “Hello, World” RESTful Web服务的过程,该服务在响应中包括跨域资源共享(CORS)的标头。你可以在该博客文章中找到有关 Spring CORS 支持的更多信息。
你将创建的应用
我们将创建一个具有静态主页的应用,该应用还将在以下地址接受 HTTP GET 请求:http://localhost:8080/greeting 并以问候语的 JSON 表示形式进行响应,如下清单所示:
{"id":1,"content":"Hello, World!"}
我们可以在查询字符串中使用可选的 name
参数来自定义问候语,如下清单所示:
http://localhost:8080/greeting?name=User
name
参数值将覆盖 World
的默认值,并且在内容中更改为 “Hello, User!” 会反映在响应中,如下清单所示:
{"id":1,"content":"Hello, User!"}
该服务与构建 RESTful Web 服务中描述的服务略有不同,因为它使用 Spring 框架的 CORS 支持来添加相关的 CORS 响应标头。
你将需要的工具
- 大概 15 分钟左右;
- 你最喜欢的文本编辑器或集成开发环境(IDE)
- JDK 1.8 或更高版本;
- Gradle 4+ 或 Maven 3.2+
- 你还可以将代码直接导入到 IDE 中:
如何完成这个指南
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
- 要从头开始,移步至从 Spring Initializr 开始;
- 要跳过基础,执行以下操作:
待一切就绪后,可以检查一下 gs-rest-service-cors/complete
目录中的代码。
从 Spring Initializr 开始
对于所有的 Spring 应用来说,你应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来提取应用程序所需的依赖,并为你完成许多设置。该示例仅需要 Spring Web 依赖。下图显示了此示例项目的 Initializr 设置:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和rest-service-cors
的值分别显示为 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>rest-service-cors</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest-service-cors</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()
}
添加 httpClient
依赖
测试(在 complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java
)需要 Apache httpclient
库。
要将 Apache httpclient
库添加到 Maven,请添加以下依赖:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
以下清单显示了完整的 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>rest-service-cors</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest-service-cors</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.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</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>
要将 Apache httpclient
库添加到 Gradle,请添加以下依赖:
testImplementation 'org.apache.httpcomponents:httpclient'
以下清单显示了完整的 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.apache.httpcomponents:httpclient'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
创建资源展示类
现在我们已经搭建了项目和构建系统,我们可以创建 Web 服务。
通过考虑服务交互来开始该过程。
该服务处理 /greeting
的 GET
请求,可以选择在查询字符串中使用 name
参数。GET
请求应返回 200 OK
响应,并在正文中使用 JSON 表示问候。它应类似于以下清单:
{
"id": 1,
"content": "Hello, World!"
}
id
字段是问候语的唯一标识符,content
是问候语的文本展示。
要建模问候语展示,需要创建资源展示类。为普通的 Java 对象提供字段、构造函数,以及 id
、content
数据的访问器,如下清单(来自 src/main/java/com/example/restservicecors/Greeting.java
)所示:
package com.example.restservicecors;
public class Greeting {
private final long id;
private final String content;
public Greeting() {
this.id = -1;
this.content = "";
}
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
Spring 使用 Jackson JSON 库自动将类型
Greeting
的实例转换为 JSON。
创建资源控制器
在 Spring 建立 RESTful 网络服务的方法中,HTTP 请求由控制器处理。这些组件可以通过 @Controller
注解轻松识别,并且以下清单中所示的 GreetingContoller
(来自 src/main/java/com/example/restservicecors/GreetingController.java
)通过返回 Greeting
类的新实例来处理 /greeting
的 GET
请求:
package com.example.restservicecors;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
该控制器简洁明了,但是内部却有很多事情要做。我们将其逐步分解。
@RequestMapping
注解可确保将对 /greeting
的 HTTP 请求映射到 greeting()
方法。
前面的示例使用
@GetMapping
注解,该注解用作@RequestMapping(method=RequestMethod.GET)
的快捷方式。
@RequestParam
将 name
查询字符串参数的值绑定到 greeting()
方法的 name
参数中。不 required
该查询字符串参数。如果请求中不存在,则使用 defaultValue
的 World
。
方法主体的实现创建并返回一个新的 Greeting
对象,其 id
属性的值基于来自 counter
的下一个值,而 content
的基于查询参数或默认值。它还使用问候 template
格式化给定 name
。
传统 MVC 控制器和 RESTful Web 服务控制器之间的主要区别在于创建 HTTP 响应主体的方式。该 RESTful Web 服务控制器填充并返回 Greeting
对象,而不是依赖于视图技术来将问候数据执行到 HTML 的服务器端渲染。对象数据作为 JSON 直接写入 HTTP 响应。
为了实现这一点,greeting()
方法上的 @ResponseBody
注解告诉 Spring MVC,它不需要通过服务器端视图层来渲染 Greeting 对象。相反,返回的问候语对象是响应主体,应直接写出。
Greeting
对象必须转换为 JSON。借助 Spring 的 HTTP 消息转换器支持,我们无需手动进行该转换。由于 Jackson 位于类路径中,因此会自动选择 Spring 的 MappingJackson2HttpMessageConverter
来将 Greeting
实例转换为 JSON。
启用 CORS
我们可以从单个控制器或全局控制器中启用跨域资源共享(CORS)。以下主题描述了如何执行该操作:
- 控制器方法 CORS 配置
- 全局 CORS 配置
控制器方法 CORS 配置
为了使 RESTful Web 服务的响应中包含 CORS 访问控制标头,我们必须在处理方法中添加 @CrossOrigin
注解,如下所示(来自 src/main/java/com/example/restservicecors/GreetingController.java
)显示:
@CrossOrigin(origins = "http://localhost:9000")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
该 @CrossOrigin
注解仅针对该特定方法启用跨域资源共享。默认情况下,它允许 @RequestMapping
注解中指定的所有来源,所有标头和 HTTP 方法。另外,使用 30 分钟的 maxAge
。我们可以通过指定以下注解属性之一的值来定义该行为:
origins
methods
allowedHeaders
exposedHeaders
allowCredentials
maxAge
在该示例中,我们仅允许 http://localhost:9000
发送跨域请求。
我们还可以在控制器类级别上添加
@CrossOrigin
注解,以在该类的所有处理方法上启用 CORS。
全局 CORS 配置
除了(或作为替代)基于细粒度的基于注解的配置,我们还可以定义一些全局 CORS 配置。这类似于使用 Filter
,但是可以在 Spring MVC 中声明与细粒度的 @CrossOrigin
配置结合使用。默认情况下,允许所有起源以及 GET
、HEAD
和 POST
方法。
以下清单(来自 src/main/java/com/example/restservicecors/GreetingController.java
)显示了 GreetingController
类中的 greetingWithJavaconfig
方法:
@GetMapping("/greeting-javaconfig")
public Greeting greetingWithJavaconfig(@RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
greetingWithJavaconfig
方法和greeting
方法(在控制器级别的 CORS 配置中使用)之间的区别是路由(/greeting-javaconfig
而不是/greeting
)和@CrossOrigin
起源的存在。
以下清单(来自 src/main/java/com/example/restservicecors/RestServiceCorsApplication.java
)显示了如何在应用类中添加 CORS 映射:
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
我们可以轻松地更改任何属性(例如示例中的 allowedOrigins
),并将该 CORS 配置应用于特定的路径模式。
我们可以结合全局和控制器级别的 CORS 配置。
创建应用类
Spring Initializr 为我们创建一个准系统应用类。以下清单(来自 initial/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java
)显示了该初始类:
package com.example.restservicecors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
}
我们需要添加一种方法来配置如何处理跨域资源共享。以下清单(来自 complete/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java
)显示了如何执行该操作:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
以下清单显示了完整的应用类:
package com.example.restservicecors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
}
@SpringBootApplication
是一个便利的注解,它添加了以下所有内容:
@Configuration
:将类标记为应用上下文 Bean 定义的源;@EnableAutoConfiguration
:告诉 Spring Boot 根据类路径配置、其他 bean 以及各种属性的配置来添加 bean。@ComponentScan
:告知 Spring 在com/example
包中寻找他组件、配置以及服务。
main()
方法使用 Spring Boot 的 SpringApplication.run()
方法启动应用。
构建可执行 JAR
我们可以结合 Gradle 或 Maven 来从命令行运行该应用。我们还可以构建一个包含所有必须依赖项、类以及资源的可执行 JAR 文件,然后运行该文件。在整个开发生命周期中,跨环境等等情况下,构建可执行 JAR 可以轻松地将服务作为应用进行发布、版本化以及部署。
如果使用 Gradle,则可以借助 ./gradlew bootRun
来运行应用。或通过借助 ./gradlew build
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar build/libs/gs-rest-service-cors-0.1.0.jar
由官网提供的以上这条命令的执行结果与我本地的不一样,我需要这样才能运行:
java -jar build/libs/rest-service-cors-0.0.1-SNAPSHOT.jar
。
如果使用 Maven,则可以借助 ./mvnw spring-boot:run
来运行该用。或可以借助 ./mvnw clean package
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar target/gs-rest-service-cors-0.1.0.jar
由官网提供的以上这条命令的执行结果与我本地的不一样,我需要这样才能运行:
java -jar target/rest-service-cors-0.0.1-SNAPSHOT.jar
。
我们还可以将 JAR 应用转换成 WAR 应用。
显示日志记录输出。该服务应在几秒内启动并运行。
测试服务
现在该服务已启动,请访问 http://localhost:8080/greeting,我们应在其中看到:
{"id":1,"content":"Hello, World!"}
通过访问 http://localhost:8080/greeting?name=User 提供名称查询字符串参数。content 属性的值从 Hello, World! 转变成 Hello User!,如下清单所示:
{"id":2,"content":"Hello, User!"}
该变更说明 GreetingController
中的 @RequestParam
安排按与其工作。name
参数的默认值为 World
,但始终可以通过查询字符串显式覆盖。
此外,id
属性已从 1
更改为 2
。这证明我们正在多个请求上针对同一个 GreetingController
实例进行操作,并且按预期,其 counter
字段在每次调用时都会递增。
现在,我们可以测试 CORS 标头是否存在,并允许其他来源的 JavaScript 客户端访问该服务。为此,我们需要创建一个 JavaScript 客户端来使用该服务。以下清单显示了这样的客户端。
首先,创建一个简单的名为 hello.js
的 JavaScript 文件(来自 complete/public/hello.js
),其内容如下:
$(document).ready(function() {
$.ajax({
url: "http://localhost:8080/greeting"
}).then(function(data, status, jqxhr) {
$('.greeting-id').append(data.id);
$('.greeting-content').append(data.content);
console.log(jqxhr);
});
});
该脚本使用 jQuery 消费 http://localhost:8080/greeting 上的 REST 服务。它由 index.html
加载,如下清单(来自 complete/public/index.html
)所示:
<!DOCTYPE html>
<html>
<head>
<title>Hello CORS</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="hello.js"></script>
</head>
<body>
<div>
<p class="greeting-id">The ID is </p>
<p class="greeting-content">The content is </p>
</div>
</body>
</html>
从本质上讲,这是在使用 jQuery 的 RESTful Web 服务中创建的 REST 客户端,当它在 localhost 的端口 8080 上运行时,对其进行了少许修改以使用该服务。有关如何开发该客户端的更多详细信息,请参见该指南。
由于 REST 服务已经在本地主机的 8080 端口上运行,因此需要确保从其他服务器或端口启动客户端。这样做不仅避免了两个应用之间的冲突,而且还要确保客户端代码与服务所处的源不同。要在 9000 端口上启动并运行客户端,请运行以下 Maven 命令:
./mvnw spring-boot:run -Dserver.port=9000
由官网提供的以上这条命令的执行结果与我本地的不一样,我需要这样才能运行:
java -jar -Dserver.port=9000 build/libs/rest-service-cors-0.0.1-SNAPSHOT.jar
。
客户端启动后,在浏览器中访问 http://localhost:9000,我们将在其中看到以下内容:
如果服务响应包含 CORS 标头,则 ID 和 content 将呈现到页面中。但是,如果缺少 CORS 标头(或为为客户端进行充分的定义),则浏览器将请求失败,并且不会将值呈现到 DOM 中。在这种情况下,我们应该看到以下内容:
概述
恭喜你!我们刚刚开发了一个 RESTful Web 服务,其中包括与 Spring 的跨域资源共享。
参见
以下指南也可能会有所帮助:
- 构建 RESTful Web 服务
- 构建超媒体驱动的 RESTful Web 服务
- 使用 Restdocs 创建 API 文档(尽请期待~)
- 使用 REST 访问 GemFire 数据(尽请期待~)
- 使用 REST 访问 MongoDB 数据(尽请期待~)
- 使用 MySQL 访问数据(尽请期待~)
- 使用 REST 访问 JPA 数据(尽请期待~)
- 使用 REST 访问 Neo4j 数据(尽请期待~)
- 消费 RESTful Web 服务(尽请期待~)
- 使用 AngularJS 消费 RESTful Web 服务
- 使用 jQuery 消费 RESTful Web 服务
- 使用 rest.js 消费 RESTful Web 服务
- 保护 Web 应用
- 使用 Spring 构建 REST 服务(尽请期待~)
- React.js 和 Spring Data Test(尽请期待~)
- 使用 Spring Boot 构建应用
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》