diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java
new file mode 100644
index 00000000..9209a6d3
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java
@@ -0,0 +1,14 @@
+package org.springframework.cloud.netflix.feign;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Spencer Gibb
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface FeignClient {
+ String value();
+ boolean loadbalance() default true;
+}
diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScan.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScan.java
new file mode 100644
index 00000000..862ab9d0
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScan.java
@@ -0,0 +1,53 @@
+package org.springframework.cloud.netflix.feign;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+/**
+ * Configures component scanning directives for use with @{@link org.springframework.context.annotation.Configuration} classes.
+ * Scan Spring Integration specific components.
+ *
+ * @author Artem Bilan
+ * @since 4.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Import(FeignClientScanRegistrar.class)
+public @interface FeignClientScan {
+
+ /**
+ * Alias for the {@link #basePackages()} attribute.
+ * Allows for more concise annotation declarations e.g.:
+ * {@code @ComponentScan("org.my.pkg")} instead of
+ * {@code @ComponentScan(basePackages="org.my.pkg")}.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] value() default {};
+
+ /**
+ * Base packages to scan for annotated components.
+ *
{@link #value()} is an alias for (and mutually exclusive with) this attribute.
+ *
Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] basePackages() default {};
+
+ /**
+ * Type-safe alternative to {@link #basePackages()} for specifying the packages
+ * to scan for annotated components. The package of each class specified will be scanned.
+ *
Consider creating a special no-op marker class or interface in each package
+ * that serves no purpose other than being referenced by this attribute.
+ *
+ * @return the array of 'basePackageClasses'.
+ */
+ Class>[] basePackageClasses() default {};
+
+}
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScanRegistrar.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScanRegistrar.java
new file mode 100644
index 00000000..6af944f1
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientScanRegistrar.java
@@ -0,0 +1,155 @@
+package org.springframework.cloud.netflix.feign;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.Data;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Spencer Gibb
+ * patterned after Spring Integration IntegrationComponentScanRegistrar
+ */
+public class FeignClientScanRegistrar extends FeignConfiguration
+ implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
+
+ private ResourceLoader resourceLoader;
+
+ private ClassLoader classLoader;
+
+ public FeignClientScanRegistrar() {
+ }
+
+ @Override
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+ Map componentScan = importingClassMetadata
+ .getAnnotationAttributes(FeignClientScan.class.getCanonicalName());
+
+ Set basePackages = new HashSet();
+ for (String pkg : (String[]) componentScan.get("value")) {
+ if (StringUtils.hasText(pkg)) {
+ basePackages.add(pkg);
+ }
+ }
+ for (String pkg : (String[]) componentScan.get("basePackages")) {
+ if (StringUtils.hasText(pkg)) {
+ basePackages.add(pkg);
+ }
+ }
+ for (Class> clazz : (Class[]) componentScan.get("basePackageClasses")) {
+ basePackages.add(ClassUtils.getPackageName(clazz));
+ }
+
+ if (basePackages.isEmpty()) {
+ basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
+ }
+
+ ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
+
+ @Override
+ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
+ if (beanDefinition.getMetadata().isIndependent()) {
+ // TODO until SPR-11711 will be resolved
+ if (beanDefinition.getMetadata().isInterface() &&
+ beanDefinition.getMetadata().getInterfaceNames().length == 1 &&
+ Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
+ try {
+ Class> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(), classLoader);
+ return !target.isAnnotation();
+ }
+ catch (Exception e) {
+ logger.error("Could not load target class: " + beanDefinition.getMetadata().getClassName(), e);
+
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
+
+ scanner.setResourceLoader(resourceLoader);
+
+ for (String basePackage : basePackages) {
+ Set candidateComponents = scanner.findCandidateComponents(basePackage);
+ for (BeanDefinition candidateComponent : candidateComponents) {
+ if (candidateComponent instanceof AnnotatedBeanDefinition) {
+ AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
+ BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignFactoryBean.class);
+ AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
+ Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
+
+ Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
+
+ String className = annotationMetadata.getClassName();
+ definition.addPropertyValue("loadbalance", attributes.get("loadbalance"));
+ definition.addPropertyValue("type", className);
+ definition.addPropertyValue("schemeName", attributes.get("value"));
+
+ String beanName = StringUtils.uncapitalize(className.substring(className.lastIndexOf(".")+1));
+ BeanDefinitionHolder holder = new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
+ BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
+ }
+ }
+ }
+ }
+
+ @Data
+ public static class FeignFactoryBean extends FeignConfiguration implements FactoryBean