Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-configure Spring Data's reactive method argument resolvers in a reactive web application #20701

Open
cbornet opened this issue Mar 27, 2020 · 12 comments
Labels
status: blocked An issue that's blocked on an external project change type: enhancement A general enhancement
Milestone

Comments

@cbornet
Copy link

cbornet commented Mar 27, 2020

Spring Data now has argument resolvers for Pageable and Sort for Webflux.
It would be great to have these autoconfigured if spring-data-commons is detected on the classpath like what is done for MVC.
From the discussion with @mp911de (spring-projects/spring-data-commons#264 (comment)), could you give advice on where it's best to provide the configuration components (esp. the creation of the resolver beans and their customizers) ?
I can work on this once settled if you want.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 27, 2020
@wilkinsona
Copy link
Member

wilkinsona commented Mar 27, 2020

I suspect that there's some subtlety to this that I am missing. On the face of it, it would seem pretty straightforward for us to add configuration that is similar to the following:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ReactiveSortHandlerMethodArgumentResolver.class)
@ConditionalOnWebApplication(type = Type.REACTIVE)
public class SpringDataWebFluxAutoConfiguration {

    @Bean
    WebFluxConfigurer springDataArgumentResolversConfigurer() {
        return new WebFluxConfigurer() {

            @Override
            public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
                configurer.addCustomResolver(new ReactiveSortHandlerMethodArgumentResolver(),
                        new ReactivePageableHandlerMethodArgumentResolver());
            }

        };
    }

}

@mp911de
Copy link
Member

mp911de commented Mar 27, 2020

I was cautious as @gregturn is working on improved configuration support for WebFlux from a Spring HATEOAS perspective. Given the proximity of this issue towards Spring Data REST I wanted to make sure to keep everyone involved in the loop and to not come up with an implementation that would contradict with ideas that are evolving around other config API in Spring HATEOAS/Spring Data REST.

@gregturn
Copy link
Contributor

gregturn commented Apr 1, 2020

This is what Spring HATEOAS does (register a WebFluxConfigurer).

https://github.com/spring-projects/spring-hateoas/blob/master/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java#L79-L94

I'm not sure if this is a collision with what Spring Data Commons is doing, or not.

@wilkinsona
Copy link
Member

Thanks, both. I can't see any danger of a clash based on that. It looks to me like we can safely add some auto-configuration like that in my earlier comment.

@wilkinsona wilkinsona added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 1, 2020
@wilkinsona wilkinsona added this to the 2.3.x milestone Apr 1, 2020
@wilkinsona wilkinsona changed the title Autoconfiguration of Spring Data with Webflux Auto-configure Spring Data's reactive method argument resolvers in a reactive web application Apr 1, 2020
@cbornet
Copy link
Author

cbornet commented Apr 1, 2020

Spring MVC has customizers for these resolvers. Maybe they should also be added for Webflux ?

@wilkinsona
Copy link
Member

@cbornet Sorry, I don't understand what you're suggesting. What Spring MVC customisers are you referring to?

@cbornet
Copy link
Author

cbornet commented Apr 2, 2020

I'm referring to the customizers in SpringDataWebConfiguration : PageableHandlerMethodArgumentResolverCustomizer and SortHandlerMethodArgumentResolverCustomizer

@wilkinsona
Copy link
Member

Thanks. FWIW, those are part of Spring Data rather than Spring MVC.

The customizers make sense for Spring Data's web support on a Servlet-based stack as it provides a way to customise the argument resolvers that are automatically defined by SpringDataWebConfiguration.

I'm changing my mind about the issue now. It's not clear to me why Spring Data Commons cannot provide a WebFlux equivalent of @EnableSpringDataWebSupport. It looks to me like a lot, if not all, of what it does would also be applicable to a WebFlux-based app that's rolling its own @RestControllers and making use of reactive Spring Data repositories.

If Spring Data Commons did this, we could then customise the reactive argument resolvers that it defines via a similar mechanism to that used today for the Servlet/MVC side of things.

@wilkinsona wilkinsona removed this from the 2.3.x milestone Apr 2, 2020
@wilkinsona wilkinsona added the status: waiting-for-triage An issue we've not yet triaged label Apr 2, 2020
@philwebb philwebb added for: team-attention An issue we'd like other members of the team to review status: on-hold We can't start working on this issue yet labels Apr 16, 2020
@philwebb
Copy link
Member

@mp911de do Andy's comments above make sense? Do you want us to raise an issue?

@mp911de
Copy link
Member

mp911de commented Apr 19, 2020

It makes sense for Spring Data Commons to provide a similar annotation to @EnableSpringDataWebSupport to register Spring Data's WebFlux HandlerMethodArgumentResolvers. We already have a corresponding ticket so we could repurpose this one to pick up the annotation or close it for the time being until Spring Data provides the desired functionality.

@philwebb philwebb added status: blocked An issue that's blocked on an external project change and removed for: team-attention An issue we'd like other members of the team to review status: on-hold We can't start working on this issue yet status: waiting-for-triage An issue we've not yet triaged labels Apr 19, 2020
@philwebb philwebb added this to the 2.x milestone Apr 19, 2020
@philwebb philwebb modified the milestones: 2.x, 3.x Aug 19, 2022
@holmofy
Copy link

holmofy commented May 5, 2023

Has this problem been resolved? The version of springboot I am using is 3.0.5. The project maven dependency is like this:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
    </dependencies>

But, ReactivePageableHandlerMethodArgumentResolver is not automatically configured into the context. Reported the following error:

java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.data.domain.Pageable
	at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[spring-beans-6.0.6.jar:6.0.6]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ HTTP GET "/search" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[spring-beans-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver.createAttribute(ModelAttributeMethodArgumentResolver.java:218) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver.prepareAttributeMono(ModelAttributeMethodArgumentResolver.java:179) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver.resolveArgument(ModelAttributeMethodArgumentResolver.java:113) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:119) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:205) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:136) ~[spring-webflux-6.0.6.jar:6.0.6]
		at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle$1(RequestMappingHandlerAdapter.java:201) ~[spring-webflux-6.0.6.jar:6.0.6]
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:189) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Operators.complete(Operators.java:137) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:121) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Mono.subscribe(Mono.java:4485) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:258) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:863) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2545) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2305) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:338) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2341) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2215) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.Mono.subscribe(Mono.java:4485) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.5.3.jar:3.5.3]
		at reactor.netty.http.server.HttpServer$HttpServerHandle.onStateChange(HttpServer.java:1006) ~[reactor-netty-http-1.1.4.jar:1.1.4]
		at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:710) ~[reactor-netty-core-1.1.4.jar:1.1.4]
		at reactor.netty.transport.ServerTransport$ChildObserver.onStateChange(ServerTransport.java:477) ~[reactor-netty-core-1.1.4.jar:1.1.4]
		at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:633) ~[reactor-netty-http-1.1.4.jar:1.1.4]
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113) ~[reactor-netty-core-1.1.4.jar:1.1.4]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:228) ~[reactor-netty-http-1.1.4.jar:1.1.4]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.89.Final.jar:4.1.89.Final]
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.89.Final.jar:4.1.89.Final]
		at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

@wilkinsona
Copy link
Member

@holmofy No. The issue's still open and is labelled as blocked. It's blocked because the related Spring Data issue (spring-projects/spring-data-commons#1846) that's linked to above has not be resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: blocked An issue that's blocked on an external project change type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

7 participants