Make container annotation for ImportHttpServices nested

See gh-33992
This commit is contained in:
rstoyanchev
2025-03-31 18:29:14 +01:00
parent a122dda596
commit 10e32c92e6
6 changed files with 35 additions and 99 deletions

View File

@@ -32,28 +32,23 @@ import org.springframework.core.type.AnnotationMetadata;
class AnnotationHttpServiceRegistrar extends AbstractHttpServiceRegistrar {
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata importMetadata) {
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
MergedAnnotation<?> groupsAnnot = importMetadata.getAnnotations().get(ImportHttpServiceGroups.class);
MergedAnnotation<?> groupsAnnot = metadata.getAnnotations().get(ImportHttpServices.Container.class);
if (groupsAnnot.isPresent()) {
HttpServiceGroup.ClientType clientType = groupsAnnot.getEnum("clientType", HttpServiceGroup.ClientType.class);
for (MergedAnnotation<?> annot : groupsAnnot.getAnnotationArray("value", ImportHttpServices.class)) {
processImportAnnotation(annot, registry, clientType);
processImportAnnotation(annot, registry);
}
}
importMetadata.getAnnotations().stream(ImportHttpServices.class).forEach(annot ->
processImportAnnotation(annot, registry, HttpServiceGroup.ClientType.UNSPECIFIED));
metadata.getAnnotations().stream(ImportHttpServices.class)
.forEach(annot -> processImportAnnotation(annot, registry));
}
private void processImportAnnotation(
MergedAnnotation<?> annotation, GroupRegistry groupRegistry,
HttpServiceGroup.ClientType containerClientType) {
private void processImportAnnotation(MergedAnnotation<?> annotation, GroupRegistry groupRegistry) {
String groupName = annotation.getString("group");
HttpServiceGroup.ClientType clientType = annotation.getEnum("clientType", HttpServiceGroup.ClientType.class);
clientType = (clientType != HttpServiceGroup.ClientType.UNSPECIFIED ? clientType : containerClientType);
groupRegistry.forGroup(groupName, clientType)
.register(annotation.getClassArray("types"))

View File

@@ -70,7 +70,6 @@ public interface HttpServiceGroup {
/**
* Not specified, falling back on a default.
* @see ImportHttpServices#clientType()
* @see ImportHttpServiceGroups#clientType()
* @see AbstractHttpServiceRegistrar#setDefaultClientType
*/
UNSPECIFIED;

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2002-2025 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.web.service.registry;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
/**
* Container annotation for the {@link ImportHttpServices} repeatable annotation.
* Typically not necessary to use as {@code @ImportHttpServices} annotations can
* be declared one after another without a wrapper, but the container annotation
* may be used to set the {@link #clientType()} and that would be inherited by
* all nested annotations.
*
* @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @since 7.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AnnotationHttpServiceRegistrar.class)
public @interface ImportHttpServiceGroups {
/**
* Alias for {@link #groups()}.
*/
@AliasFor("groups")
ImportHttpServices[] value() default {};
/**
* Nested annotations that declare HTTP Services by group.
*/
@AliasFor("value")
ImportHttpServices[] groups() default {};
/**
* Specify the type of client to use for nested {@link ImportHttpServices}
* annotations that don't specify it.
* <p>By default, this is {@link HttpServiceGroup.ClientType#UNSPECIFIED}
* in which case {@code RestClient} is used, but this default can be reset
* via {@link AbstractHttpServiceRegistrar#setDefaultClientType}.
*/
HttpServiceGroup.ClientType clientType() default HttpServiceGroup.ClientType.UNSPECIFIED;
}

View File

@@ -46,12 +46,12 @@ import org.springframework.web.service.annotation.HttpExchange;
* @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @since 7.0
* @see ImportHttpServiceGroups
* @see Container
* @see AbstractHttpServiceRegistrar
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(ImportHttpServiceGroups.class)
@Repeatable(ImportHttpServices.Container.class)
@Import(AnnotationHttpServiceRegistrar.class)
@Documented
public @interface ImportHttpServices {
@@ -96,4 +96,19 @@ public @interface ImportHttpServices {
*/
HttpServiceGroup.ClientType clientType() default HttpServiceGroup.ClientType.UNSPECIFIED;
/**
* Container annotation that is necessary for the repeatable
* {@link ImportHttpServices} annotation, but does not need to be declared
* in application code.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AnnotationHttpServiceRegistrar.class)
@interface Container {
ImportHttpServices[] value() default {};
}
}

View File

@@ -63,8 +63,8 @@ public class AnnotationHttpServiceRegistrarTests {
}
@Test
void containerWithClientType() {
doRegister(ContainerConfig.class);
void clientType() {
doRegister(ClientTypeConfig.class);
assertGroups(
StubGroup.ofListing(ECHO_GROUP, ClientType.WEB_CLIENT, EchoA.class),
StubGroup.ofListing(GREETING_GROUP, ClientType.WEB_CLIENT, GreetingA.class));
@@ -97,19 +97,17 @@ public class AnnotationHttpServiceRegistrarTests {
private static class ScanConfig {
}
@ImportHttpServiceGroups(clientType = ClientType.WEB_CLIENT, groups = {
@ImportHttpServices(group = ECHO_GROUP, types = {EchoA.class}),
@ImportHttpServices(group = GREETING_GROUP, types = {GreetingA.class})
})
private static class ContainerConfig {
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = ECHO_GROUP, types = {EchoA.class})
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = GREETING_GROUP, types = {GreetingA.class})
private static class ClientTypeConfig {
}
private static class TestAnnotationHttpServiceRegistrar extends AnnotationHttpServiceRegistrar {
@Override
public void registerHttpServices(GroupRegistry registry, AnnotationMetadata importMetadata) {
super.registerHttpServices(registry, importMetadata);
public void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
super.registerHttpServices(registry, metadata);
}
}

View File

@@ -37,9 +37,8 @@ import org.springframework.web.reactive.function.client.support.greeting.Greetin
import org.springframework.web.reactive.function.client.support.greeting.GreetingB;
import org.springframework.web.service.registry.AbstractHttpServiceRegistrar;
import org.springframework.web.service.registry.HttpServiceGroup.ClientType;
import org.springframework.web.service.registry.ImportHttpServices;
import org.springframework.web.service.registry.ImportHttpServiceGroups;
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
import org.springframework.web.service.registry.ImportHttpServices;
import static org.assertj.core.api.Assertions.assertThat;
@@ -126,19 +125,15 @@ public class WebClientProxyRegistryIntegrationTests {
@Configuration(proxyBeanMethods = false)
@ImportHttpServiceGroups(clientType = ClientType.WEB_CLIENT, groups = {
@ImportHttpServices(group = "echo", types = {EchoA.class, EchoB.class}),
@ImportHttpServices(group = "greeting", types = {GreetingA.class, GreetingB.class})
})
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = "echo", types = {EchoA.class, EchoB.class})
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = "greeting", types = {GreetingA.class, GreetingB.class})
private static class ListingConfig extends BaseEchoConfig {
}
@Configuration(proxyBeanMethods = false)
@ImportHttpServiceGroups(clientType = ClientType.WEB_CLIENT, groups = {
@ImportHttpServices(group = "echo", basePackageClasses = EchoA.class),
@ImportHttpServices(group = "greeting", basePackageClasses = GreetingA.class)
})
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = "echo", basePackageClasses = EchoA.class)
@ImportHttpServices(clientType = ClientType.WEB_CLIENT, group = "greeting", basePackageClasses = GreetingA.class)
private static class DetectConfig extends BaseEchoConfig {
}