Commit 7f9642ff by Spencer Gibb

new @FeignClient annotation

parent b7db6586
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;
}
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.
* <p>{@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>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.
* <p>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
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<String, Object> componentScan = importingClassMetadata
.getAnnotationAttributes(FeignClientScan.class.getCanonicalName());
Set<String> basePackages = new HashSet<String>();
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<BeanDefinition> 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<String, Object> 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<Object> {
boolean loadbalance;
Class<?> type;
String schemeName;
@Override
public Object getObject() throws Exception {
if (!schemeName.startsWith("http")) {
schemeName = "http://"+schemeName;
}
if (loadbalance) {
return loadBalance(type, schemeName);
}
return feign().target(type, schemeName);
}
@Override
public Class<?> getObjectType() {
return type;
}
@Override
public boolean isSingleton() {
return true;
}
}
}
package org.springframework.cloud.netflix.feign;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = FeignClientTests.Application.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "spring.application.name=feignclienttest", "spring.jmx.enabled=true" })
public class FeignClientTests extends FeignConfiguration {
@Value("${local.server.port}")
private int port = 0;
@Autowired
TestClient testClient;
//@FeignClient(value = "http://localhost:9876", loadbalance = false)
@FeignClient("feignclienttest")
protected static interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellos")
public List<Hello> getHellos();
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings")
public List<String> getHelloStrings();
}
@Configuration
@EnableAutoConfiguration
@RestController
@FeignClientScan
protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@RequestMapping(method = RequestMethod.GET, value = "/hellos")
public List<Hello> getHellos() {
ArrayList<Hello> hellos = new ArrayList<>();
hellos.add(new Hello("hello world 1"));
hellos.add(new Hello("oi terra 2"));
return hellos;
}
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings")
public List<String> getHelloStrings() {
ArrayList<String> hellos = new ArrayList<>();
hellos.add("hello world 1");
hellos.add("oi terra 2");
return hellos;
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).properties(
"spring.application.name=feignclienttest", "management.contextPath=/admin")
.run(args);
}
}
@Test
public void testClient() {
assertNotNull("testClient was null", testClient);
assertTrue("testClient is not a java Proxy", Proxy.isProxyClass(testClient.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testClient);
assertNotNull("invocationHandler was null", invocationHandler);
}
//TODO: only works if port is hardcoded cant resolve ${local.server.port} in annotation
/*@Test
public void testSimpleType() {
Hello hello = testClient.getHello();
assertNotNull("hello was null", hello);
assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
}*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Hello {
private String message;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment