Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-openfeign-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
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;
Expand All @@ -55,6 +59,8 @@
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;
Expand All @@ -64,12 +70,14 @@
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
Expand Down Expand Up @@ -102,12 +110,24 @@ public HasFeatures feignFeature() {
}

@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)
Expand Down Expand Up @@ -358,3 +378,17 @@ public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(
}

}

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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -56,20 +56,20 @@ public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignCo
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -31,11 +34,18 @@
* @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
Expand All @@ -57,4 +67,14 @@ public <T> T getInstance(String contextName, String beanName, Class<T> type) {
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -137,7 +146,7 @@ protected Feign.Builder feign(FeignContext context) {
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);

Expand All @@ -148,7 +157,7 @@ private void applyBuildCustomizers(FeignContext context, Feign.Builder builder)
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);

Expand All @@ -172,7 +181,7 @@ protected void configureFeign(FeignContext context, Feign.Builder builder) {
}
}

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);
Expand Down Expand Up @@ -340,19 +349,19 @@ private <T> T getOrInstantiate(Class<T> tClass) {
}
}

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);
}
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);
}
Expand All @@ -361,7 +370,7 @@ protected <T> T getInheritedAwareOptional(FeignContext context, Class<T> type) {
}
}

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);
}
Expand All @@ -370,7 +379,7 @@ protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context, Cl
}
}

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);
Expand All @@ -389,7 +398,7 @@ protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCod
* @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);
Expand All @@ -409,9 +418,9 @@ public Object getObject() {
*/
@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()) {
Expand All @@ -424,13 +433,13 @@ <T> T getTarget() {
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,
Expand All @@ -445,10 +454,10 @@ <T> T getTarget() {
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() {
Expand All @@ -468,7 +477,7 @@ private String cleanPath() {
}

@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);
}
Expand Down Expand Up @@ -600,6 +609,14 @@ public void setRefreshableClient(boolean refreshableClient) {
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) {
Expand Down
Loading