在项目初始化的部分有删减
本文将带你创建允许跨域资源共享( CORS,Corss-Origin Resource Sharing )的 web 服务。更多关于 Spring CORS 支持的信息可阅读 该博客(译注:已在翻译计划中)。
最终目标
本文最终的目标,是带你开发一个能够在 http://localhost:8080/greeting
接收 HTTP GET 请求的服务,该接口将以 JSON 格式返回一个问候信息,如:
{"id":1,"content":"Hello, World!"}
复制代码
该接口还能够通过 url 中的查询字符串( query string )接收一个可选的 name
参数,请求如:
http://localhost:8080/greeting?name=User
复制代码
这里的 name
参数值将覆盖默认的 World
,此时的接口响应如下:
{"id":1,"content":"Hello, User!"}
复制代码
该服务和另一篇文章 SpringBoot 开发 RESTful Web 服务 稍有不同,主要区别在于本文所述的服务使用了 Spring 框架的 CORS 支持,在 HTTP 响应头中添加了 CORS 。
如何完成该教程
和其他的 Spring 教程一样,你可以从零开始建立项目,也可以直接使用已有的代码模板。
-
若要从零开始,请阅读从零开始建立项目
-
若要跳过创建项目的部分,请进行如下步骤:
- 下载本教程的源代码:github.com/spring-guid…
- 进入
gs-rest-service-cors/initial
目录 - 直接阅读本文下面的【创建资源表示类】部分
-
当完成代码后,可以将自己的代码和
gs-rest-service-cors/complete
中的代码进行比较,检查自己的成果。
添加 httpclient
依赖
本代码对应的测试代码(complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java
)需要用到 Apache 的 httpclient
库。
要在 Maven 中添加 Apache httpclient
,需要在 pom 中添加下面的依赖:
<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.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rest-service-cors-complete</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest-service-cors-complete</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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
复制代码
Gradle 添加如下:
testImplementation 'org.apache.httpcomponents:httpclient'
复制代码
完成的 Gradle 如下:
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.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'
}
test {
useJUnitPlatform()
}
复制代码
创建资源表示类(Resource Representation Class)
首先,我们来思考一下服务交互的过程:
服务首先应该接收 /greeting
url 上面的 GET
请求,并处理可选的 name
参数。GET
请求应该返回一个 200 OK
的响应并在响应体( body )部分加上最终的 JSON ,样子如下:
{
"id":1,
"content":"Hello, World!"
}
复制代码
id
字段是每个问候响应的唯一标识符,content
是问候文本。
我们需要建立一个资源表示类( resource representation class )作为问候响应的数据模型。这个类是一个包含数据(表示 id
、content
的变量其访问他们的方法)、构造器的普通 Java 类,代码如下:
package com.example.restservice;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
复制代码
本应用程序使用 Jackson JSON 库来自动将
Greeting
对象转为 JSON。Jackson 默认包含在 Web 初始模板中
创建资源控制器(Resource Controller)
在 Spring 的 RESTful Web 服务中,HTTP 请求都是由控制器来管理的。这些控制器部件都被 @RestController
注解修饰,下面代码中的 GreetingController
会在 /greeting
接收 GET
请求,并返回一个新创建的 Greeting
类实例:
package com.example.restservice;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello,%s!";
private final AtomicLong counter = new AtomicLong();
@CrossOrigin(origins="http://localhost:8080")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
System.out.printLn("====get greeting====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
复制代码
下面我们来一步步解读上面的代码:
@GetMapping
注解让到 /greeting
的请求映射到 greeting()
方法。
此外还有几个处理其他 HTTP 方法的注解(例如给 POST 用的
@PostMapping
),它们都派生自@RequestMapping
注解。也可以通过@RequestMapping(method=GET)
的方法来直接使用 RequestMapping。Spring 仍然会拒绝 origin 与 CORS 配置不相符的 GET 请求。浏览器不需要发送 CORS 预检请求(Preflight request,浏览器用该请求来检查服务器是否支持 CORS)。但我们我们想要触发预检,可以通过
@PostMapping
来接收请求 body 中的 JSON
@RequestParam
能够将请求中的查询字符串参数 name
转为 greeting()
方法的 name
参数。如果请求中不含 name
参数,则将使用defaultValue
的值 World
作为默认参数。
方法体中创建并返回一个 Greeting
实例,并设置其成员变量 counter
(通过 incrementAndGet()
方法) 和 content
(通过对 template
用 name
参数来格式化)的值。
传统 MVC 控制器和 RESTful web 服务控制器的关键差异在于他们创建 HTTP 响应体的方式:传统 MVC 会使用服务端渲染(SSR)的方式,将问候数据直接转为 HTML 。而这里的 RESTful web 服务控制器会生成并返回一个 Greeting
对象。该对象的数据会被直接以 JSON 的方式返回给请求者。
为了实现该功能,@RestController
注解假设每个方法都默认继承了 @ResponseBody
语义。因此,被返回的对象数据会被直接插入到响应体中。
Greeting
对象需要被转为 JSON 才能返回给请求者,该工作由 Spring HTTP 消息转换支持来实现,因此不需要我们对其进行手动转换。因为 classpath 中包含 Jackson 2
,因此 Spring 会自动选择 MappingJackson2HttpMessageConverter
来将 Greeting
实例转为 JSON 文本。
启用 CORS
用户可以在每个 controller 中分别启用 CORS ,也可以全局启用。下面分别讲述这两种用法:
为控制器方法配置 CORS
因为 RESTful web 服务本身就会在响应中包含 CORS 访问控制头,因此可以直接在需要提供 CORS 支持的方法前添加 @CrossOrigin
注解。例子如下:
@CrossOrigin(origins="http://localhost:8080")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required=false, defaultValue="World") String name) {
//...
}
复制代码
这里的 @CrossOrigin
注解只会为该方法启用跨域资源共享。默认情况下,其会允许所有的 origin,所有的 header 和由 @RequestMapping
注解指定的 HTTP 方法。你还可以通过在注解中添加下面的注解属性值来影响跨域的行为:
origins
methods
allowedHeaders
exposedHeaders
allowCredentials
maxAge
在上面的例子中,我们只允许 http://localhost:8080
发送跨域请求。
你还可以在控制器类上面添加
@CrossOrigin
注解,这会为该类中的所有方法启用 CORS
配置全局 CORS
除了像上文中那样仔细控制跨域之外,我们还可以定义一些全局的 CORS 配置。该方法与使用 Filter
类似,但可以在 Spring MVC 中声明,并可以和单独配置的 @CrossOrigin
配置混合使用。默认情况下会允许所有的 origin 、GET
、HEAD
,以及POST
方法。
下面的代码展示了 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
方法的不同之处在于路由地址和是否写了@CrossOrigin
下面的代码展示如何在应用类中添加 CORS (代码位于 /src/main/java/com/example/restservicecors/RestServiceCorsApplication.java):
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowOrigins("http://localhost:8080");
}
}
}
复制代码
我们可以在这里修改任何配置(比如上面例子中的 allowedOrigins
),也可以将 CORS 配置应用到某个具体的请求路径上。
全局配置可以和单独配置的
@CrossOrigin
配置混合使用
创建应用类
Spring Initializr 为我们创建了一个最基础的应用类,如下:
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);
}
}
复制代码
我们需要在这个模板的基础上,添加前文所述的处理跨域资源共享的配置方法,如下:
@bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
}
}
}
复制代码
完整的应用类代码如下:
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:8080");
}
};
}
}
复制代码
@SpringBootApplication
是下面三个注解的简写:
@Configuration
: 将类标记为应用上下文( application context )bean 的定义;@EnableAutoConfiguration
:让 Spring Boot 根据 classpath 设置、其他的属性设置( various property settings )等来添加 bean 。例如:如果 classpath 中有一个spring-webmvc
,那么该标记就会将应用程序标记为一个 web 应用,并启用相关的行为——比如启动一个DispatcherServlet
;@ComponentScan
:让 Spring 寻找在com/example
包下面的其他组件、配置、服务等,并找到所有的控制器。
创建可执行 JAR
写好的程序可以通过命令行或使用 Gradle / Maven 来运行。此外我们也可以将应用构建为一个可执行的 JAR 文件,该文件会包含所有必须的依赖、类和运行所需的资源。构建可执行 JAR 能够方便发布、管理版本以及在不同的环境中部署等。
如果你用 Gradle 管理项目,那么可以使用 ./gradlew bootRun
来运行应用程序。如果想将程序构建为 JAR ,则可以使用 ./gradlew build
构建然后运行,运行方法如下:
java -jar build/libs/gs-rest-service-cors-0.1.0.jar
复制代码
如果你用 Maven 管理项目,可以通过 ./mvnw spring-boot:run
运行程序。如果要构建为 JAR ,使用命令 ./mvnw clean package
并通过和上面一样的方式运行 jar 文件。
测试服务
测试 API 基本功能可用
程序运行起来之后,使用浏览器访问 http://localhost:8080/greeting
,可以看到
{"id":1,"content":"Hello, World!"}
复制代码
也可以在 url 中加上查询参数:http://localhost:8080/greeting?name=User
,显示如下:
{"id":2,"content":"Hello, User!"}
复制代码
测试 CORS:
要测试 CORS ,需要一个客户端(前端)。该客户端在 通过 jQuery 使用 RESTful Web 服务 一文中有具体的讲解。
创建两个文件,首先是进行网络请求的 js 文件,我们将其命名为 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 请求 localhost:8080/greeting
,其将被下面的 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>
复制代码
使用启动该站点后(译注:直接使用浏览器打开 index.html 文件是不行的,需要为该文件起一个 Web 服务器。详见 通过 jQuery 使用 RESTful Web 服务 一文),访问前端,即可看到网页能够成功取回数据,这说明服务器接受了来自 localhost:8080
的请求
为了测试 CORS,我们可以将该前端的运行端口改为 9000 ,此时便无法取回数据了。