Micronaut 自定義參數(shù)綁定

2023-03-02 16:51 更新

Micronaut 使用 ArgumentBinderRegistry 來查找能夠綁定到控制器方法中的參數(shù)的 ArgumentBinder beans。默認(rèn)實(shí)現(xiàn)在使用@Bindable 進(jìn)行元注釋的參數(shù)上查找注釋。如果存在,則參數(shù)綁定器注冊(cè)表會(huì)搜索支持該注釋的參數(shù)綁定器。

如果未找到合適的注解,Micronaut 會(huì)嘗試尋找支持參數(shù)類型的參數(shù)綁定器。

參數(shù)綁定器返回 ArgumentBinder.BindingResult。綁定結(jié)果為 Micronaut 提供了比值更多的信息。綁定結(jié)果要么滿足要么不滿足,要么為空要么不為空。如果參數(shù)綁定器返回不滿意的結(jié)果,則可能會(huì)在請(qǐng)求處理的不同時(shí)間再次調(diào)用綁定器。參數(shù)綁定器最初在讀取主體之前和執(zhí)行任何過濾器之前調(diào)用。如果活頁夾依賴于任何該數(shù)據(jù)但不存在,則返回 ArgumentBinder.BindingResult#UNSATISFIED 結(jié)果。返回 ArgumentBinder.BindingResult#EMPTY 或滿意的結(jié)果將是最終結(jié)果,并且不會(huì)為該請(qǐng)求再次調(diào)用活頁夾。

處理結(jié)束時(shí),如果結(jié)果仍然是ArgumentBinder.BindingResult#UNSATISFIED,則認(rèn)為是ArgumentBinder.BindingResult#EMPTY。

關(guān)鍵接口有:

AnnotatedRequestArgumentBinder

基于注解的存在進(jìn)行綁定的參數(shù)綁定器必須實(shí)現(xiàn) AnnotatedRequestArgumentBinder,并且可以通過創(chuàng)建使用 Bindable 進(jìn)行注解的注解來使用。例如:

綁定注釋的示例

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.AliasFor;
import io.micronaut.core.bind.annotation.Bindable;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({FIELD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Bindable //(1)
public @interface ShoppingCart {

    @AliasFor(annotation = Bindable.class, member = "value")
    String value() default "";
}
import groovy.transform.CompileStatic
import io.micronaut.context.annotation.AliasFor
import io.micronaut.core.bind.annotation.Bindable

import java.lang.annotation.Retention
import java.lang.annotation.Target

import static java.lang.annotation.ElementType.ANNOTATION_TYPE
import static java.lang.annotation.ElementType.FIELD
import static java.lang.annotation.ElementType.PARAMETER
import static java.lang.annotation.RetentionPolicy.RUNTIME

@CompileStatic
@Target([FIELD, PARAMETER, ANNOTATION_TYPE])
@Retention(RUNTIME)
@Bindable //(1)
@interface ShoppingCart {
    @AliasFor(annotation = Bindable, member = "value")
    String value() default ""
}
import io.micronaut.core.bind.annotation.Bindable
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER

@Target(FIELD, VALUE_PARAMETER, ANNOTATION_CLASS)
@Retention(RUNTIME)
@Bindable //(1)
annotation class ShoppingCart(val value: String = "")
  1. 綁定注解本身必須被注解為 Bindable

帶注釋的數(shù)據(jù)綁定示例

 Java Groovy  Kotlin 
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.bind.binders.AnnotatedRequestArgumentBinder;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.jackson.serialize.JacksonObjectSerializer;

import jakarta.inject.Singleton;
import java.util.Map;
import java.util.Optional;

@Singleton
public class ShoppingCartRequestArgumentBinder
        implements AnnotatedRequestArgumentBinder<ShoppingCart, Object> { //(1)

    private final ConversionService<?> conversionService;
    private final JacksonObjectSerializer objectSerializer;

    public ShoppingCartRequestArgumentBinder(ConversionService<?> conversionService,
                                             JacksonObjectSerializer objectSerializer) {
        this.conversionService = conversionService;
        this.objectSerializer = objectSerializer;
    }

    @Override
    public Class<ShoppingCart> getAnnotationType() {
        return ShoppingCart.class;
    }

    @Override
    public BindingResult<Object> bind(
            ArgumentConversionContext<Object> context,
            HttpRequest<?> source) { //(2)

        String parameterName = context.getAnnotationMetadata()
                .stringValue(ShoppingCart.class)
                .orElse(context.getArgument().getName());

        Cookie cookie = source.getCookies().get("shoppingCart");
        if (cookie == null) {
            return BindingResult.EMPTY;
        }

        Optional<Map<String, Object>> cookieValue = objectSerializer.deserialize(
                cookie.getValue().getBytes(),
                Argument.mapOf(String.class, Object.class));

        return () -> cookieValue.flatMap(map -> {
            Object obj = map.get(parameterName);
            return conversionService.convert(obj, context);
        });
    }
}
import groovy.transform.CompileStatic
import io.micronaut.core.convert.ArgumentConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.bind.binders.AnnotatedRequestArgumentBinder
import io.micronaut.http.cookie.Cookie
import io.micronaut.jackson.serialize.JacksonObjectSerializer

import jakarta.inject.Singleton

@CompileStatic
@Singleton
class ShoppingCartRequestArgumentBinder
        implements AnnotatedRequestArgumentBinder<ShoppingCart, Object> { //(1)

    private final ConversionService<?> conversionService
    private final JacksonObjectSerializer objectSerializer

    ShoppingCartRequestArgumentBinder(
            ConversionService<?> conversionService,
            JacksonObjectSerializer objectSerializer) {
        this.conversionService = conversionService
        this.objectSerializer = objectSerializer
    }

    @Override
    Class<ShoppingCart> getAnnotationType() {
        ShoppingCart
    }

    @Override
    BindingResult<Object> bind(
            ArgumentConversionContext<Object> context,
            HttpRequest<?> source) { //(2)

        String parameterName = context.annotationMetadata
                .stringValue(ShoppingCart)
                .orElse(context.argument.name)

        Cookie cookie = source.cookies.get("shoppingCart")
        if (!cookie) {
            return BindingResult.EMPTY
        }

        Optional<Map<String, Object>> cookieValue = objectSerializer.deserialize(
                cookie.value.bytes,
                Argument.mapOf(String, Object))

        return (BindingResult) { ->
            cookieValue.flatMap({value ->
                conversionService.convert(value.get(parameterName), context)
            })
        }
    }
}
import io.micronaut.core.bind.ArgumentBinder.BindingResult
import io.micronaut.core.convert.ArgumentConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.bind.binders.AnnotatedRequestArgumentBinder
import io.micronaut.jackson.serialize.JacksonObjectSerializer
import java.util.Optional
import jakarta.inject.Singleton

@Singleton
class ShoppingCartRequestArgumentBinder(
        private val conversionService: ConversionService<*>,
        private val objectSerializer: JacksonObjectSerializer
) : AnnotatedRequestArgumentBinder<ShoppingCart, Any> { //(1)

    override fun getAnnotationType(): Class<ShoppingCart> {
        return ShoppingCart::class.java
    }

    override fun bind(context: ArgumentConversionContext<Any>,
                      source: HttpRequest<*>): BindingResult<Any> { //(2)

        val parameterName = context.annotationMetadata
            .stringValue(ShoppingCart::class.java)
            .orElse(context.argument.name)

        val cookie = source.cookies.get("shoppingCart") ?: return BindingResult.EMPTY

        val cookieValue: Optional<Map<String, Any>> = objectSerializer.deserialize(
                cookie.value.toByteArray(),
                Argument.mapOf(String::class.java, Any::class.java))

        return BindingResult {
            cookieValue.flatMap { map: Map<String, Any> ->
                conversionService.convert(map[parameterName], context)
            }
        }
    }
}
  1. 自定義參數(shù)綁定器必須實(shí)現(xiàn) AnnotatedRequestArgumentBinder,包括觸發(fā)綁定器的注釋類型(在本例中為 MyBindingAnnotation)和預(yù)期的參數(shù)類型(在本例中為 Object)

  2. 使用自定義參數(shù)綁定邏輯覆蓋 bind 方法 - 在這種情況下,我們解析帶注釋的參數(shù)的名稱,從具有相同名稱的 cookie 中提取一個(gè)值,并將該值轉(zhuǎn)換為參數(shù)類型

通常使用 ConversionService 將數(shù)據(jù)轉(zhuǎn)換為參數(shù)的類型。

創(chuàng)建活頁夾后,我們可以在我們的控制器方法中注釋一個(gè)參數(shù),該方法將使用我們指定的自定義邏輯進(jìn)行綁定。

具有此注釋綁定的控制器操作

 Java Groovy  Kotlin 
    @Get("/annotated")
    HttpResponse<String> checkSession(@ShoppingCart Long sessionId) { //(1)
        return HttpResponse.ok("Session:" + sessionId);
    }
    // end::method
}
    @Get("/annotated")
    HttpResponse<String> checkSession(@ShoppingCart Long sessionId) { //(1)
        HttpResponse.ok("Session:" + sessionId)
    }
    // end::method
}
@Get("/annotated")
fun checkSession(@ShoppingCart sessionId: Long): HttpResponse<String> { //(1)
    return HttpResponse.ok("Session:$sessionId")
}
  1. 該參數(shù)與與 MyBindingAnnotation 關(guān)聯(lián)的活頁夾綁定。如果適用,這優(yōu)先于基于類型的活頁夾。

TypedRequestArgumentBinder

基于參數(shù)類型綁定的參數(shù)綁定器必須實(shí)現(xiàn) TypedRequestArgumentBinder。例如,給定這個(gè)類:

POJO 的例子

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Introspected;

@Introspected
public class ShoppingCart {

    private String sessionId;
    private Integer total;

    public String getSessionId() {
        return sessionId;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public Integer getTotal() {
        return total;
    }

    public void setTotal(Integer total) {
        this.total = total;
    }
}
import io.micronaut.core.annotation.Introspected

@Introspected
class ShoppingCart {
    String sessionId
    Integer total
}
import io.micronaut.core.annotation.Introspected

@Introspected
class ShoppingCart {
    var sessionId: String? = null
    var total: Int? = null
}

我們可以為這個(gè)類定義一個(gè) TypedRequestArgumentBinder,如下所示:

類型化數(shù)據(jù)綁定示例

 Java Groovy  Kotlin 
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.bind.binders.TypedRequestArgumentBinder;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.jackson.serialize.JacksonObjectSerializer;

import jakarta.inject.Singleton;
import java.util.Optional;

@Singleton
public class ShoppingCartRequestArgumentBinder
        implements TypedRequestArgumentBinder<ShoppingCart> {

    private final JacksonObjectSerializer objectSerializer;

    public ShoppingCartRequestArgumentBinder(JacksonObjectSerializer objectSerializer) {
        this.objectSerializer = objectSerializer;
    }

    @Override
    public BindingResult<ShoppingCart> bind(ArgumentConversionContext<ShoppingCart> context,
                                            HttpRequest<?> source) { //(1)

        Cookie cookie = source.getCookies().get("shoppingCart");
        if (cookie == null) {
            return Optional::empty;
        }

        return () -> objectSerializer.deserialize( //(2)
                cookie.getValue().getBytes(),
                ShoppingCart.class);
    }

    @Override
    public Argument<ShoppingCart> argumentType() {
        return Argument.of(ShoppingCart.class); //(3)
    }
}
import io.micronaut.core.convert.ArgumentConversionContext
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.bind.binders.TypedRequestArgumentBinder
import io.micronaut.http.cookie.Cookie
import io.micronaut.jackson.serialize.JacksonObjectSerializer

import jakarta.inject.Singleton

@Singleton
class ShoppingCartRequestArgumentBinder
        implements TypedRequestArgumentBinder<ShoppingCart> {

    private final JacksonObjectSerializer objectSerializer

    ShoppingCartRequestArgumentBinder(JacksonObjectSerializer objectSerializer) {
        this.objectSerializer = objectSerializer
    }

    @Override
    BindingResult<ShoppingCart> bind(ArgumentConversionContext<ShoppingCart> context,
                                     HttpRequest<?> source) { //(1)

        Cookie cookie = source.cookies.get("shoppingCart")
        if (!cookie) {
            return BindingResult.EMPTY
        }

        return () -> objectSerializer.deserialize( //(2)
                cookie.value.bytes,
                ShoppingCart)
    }

    @Override
    Argument<ShoppingCart> argumentType() {
        Argument.of(ShoppingCart) //(3)
    }
}
import io.micronaut.core.bind.ArgumentBinder
import io.micronaut.core.bind.ArgumentBinder.BindingResult
import io.micronaut.core.convert.ArgumentConversionContext
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.bind.binders.TypedRequestArgumentBinder
import io.micronaut.jackson.serialize.JacksonObjectSerializer
import java.util.Optional
import jakarta.inject.Singleton

@Singleton
class ShoppingCartRequestArgumentBinder(private val objectSerializer: JacksonObjectSerializer) :
    TypedRequestArgumentBinder<ShoppingCart> {

    override fun bind(
        context: ArgumentConversionContext<ShoppingCart>,
        source: HttpRequest<*>
    ): BindingResult<ShoppingCart> { //(1)

        val cookie = source.cookies["shoppingCart"]

        return if (cookie == null)
            BindingResult {
                Optional.empty()
            }
        else {
            BindingResult {
                objectSerializer.deserialize( // (2)
                    cookie.value.toByteArray(),
                    ShoppingCart::class.java
                )
            }
        }
    }

    override fun argumentType(): Argument<ShoppingCart> {
        return Argument.of(ShoppingCart::class.java) //(3)
    }
}
  1. 使用要綁定的數(shù)據(jù)類型覆蓋綁定方法,在本例中為 ShoppingCart 類型

  2. 檢索數(shù)據(jù)后(在本例中,通過反序列化 cookie 中的 JSON 文本),作為 ArgumentBinder.BindingResult 返回

  3. 還要覆蓋 ArgumentBinderRegistry 使用的 argumentType 方法。

創(chuàng)建活頁夾后,它將用于關(guān)聯(lián)類型的任何控制器參數(shù):

具有此類型綁定的控制器操作

 Java Groovy  Kotlin 
@Get("/typed")
public HttpResponse<?> loadCart(ShoppingCart shoppingCart) { //(1)
    Map<String, Object> responseMap = new HashMap<>();
    responseMap.put("sessionId", shoppingCart.getSessionId());
    responseMap.put("total", shoppingCart.getTotal());

    return HttpResponse.ok(responseMap);
}
@Get("/typed")
HttpResponse<Map<String, Object>> loadCart(ShoppingCart shoppingCart) { //(1)
    HttpResponse.ok(
            sessionId: shoppingCart.sessionId,
            total: shoppingCart.total)
}
@Get("/typed")
fun loadCart(shoppingCart: ShoppingCart): HttpResponse<*> { //(1)
    return HttpResponse.ok(mapOf(
        "sessionId" to shoppingCart.sessionId,
        "total" to shoppingCart.total))
}
  1. 該參數(shù)使用在我們的 TypedRequestArgumentBinder 中為此類型定義的自定義邏輯進(jìn)行綁定


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)