默认情况下,Micronaut 是一个无状态的 HTTP 服务器,但是根据您的应用程序要求,您可能需要 HTTP 会话的概念。
Micronaut 包含一个受 Spring Session 启发的会话模块,该模块目前有两个实现:
启用会话
要启用对内存中会话的支持,您只需要会话依赖项:
Gradle |
Maven |
implementation("io.micronaut:micronaut-session")
|
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-session</artifactId>
</dependency>
|
Redis 会话
要在 Redis 中存储 Session 实例,请使用包含详细说明的 Micronaut Redis 模块。
要快速启动并运行 Redis 会话,您还必须在构建中具有 redis-lettuce 依赖项:
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 配置会话解析。
默认情况下,使用 HttpSessionFilter 解析会话,该过滤器通过 HTTP 标头(使用 Authorization-Info 或 X-Auth-Token 标头)或通过名为 SESSION 的 Cookie 查找会话标识符。
您可以通过配置文件(例如 application.yml)中的配置禁用标头解析或 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 解析。您还可以配置标头和 cookie 名称。
使用会话
可以在控制器方法中使用 Session 类型的参数来检索 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 声明了一个名为 cart 的 Session 属性
Session 声明为方法参数
检索购物车属性
否则会创建一个新的 Cart 实例并将其存储在会话中
请注意,由于 Session 被声明为必需参数,因此要执行控制器操作,将创建一个 Session 并将其保存到 SessionStore。
如果您不想创建不必要的会话,请将 Session 声明为@Nullable,在这种情况下不会创建和保存不必要的会话。例如:
将@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)
}
|
如果会话已经存在,上述方法只会注入一个新会话。
会话客户端
如果客户端是 Web 浏览器,则在启用 cookie 的情况下会话应该可以正常工作。但是,对于编程式 HTTP 客户端,您需要在 HTTP 调用之间传播会话 ID。
例如,在前面的示例中调用 StoreController 的 viewCart 方法时,HTTP 客户端默认接收一个 AUTHORIZATION_INFO 标头。以下示例使用 Spock 测试演示了这一点:
检索 AUTHORIZATION_INFO 标头
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 标头存在于响应中
然后,您可以在后续请求中传递此 AUTHORIZATION_INFO 以重用现有会话:
发送 AUTHORIZATION_INFO 标头
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()
|
从响应中检索 AUTHORIZATION_INFO
然后在后续请求中作为header发送
使用@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 在方法上声明,导致返回值存储在 Session 中。请注意,在返回值上使用时必须指定属性名称
@SessionValue 用于 @Nullable 参数,这导致以非阻塞方式从 Session 中查找值并在存在时提供它。在没有为 @SessionValue 指定值的情况下,会导致使用参数名称来查找属性。
会话事件
您可以注册 ApplicationEventListener beans 以侦听位于 io.micronaut.session.event 包中的会话相关事件。
下表总结了事件:
更多建议: