默認(rèn)情況下,Micronaut 是一個(gè)無狀態(tài)的 HTTP 服務(wù)器,但是根據(jù)您的應(yīng)用程序要求,您可能需要 HTTP 會話的概念。
Micronaut 包含一個(gè)受 Spring Session 啟發(fā)的會話模塊,該模塊目前有兩個(gè)實(shí)現(xiàn):
啟用會話
要啟用對內(nèi)存中會話的支持,您只需要會話依賴項(xiàng):
Gradle |
Maven |
implementation("io.micronaut:micronaut-session")
|
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-session</artifactId>
</dependency>
|
Redis 會話
要在 Redis 中存儲 Session 實(shí)例,請使用包含詳細(xì)說明的 Micronaut Redis 模塊。
要快速啟動(dòng)并運(yùn)行 Redis 會話,您還必須在構(gòu)建中具有 redis-lettuce 依賴項(xiàng):
build.gradle
compile "io.micronaut:micronaut-session"
compile "io.micronaut.redis:micronaut-redis-lettuce"
并通過配置文件中的配置啟用 Redis 會話(例如 application.yml):
啟用 Redis 會話
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
redis.uri=redis://localhost:6379
micronaut.session.http.redis.enabled=true
|
redis:
uri: redis://localhost:6379
micronaut:
session:
http:
redis:
enabled: true
|
[redis]
uri="redis://localhost:6379"
[micronaut]
[micronaut.session]
[micronaut.session.http]
[micronaut.session.http.redis]
enabled=true
|
redis {
uri = "redis://localhost:6379"
}
micronaut {
session {
http {
redis {
enabled = true
}
}
}
}
|
{
redis {
uri = "redis://localhost:6379"
}
micronaut {
session {
http {
redis {
enabled = true
}
}
}
}
}
|
{
"redis": {
"uri": "redis://localhost:6379"
},
"micronaut": {
"session": {
"http": {
"redis": {
"enabled": true
}
}
}
}
}
|
配置會話解析
可以使用 HttpSessionConfiguration 配置會話解析。
默認(rèn)情況下,使用 HttpSessionFilter 解析會話,該過濾器通過 HTTP 標(biāo)頭(使用 Authorization-Info 或 X-Auth-Token 標(biāo)頭)或通過名為 SESSION 的 Cookie 查找會話標(biāo)識符。
您可以通過配置文件(例如 application.yml)中的配置禁用標(biāo)頭解析或 cookie 解析:
禁用 Cookie 解析
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.session.http.cookie=false
micronaut.session.http.header=true
|
micronaut:
session:
http:
cookie: false
header: true
|
[micronaut]
[micronaut.session]
[micronaut.session.http]
cookie=false
header=true
|
micronaut {
session {
http {
cookie = false
header = true
}
}
}
|
{
micronaut {
session {
http {
cookie = false
header = true
}
}
}
}
|
{
"micronaut": {
"session": {
"http": {
"cookie": false,
"header": true
}
}
}
}
|
上面的配置啟用了 header 解析,但禁用了 cookie 解析。您還可以配置標(biāo)頭和 cookie 名稱。
使用會話
可以在控制器方法中使用 Session 類型的參數(shù)來檢索 Session。例如考慮以下控制器:
Java |
Groovy |
Kotlin |
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.session.Session;
import io.micronaut.session.annotation.SessionValue;
import io.micronaut.core.annotation.Nullable;
import javax.validation.constraints.NotBlank;
@Controller("/shopping")
public class ShoppingController {
private static final String ATTR_CART = "cart"; // (1)
@Post("/cart/{name}")
Cart addItem(Session session, @NotBlank String name) { // (2)
Cart cart = session.get(ATTR_CART, Cart.class).orElseGet(() -> { // (3)
Cart newCart = new Cart();
session.put(ATTR_CART, newCart); // (4)
return newCart;
});
cart.getItems().add(name);
return cart;
}
}
|
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
import javax.annotation.Nullable
import javax.validation.constraints.NotBlank
@Controller("/shopping")
class ShoppingController {
private static final String ATTR_CART = "cart" // (1)
@Post("/cart/{name}")
Cart addItem(Session session, @NotBlank String name) { // (2)
Cart cart = session.get(ATTR_CART, Cart).orElseGet({ -> // (3)
Cart newCart = new Cart()
session.put(ATTR_CART, newCart) // (4)
newCart
})
cart.items << name
cart
}
}
|
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
@Controller("/shopping")
class ShoppingController {
companion object {
private const val ATTR_CART = "cart" // (1)
}
@Post("/cart/{name}")
internal fun addItem(session: Session, name: String): Cart { // (2)
require(name.isNotBlank()) { "Name cannot be blank" }
val cart = session.get(ATTR_CART, Cart::class.java).orElseGet { // (3)
val newCart = Cart()
session.put(ATTR_CART, newCart) // (4)
newCart
}
cart.items.add(name)
return cart
}
}
|
ShoppingController 聲明了一個(gè)名為 cart 的 Session 屬性
Session 聲明為方法參數(shù)
檢索購物車屬性
否則會創(chuàng)建一個(gè)新的 Cart 實(shí)例并將其存儲在會話中
請注意,由于 Session 被聲明為必需參數(shù),因此要執(zhí)行控制器操作,將創(chuàng)建一個(gè) Session 并將其保存到 SessionStore。
如果您不想創(chuàng)建不必要的會話,請將 Session 聲明為@Nullable,在這種情況下不會創(chuàng)建和保存不必要的會話。例如:
將@Nullable 與會話一起使用
Java |
Groovy |
Kotlin |
@Post("/cart/clear")
void clearCart(@Nullable Session session) {
if (session != null) {
session.remove(ATTR_CART);
}
}
|
@Post("/cart/clear")
void clearCart(@Nullable Session session) {
session?.remove(ATTR_CART)
}
|
@Post("/cart/clear")
internal fun clearCart(session: Session?) {
session?.remove(ATTR_CART)
}
|
如果會話已經(jīng)存在,上述方法只會注入一個(gè)新會話。
會話客戶端
如果客戶端是 Web 瀏覽器,則在啟用 cookie 的情況下會話應(yīng)該可以正常工作。但是,對于編程式 HTTP 客戶端,您需要在 HTTP 調(diào)用之間傳播會話 ID。
例如,在前面的示例中調(diào)用 StoreController 的 viewCart 方法時(shí),HTTP 客戶端默認(rèn)接收一個(gè) AUTHORIZATION_INFO 標(biāo)頭。以下示例使用 Spock 測試演示了這一點(diǎn):
檢索 AUTHORIZATION_INFO 標(biāo)頭
Java |
Groovy |
Kotlin |
HttpResponse<Cart> response = Flux.from(client.exchange(HttpRequest.GET("/shopping/cart"), Cart.class)) // (1)
.blockFirst();
Cart cart = response.body();
assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)); // (2)
assertNotNull(cart);
assertTrue(cart.getItems().isEmpty());
|
when: "The shopping cart is retrieved"
HttpResponse<Cart> response = client.exchange(HttpRequest.GET('/shopping/cart'), Cart) // (1)
.blockFirst()
Cart cart = response.body()
then: "The shopping cart is present as well as a session id header"
response.header(HttpHeaders.AUTHORIZATION_INFO) != null // (2)
cart != null
cart.items.isEmpty()
|
var response = Flux.from(client.exchange(HttpRequest.GET<Cart>("/shopping/cart"), Cart::class.java)) // (1)
.blockFirst()
var cart = response.body()
assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)) // (2)
assertNotNull(cart)
cart.items.isEmpty()
|
向 /shopping/cart 提出請求
AUTHORIZATION_INFO 標(biāo)頭存在于響應(yīng)中
然后,您可以在后續(xù)請求中傳遞此 AUTHORIZATION_INFO 以重用現(xiàn)有會話:
發(fā)送 AUTHORIZATION_INFO 標(biāo)頭
Java |
Groovy |
Kotlin |
String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO); // (1)
response = Flux.from(client.exchange(HttpRequest.POST("/shopping/cart/Apple", "")
.header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart.class)) // (2)
.blockFirst();
cart = response.body();
|
String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) // (1)
response = client.exchange(HttpRequest.POST('/shopping/cart/Apple', "")
.header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart) // (2)
.blockFirst()
cart = response.body()
|
val sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) // (1)
response = Flux.from(client.exchange(HttpRequest.POST("/shopping/cart/Apple", "")
.header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart::class.java)) // (2)
.blockFirst()
cart = response.body()
|
從響應(yīng)中檢索 AUTHORIZATION_INFO
然后在后續(xù)請求中作為header發(fā)送
使用@SessionValue
您可以使用@SessionValue,而不是顯式地將 Session 注入到控制器方法中。例如:
使用@SessionValue
Java |
Groovy |
Kotlin |
@Get("/cart")
@SessionValue(ATTR_CART) // (1)
Cart viewCart(@SessionValue @Nullable Cart cart) { // (2)
if (cart == null) {
cart = new Cart();
}
return cart;
}
|
@Get("/cart")
@SessionValue("cart") // (1)
Cart viewCart(@SessionValue @Nullable Cart cart) { // (2)
cart ?: new Cart()
}
|
@Get("/cart")
@SessionValue(ATTR_CART) // (1)
internal fun viewCart(@SessionValue cart: Cart?): Cart { // (2)
return cart ?: Cart()
}
|
@SessionValue 在方法上聲明,導(dǎo)致返回值存儲在 Session 中。請注意,在返回值上使用時(shí)必須指定屬性名稱
@SessionValue 用于 @Nullable 參數(shù),這導(dǎo)致以非阻塞方式從 Session 中查找值并在存在時(shí)提供它。在沒有為 @SessionValue 指定值的情況下,會導(dǎo)致使用參數(shù)名稱來查找屬性。
會話事件
您可以注冊 ApplicationEventListener beans 以偵聽位于 io.micronaut.session.event 包中的會話相關(guān)事件。
下表總結(jié)了事件:
更多建議: