Browse Source

Add AOT and native image support (#794)

pull/798/head
Olga Maciaszek-Sharma 2 years ago committed by GitHub
parent
commit
7f66cf3829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      docs/src/main/asciidoc/spring-cloud-openfeign.adoc
  2. 2
      pom.xml
  3. 5
      spring-cloud-openfeign-core/pom.xml
  4. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultTargeter.java
  5. 38
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java
  6. 10
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java
  7. 26
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java
  8. 55
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
  9. 30
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientSpecification.java
  10. 69
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java
  11. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/Targeter.java
  12. 126
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java
  13. 182
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java
  14. 6
      spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  15. 2
      spring-cloud-openfeign-core/src/main/resources/META-INF/spring/aot.factories
  16. 169
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EagerInitFeignClientUsingConfigurerTests.java
  17. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java
  18. 8
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java
  19. 3
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java
  20. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java
  21. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java
  22. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java
  23. 38
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java
  24. 19
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTests.java
  25. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java
  26. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java
  27. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientWithRefreshableOptionsTest.java
  28. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignCompressionTests.java
  29. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java
  30. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlWithRetryableLoadBalancerTests.java
  31. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/GzipDecodingTests.java
  32. 10
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/LazyInitFeignClientUsingConfigurerTests.java
  33. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java
  34. 171
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignAotTests.java
  35. 3
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/AsyncCircuitBreakerTests.java
  36. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java
  37. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoderTests.java
  38. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java

20
docs/src/main/asciidoc/spring-cloud-openfeign.adoc

@ -78,6 +78,15 @@ TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-class @@ -78,6 +78,15 @@ TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-class
or list them explicitly:
`@EnableFeignClients(clients = InventoryServiceFeignClient.class)`
[[attribute-resolution-mode]]
==== Attribute resolution mode
While creating `Feign` client beans, we resolve the values passed via the `@FeignClient` annotation. As of `4.x`, the values are being resolved eagerly. This is a good solution for most use-cases, and it also allows for AOT support.
If you need the attributes to be resolved lazily, set the `spring.cloud.openfeign.lazy-attributes-resolution` property value to `true`.
TIP: For Spring Cloud Contract test integration, lazy attribute resolution should be used.
[[spring-cloud-feign-overriding-defaults]]
=== Overriding Feign Defaults
@ -905,6 +914,17 @@ The URL provided in the configuration properties remains unused. @@ -905,6 +914,17 @@ The URL provided in the configuration properties remains unused.
|===
=== AOT and Native Image Support
Spring Cloud OpenFeign supports Spring AOT transformations and native images, however, only with refresh mode disabled, Feign clients refresh disabled (default setting) and <<attribute-resolution-mode,lazy `@FeignClient` attribute resolution>> disabled (default setting).
WARNING: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, make sure to set `spring.cloud.refresh.enabled` to `false`.
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.client.refresh-enabled` has not been set to `true`.
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.lazy-attributes-resolution` has not been set to `true`.
== Configuration properties
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page].

2
pom.xml

@ -228,7 +228,7 @@ @@ -228,7 +228,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Sets the VM argument line used when unit tests are run. -->
<argLine>${surefireArgLine}</argLine>
<argLine>${surefireArgLine} --add-opens=java.base/java.net=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>

5
spring-cloud-openfeign-core/pom.xml

@ -191,6 +191,11 @@ @@ -191,6 +191,11 @@
<version>2.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>

2
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultTargeter.java

@ -25,7 +25,7 @@ import feign.Target; @@ -25,7 +25,7 @@ import feign.Target;
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}

38
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java

@ -44,6 +44,10 @@ import okhttp3.ConnectionPool; @@ -44,6 +44,10 @@ import okhttp3.ConnectionPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -55,6 +59,8 @@ import org.springframework.cache.interceptor.CacheInterceptor; @@ -55,6 +59,8 @@ import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.aot.FeignChildContextInitializer;
import org.springframework.cloud.openfeign.aot.FeignClientBeanFactoryInitializationAotProcessor;
import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
@ -64,12 +70,14 @@ import org.springframework.context.annotation.Bean; @@ -64,12 +70,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.util.ClassUtils;
/**
* @author Spencer Gibb
@ -102,12 +110,24 @@ public class FeignAutoConfiguration { @@ -102,12 +110,24 @@ public class FeignAutoConfiguration {
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
public FeignClientFactory feignContext() {
FeignClientFactory context = new FeignClientFactory();
context.setConfigurations(this.configurations);
return context;
}
@Bean
static FeignChildContextInitializer feignChildContextInitializer(GenericApplicationContext parentContext,
FeignClientFactory feignClientFactory) {
return new FeignChildContextInitializer(parentContext, feignClientFactory);
}
@Bean
static FeignClientBeanFactoryInitializationAotProcessor feignClientBeanFactoryInitializationCodeGenerator(
GenericApplicationContext applicationContext, FeignClientFactory feignClientFactory) {
return new FeignClientBeanFactoryInitializationAotProcessor(applicationContext, feignClientFactory);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.openfeign.cache.enabled", matchIfMissing = true)
@ConditionalOnBean(CacheInterceptor.class)
@ -358,3 +378,17 @@ public class FeignAutoConfiguration { @@ -358,3 +378,17 @@ public class FeignAutoConfiguration {
}
}
class FeignHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (!ClassUtils.isPresent("feign.Feign", classLoader)) {
return;
}
hints.reflection().registerType(TypeReference.of(FeignClientFactoryBean.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
}
}

10
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java

@ -39,7 +39,7 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -39,7 +39,7 @@ class FeignCircuitBreakerTargeter implements Targeter {
}
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {
return feign.target(target);
@ -56,20 +56,20 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -56,20 +56,20 @@ class FeignCircuitBreakerTargeter implements Targeter {
return builder(name, builder).target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
private <T> T targetWithFallbackFactory(String feignClientName, FeignClientFactory context,
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",
feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder(feignClientName, builder).target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
FeignCircuitBreaker.Builder builder, Class<?> fallback) {
private <T> T targetWithFallback(String feignClientName, FeignClientFactory context,
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder(feignClientName, builder).target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignClientFactory context,
Class<?> beanType, Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {

26
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignContext.java → spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java

@ -16,11 +16,14 @@ @@ -16,11 +16,14 @@
package org.springframework.cloud.openfeign;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.lang.Nullable;
/**
@ -31,11 +34,18 @@ import org.springframework.lang.Nullable; @@ -31,11 +34,18 @@ import org.springframework.lang.Nullable;
* @author Dave Syer
* @author Matt King
* @author Jasbir Singh
* @author Olga Maciaszek-Sharma
*/
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public class FeignClientFactory extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name");
public FeignClientFactory() {
this(new HashMap<>());
}
public FeignClientFactory(
Map<String, ApplicationContextInitializer<GenericApplicationContext>> applicationContextInitializers) {
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name",
applicationContextInitializers);
}
@Nullable
@ -57,4 +67,14 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification> @@ -57,4 +67,14 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification>
return getContext(contextName).getBean(beanName, type);
}
@SuppressWarnings("unchecked")
public FeignClientFactory withApplicationContextInitializers(Map<String, Object> applicationContextInitializers) {
Map<String, ApplicationContextInitializer<GenericApplicationContext>> convertedInitializers = new HashMap<>();
applicationContextInitializers.keySet()
.forEach(contextId -> convertedInitializers.put(contextId,
(ApplicationContextInitializer<GenericApplicationContext>) applicationContextInitializers
.get(contextId)));
return new FeignClientFactory(convertedInitializers);
}
}

55
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java

@ -113,13 +113,22 @@ public class FeignClientFactoryBean @@ -113,13 +113,22 @@ public class FeignClientFactoryBean
private final List<FeignBuilderCustomizer> additionalCustomizers = new ArrayList<>();
private String[] qualifiers = new String[] {};
// For AOT testing
public FeignClientFactoryBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating a FeignClientFactoryBean.");
}
}
@Override
public void afterPropertiesSet() {
Assert.hasText(contextId, "Context id must be set");
Assert.hasText(name, "Name must be set");
}
protected Feign.Builder feign(FeignContext context) {
protected Feign.Builder feign(FeignClientFactory context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
@ -137,7 +146,7 @@ public class FeignClientFactoryBean @@ -137,7 +146,7 @@ public class FeignClientFactoryBean
return builder;
}
private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
private void applyBuildCustomizers(FeignClientFactory context, Feign.Builder builder) {
Map<String, FeignBuilderCustomizer> customizerMap = context.getInstances(contextId,
FeignBuilderCustomizer.class);
@ -148,7 +157,7 @@ public class FeignClientFactoryBean @@ -148,7 +157,7 @@ public class FeignClientFactoryBean
additionalCustomizers.forEach(customizer -> customizer.customize(builder));
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
protected void configureFeign(FeignClientFactory context, Feign.Builder builder) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
@ -172,7 +181,7 @@ public class FeignClientFactoryBean @@ -172,7 +181,7 @@ public class FeignClientFactoryBean
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
protected void configureUsingConfiguration(FeignClientFactory context, Feign.Builder builder) {
Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
@ -340,7 +349,7 @@ public class FeignClientFactoryBean @@ -340,7 +349,7 @@ public class FeignClientFactoryBean
}
}
protected <T> T get(FeignContext context, Class<T> type) {
protected <T> T get(FeignClientFactory context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
@ -348,11 +357,11 @@ public class FeignClientFactoryBean @@ -348,11 +357,11 @@ public class FeignClientFactoryBean
return instance;
}
protected <T> T getOptional(FeignContext context, Class<T> type) {
protected <T> T getOptional(FeignClientFactory context, Class<T> type) {
return context.getInstance(contextId, type);
}
protected <T> T getInheritedAwareOptional(FeignContext context, Class<T> type) {
protected <T> T getInheritedAwareOptional(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
return getOptional(context, type);
}
@ -361,7 +370,7 @@ public class FeignClientFactoryBean @@ -361,7 +370,7 @@ public class FeignClientFactoryBean
}
}
protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context, Class<T> type) {
protected <T> Map<String, T> getInheritedAwareInstances(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
return context.getInstances(contextId, type);
}
@ -370,7 +379,7 @@ public class FeignClientFactoryBean @@ -370,7 +379,7 @@ public class FeignClientFactoryBean
}
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
protected <T> T loadBalance(Feign.Builder builder, FeignClientFactory context, HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
@ -389,7 +398,7 @@ public class FeignClientFactoryBean @@ -389,7 +398,7 @@ public class FeignClientFactoryBean
* @param contextId name of feign client
* @return returns Options found in context
*/
protected Request.Options getOptionsByName(FeignContext context, String contextId) {
protected Request.Options getOptionsByName(FeignClientFactory context, String contextId) {
if (refreshableClient) {
return context.getInstance(contextId, Request.Options.class.getCanonicalName() + "-" + contextId,
Request.Options.class);
@ -409,9 +418,9 @@ public class FeignClientFactoryBean @@ -409,9 +418,9 @@ public class FeignClientFactoryBean
*/
@SuppressWarnings("unchecked")
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
: applicationContext.getBean(FeignClientFactory.class);
Feign.Builder builder = feign(feignClientFactory);
if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
if (LOG.isInfoEnabled()) {
@ -424,13 +433,13 @@ public class FeignClientFactoryBean @@ -424,13 +433,13 @@ public class FeignClientFactoryBean
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
Client client = getOptional(feignClientFactory, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
@ -445,10 +454,10 @@ public class FeignClientFactoryBean @@ -445,10 +454,10 @@ public class FeignClientFactoryBean
builder.client(client);
}
applyBuildCustomizers(context, builder);
applyBuildCustomizers(feignClientFactory, builder);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, resolveTarget(context, contextId, url));
Targeter targeter = get(feignClientFactory, Targeter.class);
return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
}
private String cleanPath() {
@ -468,7 +477,7 @@ public class FeignClientFactoryBean @@ -468,7 +477,7 @@ public class FeignClientFactoryBean
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> HardCodedTarget<T> resolveTarget(FeignContext context, String contextId, String url) {
private <T> HardCodedTarget<T> resolveTarget(FeignClientFactory context, String contextId, String url) {
if (StringUtils.hasText(url)) {
return new HardCodedTarget(type, name, url);
}
@ -600,6 +609,14 @@ public class FeignClientFactoryBean @@ -600,6 +609,14 @@ public class FeignClientFactoryBean
this.refreshableClient = refreshableClient;
}
public String[] getQualifiers() {
return qualifiers;
}
public void setQualifiers(String[] qualifiers) {
this.qualifiers = qualifiers;
}
@Override
public boolean equals(Object o) {
if (this == o) {

30
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientSpecification.java

@ -24,18 +24,22 @@ import org.springframework.cloud.context.named.NamedContextFactory; @@ -24,18 +24,22 @@ import org.springframework.cloud.context.named.NamedContextFactory;
/**
* @author Dave Syer
* @author Gregor Zurowski
* @author Olga Maciaszek-Sharma
*/
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private String className;
private Class<?>[] configuration;
FeignClientSpecification() {
public FeignClientSpecification() {
}
public FeignClientSpecification(String name, Class<?>[] configuration) {
public FeignClientSpecification(String name, String className, Class<?>[] configuration) {
this.name = name;
this.className = className;
this.configuration = configuration;
}
@ -47,6 +51,14 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati @@ -47,6 +51,14 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Class<?>[] getConfiguration() {
return this.configuration;
}
@ -60,22 +72,24 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati @@ -60,22 +72,24 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof FeignClientSpecification that)) {
return false;
}
FeignClientSpecification that = (FeignClientSpecification) o;
return Objects.equals(name, that.name) && Arrays.equals(configuration, that.configuration);
return Objects.equals(name, that.name) && Objects.equals(className, that.className)
&& Arrays.equals(configuration, that.configuration);
}
@Override
public int hashCode() {
return Objects.hash(name, Arrays.hashCode(configuration));
int result = Objects.hash(name, className);
result = 31 * result + Arrays.hashCode(configuration);
return result;
}
@Override
public String toString() {
return new StringBuilder("FeignClientSpecification{").append("name='").append(name).append("', ")
.append("configuration=").append(Arrays.toString(configuration)).append("}").toString();
return "FeignClientSpecification{" + "name='" + name + "', " + "className='" + className + "', "
+ "configuration=" + Arrays.toString(configuration) + "}";
}
}

69
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java

@ -71,7 +71,7 @@ import org.springframework.util.StringUtils; @@ -71,7 +71,7 @@ import org.springframework.util.StringUtils;
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
// and RibbonClientsConfigurationRegistrar
private ResourceLoader resourceLoader;
@ -162,12 +162,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -162,12 +162,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
@ -196,20 +195,74 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -196,20 +195,74 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
String className = annotationMetadata.getClassName();
registerClientConfiguration(registry, name, className, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
@SuppressWarnings("unchecked")
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
if (String.valueOf(false).equals(
environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
else {
lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
}
private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
BeanDefinitionRegistry registry) {
validate(attributes);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(null, attributes));
definition.addPropertyValue("path", getPath(null, attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(null, attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("dismiss404", Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
definition.addPropertyValue("fallback",
(fallback instanceof Class ? fallback : ClassUtils.resolveClassName(fallback.toString(), null)));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
definition.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class ? fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.addPropertyValue("refreshableClient", isClientRefreshEnabled());
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// This is done so that there's a way to retrieve qualifiers while generating AOT
// code
definition.addPropertyValue("qualifiers", qualifiers);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}
private void lazilyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
BeanDefinitionRegistry registry) {
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
Class clazz = ClassUtils.resolveClassName(className, null);
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
@ -407,9 +460,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -407,9 +460,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(className);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());

2
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/Targeter.java

@ -24,7 +24,7 @@ import feign.Target; @@ -24,7 +24,7 @@ import feign.Target;
*/
public interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target);
}

126
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
/*
* Copyright 2022-2022 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.cloud.openfeign.aot;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.cloud.openfeign.FeignClientSpecification;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.ClassName;
import org.springframework.util.Assert;
/**
* A {@link BeanRegistrationAotProcessor} that creates an
* {@link BeanRegistrationAotContribution} for Feign child contexts.
*
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
public class FeignChildContextInitializer implements BeanRegistrationAotProcessor {
private final ApplicationContext applicationContext;
private final FeignClientFactory feignClientFactory;
public FeignChildContextInitializer(ApplicationContext applicationContext, FeignClientFactory feignClientFactory) {
this.applicationContext = applicationContext;
this.feignClientFactory = feignClientFactory;
}
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
ConfigurableApplicationContext context = ((ConfigurableApplicationContext) applicationContext);
BeanFactory applicationBeanFactory = context.getBeanFactory();
if (!((registeredBean.getBeanClass().equals(FeignClientFactory.class))
&& registeredBean.getBeanFactory().equals(applicationBeanFactory))) {
return null;
}
Set<String> contextIds = new HashSet<>(getContextIdsFromConfig());
Map<String, GenericApplicationContext> childContextAotContributions = contextIds.stream()
.map(contextId -> Map.entry(contextId, buildChildContext(contextId)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return new AotContribution(childContextAotContributions);
}
private GenericApplicationContext buildChildContext(String contextId) {
GenericApplicationContext childContext = feignClientFactory.buildContext(contextId);
feignClientFactory.registerBeans(contextId, childContext);
return childContext;
}
private Collection<String> getContextIdsFromConfig() {
Map<String, FeignClientSpecification> configurations = feignClientFactory.getConfigurations();
return configurations.keySet().stream().filter(key -> !key.startsWith("default.")).collect(Collectors.toSet());
}
private static class AotContribution implements BeanRegistrationAotContribution {
private final Map<String, GenericApplicationContext> childContexts;
AotContribution(Map<String, GenericApplicationContext> childContexts) {
this.childContexts = childContexts.entrySet().stream().filter(entry -> entry.getValue() != null)
.map(entry -> Map.entry(entry.getKey(), entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
Map<String, ClassName> generatedInitializerClassNames = childContexts.entrySet().stream().map(entry -> {
String name = entry.getValue().getDisplayName();
name = name.replaceAll("[-]", "_");
GenerationContext childGenerationContext = generationContext.withName(name);
ClassName initializerClassName = new ApplicationContextAotGenerator()
.processAheadOfTime(entry.getValue(), childGenerationContext);
return Map.entry(entry.getKey(), initializerClassName);
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
GeneratedMethod postProcessorMethod = beanRegistrationCode.getMethods()
.add("addFeignChildContextInitializer", method -> {
method.addJavadoc("Use AOT child context management initialization")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.addParameter(RegisteredBean.class, "registeredBean")
.addParameter(FeignClientFactory.class, "instance").returns(FeignClientFactory.class)
.addStatement("$T<String, Object> initializers = new $T<>()", Map.class, HashMap.class);
generatedInitializerClassNames.keySet()
.forEach(contextId -> method.addStatement("initializers.put($S, new $L())", contextId,
generatedInitializerClassNames.get(contextId)));
method.addStatement("return instance.withApplicationContextInitializers(initializers)");
});
beanRegistrationCode.addInstancePostProcessor(postProcessorMethod.toMethodReference());
}
}
}

182
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
/*
* Copyright 2022-2022 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.cloud.openfeign.aot;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.hint.ProxyHints;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.openfeign.FeignClientSpecification;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.MethodSpec;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A {@link BeanFactoryInitializationAotProcessor} that creates an
* {@link BeanFactoryInitializationAotContribution} that registers bean definitions and
* proxy hints for Feign client beans.
*
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
public class FeignClientBeanFactoryInitializationAotProcessor
implements BeanRegistrationExcludeFilter, BeanFactoryInitializationAotProcessor {
private final GenericApplicationContext context;
private final Map<String, BeanDefinition> feignClientBeanDefinitions;
public FeignClientBeanFactoryInitializationAotProcessor(GenericApplicationContext context,
FeignClientFactory feignClientFactory) {
this.context = context;
this.feignClientBeanDefinitions = getFeignClientBeanDefinitions(feignClientFactory);
}
@Override
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
return registeredBean.getBeanClass().equals(FeignClientFactoryBean.class)
|| feignClientBeanDefinitions.containsKey(registeredBean.getBeanClass().getName());
}
private Map<String, BeanDefinition> getFeignClientBeanDefinitions(FeignClientFactory feignClientFactory) {
Map<String, FeignClientSpecification> configurations = feignClientFactory.getConfigurations();
return configurations.values().stream().map(FeignClientSpecification::getClassName).filter(Objects::nonNull)
.filter(className -> !className.equals("default"))
.map(className -> Map.entry(className, context.getBeanDefinition(className)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
BeanFactory applicationBeanFactory = context.getBeanFactory();
if (feignClientBeanDefinitions.isEmpty() || !beanFactory.equals(applicationBeanFactory)) {
return null;
}
return new AotContribution(feignClientBeanDefinitions);
}
private static final class AotContribution implements BeanFactoryInitializationAotContribution {
private final Map<String, BeanDefinition> feignClientBeanDefinitions;
private AotContribution(Map<String, BeanDefinition> feignClientBeanDefinitions) {
this.feignClientBeanDefinitions = feignClientBeanDefinitions;
}
@Override
public void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode) {
ProxyHints proxyHints = generationContext.getRuntimeHints().proxies();
Set<String> feignClientRegistrationMethods = feignClientBeanDefinitions.values().stream()
.map(beanDefinition -> {
Assert.notNull(beanDefinition, "beanDefinition cannot be null");
Assert.isInstanceOf(GenericBeanDefinition.class, beanDefinition);
GenericBeanDefinition registeredBeanDefinition = (GenericBeanDefinition) beanDefinition;
MutablePropertyValues feignClientProperties = registeredBeanDefinition.getPropertyValues();
String className = (String) feignClientProperties.get("type");
Assert.notNull(className, "className cannot be null");
Class clazz = ClassUtils.resolveClassName(className, null);
proxyHints.registerJdkProxy(clazz);
return beanFactoryInitializationCode.getMethods()
.add(buildMethodName(className), method -> generateFeignClientRegistrationMethod(method,
feignClientProperties, registeredBeanDefinition))
.getName();
}).collect(Collectors.toSet());
MethodReference initializerMethod = beanFactoryInitializationCode.getMethods()
.add("initialize", method -> generateInitializerMethod(method, feignClientRegistrationMethods))
.toMethodReference();
beanFactoryInitializationCode.addInitializer(initializerMethod);
}
private String buildMethodName(String clientName) {
return "register" + clientName + "FeignClient";
}
private void generateInitializerMethod(MethodSpec.Builder method, Set<String> feignClientRegistrationMethods) {
method.addModifiers(Modifier.PUBLIC);
method.addParameter(DefaultListableBeanFactory.class, "registry");
feignClientRegistrationMethods.forEach(feignClientRegistrationMethod -> method.addStatement("$N(registry)",
feignClientRegistrationMethod));
}
private void generateFeignClientRegistrationMethod(MethodSpec.Builder method,
MutablePropertyValues feignClientPropertyValues, GenericBeanDefinition registeredBeanDefinition) {
Object feignQualifiers = feignClientPropertyValues.get("qualifiers");
Assert.notNull(feignQualifiers, "Feign qualifiers cannot be null");
String qualifiers = "{\"" + String.join("\",\"", (String[]) feignQualifiers) + "\"}";
method.addJavadoc("register Feign Client: $L", feignClientPropertyValues.get("type"))
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(BeanDefinitionRegistry.class, "registry")
.addStatement("Class clazz = $T.resolveClassName(\"$L\", null)", ClassUtils.class,
feignClientPropertyValues.get("type"))
.addStatement("$T definition = $T.genericBeanDefinition($T.class)", BeanDefinitionBuilder.class,
BeanDefinitionBuilder.class, FeignClientFactoryBean.class)
.addStatement("definition.addPropertyValue(\"name\",\"$L\")", feignClientPropertyValues.get("name"))
.addStatement("definition.addPropertyValue(\"contextId\", \"$L\")",
feignClientPropertyValues.get("contextId"))
.addStatement("definition.addPropertyValue(\"type\", clazz)")
.addStatement("definition.addPropertyValue(\"url\", \"$L\")", feignClientPropertyValues.get("url"))
.addStatement("definition.addPropertyValue(\"path\", \"$L\")",
feignClientPropertyValues.get("path"))
.addStatement("definition.addPropertyValue(\"dismiss404\", $L)",
feignClientPropertyValues.get("dismiss404"))
.addStatement("definition.addPropertyValue(\"fallback\", $T.class)",
feignClientPropertyValues.get("fallback"))
.addStatement("definition.addPropertyValue(\"fallbackFactory\", $T.class)",
feignClientPropertyValues.get("fallbackFactory"))
.addStatement("definition.setAutowireMode($L)", registeredBeanDefinition.getAutowireMode())
.addStatement("definition.setLazyInit($L)",
registeredBeanDefinition.getLazyInit() != null ? registeredBeanDefinition.getLazyInit()
: false)
.addStatement("$T beanDefinition = definition.getBeanDefinition()", AbstractBeanDefinition.class)
.addStatement("beanDefinition.setAttribute(\"$L\", clazz)", FactoryBean.OBJECT_TYPE_ATTRIBUTE)
.addStatement("beanDefinition.setPrimary($L)", registeredBeanDefinition.isPrimary())
.addStatement("$T holder = new $T(beanDefinition, \"$L\", new String[]$L)",
BeanDefinitionHolder.class, BeanDefinitionHolder.class,
feignClientPropertyValues.get("type"), qualifiers)
.addStatement("$T.registerBeanDefinition(holder, registry) ", BeanDefinitionReaderUtils.class);
}
}
}

6
spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -79,6 +79,12 @@ @@ -79,6 +79,12 @@
"type": "java.lang.String",
"description": "Provides a clientId to be used with OAuth2.",
"defaultValue": ""
},
{
"name": "spring.cloud.openfeign.lazy-attributes-resolution",
"type": "java.lang.Boolean",
"description": "Switches @FeignClient attributes resolution mode to lazy.",
"defaultValue": "false"
}
]
}

2
spring-cloud-openfeign-core/src/main/resources/META-INF/spring/aot.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.cloud.openfeign.FeignHints

169
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EagerInitFeignClientUsingConfigurerTests.java

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
/*
* Copyright 2013-2022 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.cloud.openfeign;
import java.lang.reflect.Field;
import java.util.List;
import feign.Capability;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.micrometer.MicrometerObservationCapability;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author matt king
* @author Jonatan Ivanov
* @author Olga Maciaszek-Sharma
*/
@DirtiesContext
@SpringBootTest(classes = EagerInitFeignClientUsingConfigurerTests.Application.class, value = {
"spring.cloud.openfeign.client.config.default.loggerLevel=full",
"spring.cloud.openfeign.client.config.default.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor",
"spring.cloud.openfeign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
class EagerInitFeignClientUsingConfigurerTests {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FeignClientFactory context;
private static final String BEAN_NAME_PREFIX = "&org.springframework.cloud.openfeign.EagerInitFeignClientUsingConfigurerTests$";
@Test
public void testFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "TestFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
assertThat(interceptors.size()).as("interceptors not set").isEqualTo(3);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.FULL);
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
private Object getBuilderValue(Feign.Builder builder, String member) {
Field builderField = ReflectionUtils.findField(Feign.Builder.class, member);
ReflectionUtils.makeAccessible(builderField);
return ReflectionUtils.getField(builderField, builder);
}
@Test
public void testNoInheritFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
assertThat(interceptors).as("interceptors not set").isEmpty();
assertThat(factoryBean.isInheritParentContext()).as("is inheriting from parent configuration").isFalse();
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
@Test
public void testNoInheritFeignClient_ignoreProperties() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.HEADERS);
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { TestFeignClient.class, NoInheritFeignClient.class })
protected static class Application {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
};
}
@Bean
public NoOpCapability noOpCapability() {
return new NoOpCapability();
}
}
public static class NoInheritConfiguration {
@Bean
public Logger.Level logLevel() {
return Logger.Level.HEADERS;
}
@Bean
public NoOpCapability noOpCapability() {
return new NoOpCapability();
}
@Bean
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
@Override
public boolean inheritParentConfiguration() {
return false;
}
};
}
}
@FeignClient("testFeignClient")
interface TestFeignClient {
}
@FeignClient(name = "noInheritFeignClient", configuration = NoInheritConfiguration.class)
interface NoInheritFeignClient {
}
private static class NoOpCapability implements Capability {
}
}

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java

@ -34,11 +34,11 @@ import org.springframework.test.annotation.DirtiesContext; @@ -34,11 +34,11 @@ import org.springframework.test.annotation.DirtiesContext;
class EnableFeignClientsSpringDataTests {
@Autowired
private FeignContext feignContext;
private FeignClientFactory feignClientFactory;
@Test
void encoderDefaultCorrect() {
PageableSpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class));
PageableSpringEncoder.class.cast(this.feignClientFactory.getInstance("foo", Encoder.class));
}
@Configuration(proxyBeanMethods = false)

8
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java

@ -162,8 +162,8 @@ class FeignBuilderCustomizerTests { @@ -162,8 +162,8 @@ class FeignBuilderCustomizerTests {
protected static class SampleConfiguration2 {
@Bean
FeignContext feignContext() {
return new FeignContext();
FeignClientFactory feignContext() {
return new FeignClientFactory();
}
@Bean
@ -198,8 +198,8 @@ class FeignBuilderCustomizerTests { @@ -198,8 +198,8 @@ class FeignBuilderCustomizerTests {
protected static class SampleConfiguration3 {
@Bean
FeignContext feignContext() {
return new FeignContext();
FeignClientFactory feignContext() {
return new FeignClientFactory();
}
@Bean

3
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java

@ -158,7 +158,8 @@ class FeignClientBuilderTests { @@ -158,7 +158,8 @@ class FeignClientBuilderTests {
@Test
void forType_build() {
// given:
Mockito.when(this.applicationContext.getBean(FeignContext.class)).thenThrow(new ClosedFileSystemException()); // throw
Mockito.when(this.applicationContext.getBean(FeignClientFactory.class))
.thenThrow(new ClosedFileSystemException()); // throw
// an
// unusual
// exception

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java

@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class FeignClientDisabledClientLevelFeaturesTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java

@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class FeignClientDisabledFeaturesTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java

@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class FeignClientErrorDecoderTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

38
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignContextTest.java → spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java

@ -30,24 +30,24 @@ import org.springframework.context.annotation.Import; @@ -30,24 +30,24 @@ import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
class FeignContextTest {
class FeignClientFactoryTest {
@Test
void getInstanceWithoutAncestors_verifyNullForMissing() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("empty", null, EmptyConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("empty", Logger.Level.class);
Logger.Level level = feignClientFactory.getInstanceWithoutAncestors("empty", Logger.Level.class);
assertThat(level).as("Logger was not null").isNull();
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass });
private FeignClientSpecification getSpec(String name, String className, Class<?> configClass) {
return new FeignClientSpecification(name, className, new Class[] { configClass });
}
@Test
@ -55,11 +55,11 @@ class FeignContextTest { @@ -55,11 +55,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("empty", null, EmptyConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
Collection<RequestInterceptor> interceptors = feignClientFactory
.getInstancesWithoutAncestors("empty", RequestInterceptor.class).values();
assertThat(interceptors).as("Interceptors is not empty").isEmpty();
@ -70,11 +70,11 @@ class FeignContextTest { @@ -70,11 +70,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("demo", null, DemoConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("demo", Logger.Level.class);
Logger.Level level = feignClientFactory.getInstanceWithoutAncestors("demo", Logger.Level.class);
assertThat(level).isEqualTo(Logger.Level.FULL);
}
@ -84,11 +84,11 @@ class FeignContextTest { @@ -84,11 +84,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("demo", null, DemoConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
Collection<RequestInterceptor> interceptors = feignClientFactory
.getInstancesWithoutAncestors("demo", RequestInterceptor.class).values();
assertThat(interceptors.size()).isEqualTo(1);

19
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTests.java

@ -52,9 +52,10 @@ public class FeignClientFactoryTests { @@ -52,9 +52,10 @@ public class FeignClientFactoryTests {
public void testChildContexts() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext context = new FeignContext();
FeignClientFactory context = new FeignClientFactory();
context.setApplicationContext(parent);
context.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class), getSpec("bar", BarConfig.class)));
context.setConfigurations(
Arrays.asList(getSpec("foo", null, FooConfig.class), getSpec("bar", null, BarConfig.class)));
Foo foo = context.getInstance("foo", Foo.class);
assertThat(foo).as("foo was null").isNotNull();
@ -83,8 +84,8 @@ public class FeignClientFactoryTests { @@ -83,8 +84,8 @@ public class FeignClientFactoryTests {
assertThat(client).isInstanceOf(Client.Default.class);
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass });
private FeignClientSpecification getSpec(String name, String className, Class<?> configClass) {
return new FeignClientSpecification(name, className, new Class[] { configClass });
}
interface TestType {
@ -103,11 +104,11 @@ public class FeignClientFactoryTests { @@ -103,11 +104,11 @@ public class FeignClientFactoryTests {
}
@Bean
FeignContext feignContext() {
FeignContext feignContext = new FeignContext();
feignContext.setConfigurations(Collections.singletonList(
new FeignClientSpecification("test", new Class[] { LoadBalancerAutoConfiguration.class })));
return feignContext;
FeignClientFactory feignContext() {
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setConfigurations(Collections.singletonList(
new FeignClientSpecification("test", null, new Class[] { LoadBalancerAutoConfiguration.class })));
return feignClientFactory;
}
@Bean

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java

@ -63,7 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; @@ -63,7 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatCode;
class FeignClientOverrideDefaultsTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java

@ -87,7 +87,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @@ -87,7 +87,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
public class FeignClientUsingPropertiesTests {
@Autowired
FeignContext context;
FeignClientFactory context;
@Autowired
private ApplicationContext applicationContext;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientWithRefreshableOptionsTest.java

@ -67,7 +67,7 @@ public class FeignClientWithRefreshableOptionsTest { @@ -67,7 +67,7 @@ public class FeignClientWithRefreshableOptionsTest {
private FeignClientProperties clientProperties;
@Test
public void overridedOptionsBeanShouldBePresentInsteadOfRefreshable() {
public void overriddenOptionsBeanShouldBePresentInsteadOfRefreshable() {
OptionsTestClient.OptionsResponseForTests options = overrideOptionsClient.override();
assertConnectionAndReadTimeout(options, 1, 1);
}

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignCompressionTests.java

@ -47,8 +47,8 @@ class FeignCompressionTests { @@ -47,8 +47,8 @@ class FeignCompressionTests {
FeignContentGzipEncodingAutoConfiguration.class,
FeignAcceptGzipEncodingAutoConfiguration.class))
.run(context -> {
FeignContext feignContext = context.getBean(FeignContext.class);
Map<String, RequestInterceptor> interceptors = feignContext.getInstances("foo",
FeignClientFactory feignClientFactory = context.getBean(FeignClientFactory.class);
Map<String, RequestInterceptor> interceptors = feignClientFactory.getInstances("foo",
RequestInterceptor.class);
assertThat(interceptors.size()).isEqualTo(2);
assertThat(interceptors.get("feignAcceptGzipEncodingInterceptor"))

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java

@ -153,7 +153,7 @@ class FeignHttpClientUrlTests { @@ -153,7 +153,7 @@ class FeignHttpClientUrlTests {
public Targeter feignTargeter() {
return new Targeter() {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
Field field = ReflectionUtils.findField(Feign.Builder.class, "client");
ReflectionUtils.makeAccessible(field);

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlWithRetryableLoadBalancerTests.java

@ -154,7 +154,7 @@ class FeignHttpClientUrlWithRetryableLoadBalancerTests { @@ -154,7 +154,7 @@ class FeignHttpClientUrlWithRetryableLoadBalancerTests {
public Targeter feignTargeter() {
return new Targeter() {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
Field field = ReflectionUtils.findField(Feign.Builder.class, "client");
ReflectionUtils.makeAccessible(field);

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/GzipDecodingTests.java

@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class GzipDecodingTests extends FeignClientFactoryBean {
@Autowired
FeignContext context;
FeignClientFactory context;
@Value("${local.server.port}")
private int port = 0;

10
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java → spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/LazyInitFeignClientUsingConfigurerTests.java

@ -41,21 +41,23 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -41,21 +41,23 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author matt king
* @author Jonatan Ivanov
* @author Olga Maciaszek-Sharma
*/
@DirtiesContext
@SpringBootTest(classes = FeignClientUsingConfigurerTest.Application.class, value = {
@SpringBootTest(classes = LazyInitFeignClientUsingConfigurerTests.Application.class, value = {
"spring.cloud.openfeign.lazy-attributes-resolution=true",
"spring.cloud.openfeign.client.config.default.loggerLevel=full",
"spring.cloud.openfeign.client.config.default.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor",
"spring.cloud.openfeign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
class FeignClientUsingConfigurerTest {
class LazyInitFeignClientUsingConfigurerTests {
private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$";
private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.LazyInitFeignClientUsingConfigurerTests$";
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private FeignContext context;
private FeignClientFactory context;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java

@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class SpringDecoderTests extends FeignClientFactoryBean {
@Autowired
FeignContext context;
FeignClientFactory context;
@LocalServerPort
private int port = 0;

171
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignAotTests.java

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
/*
* Copyright 2022-2022 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.cloud.openfeign.aot;
import java.net.URL;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.aot.AotDetector;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.ClassName;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
/**
* AOT processing tests.
*
* @author Olga Maciaszek-Sharma
*/
@ExtendWith(OutputCaptureExtension.class)
public class FeignAotTests {
private static final Log LOG = LogFactory.getLog(FeignAotTests.class);
@BeforeEach
@AfterEach
void reset() {
ReflectionTestUtils.setField(TomcatURLStreamHandlerFactory.class, "instance", null);
ReflectionTestUtils.setField(URL.class, "factory", null);
}
@Test
@CompileWithForkedClassLoader
@SuppressWarnings("unchecked")
void shouldStartFeignChildContextsFromAotContributions(CapturedOutput output) {
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
AnnotationConfigServletWebApplicationContext::new)
.withConfiguration(AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class,
FeignAutoConfiguration.class))
.withConfiguration(UserConfigurations.of(TestFeignConfiguration.class))
.withPropertyValues("logging.level.org.springframework.cloud=DEBUG");
contextRunner.prepare(context -> {
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(
(GenericApplicationContext) context.getSourceApplicationContext(), generationContext);
generationContext.writeGeneratedContent();
TestCompiler compiler = TestCompiler.forSystem();
compiler.with(generationContext).compile(compiled -> {
ServletWebServerApplicationContext freshApplicationContext = new ServletWebServerApplicationContext();
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
.getInstance(ApplicationContextInitializer.class, className.toString());
initializer.initialize(freshApplicationContext);
assertThat(output).contains("Creating a FeignClientFactoryBean.");
assertThat(output).contains("Refreshing FeignClientFactory-test-with-config",
"Refreshing FeignClientFactory-test");
assertThat(output).doesNotContain("Instantiating bean from Test custom config",
"Instantiating bean from default custom config");
TestPropertyValues.of(AotDetector.AOT_ENABLED + "=true")
.applyToSystemProperties(freshApplicationContext::refresh);
assertThat(output).contains("Instantiating bean from Test custom config",
"Instantiating bean from default custom config");
assertThat(freshApplicationContext.getBean(TestFeignClient.class)).isNotNull();
assertThat(freshApplicationContext.getBean(TestFeignClientWithConfig.class)).isNotNull();
});
});
}
static class TestTarget {
}
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { TestFeignClient.class, TestFeignClientWithConfig.class },
defaultConfiguration = DefaultConfiguration.class)
public static class TestFeignConfiguration {
@Autowired
TestFeignClient testFeignClient;
@Autowired
TestFeignClientWithConfig testFeignClientWithConfig;
}
public static class TestConfiguration {
@Bean
TestBean testBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Instantiating bean from Test custom config");
}
return new TestBean();
}
}
public static class DefaultConfiguration {
@Bean
TestBean defaultTestBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Instantiating bean from default custom config");
}
return new TestBean();
}
}
public static class TestBean {
}
@FeignClient(value = "test", dismiss404 = true, url = "http://example.com")
interface TestFeignClient {
@GetMapping
void test();
}
@FeignClient(value = "test-with-config", configuration = TestConfiguration.class)
interface TestFeignClientWithConfig {
@GetMapping
void test();
}
}

3
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/AsyncCircuitBreakerTests.java

@ -65,7 +65,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -65,7 +65,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author John Niang
*/
@SpringBootTest(classes = AsyncCircuitBreakerTests.Application.class, webEnvironment = RANDOM_PORT,
properties = "spring.cloud.openfeign.circuitbreaker.enabled=true")
properties = { "spring.cloud.openfeign.circuitbreaker.enabled=true",
"spring.cloud.openfeign.lazy-attributes-resolution=true" })
@AutoConfigureMockMvc
class AsyncCircuitBreakerTests {

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java

@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -51,7 +51,7 @@ class PageableEncoderTests { @@ -51,7 +51,7 @@ class PageableEncoderTests {
public static final String SORT_1 = "sort1";
@Autowired
private FeignContext context;
private FeignClientFactory context;
protected String getPageParameter() {
return "page";

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoderTests.java

@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -52,7 +52,7 @@ class PageableSpringQueryMapEncoderTests { @@ -52,7 +52,7 @@ class PageableSpringQueryMapEncoderTests {
public static final String SORT_1 = "sort1";
@Autowired
private FeignContext context;
private FeignClientFactory context;
protected String getPageParameter() {
return "page";

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java

@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Qualifier; @@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -77,7 +77,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -77,7 +77,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
class SpringEncoderTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
@Qualifier("myHttpMessageConverter")

Loading…
Cancel
Save