From 8ff438678dcc978e7a84f210ae7484cba6d8d16e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 24 Sep 2020 11:25:03 +0200 Subject: [PATCH] DATAREST-1567 - Replace component scan with explicit bean methods. We replaced the component scan in RespositoryRestMvcConfiguration with dedicated bean methods. This allows us to be more explicit in resolving required components, which is required for better support of GraalVM native image. Original pull request: #382. --- .../halexplorer/HalExplorerConfiguration.java | 17 +++ .../webmvc/RestControllerConfiguration.java | 141 ++++++++++++++++++ .../RepositoryRestMvcConfiguration.java | 8 +- .../config/RestControllerImportSelector.java | 48 ++++++ 4 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestControllerConfiguration.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java diff --git a/spring-data-rest-hal-explorer/src/main/java/org/springframework/data/rest/webmvc/halexplorer/HalExplorerConfiguration.java b/spring-data-rest-hal-explorer/src/main/java/org/springframework/data/rest/webmvc/halexplorer/HalExplorerConfiguration.java index 7c9519f52..c99e0ad4d 100644 --- a/spring-data-rest-hal-explorer/src/main/java/org/springframework/data/rest/webmvc/halexplorer/HalExplorerConfiguration.java +++ b/spring-data-rest-hal-explorer/src/main/java/org/springframework/data/rest/webmvc/halexplorer/HalExplorerConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.data.rest.webmvc.halexplorer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.webmvc.config.StaticResourceProvider; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; @@ -23,9 +25,11 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry * {@link StaticResourceProvider} to expose the HAL Browser WebJar content via a static resource route. * * @author Oliver Drotbohm + * @author Christoph Strobl * @since 3.2 * @soundtrack Tedeschi Trucks Band - Signs, High Times (Signs) */ +@Configuration(proxyBeanMethods = false) class HalExplorerConfiguration implements StaticResourceProvider { /* @@ -39,4 +43,17 @@ class HalExplorerConfiguration implements StaticResourceProvider { registry.addResourceHandler(basePath.concat("/**")).addResourceLocations(rootLocation); } + + /** + * The controller that exposes the {@link HalExplorer}. + * + * @return never {@literal null}. + * @since 3.4 + */ + @Bean + HalExplorer halExplorer() { + + // TODO: maybe register this one directly on mvc via WebMvcConfigurer.addRedirectVieController() + return new HalExplorer(); + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestControllerConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestControllerConfiguration.java new file mode 100644 index 000000000..d9c636e5f --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestControllerConfiguration.java @@ -0,0 +1,141 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.repository.support.RepositoryInvokerFactory; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.RepositoryResourceMappings; +import org.springframework.data.rest.webmvc.alps.AlpsController; +import org.springframework.data.rest.webmvc.json.JsonSchema; +import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter; +import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.server.EntityLinks; + +/** + * Configuration class registering required {@link org.springframework.stereotype.Component components} that declare + * request mappings as {@link Bean beans}. + * + * @author Christoph Strobl + * @since 3.4 + */ +@Configuration(proxyBeanMethods = false) +public class RestControllerConfiguration { + + private final RepositoryRestConfiguration restConfiguration; + private final RepositoryResourceMappings resourceMappings; + private final PagedResourcesAssembler resourcesAssembler; + private final Repositories repositories; + + RestControllerConfiguration(RepositoryRestConfiguration restConfiguration, + RepositoryResourceMappings resourceMappings, PagedResourcesAssembler resourcesAssembler, + Repositories repositories) { + + this.restConfiguration = restConfiguration; + this.resourceMappings = resourceMappings; + this.resourcesAssembler = resourcesAssembler; + this.repositories = repositories; + } + + /** + * The controller for the root resource exposing links to the repository resources. + * + * @param entityLinks the accessor to links pointing to controllers backing an entity type. Must not be + * {@literal null}. + * @return never {@literal null}. + */ + @Bean + protected RepositoryController repositoryController(EntityLinks entityLinks) { + return new RepositoryController(resourcesAssembler, repositories, entityLinks, resourceMappings); + } + + /** + * The root controller for entities reachable via {@code /{repository}}. + * + * @param entityLinks the accessor to links pointing to controllers backing an entity type. Must not be * + * {@literal null}. + * @param headersPreparer must not be {@literal null}. + * @return never {@literal null}. + */ + @Bean + protected RepositoryEntityController repositoryEntityController(RepositoryEntityLinks entityLinks, + HttpHeadersPreparer headersPreparer) { + return new RepositoryEntityController(repositories, restConfiguration, entityLinks, resourcesAssembler, + headersPreparer); + } + + /** + * The controller to access referenced properties via {@code /{repository}/{id}/{property}}. + * + * @param repositoryInvokerFactory must not be {@literal null}. + * @return never {@literal null}. + */ + @Bean + protected RepositoryPropertyReferenceController repositoryPropertyReferenceController( + RepositoryInvokerFactory repositoryInvokerFactory) { + return new RepositoryPropertyReferenceController(repositories, repositoryInvokerFactory, resourcesAssembler); + } + + /** + * The controller that performs lookups and executes searches. + * + * @param entityLinks he accessor to links pointing to controllers backing an entity type. Must not be * + * {@literal null}. + * @param headersPreparer must not be {@literal null}. + * @return never {@literal null}. + */ + @Bean + protected RepositorySearchController repositorySearchController(RepositoryEntityLinks entityLinks, + HttpHeadersPreparer headersPreparer) { + return new RepositorySearchController(resourcesAssembler, entityLinks, resourceMappings, headersPreparer); + } + + /** + * The controller that exposes the JSON schema via {@code /repository/schema}. + * + * @param jsonSchemaConverter the converter to create the {@link JsonSchema}. Must not be {@literal null}. + * @return never {@literal null}. + */ + @Bean + protected RepositorySchemaController repositorySchemaController( + PersistentEntityToJsonSchemaConverter jsonSchemaConverter) { + return new RepositorySchemaController(jsonSchemaConverter); + } + + /** + * The controller that exposes semantic documentation in the ALPS (Application Level + * Profile Semantics) format. + * + * @return never {@literal null}. + */ + @Bean + protected AlpsController alpsController() { + return new AlpsController(restConfiguration); + } + + /** + * Profile-based controller exposing multiple forms of metadata via {@code /profile}. + * + * @return never {@literal null}. + */ + @Bean + protected ProfileController profileController() { + return new ProfileController(restConfiguration, resourceMappings, repositories); + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 72356dcfa..40f44d2a4 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -32,8 +32,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; @@ -144,13 +142,13 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; * @author Jon Brisbin * @author Greg Turnquist * @author Mark Paluch + * @author Christoph Strobl */ @Configuration(proxyBeanMethods = false) @EnableHypermediaSupport(type = HypermediaType.HAL) -@ComponentScan(basePackageClasses = RepositoryRestController.class, - includeFilters = @Filter(BasePathAwareController.class), useDefaultFilters = false) @ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml") -@Import({ SpringDataJacksonConfiguration.class, EnableSpringDataWebSupport.QuerydslActivator.class }) +@Import({ RestControllerImportSelector.class, SpringDataJacksonConfiguration.class, + EnableSpringDataWebSupport.QuerydslActivator.class }) public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration implements BeanClassLoaderAware { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java new file mode 100644 index 000000000..a289b9af7 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.config; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.rest.webmvc.RestControllerConfiguration; +import org.springframework.util.ClassUtils; + +/** + * {@link ImportSelector} choosing additional controller configuration (eg. for the {@litearl Hal Explorer}) when + * present. + * + * @author Christoph Strobl + * @since 3.4 + */ +class RestControllerImportSelector implements ImportSelector { + + /* + * (non-Javadoc) + * @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata) + */ + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + + if (ClassUtils.isPresent("org.springframework.data.rest.webmvc.halexplorer.HalExplorerConfiguration", + importingClassMetadata.getClass().getClassLoader())) { + + return new String[] { RestControllerConfiguration.class.getName(), + "org.springframework.data.rest.webmvc.halexplorer.HalExplorerConfiguration" }; + } + + return new String[] { RestControllerConfiguration.class.getName() }; + } +}