阅读(1269) (0)

Micronaut 简单请求绑定

2023-02-23 11:22:10 更新

上一节中的示例演示了 Micronaut 如何让您从 URI 路径变量绑定方法参数。本节介绍如何绑定请求其他部分的参数。

绑定注解

所有绑定注解都支持自定义被绑定的变量的名称及其名称成员。

下表总结了注释及其用途,并提供了示例:

表 1. 参数绑定注解
注解 描述 示例

@Body

从请求正文绑定

@Body String body

@CookieValue

从 cookie 绑定参数

@CookieValue String myCookie

@Header

绑定来自 HTTP 标头的参数

@Header String requestId

@QueryValue

从请求查询参数绑定

@QueryValue String myParam

@Part

从多部分请求的一部分绑定

@Part CompletedFileUpload file

@RequestAttribute

从请求的属性绑定。属性通常在过滤器中创建

@RequestAttribute String myAttribute

@PathVariable

从请求的路径绑定

@PathVariable String id

@RequestBean

将任何 Bindable 值绑定到单个 Bean 对象

@RequestBean MyBean bean

当绑定注释中未指定值时,将使用方法参数名称。换句话说,以下两个方法是等效的,并且都从名为 myCookie 的 cookie 绑定:

 Java Groovy  Kotlin 
@Get("/cookieName")
public String cookieName(@CookieValue("myCookie") String myCookie) {
    // ...
}

@Get("/cookieInferred")
public String cookieInferred(@CookieValue String myCookie) {
    // ...
}
@Get("/cookieName")
String cookieName(@CookieValue("myCookie") String myCookie) {
    // ...
}

@Get("/cookieInferred")
String cookieInferred(@CookieValue String myCookie) {
    // ...
}
@Get("/cookieName")
fun cookieName(@CookieValue("myCookie") myCookie: String): String {
    // ...
}

@Get("/cookieInferred")
fun cookieInferred(@CookieValue myCookie: String): String {
    // ...
}

由于变量名称中不允许使用连字符,因此可能需要在注释中设置名称。下面的定义是等价的:

 Java Groovy  Kotlin 
@Get("/headerName")
public String headerName(@Header("Content-Type") String contentType) {
    // ...
}

@Get("/headerInferred")
public String headerInferred(@Header String contentType) {
    // ...
}
@Get("/headerName")
String headerName(@Header("Content-Type") String contentType) {
    // ...
}

@Get("/headerInferred")
String headerInferred(@Header String contentType) {
    // ...
}
@Get("/headerName")
fun headerName(@Header("Content-Type") contentType: String): String {
    // ...
}

@Get("/headerInferred")
fun headerInferred(@Header contentType: String): String {
    // ...
}

流支持

Micronaut 还支持将正文绑定到 InputStream。如果该方法正在读取流,则必须将方法执行卸载到另一个线程池以避免阻塞事件循环。

使用 InputStream 执行阻塞 I/O

 Java Groovy Kotlin 
@Post(value = "/read", processes = MediaType.TEXT_PLAIN)
@ExecuteOn(TaskExecutors.IO) // (1)
String read(@Body InputStream inputStream) throws IOException { // (2)
    return IOUtils.readText(new BufferedReader(new InputStreamReader(inputStream))); // (3)
}
@Post(value = "/read", processes = MediaType.TEXT_PLAIN)
@ExecuteOn(TaskExecutors.IO) // (1)
String read(@Body InputStream inputStream) throws IOException { // (2)
    IOUtils.readText(new BufferedReader(new InputStreamReader(inputStream))) // (3)
}
@Post(value = "/read", processes = [MediaType.TEXT_PLAIN])
@ExecuteOn(TaskExecutors.IO) // (1)
fun read(@Body inputStream: InputStream): String { // (2)
    return IOUtils.readText(BufferedReader(InputStreamReader(inputStream))) // (3)
}
  1. 控制器方法在IO线程池上执行

  2. 正文作为输入流传递给方法

  3. 流被读取

从多个查询值绑定

与其从请求的单个部分进行绑定,不如将所有查询值绑定到例如 POJO。这可以通过在 URI 模板中使用展开运算符 (?pojo*) 来实现。例如:

绑定请求参数到POJO

 Java Groovy  Kotlin 
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.core.annotation.Nullable;

import javax.validation.Valid;

@Controller("/api")
public class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    public HttpStatus list(@Valid @Nullable PaginationCommand paginationCommand) {
        return HttpStatus.OK;
    }
}
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

import javax.annotation.Nullable
import javax.validation.Valid

@Controller("/api")
class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    HttpStatus list(@Valid @Nullable PaginationCommand paginationCommand) {
        HttpStatus.OK
    }
}
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import javax.validation.Valid

@Controller("/api")
open class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    open fun list(@Valid paginationCommand: PaginationCommand): HttpStatus {
        return HttpStatus.OK
    }
}

从多个可绑定值绑定

除了只绑定查询值,还可以将任何 Bindable 值绑定到 POJO(例如,将 HttpRequest、@PathVariable、@QueryValue 和 @Header 绑定到单个 POJO)。这可以通过 @RequestBean 注释和自定义 Bean 类来实现,该类具有带有 Bindable 注释的字段,或者可以按类型(例如 HttpRequest、BasicAuth、Authentication 等)绑定的字段。

例如:

将 Bindable 值绑定到 POJO

 Java Groovy  Kotlin 
@Controller("/api")
public class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    public HttpStatus list(@Valid @RequestBean MovieTicketBean bean) {
        return HttpStatus.OK;
    }
}
@Controller("/api")
class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    HttpStatus list(@Valid @RequestBean MovieTicketBean bean) {
        HttpStatus.OK
    }
}
@Controller("/api")
open class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    open fun list(@Valid @RequestBean bean: MovieTicketBean): HttpStatus {
        return HttpStatus.OK
    }

}

它使用这个 bean 类:

Bean定义

 Java Groovy  Kotlin 
@Introspected
public class MovieTicketBean {

    private HttpRequest<?> httpRequest;

    @PathVariable
    private String movieId;

    @Nullable
    @QueryValue
    @PositiveOrZero
    private Double minPrice;

    @Nullable
    @QueryValue
    @PositiveOrZero
    private Double maxPrice;

    public MovieTicketBean(HttpRequest<?> httpRequest,
                           String movieId,
                           Double minPrice,
                           Double maxPrice) {
        this.httpRequest = httpRequest;
        this.movieId = movieId;
        this.minPrice = minPrice;
        this.maxPrice = maxPrice;
    }

    public HttpRequest<?> getHttpRequest() {
        return httpRequest;
    }

    public String getMovieId() {
        return movieId;
    }

    @Nullable
    public Double getMaxPrice() {
        return maxPrice;
    }

    @Nullable
    public Double getMinPrice() {
        return minPrice;
    }
}
@Introspected
class MovieTicketBean {

    private HttpRequest<?> httpRequest

    @PathVariable
    String movieId

    @Nullable
    @QueryValue
    @PositiveOrZero
    Double minPrice

    @Nullable
    @QueryValue
    @PositiveOrZero
    Double maxPrice
}
@Introspected
data class MovieTicketBean(
    val httpRequest: HttpRequest<Any>,
    @field:PathVariable val movieId: String,
    @field:QueryValue @field:PositiveOrZero @field:Nullable val minPrice: Double,
    @field:QueryValue @field:PositiveOrZero @field:Nullable val maxPrice: Double
)

bean 类必须使用@Introspected 进行自省。它可以是以下之一:

  1. 具有 setter 和 getter 的可变 Bean 类

  2. 具有 getter 和全参数构造函数(或构造函数或静态方法上的 @Creator 注释)的不可变 Bean 类。构造函数的参数必须匹配字段名称,这样对象就可以在没有反射的情况下被实例化。

由于 Java 不在字节码中保留参数名称,因此您必须使用 -parameters 编译代码才能使用来自另一个 jar 的不可变 bean 类。另一种选择是在您的源代码中扩展 Bean 类。

可绑定类型

通常,可以绑定任何可以通过 ConversionService API 从 String 表示形式转换为 Java 类型的类型。

这包括最常见的 Java 类型,但是可以通过创建类型为 TypeConverter 的 @Singleton bean 来注册其他 TypeConverter 实例。

可空性的处理值得特别提及。例如考虑以下示例:

 Java Groovy  Kotlin 
@Get("/headerInferred")
public String headerInferred(@Header String contentType) {
    // ...
}
@Get("/headerInferred")
String headerInferred(@Header String contentType) {
    // ...
}
@Get("/headerInferred")
fun headerInferred(@Header contentType: String): String {
    // ...
}

在这种情况下,如果请求中不存在 HTTP 标头 Content-Type,则路由被视为无效,因为它无法满足,并返回 HTTP 400 BAD REQUEST。

要使 Content-Type 标头可选,您可以改为编写:

 Java Groovy  Kotlin 
@Get("/headerNullable")
public String headerNullable(@Nullable @Header String contentType) {
    // ...
}
@Get("/headerNullable")
String headerNullable(@Nullable @Header String contentType) {
    // ...
}
@Get("/headerNullable")
fun headerNullable(@Header contentType: String?): String? {
    // ...
}

如果请求中没有标头,则传递一个空字符串。

也可以使用 java.util.Optional,但不鼓励将其用于方法参数。

此外,任何符合 RFC-1123 的 DateTime 都可以绑定到参数。或者,可以使用 Format 注释自定义格式:

 Java Groovy  Kotlin 
@Get("/date")
public String date(@Header ZonedDateTime date) {
    // ...
}

@Get("/dateFormat")
public String dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header ZonedDateTime date) {
    // ...
}
@Get("/date")
String date(@Header ZonedDateTime date) {
    // ...
}

@Get("/dateFormat")
String dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header ZonedDateTime date) {
    // ...
}
@Get("/date")
fun date(@Header date: ZonedDateTime): String {
    // ...
}

@Get("/dateFormat")
fun dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header date: ZonedDateTime): String {
    // ...
}

基于类型的绑定参数

一些参数通过它们的类型而不是它们的注释来识别。下表总结了参数类型及其用途,并提供了一个示例:

类型 描述 示例

BasicAuth

允许绑定基本授权凭据

BasicAuth basicAuth

可变分辨率

Micronaut 尝试按以下顺序填充方法参数:

  1. URI 变量,如 /{id}。

  2. 如果请求是 GET 请求(例如?foo=bar),则来自查询参数。

  3. 如果有 @Body 并且请求允许主体,则将主体绑定到它。

  4. 如果请求可以有主体并且没有定义 @Body 则尝试解析主体(JSON 或表单数据)并从主体绑定方法参数。

  5. 最后,如果无法填充方法参数,则返回 400 BAD REQUEST。