diff --git a/spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java b/spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java index 570a2d3cd995..dc3583fab96a 100644 --- a/spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java +++ b/spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Objects; +import java.util.function.Function; import org.jspecify.annotations.Nullable; @@ -44,6 +45,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.registry.HttpServiceGroup.ClientType; /** * Abstract registrar class that imports: @@ -226,9 +228,21 @@ default GroupSpec forGroup(String name) { } /** - * Variant of {@link #forGroup(String)} with a client type. + * Perform HTTP Service registrations for the given group and client type. */ - GroupSpec forGroup(String name, HttpServiceGroup.ClientType clientType); + default GroupSpec forGroup(String name, HttpServiceGroup.ClientType clientType) { + return forGroup(serviceType -> name, serviceType -> clientType); + } + + /** + * Perform HTTP Service registrations for the given group and client type. + * @param nameProvider a function that will provide the name given a service type + * or return {@code null} if the HTTP service should not be registered + * @param clientTypeProvider a function that will provide the client type given a + * service type + */ + GroupSpec forGroup(Function, @Nullable String> nameProvider, + Function, HttpServiceGroup.ClientType> clientTypeProvider); /** * Perform HTTP Service registrations for the @@ -269,22 +283,28 @@ interface GroupSpec { private class DefaultGroupRegistry implements GroupRegistry { @Override - public GroupSpec forGroup(String name, HttpServiceGroup.ClientType clientType) { - return new DefaultGroupSpec(name, clientType); + public GroupSpec forGroup(Function, @Nullable String> nameProvider, + Function, ClientType> clientTypeProvider) { + + return new DefaultGroupSpec(nameProvider, clientTypeProvider); } private class DefaultGroupSpec implements GroupSpec { - private final GroupsMetadata.Registration registration; + private final Function, @Nullable String> groupNameProvider; + + private final Function, ClientType> clientTypeProvider; - DefaultGroupSpec(String groupName, HttpServiceGroup.ClientType clientType) { - clientType = (clientType != HttpServiceGroup.ClientType.UNSPECIFIED ? clientType : defaultClientType); - this.registration = groupsMetadata.getOrCreateGroup(groupName, clientType); + DefaultGroupSpec(Function, @Nullable String> groupNameProvider, + Function, ClientType> clientTypeProvider) { + + this.groupNameProvider = groupNameProvider; + this.clientTypeProvider = clientTypeProvider; } @Override public GroupRegistry.GroupSpec register(Class... serviceTypes) { - Arrays.stream(serviceTypes).map(Class::getName).forEach(this::registerServiceTypeName); + Arrays.stream(serviceTypes).forEach(this::registerServiceType); return this; } @@ -304,11 +324,19 @@ private void detectInBasePackage(String packageName) { getScanner().findCandidateComponents(packageName).stream() .map(BeanDefinition::getBeanClassName) .filter(Objects::nonNull) - .forEach(this::registerServiceTypeName); + .map(serviceTypeName -> ClassUtils.resolveClassName(serviceTypeName, beanClassLoader)) + .forEach(this::registerServiceType); } - private void registerServiceTypeName(String httpServiceTypeName) { - this.registration.httpServiceTypeNames().add(httpServiceTypeName); + private void registerServiceType(Class httpServiceType) { + String groupName = this.groupNameProvider.apply(httpServiceType); + if (groupName != null) { + ClientType clientType = this.clientTypeProvider.apply(httpServiceType); + clientType = (clientType != HttpServiceGroup.ClientType.UNSPECIFIED ? clientType : defaultClientType); + groupsMetadata.getOrCreateGroup(groupName, clientType) + .httpServiceTypeNames() + .add(httpServiceType.getName()); + } } } } diff --git a/spring-web/src/test/java/org/springframework/web/service/registry/AnnotationHttpServiceRegistrarTests.java b/spring-web/src/test/java/org/springframework/web/service/registry/AnnotationHttpServiceRegistrarTests.java index 84188de38042..1bb49a8c5038 100644 --- a/spring-web/src/test/java/org/springframework/web/service/registry/AnnotationHttpServiceRegistrarTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/registry/AnnotationHttpServiceRegistrarTests.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.Function; import org.junit.jupiter.api.Test; @@ -179,13 +180,14 @@ public Map groupMap() { } @Override - public GroupSpec forGroup(String name, ClientType clientType) { - return new TestGroupSpec(this.groupMap, name, clientType); + public GroupSpec forGroup(Function, String> nameProvider, + Function, ClientType> clientTypeProvider) { + return new TestGroupSpec(this.groupMap, nameProvider, clientTypeProvider); } - private record TestGroupSpec(Map groupMap, String groupName, - ClientType clientType) implements GroupSpec { + private record TestGroupSpec(Map groupMap, Function, String> groupNameProvider, + Function, ClientType> clientTypeProvider) implements GroupSpec { @Override public GroupSpec register(Class... serviceTypes) { @@ -206,7 +208,9 @@ public GroupSpec detectInBasePackages(String... packageNames) { } private StubGroup getOrCreateGroup() { - return this.groupMap.computeIfAbsent(this.groupName, name -> new StubGroup(name, this.clientType)); + String groupName = this.groupNameProvider.apply(Object.class); + ClientType clientType = this.clientTypeProvider.apply(Object.class); + return this.groupMap.computeIfAbsent(groupName, name -> new StubGroup(name, clientType)); } } } diff --git a/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceRegistrarTests.java b/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceRegistrarTests.java index 80b34af8105e..0c6266dbea60 100644 --- a/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceRegistrarTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceRegistrarTests.java @@ -70,6 +70,33 @@ void basicScan() { assertBeanDefinitionCount(3); } + @Test + void scanWithProviders() { + doRegister(registry -> registry + .forGroup(type -> type.getName().substring(type.getName().length() - 1), + type -> type.getName().endsWith("B") ? ClientType.REST_CLIENT : ClientType.UNSPECIFIED) + .detectInBasePackages(EchoA.class)); + assertRegistryBeanDef(new TestGroup("A", EchoA.class), new TestGroup("B", EchoB.class)); + assertProxyBeanDef("A", EchoA.class); + assertProxyBeanDef("B", EchoB.class); + assertBeanDefinitionCount(3); + GroupsMetadata groupsMetadata = groupsMetadata(); + assertThat(getRegistration(groupsMetadata, "A").clientType()).isEqualTo(ClientType.UNSPECIFIED); + assertThat(getRegistration(groupsMetadata, "B").clientType()).isEqualTo(ClientType.REST_CLIENT); + } + + @Test + void scanWithProvidersWhenProviderReturnsNull() { + doRegister(registry -> registry + .forGroup(type -> type.getName().endsWith("A") ? null : ECHO_GROUP, type -> ClientType.UNSPECIFIED) + .detectInBasePackages(EchoA.class)); + assertRegistryBeanDef(new TestGroup(ECHO_GROUP, EchoB.class)); + } + + private GroupsMetadata.Registration getRegistration(GroupsMetadata groupsMetadata, String name) { + return groupsMetadata.registrations().filter(candidate -> candidate.name().equals(name)).findFirst().get(); + } + @Test void merge() { doRegister( @@ -149,6 +176,13 @@ private void assertRegistryBeanDef(HttpServiceGroup... expectedGroups) { } private Map groupMap() { + GroupsMetadata metadata = groupsMetadata(); + assertThat(metadata).isNotNull(); + return metadata.groups(null).stream() + .collect(Collectors.toMap(HttpServiceGroup::name, Function.identity())); + } + + private GroupsMetadata groupsMetadata() { BeanDefinition beanDef = this.beanDefRegistry.getBeanDefinition(AbstractHttpServiceRegistrar.HTTP_SERVICE_PROXY_REGISTRY_BEAN_NAME); assertThat(beanDef.getBeanClassName()).isEqualTo(HttpServiceProxyRegistryFactoryBean.class.getName()); @@ -156,11 +190,7 @@ private Map groupMap() { ConstructorArgumentValues.ValueHolder valueHolder = args.getArgumentValue(0, Map.class); assertThat(valueHolder).isNotNull(); - GroupsMetadata metadata = (GroupsMetadata) valueHolder.getValue(); - assertThat(metadata).isNotNull(); - - return metadata.groups(null).stream() - .collect(Collectors.toMap(HttpServiceGroup::name, Function.identity())); + return (GroupsMetadata) valueHolder.getValue(); } private void assertProxyBeanDef(String group, Class httpServiceType) {