If a user only provides RouterFunction
endpoints it's a shame to load up the application context with loads of annotation-processing (RequestMappingHandlerMapping
etc.). But everything happens in the same place right now.
See https://github.com/spring-projects/spring-boot/issues/22314.
Comment From: dsyer
This (Spring-Boot style) works for me, but I can't say if it is more or less complicated than it needs to be. It's a drop-in replacement for WebFluxAutoConfiguration
, so I suppose the most relevant part for spring-framework is the nested EnableFunctionalConfiguration
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebFluxConfigurer.class)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class, CodecsAutoConfiguration.class,
ValidationAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class RouterFunctionAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.webflux.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Configuration(proxyBeanMethods = false)
public static class WelcomePageConfiguration {
@Bean
public RouterFunctionMapping welcomePageRouterFunctionMapping(ApplicationContext applicationContext,
WebFluxProperties webFluxProperties, ResourceProperties resourceProperties) {
WelcomePageRouterFunctionFactory factory = new WelcomePageRouterFunctionFactory(
new TemplateAvailabilityProviders(applicationContext), applicationContext,
resourceProperties.getStaticLocations(), webFluxProperties.getStaticPathPattern());
RouterFunction<ServerResponse> routerFunction = factory.createRouterFunction();
if (routerFunction != null) {
RouterFunctionMapping routerFunctionMapping = new RouterFunctionMapping(routerFunction);
routerFunctionMapping.setOrder(1);
return routerFunctionMapping;
}
return null;
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
public static class EnableFunctionalConfiguration implements ApplicationContextAware {
private WebFluxAutoConfiguration.EnableWebFluxConfiguration delegate;
@Override
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
delegate.setApplicationContext(applicationContext);
}
public EnableFunctionalConfiguration(WebFluxProperties webFluxProperties,
ObjectProvider<WebFluxRegistrations> webFluxRegistrations) {
delegate = new WebFluxAutoConfiguration.EnableWebFluxConfiguration(webFluxProperties, webFluxRegistrations);
}
@Bean
public DispatcherHandler webHandler() {
return delegate.webHandler();
}
@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return delegate.responseStatusExceptionHandler();
}
@Bean
public RequestedContentTypeResolver webFluxContentTypeResolver() {
return delegate.webFluxContentTypeResolver();
}
@Bean
public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) {
return delegate.routerFunctionMapping(serverCodecConfigurer);
}
@Bean
public ResourceUrlProvider resourceUrlProvider() {
return delegate.resourceUrlProvider();
}
@Bean
public ServerCodecConfigurer serverCodecConfigurer() {
return delegate.serverCodecConfigurer();
}
@Bean
public LocaleContextResolver localeContextResolver() {
return delegate.localeContextResolver();
}
@Bean
public ReactiveAdapterRegistry webFluxAdapterRegistry() {
return delegate.webFluxAdapterRegistry();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return delegate.handlerFunctionAdapter();
}
@Bean
public SimpleHandlerAdapter simpleHandlerAdapter() {
return delegate.simpleHandlerAdapter();
}
@Bean
public ServerResponseResultHandler serverResponseResultHandler(ServerCodecConfigurer serverCodecConfigurer) {
return delegate.serverResponseResultHandler(serverCodecConfigurer);
}
@Bean
public FormattingConversionService webFluxConversionService() {
return delegate.webFluxConversionService();
}
@Bean
public Validator webFluxValidator() {
return delegate.webFluxValidator();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnEnabledResourceChain
static class ResourceChainCustomizerConfiguration {
@Bean
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
return new ResourceChainResourceHandlerRegistrationCustomizer();
}
}
}
and the MVC version:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
@EnableConfigurationProperties({ResourceProperties.class, WebMvcProperties.class})
public class RouterFunctionAutoConfiguration {
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
@Configuration(proxyBeanMethods = false)
public static class EnableFunctionalConfiguration implements ResourceLoaderAware {
private WebMvcAutoConfiguration.EnableWebMvcConfiguration delegate;
@Nullable
private List<Object> interceptors;
public EnableFunctionalConfiguration(ResourceProperties resourceProperties,
ObjectProvider<WebMvcProperties> mvcPropertiesProvider, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
delegate = new WebMvcAutoConfiguration.EnableWebMvcConfiguration(resourceProperties, mvcPropertiesProvider, mvcRegistrationsProvider, beanFactory);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
delegate.setResourceLoader(resourceLoader);
}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
return delegate.welcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale", matchIfMissing = true)
public LocaleResolver localeResolver() {
return delegate.localeResolver();
}
@Bean
public FormattingConversionService mvcConversionService() {
return delegate.mvcConversionService();
}
@Bean
public Validator mvcValidator() {
return delegate.mvcValidator();
}
@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
return delegate.mvcContentNegotiationManager();
}
@Bean
public UrlPathHelper mvcUrlPathHelper() {
return delegate.mvcUrlPathHelper();
}
@Bean
public PathMatcher mvcPathMatcher() {
return delegate.mvcPathMatcher();
}
@Bean
public RouterFunctionMapping routerFunctionMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
return delegate.routerFunctionMapping(conversionService, resourceUrlProvider);
}
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
return delegate.resourceHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider);
}
@Bean
public ResourceUrlProvider mvcResourceUrlProvider() {
return delegate.mvcResourceUrlProvider();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return delegate.handlerFunctionAdapter();
}
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return delegate.httpRequestHandlerAdapter();
}
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
return delegate.handlerExceptionResolver(contentNegotiationManager);
}
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
return delegate.mvcViewResolver(contentNegotiationManager);
}
@Bean
@Lazy
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return delegate.mvcHandlerMappingIntrospector();
}
@Bean
public ThemeResolver themeResolver() {
return delegate.themeResolver();
}
@Bean
public FlashMapManager flashMapManager() {
return delegate.flashMapManager();
}
@Bean
public RequestToViewNameTranslator viewNameTranslator() {
return delegate.viewNameTranslator();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnEnabledResourceChain
static class ResourceChainCustomizerConfiguration {
@Bean
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
return new ResourceChainResourceHandlerRegistrationCustomizer();
}
}
}
Comment From: sdeleuze
After a team related discussion, we think this is an area where there could be interesting options to explore in the future, but I am going to decline this specific enhancement request for several reasons: - As of Spring Framework 6.1, functional mappings are ordered first, avoiding to pay the price of annotation-based mapping resolution for functional web endpoints, more details on https://github.com/spring-projects/spring-framework/issues/30278. - Just one 👍🏼 in multiple years seems to indicate low traction for the proposed feature - What is asked here would probably have to be part of a wider first class functional story that we don't have yet, so whatever we do here would feel incomplete and could be inconsistent with a wider potential functional story (for exemple that wouldn't be a good fit with approaches like https://github.com/spring-projects-experimental/spring-fu).