From 715bc68c164ab5a2486c25e29f62b4ecbb860d11 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 30 Oct 2025 12:17:51 +0000 Subject: [PATCH] Rename methods in FragmentsRendering Previous commit 81ea35c72690434c06a0d196180e64373663063d in main for 7.0 should have been applied in 6.2.x first for 6.2.1. This commit applies the changes in 6.2.x as intended, effective as of 6.2.13. Closes gh-33974 --- .../modules/ROOT/pages/web/webflux-view.adoc | 4 +- .../pages/web/webmvc-view/mvc-fragments.adoc | 4 +- .../DefaultFragmentsRenderingBuilder.java | 18 ++- .../result/view/FragmentsRendering.java | 135 +++++++++++++----- .../view/ViewResolutionResultHandler.java | 4 +- ...gmentViewResolutionResultHandlerTests.java | 4 +- .../ModelAndViewMethodReturnValueHandler.java | 2 +- ...ResponseBodyEmitterReturnValueHandler.java | 2 +- .../web/servlet/view/FragmentsRendering.java | 85 +++++++---- ...lAndViewMethodReturnValueHandlerTests.java | 2 +- .../view/DefaultFragmentsRenderingTests.java | 2 +- 11 files changed, 181 insertions(+), 81 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc index 2236ce36182..305d518d472 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc @@ -448,7 +448,7 @@ Java:: ---- @GetMapping FragmentsRendering handle() { - return FragmentsRendering.with("posts").fragment("comments").build(); + return FragmentsRendering.fragment("posts").fragment("comments").build(); } ---- @@ -458,7 +458,7 @@ Kotlin:: ---- @GetMapping fun handle(): FragmentsRendering { - return FragmentsRendering.with("posts").fragment("comments").build() + return FragmentsRendering.fragment("posts").fragment("comments").build() } ---- ====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc index 2e14cf0d056..10a4842bf0a 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc @@ -48,7 +48,7 @@ Java:: ---- @GetMapping FragmentsRendering handle() { - return FragmentsRendering.with("posts").fragment("comments").build(); + return FragmentsRendering.fragment("posts").fragment("comments").build(); } ---- @@ -58,7 +58,7 @@ Kotlin:: ---- @GetMapping fun handle(): FragmentsRendering { - return FragmentsRendering.with("posts").fragment("comments").build() + return FragmentsRendering.fragment("posts").fragment("comments").build() } ---- ====== diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java index a73da7e4938..1ced51d1cf1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java @@ -49,8 +49,8 @@ class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder { @Nullable private HttpHeaders headers; - DefaultFragmentsRenderingBuilder(Collection fragments) { - this.fragmentsCollection = new ArrayList<>(fragments); + DefaultFragmentsRenderingBuilder() { + this.fragmentsCollection = null; this.fragmentsFlux = null; } @@ -85,13 +85,13 @@ class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder { } @Override - public FragmentsRendering.Builder fragment(String viewName, Map model) { - return fragment(Fragment.create(viewName, model)); + public FragmentsRendering.Builder fragment(String viewName) { + return fragment(Fragment.create(viewName)); } @Override - public FragmentsRendering.Builder fragment(String viewName) { - return fragment(Fragment.create(viewName)); + public FragmentsRendering.Builder fragment(String viewName, Map model) { + return fragment(Fragment.create(viewName, model)); } @Override @@ -100,6 +100,12 @@ class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder { return this; } + @Override + public FragmentsRendering.Builder fragments(Collection fragments) { + initFragmentsCollection().addAll(fragments); + return this; + } + private Collection initFragmentsCollection() { if (this.fragmentsCollection == null) { this.fragmentsCollection = new ArrayList<>(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java index d30b510e99b..b454d09f9d0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java @@ -17,7 +17,6 @@ package org.springframework.web.reactive.result.view; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -32,13 +31,16 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Public API for HTML rendering of a collection of fragments each with a view - * and independent model. For use with frontends technologies such as + * Public API to render HTML fragments. A fragment is a portion of an HTML page. + * Normally HTML is rendered with a single model and view. This API allows + * using multiple model and view pairs, one for each HTML fragment. + * + *

For use with frontends technologies such as * htmx where multiple page fragments may be - * rendered in one response. Supported as a return value from Spring WebFlux - * controller methods. + * rendered in one response. * - *

For full page rendering with a single model and view, use {@link Rendering}. + *

Supported as a return value from annotated controller methods. + * For full page rendering with a single model and view, use {@link Rendering}. * * @author Rossen Stoyanchev * @since 6.2 @@ -63,59 +65,107 @@ public interface FragmentsRendering { /** - * Create a builder and add a fragment with a view name and a model. + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the model for the request * @return this builder + * @since 6.2.13 */ - static Builder with(String viewName, Map model) { - return withCollection(List.of(Fragment.create(viewName, model))); + static Builder fragment(String viewName) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName); } /** - * Variant of {@link #with(String, Map)} with a view name only, but also - * inheriting model attributes from the shared model for the request. - * @param viewName the name of the view for the fragment + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request * @return this builder + * @since 6.2.13 */ - static Builder with(String viewName) { - return withCollection(List.of(Fragment.create(viewName))); + static Builder fragment(String viewName, Map model) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName, model); } /** - * Variant of {@link #with(String, Map)} with a collection of fragments. - * @param fragments the fragments to add; each fragment also inherits model + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder + * @since 6.2.13 */ - static Builder withCollection(Collection fragments) { - return new DefaultFragmentsRenderingBuilder(fragments); + static Builder fragments(Collection fragments) { + return new DefaultFragmentsRenderingBuilder().fragments(fragments); } /** - * Variant of {@link #with(String, Map)} with a {@link Publisher} of fragments. + * Create a builder with a {@link Publisher} of fragments. * @param fragmentsPublisher the fragments to add; each fragment also * inherits model attributes from the shared model for the request * @return the created builder + * @since 6.2.13 */ - static

> Builder withPublisher(P fragmentsPublisher) { + static

> Builder fragmentsPublisher(P fragmentsPublisher) { return new DefaultFragmentsRenderingBuilder(fragmentsPublisher); } /** - * Variant of {@link #withPublisher(Publisher)} that allows using any + * Variant of {@link #fragmentsPublisher(Publisher)} that allows using any * producer that can be resolved to {@link Publisher} via * {@link ReactiveAdapterRegistry}. + * @since 6.2.13 */ - static Builder withProducer(Object fragmentsProducer) { - return new DefaultFragmentsRenderingBuilder(adaptProducer(fragmentsProducer)); + static Builder fragmentsProducer(Object fragmentsProducer) { + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(fragmentsProducer.getClass()); + Assert.isTrue(adapter != null, "Unknown producer " + fragmentsProducer.getClass()); + Publisher publisher = adapter.toPublisher(fragmentsProducer); + return fragmentsPublisher(publisher); } - private static Publisher adaptProducer(Object producer) { - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass()); - Assert.isTrue(adapter != null, "Unknown producer " + producer.getClass()); - return adapter.toPublisher(producer); + + /** + * The same as {@link #fragment(String, Map)}. + * @deprecated in favor of {@link #fragment(String, Map)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder with(String viewName, Map model) { + return fragment(viewName, model); + } + + /** + * The same as {@link #fragments(Collection)}. + * @deprecated in favor of {@link #fragments(Collection)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder with(String viewName) { + return fragment(viewName); + } + + /** + * The same as {@link #fragments(Collection)}. + * @deprecated in favor of {@link #fragments(Collection)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder withCollection(Collection fragments) { + return fragments(fragments); + } + + /** + * The same as {@link #fragmentsPublisher(Publisher)}. + * @deprecated in favor of {@link #fragmentsPublisher(Publisher)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static

> Builder withPublisher(P fragmentsPublisher) { + return fragmentsPublisher(fragmentsPublisher); + } + + /** + * The same as {@link #fragmentsProducer(Object)}. + * @deprecated in favor of {@link #fragmentsProducer(Object)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder withProducer(Object fragmentsProducer) { + return fragmentsProducer(fragmentsProducer); } @@ -148,30 +198,39 @@ public interface FragmentsRendering { Builder headers(Consumer headersConsumer); /** - * Add a fragment with a view name and a model. - * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the model for the request + * Add an HTML fragment. + * @param viewName the view name for the fragment + * @param model fragment attributes in addition to attributes from the + * shared model for the request * @return this builder */ Builder fragment(String viewName, Map model); /** - * Variant of {@link #fragment(String, Map)} with a view name only, where - * the fragment model also inherits model attributes from the shared + * Add an HTML fragment. The fragment will use attributes from the shared * model for the request. - * @param viewName the name of the view for the fragment + * @param viewName the view name for the fragment * @return this builder */ Builder fragment(String viewName); /** - * Variant of {@link #fragment(String, Map)} with a {@link Fragment}. - * @param fragment the fragment to add + * Add an HTML fragment. + * @param fragment the fragment to add; the fragment also inherits + * attributes from the shared model for the request * @return this builder */ Builder fragment(Fragment fragment); + /** + * Add HTML fragments. + * @param fragments the fragments to add; each fragment also inherits + * attributes from the shared model for the request + * @return this builder + * @since 6.2.13 + */ + Builder fragments(Collection fragments); + /** * Build the {@link FragmentsRendering} instance. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index 5380afde7fd..bfc24cb9abe 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -220,7 +220,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp } valueMono = (result.getReturnValue() != null ? - Mono.just(FragmentsRendering.withPublisher(adapter.toPublisher(result.getReturnValue())).build()) : + Mono.just(FragmentsRendering.fragmentsPublisher(adapter.toPublisher(result.getReturnValue())).build()) : Mono.empty()); valueType = ResolvableType.forClass(FragmentsRendering.class); @@ -252,7 +252,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp } if (Collection.class.isAssignableFrom(clazz)) { - returnValue = FragmentsRendering.withCollection((Collection) returnValue).build(); + returnValue = FragmentsRendering.fragments((Collection) returnValue).build(); clazz = FragmentsRendering.class; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java index 0973e1d27be..8bf3d78db5b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java @@ -66,9 +66,9 @@ public class FragmentViewResolutionResultHandlerTests { static Stream arguments() { Flux fragmentFlux = Flux.just(fragment1, fragment2).subscribeOn(Schedulers.boundedElastic()); return Stream.of( - Arguments.of(FragmentsRendering.withPublisher(fragmentFlux).build(), + Arguments.of(FragmentsRendering.fragmentsPublisher(fragmentFlux).build(), on(Handler.class).resolveReturnType(FragmentsRendering.class)), - Arguments.of(FragmentsRendering.withCollection(List.of(fragment1, fragment2)).build(), + Arguments.of(FragmentsRendering.fragments(List.of(fragment1, fragment2)).build(), on(Handler.class).resolveReturnType(FragmentsRendering.class)), Arguments.of(fragmentFlux, on(Handler.class).resolveReturnType(Flux.class, Fragment.class)), diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java index e4e57938e88..c09690f1744 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java @@ -92,7 +92,7 @@ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturn } if (returnValue instanceof Collection mavs) { - returnValue = FragmentsRendering.with((Collection) mavs).build(); + returnValue = FragmentsRendering.fragments((Collection) mavs).build(); } if (returnValue instanceof FragmentsRendering rendering) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java index 73992490bb7..f293316d4f3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java @@ -398,7 +398,7 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur FragmentHttpServletResponse fragmentResponse = new FragmentHttpServletResponse(this.response, this.charset); - FragmentsRendering render = FragmentsRendering.with(List.of(modelAndView)).build(); + FragmentsRendering render = FragmentsRendering.fragments(List.of(modelAndView)).build(); render.resolveNestedViews(this::resolveViewName, this.locale); render.render(modelAndView.getModel(), this.request, fragmentResponse); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java index 54a2f6ae1b2..edf33e7e5f8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java @@ -27,11 +27,15 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.SmartView; /** - * Public API for HTML rendering of a collection of fragments each with a view - * and independent model. For use with frontends technologies such as + * Public API to render HTML fragments. A fragment is a portion of an HTML page. + * Normally HTML is rendered with a single model and view. This API allows + * using multiple model and view pairs, one for each HTML fragment. + * + *

For use with frontends technologies such as * htmx where multiple page fragments may be - * rendered in one response. Supported as a return value from Spring MVC - * controller methods. + * rendered in one response. + * + *

Supported as a return value from controller methods. * * @author Rossen Stoyanchev * @since 6.2 @@ -51,37 +55,68 @@ public interface FragmentsRendering extends SmartView { /** - * Create a builder and add a fragment with a view name and a model. + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the shared model for the request * @return the created builder + * @since 6.2.13 */ - static Builder with(String viewName, Map model) { - return new DefaultFragmentsRenderingBuilder().fragment(viewName, model); + static Builder fragment(String viewName) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName); } /** - * Variant of {@link #with(String, Map)} with a view name only, but also - * inheriting model attributes from the shared model for the request. - * @param viewName the name of the view for the fragment + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request * @return the created builder + * @since 6.2.13 */ - static Builder with(String viewName) { - return new DefaultFragmentsRenderingBuilder().fragment(viewName); + static Builder fragment(String viewName, Map model) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName, model); } /** - * Variant of {@link #with(String, Map)} with a collection of fragments. - * @param fragments the fragments to add; each fragment also inherits model + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder + * @since 6.2.13 */ - static Builder with(Collection fragments) { + static Builder fragments(Collection fragments) { return new DefaultFragmentsRenderingBuilder().fragments(fragments); } + /** + * The same as {@link #fragment(String, Map)}. + * @deprecated in favor of {@link #fragment(String, Map)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder with(String viewName, Map model) { + return fragment(viewName, model); + } + + /** + * The same as {@link #fragment(String)}. + * @deprecated in favor of {@link #fragment(String)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder with(String viewName) { + return fragment(viewName); + } + + /** + * The same as {@link #fragments(Collection)}. + * @deprecated in favor of {@link #fragments(Collection)} + */ + @Deprecated(since = "6.2.13", forRemoval = true) + static Builder with(Collection fragments) { + return fragments(fragments); + } + + /** * Defines a builder for {@link FragmentsRendering}. */ @@ -111,32 +146,32 @@ public interface FragmentsRendering extends SmartView { Builder headers(Consumer headersConsumer); /** - * Add a fragment with a view name and a model. + * Add an HTML fragment. * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the shared model for the request + * @param model fragment attributes in addition to attributes from the + * shared model for the request * @return this builder */ Builder fragment(String viewName, Map model); /** - * Variant of {@link #fragment(String, Map)} with a view name only, but - * also inheriting model attributes from the shared model for the request. + * Add an HTML fragment. The fragment will use attributes from the shared + * model for the request. * @param viewName the name of the view for the fragment * @return this builder */ Builder fragment(String viewName); /** - * Variant of {@link #fragment(String, Map)} with a {@link ModelAndView}. - * @param fragment the fragment to add; the fragment also inherits model + * Add an HTML fragment. + * @param fragment the fragment to add; the fragment also inherits * attributes from the shared model for the request * @return this builder */ Builder fragment(ModelAndView fragment); /** - * Variant of {@link #fragment(String, Map)} with a collection of {@link ModelAndView}s. + * Add multiple HTML fragments. * @param fragments the fragments to add; each fragment also inherits model * attributes from the shared model for the request * @return this builder diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java index 858ed5e5d83..af78c51f3d2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java @@ -91,7 +91,7 @@ class ModelAndViewMethodReturnValueHandlerTests { @Test void handleFragmentsRendering() throws Exception { - FragmentsRendering rendering = FragmentsRendering.with("viewName").build(); + FragmentsRendering rendering = FragmentsRendering.fragment("viewName").build(); handler.handleReturnValue(rendering, returnParamModelAndView, mavContainer, webRequest); assertThat(mavContainer.getView()).isInstanceOf(SmartView.class); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java index 015d98a2a69..267a38b681b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java @@ -56,7 +56,7 @@ public class DefaultFragmentsRenderingTests { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - FragmentsRendering view = FragmentsRendering.with("fragment1", Map.of("foo", "Foo")) + FragmentsRendering view = FragmentsRendering.fragment("fragment1", Map.of("foo", "Foo")) .fragment("fragment2", Map.of("bar", "Bar")) .header("headerName", "headerValue") .build();