{"id":1,"content":"Hello, World!"}
為 RESTful Web 服務啟用跨來源請求
本指南將引導您完成使用 Spring 建立 “Hello, World” RESTful Web 服務的流程,該服務在回應中包含跨來源資源共享 (CORS) 的標頭。您可以在這篇部落格文章中找到更多關於 Spring CORS 支援的資訊。
您將建置的內容
您將建置一個服務,該服務接受在 https://127.0.0.1:8080/greeting
的 HTTP GET 請求,並以 JSON 格式回應問候語,如下列清單所示
您可以使用查詢字串中的選用 name
參數來自訂問候語,如下列清單所示
https://127.0.0.1:8080/greeting?name=User
name
參數值會覆寫預設值 World
,並反映在回應中,如下列清單所示
{"id":1,"content":"Hello, User!"}
此服務與建置 RESTful Web 服務中描述的服務略有不同,因為它使用 Spring Framework CORS 支援來新增相關的 CORS 回應標頭。
您需要的東西
-
約 15 分鐘
-
您慣用的文字編輯器或 IDE
-
Java 17 或更高版本
-
您也可以直接將程式碼匯入您的 IDE
如何完成本指南
如同大多數 Spring 入門指南,您可以從頭開始並完成每個步驟,或者您可以跳過您已熟悉的基礎設定步驟。無論哪種方式,您最終都會得到可運作的程式碼。
若要從頭開始,請移至從 Spring Initializr 開始。
若要跳過基礎知識,請執行下列操作
-
下載並解壓縮本指南的原始碼儲存庫,或使用 Git 複製它:
git clone https://github.com/spring-guides/gs-rest-service-cors.git
-
cd 進入
gs-rest-service-cors/initial
-
跳到建立資源表示類別。
當您完成時,您可以根據 gs-rest-service-cors/complete
中的程式碼檢查您的結果。
從 Spring Initializr 開始
您可以使用這個預先初始化的專案,然後按一下 Generate 來下載 ZIP 檔案。此專案已設定為符合本教學課程中的範例。
若要手動初始化專案
-
導覽至 https://start.spring.io。此服務會提取應用程式所需的所有相依性,並為您完成大部分的設定。
-
選擇 Gradle 或 Maven 以及您想要使用的語言。本指南假設您選擇 Java。
-
按一下Dependencies,然後選取 Spring Web。
-
按一下Generate。
-
下載產生的 ZIP 檔案,這是一個已使用您的選擇設定的 Web 應用程式的封存檔。
如果您的 IDE 具有 Spring Initializr 整合,您可以從您的 IDE 完成此流程。 |
您也可以從 Github 分支專案,並在您的 IDE 或其他編輯器中開啟它。 |
新增 httpclient5
相依性
測試 (在 complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java
中) 需要 Apache httpclient5
程式庫。
若要將 Apache httpclient5
程式庫新增至 Maven,請新增下列相依性
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</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>3.3.0</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>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</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>
若要將 Apache httpclient5
程式庫新增至 Gradle,請新增下列相依性
testImplementation 'org.apache.httpcomponents.client5:httpclient5'
下列清單顯示完成的 build.gradle
檔案
plugins {
id 'org.springframework.boot' version '3.3.0'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.apache.httpcomponents.client5:httpclient5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
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 Web 服務的方法中,HTTP 請求由控制器處理。這些元件很容易透過 @Controller
註解來識別,而下列清單 (來自 src/main/java/com/example/restservicecors/GreetingController.java
) 中顯示的 GreetingController
會處理對 /greeting
的 GET
請求,方法是傳回 Greeting
類別的新實例
package com.example.restservicecors;
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 = "https://127.0.0.1:9000")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
System.out.println("==== get greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
此控制器簡潔而簡單,但在底層有很多事情正在進行。我們逐步分解它。
@RequestMapping
註解確保對 /greeting
的 HTTP 請求會對應到 greeting()
方法。
先前的範例使用 @GetMapping 註解,它充當 @RequestMapping(method = RequestMethod.GET) 的捷徑。我們在此案例中使用 GET ,因為它方便測試。如果來源與 CORS 設定不符,Spring 仍然會拒絕 GET 請求。瀏覽器不需要傳送 CORS 預檢請求,但如果我們想要觸發預檢檢查,我們可以改用 @PostMapping 並在主體中接受一些 JSON。 |
@RequestParam
將 name
查詢字串參數的值繫結到 greeting()
方法的 name
參數。此查詢字串參數不是 required
。如果請求中不存在,則會使用 World
的 defaultValue
。
方法主體的實作會建立並傳回新的 Greeting
物件,其中 id
屬性的值基於來自 counter
的下一個值,而 content
的值基於查詢參數或預設值。它還使用問候語 template
來格式化給定的 name
。
傳統 MVC 控制器與先前顯示的 RESTful Web 服務控制器之間的主要差異在於建立 HTTP 回應主體的方式。此 RESTful Web 服務控制器不是依賴檢視技術來執行問候語資料到 HTML 的伺服器端呈現,而是填入並傳回 Greeting
物件。物件資料會直接寫入 HTTP 回應作為 JSON。
為了實現這一點,@RestController
註解假設每個方法預設都會繼承 @ResponseBody
語意。因此,傳回的物件資料會直接插入回應主體中。
由於 Spring 的 HTTP 訊息轉換器支援,Greeting
物件自然會轉換為 JSON。由於 Jackson 在類別路徑上,Spring 的 MappingJackson2HttpMessageConverter
會自動選擇將 Greeting
實例轉換為 JSON。
啟用 CORS
您可以從個別控制器或全域啟用跨來源資源共享 (CORS)。以下主題描述如何執行此操作
控制器方法 CORS 設定
為了讓 RESTful Web 服務在其回應中包含 CORS 存取控制標頭,您必須將 @CrossOrigin
註解新增至處理常式方法,如下列清單 (來自 src/main/java/com/example/restservicecors/GreetingController.java
) 所示
@CrossOrigin(origins = "https://127.0.0.1:9000")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
System.out.println("==== get greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
此 @CrossOrigin
註解僅針對此特定方法啟用跨來源資源共享。依預設,它允許所有來源、所有標頭以及 @RequestMapping
註解中指定的 HTTP 方法。此外,還使用了 30 分鐘的 maxAge
。您可以透過指定下列其中一個註解屬性的值來自訂此行為
-
origins
-
originPatterns
-
methods
-
allowedHeaders
-
exposedHeaders
-
allowCredentials
-
maxAge
.
在本範例中,我們僅允許 https://127.0.0.1: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("https://127.0.0.1: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("https://127.0.0.1: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("https://127.0.0.1:9000");
}
};
}
}
@SpringBootApplication
是一個便利的註解,它新增了以下所有內容
-
@Configuration
:將類別標記為應用程式內容的 Bean 定義來源。 -
@EnableAutoConfiguration
:告知 Spring Boot 根據類別路徑設定、其他 Bean 和各種屬性設定開始新增 Bean。例如,如果spring-webmvc
在類別路徑上,則此註解會將應用程式標記為 Web 應用程式,並啟動關鍵行為,例如設定DispatcherServlet
。 -
@ComponentScan
:告知 Spring 在com/example
套件中尋找其他元件、設定和服務,使其找到控制器。
main()
方法使用 Spring Boot 的 SpringApplication.run()
方法來啟動應用程式。您是否注意到沒有一行 XML 程式碼?也沒有 web.xml
檔案。此 Web 應用程式是 100% 純 Java,您不必處理設定任何基礎架構。
建置可執行 JAR 檔
您可以使用 Gradle 或 Maven 從命令列執行應用程式。您也可以建置一個包含所有必要相依性、類別和資源的單一可執行 JAR 檔案並執行它。建置可執行 JAR 檔可以輕鬆地在整個開發生命週期、跨不同環境等情況下傳送、版本控制和部署服務作為應用程式。
如果您使用 Gradle,您可以使用 ./gradlew bootRun
執行應用程式。或者,您可以使用 ./gradlew build
建置 JAR 檔案,然後執行 JAR 檔案,如下所示
如果您使用 Maven,您可以使用 ./mvnw spring-boot:run
執行應用程式。或者,您可以使用 ./mvnw clean package
建置 JAR 檔案,然後執行 JAR 檔案,如下所示
此處描述的步驟會建立可執行的 JAR。您也可以建置傳統 WAR 檔案。 |
會顯示記錄輸出。服務應在幾秒鐘內啟動並執行。
測試服務
現在服務已啟動,請在您的瀏覽器中造訪 https://127.0.0.1:8080/greeting
,您應該會看到
{"id":1,"content":"Hello, World!"}
透過造訪 https://127.0.0.1:8080/greeting?name=User
來提供 name
查詢字串參數。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: "https://127.0.0.1:8080/greeting"
}).then(function(data, status, jqxhr) {
$('.greeting-id').append(data.id);
$('.greeting-content').append(data.content);
console.log(jqxhr);
});
});
此腳本使用 jQuery 來取用 https://127.0.0.1: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>
為了測試 CORS 行為,您需要從另一個伺服器或連接埠啟動用戶端。這樣做不僅可以避免兩個應用程式之間的衝突,還可以確保用戶端程式碼是從與服務不同的來源提供的。
若要啟動在連接埠 9000 的 localhost 上執行的用戶端,請保持應用程式在連接埠 8080 上執行,並在另一個終端機中執行下列 Maven 命令
./mvnw spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9000'
如果您使用 Gradle,您可以使用此命令
./gradlew bootRun --args="--server.port=9000"
應用程式啟動後,在您的瀏覽器中開啟 https://127.0.0.1:9000,您應該會看到以下內容,因為服務回應包含相關的 CORS 標頭,因此 ID 和內容會呈現到頁面中

現在,停止在連接埠 9000 上執行的應用程式,保持應用程式在連接埠 8080 上執行,並在另一個終端機中執行下列 Maven 命令
./mvnw spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9001'
如果您使用 Gradle,您可以使用此命令
./gradlew bootRun --args="--server.port=9001"
應用程式啟動後,在您的瀏覽器中開啟 https://127.0.0.1:9001,您應該會看到以下內容

在這裡,瀏覽器請求失敗,並且值未呈現到 DOM 中,因為 CORS 標頭遺失 (或對於用戶端而言不足),因為我們僅允許來自 https://127.0.0.1:9000 而不是 https://127.0.0.1:9001 的跨來源請求。
總結
恭喜!您剛剛開發了一個包含 Spring 的跨來源資源共享的 RESTful Web 服務。