Как использовать фильтры JAX-RS с сгенерированными интерфейсами контроллера? (JavaEE, Wildfly18)

#java #jakarta-ee #jax-rs #resteasy #wildfly-18

#java #джакарта-ee #jax-rs #resteasy #wildfly-18

Вопрос:

У меня есть генератор кода, который генерирует интерфейсы для конечных точек JAX-RS, и мое серверное приложение реализует эти интерфейсы для обеспечения бизнес-логики.

Проблема сейчас в том, что я не могу использовать фильтры контейнеров с привязкой к имени для улучшения бизнес-логики или для повышения безопасности: любая @NameBinding аннотация маркера в реализующем классе или его методах игнорируется, и соответствующий фильтр не вызывается.

Вот минимальный пример: (код находится на Kotlin, но проблема та же, когда реализована на чистой Java)

 // generated
data class FooDto(val filtered: Boolean)

// generated
@Path("/")
interface OpenApiGeneratedInterface {
    @GET
    @Path("/foo/bar")
    @Produces("application/json")
    fun foo(): FooDto
}

// my implementation
class ImplementingApiController : OpenApiGeneratedInterface {
    @TestMarker
    override fun foo() = FooDto(filtered = false)
}

// may come from external dependency
@NameBinding
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class TestMarker

// may come from external dependency
@Provider
@TestMarker
class TestFilter: ContainerResponseFilter {
    override fun filter(
        requestContext: ContainerRequestContext,
        responseContext: ContainerResponseContext,
    ) {
        responseContext.entity = FooDto(filtered = true)
    }
}
  

Когда делается запрос на /foo/bar , я получаю {"filtered":false} , поэтому фильтр не запущен.
Когда я перемещаю @TestMarker аннотацию из ImplementingApiController::foo в OpenApiGeneratedInterface::foo , я вместо этого получаю {"filtered":true} , так что на этот раз фильтр действительно запустился. Обратите внимание, что изменение интерфейса в действительности невозможно, поскольку генерируются реальные интерфейсы. Я сделал это только в примере, чтобы показать, что фильтр работает в целом.

Проблема, по-видимому, заключается в том, что система ищет аннотации маркеров только в интерфейсах и никогда в реализующих классах.

Вот полная картина; Я контролирую:

  • Класс ImplementingApiController
  • Система, в которой запущено приложение (чтобы я мог изменить конфигурацию или добавить дополнительные фильтры / перехватчики)

У меня нет или почти нет контроля над:

  • OpenApiGeneratedInterface Интерфейс (генерируется из спецификации OpenAPI)
  • Классы DTO, такие как FooDto (также сгенерированные)
  • Генератор кода, который создает эти интерфейсы (это удаленный проект)
  • @TestMarker Аннотация и соответствующий ей фильтр (взяты из еще одного проекта)

Это оставляет мне мало места для маневра, чтобы заставить это работать.

Возможно ли это вообще в этом созвездии, и если да, то как это будет работать?

Что я пробовал до сих пор:

  • Добавьте @Path @Provider аннотацию или ImplementingApiController , чтобы заставить систему использовать этот класс для обнаружения аннотаций (не сработало)
  • Добавьте javax.ws.rs.container.DynamicFeature и подключите фильтры, выполнив поиск реализаций интерфейса с помощью отражения (может сработать, но это будет очень некрасиво, когда интерфейс и реализация не управляются одним и тем же загрузчиком классов)
  • Добавьте мой собственный ContainerResponseFilter , который всегда активен, и вызывайте фактические фильтры динамически (также требуется то же безумие отражения, что и с a DynamicFeature )

Дополнительные идеи:

  • Измените генератор кода, чтобы исключить аннотации JAX-RS в интерфейсе и аннотировать все самостоятельно (работает, но почти полностью теряет смысл)
  • Измените генератор кода, чтобы включить все виды аннотаций маркеров, которые мне нужны (тогда я столкнусь с проблемами циклических зависимостей при создании сгенерированного кода)

Комментарии:

1. Если я правильно помню, у меня была похожая проблема. Чтобы убедить JAX-RS читать аннотации из моего класса реализации, мне пришлось поместить @Path в реализацию и исключить ее из интерфейса. Если сделать это подобным образом, JAX-RS объединит все остальные аннотации из моего класса интерфейс и даст мне то, что я хочу. Если это правильно, вы могли бы выбрать вариант внесения минимальных изменений в генератор.

2. Спасибо за ответ, Никос. К сожалению, это не работает: (

3. на самом деле, это меняет ситуацию тонким способом, который я могу обнаружить с помощью a DynamicFeature : — когда @Path("/") только в интерфейсе, обнаруживается только интерфейс, а полный класс реализации игнорируется — когда @Path("/") только в классе реализации, обнаруживается класс реализации, но только аннотации наинтерфейс все еще используется. В настоящее время я экспериментирую с a DynamicFeature , который исправил бы это, выполнив поиск аннотаций маркеров вручную… это может сработать!

4. Оглядываясь назад на спецификацию JAX-RS v2.1, глава 3.6, она описывает именно это поведение. В нем, среди прочего, говорится: » аннотации наследуются […] при условии, что метод [подкласса] и его параметры не имеют собственных аннотаций JAX-RS», «Если у подкласса или метода реализации есть какие-либо аннотации JAX-RS , тогда все аннотации суперкласса илиметоды интерфейса игнорируются » и » рекомендуется всегда повторять аннотации, а не полагаться на наследование аннотаций «.

5. У меня почти получилось. Я смог написать DynamicFeature , который правильно определяет все случаи наследования, когда дополнительные интерфейсы маркеров будут игнорироваться, и пытается зарегистрировать соответствующие фильтры. суть кода , но система не позволяет мне зарегистрировать фильтр, который уже зарегистрирован автоматически @Provider . Мне нужно как-то включить фильтр вместо его регистрации…