Add AOT and native image support (#794)
This commit is contained in:
committed by
GitHub
parent
62f9d76127
commit
7f66cf3829
@@ -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.
|
||||
|
||||
|===
|
||||
|
||||
=== 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
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
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 {
|
||||
}
|
||||
|
||||
@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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
* @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>
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @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
|
||||
*/
|
||||
@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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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) + "}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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
|
||||
.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
|
||||
"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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.cloud.openfeign.FeignHints
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
protected static class SampleConfiguration3 {
|
||||
|
||||
@Bean
|
||||
FeignContext feignContext() {
|
||||
return new FeignContext();
|
||||
FeignClientFactory feignContext() {
|
||||
return new FeignClientFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
@@ -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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
public static final String SORT_1 = "sort1";
|
||||
|
||||
@Autowired
|
||||
private FeignContext context;
|
||||
private FeignClientFactory context;
|
||||
|
||||
protected String getPageParameter() {
|
||||
return "page";
|
||||
|
||||
@@ -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 {
|
||||
public static final String SORT_1 = "sort1";
|
||||
|
||||
@Autowired
|
||||
private FeignContext context;
|
||||
private FeignClientFactory context;
|
||||
|
||||
protected String getPageParameter() {
|
||||
return "page";
|
||||
|
||||
@@ -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;
|
||||
class SpringEncoderTests {
|
||||
|
||||
@Autowired
|
||||
private FeignContext context;
|
||||
private FeignClientFactory context;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myHttpMessageConverter")
|
||||
|
||||
Reference in New Issue
Block a user