Browse Source

Remove HttpServiceClient annotation

Closes gh-35431
pull/35447/head
rstoyanchev 5 months ago
parent
commit
736383e6cb
  1. 40
      framework-docs/modules/ROOT/pages/integration/rest-clients.adoc
  2. 62
      spring-web/src/main/java/org/springframework/web/service/registry/AbstractClientHttpServiceRegistrar.java
  3. 13
      spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java
  4. 54
      spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceClient.java
  5. 3
      spring-web/src/main/java/org/springframework/web/service/registry/ImportHttpServices.java
  6. 102
      spring-web/src/test/java/org/springframework/web/service/registry/ClientHttpServiceRegistrarTests.java
  7. 29
      spring-web/src/test/java/org/springframework/web/service/registry/basic/BasicClient.java
  8. 29
      spring-web/src/test/java/org/springframework/web/service/registry/echo/EchoClientA.java
  9. 29
      spring-web/src/test/java/org/springframework/web/service/registry/echo/EchoClientB.java

40
framework-docs/modules/ROOT/pages/integration/rest-clients.adoc

@ -1195,46 +1195,6 @@ One way to declare HTTP Service groups is via `@ImportHttpServices` annotations @@ -1195,46 +1195,6 @@ One way to declare HTTP Service groups is via `@ImportHttpServices` annotations
<1> Manually list interfaces for group "echo"
<2> Detect interfaces for group "greeting" under a base package
The above lets you declare HTTP Services and groups. As an alternative, you can also
annotate HTTP interfaces as follows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@HttpServiceClient("echo")
public interface EchoServiceA {
// ...
}
@HttpServiceClient("echo")
public interface EchoServiceB {
// ...
}
----
The above requires a dedicated import registrar as follows:
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyClientHttpServiceRegistrar implements AbstractClientHttpServiceRegistrar { // <1>
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
findAndRegisterHttpServiceClients(groupRegistry, List.of("org.example.echo")); // <2>
}
}
@Configuration
@Import(MyClientHttpServiceRegistrar.class) // <3>
public class ClientConfig {
}
----
<1> Extend dedicated `AbstractClientHttpServiceRegistrar`
<2> Specify base packages where to find client interfaces
<3> Import the registrar
TIP: `@HttpServiceClient` interfaces are excluded from `@ImportHttpServices` scans, so there
is no overlap with scans for client interfaces when pointed at the same package.
It is also possible to declare groups programmatically by creating an HTTP Service
registrar and then importing it:

62
spring-web/src/main/java/org/springframework/web/service/registry/AbstractClientHttpServiceRegistrar.java

@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
/*
* Copyright 2002-present 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.util.List;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
/**
* Base class for an HTTP Service registrar that detects
* {@link HttpServiceClient @HttpServiceClient} annotated interfaces and
* registers them.
*
* <p>Subclasses need to implement
* {@link #registerHttpServices(GroupRegistry, AnnotationMetadata)} and invoke
* {@link #findAndRegisterHttpServiceClients(GroupRegistry, List)} with the
* list of base packages to scan.
*
* @author Rossen Stoyanchev
* @since 7.0
*/
public abstract class AbstractClientHttpServiceRegistrar extends AbstractHttpServiceRegistrar {
/**
* Find all HTTP Services under the given base packages that also have an
* {@link HttpServiceClient @HttpServiceClient} annotation, and register them
* in the group specified on the annotation.
* @param registry the registry from {@link #registerHttpServices(GroupRegistry, AnnotationMetadata)}
* @param basePackages the base packages to scan
*/
protected void findAndRegisterHttpServiceClients(GroupRegistry registry, List<String> basePackages) {
basePackages.stream()
.flatMap(this::findHttpServices)
.filter(definition -> definition instanceof AnnotatedBeanDefinition)
.map(definition -> (AnnotatedBeanDefinition) definition)
.filter(definition -> definition.getMetadata().hasAnnotation(HttpServiceClient.class.getName()))
.filter(definition -> definition.getBeanClassName() != null)
.forEach(definition -> {
MergedAnnotations annotations = definition.getMetadata().getAnnotations();
String group = annotations.get(HttpServiceClient.class).getString("group");
registry.forGroup(group).registerTypeNames(definition.getBeanClassName());
});
}
}

13
spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java

@ -211,7 +211,7 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -211,7 +211,7 @@ public abstract class AbstractHttpServiceRegistrar implements
* @param basePackage the names of packages to look under
* @return match bean definitions
*/
protected Stream<BeanDefinition> findHttpServices(String basePackage) {
private Stream<BeanDefinition> findHttpServices(String basePackage) {
if (this.scanner == null) {
Assert.state(this.environment != null, "Environment has not been set");
Assert.state(this.resourceLoader != null, "ResourceLoader has not been set");
@ -267,9 +267,6 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -267,9 +267,6 @@ public abstract class AbstractHttpServiceRegistrar implements
/**
* Detect HTTP Service types in the given packages, looking for
* interfaces with type or method {@link HttpExchange} annotations.
* <p>The performed scan, however, filters out any interfaces
* annotated with {@link HttpServiceClient} that are instead supported
* by {@link AbstractClientHttpServiceRegistrar}.
*/
GroupSpec detectInBasePackages(Class<?>... packageClasses);
@ -326,19 +323,11 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -326,19 +323,11 @@ public abstract class AbstractHttpServiceRegistrar implements
private void detectInBasePackage(String packageName) {
findHttpServices(packageName)
.filter(DefaultGroupSpec::isNotHttpServiceClientAnnotated)
.map(BeanDefinition::getBeanClassName)
.filter(Objects::nonNull)
.forEach(this::registerServiceTypeName);
}
private static boolean isNotHttpServiceClientAnnotated(BeanDefinition defintion) {
if (defintion instanceof AnnotatedBeanDefinition abd) {
return !abd.getMetadata().hasAnnotation(HttpServiceClient.class.getName());
}
return true;
}
private void registerServiceTypeName(String httpServiceTypeName) {
this.registration.httpServiceTypeNames().add(httpServiceTypeName);
}

54
spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceClient.java

@ -1,54 +0,0 @@ @@ -1,54 +0,0 @@
/*
* Copyright 2002-present 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.core.annotation.AliasFor;
/**
* Annotation to mark an HTTP Service interface as a candidate client proxy creation.
* Supported through the import of an {@link AbstractClientHttpServiceRegistrar}.
*
* @author Rossen Stoyanchev
* @since 7.0
* @see AbstractClientHttpServiceRegistrar
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpServiceClient {
/**
* An alias for {@link #group()}.
*/
@AliasFor("group")
String value() default HttpServiceGroup.DEFAULT_GROUP_NAME;
/**
* The name of the HTTP Service group for this client.
* <p>By default, this is {@link HttpServiceGroup#DEFAULT_GROUP_NAME}.
*/
@AliasFor("value")
String group() default HttpServiceGroup.DEFAULT_GROUP_NAME;
}

3
spring-web/src/main/java/org/springframework/web/service/registry/ImportHttpServices.java

@ -78,9 +78,6 @@ public @interface ImportHttpServices { @@ -78,9 +78,6 @@ public @interface ImportHttpServices {
/**
* Detect HTTP Services in the packages of the specified classes, looking
* for interfaces with type or method {@link HttpExchange} annotations.
* <p>The performed scan, however, filters out interfaces annotated with
* {@link HttpServiceClient} that are instead supported by
* {@link AbstractClientHttpServiceRegistrar}.
*/
Class<?>[] basePackageClasses() default {};

102
spring-web/src/test/java/org/springframework/web/service/registry/ClientHttpServiceRegistrarTests.java

@ -1,102 +0,0 @@ @@ -1,102 +0,0 @@
/*
* Copyright 2002-present 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.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.service.registry.basic.BasicClient;
import org.springframework.web.service.registry.echo.EchoClientA;
import org.springframework.web.service.registry.echo.EchoClientB;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link AbstractClientHttpServiceRegistrar}.
*
* @author Rossen Stoyanchev
*/
public class ClientHttpServiceRegistrarTests {
private final TestGroupRegistry groupRegistry = new TestGroupRegistry();
@Test
void register() {
List<String> basePackages = List.of(
BasicClient.class.getPackageName(), EchoClientA.class.getPackageName());
AbstractClientHttpServiceRegistrar registrar = new AbstractClientHttpServiceRegistrar() {
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata importingClassMetadata) {
findAndRegisterHttpServiceClients(groupRegistry, basePackages);
}
};
registrar.setEnvironment(new StandardEnvironment());
registrar.setResourceLoader(new PathMatchingResourcePatternResolver());
registrar.registerHttpServices(groupRegistry, mock(AnnotationMetadata.class));
assertGroups(
TestGroup.ofListing("default", BasicClient.class),
TestGroup.ofListing("echo", EchoClientA.class, EchoClientB.class));
}
@Test
void registerWhenNoClientsDoesNotCreateBeans() {
try (AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(NoOpImportConfig.class)) {
assertThat(cxt.getBeanNamesForType(HttpServiceProxyRegistry.class)).isEmpty();
}
}
private void assertGroups(TestGroup... expectedGroups) {
Map<String, TestGroup> groupMap = this.groupRegistry.groupMap();
assertThat(groupMap.size()).isEqualTo(expectedGroups.length);
for (TestGroup expected : expectedGroups) {
TestGroup actual = groupMap.get(expected.name());
assertThat(actual.httpServiceTypes()).isEqualTo(expected.httpServiceTypes());
assertThat(actual.clientType()).isEqualTo(expected.clientType());
assertThat(actual.packageNames()).isEqualTo(expected.packageNames());
assertThat(actual.packageClasses()).isEqualTo(expected.packageClasses());
}
}
@Configuration(proxyBeanMethods = false)
@Import(NoOpRegistrar.class)
static class NoOpImportConfig {
}
static class NoOpRegistrar extends AbstractClientHttpServiceRegistrar {
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
}
}
}

29
spring-web/src/test/java/org/springframework/web/service/registry/basic/BasicClient.java

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
/*
* Copyright 2002-present 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.basic;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.registry.HttpServiceClient;
@HttpServiceClient
public interface BasicClient {
@GetExchange
String handle(@RequestParam String input);
}

29
spring-web/src/test/java/org/springframework/web/service/registry/echo/EchoClientA.java

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
/*
* Copyright 2002-present 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.echo;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.registry.HttpServiceClient;
@HttpServiceClient("echo")
public interface EchoClientA {
@GetExchange
String handle(@RequestParam String input);
}

29
spring-web/src/test/java/org/springframework/web/service/registry/echo/EchoClientB.java

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
/*
* Copyright 2002-present 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.echo;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.registry.HttpServiceClient;
@HttpServiceClient("echo")
public interface EchoClientB {
@GetExchange
String handle(@RequestParam String input);
}
Loading…
Cancel
Save