Feign的目的是为了减少复杂度尽快地实现Http API的连接。在Spring项目中使用原生的Feign代替HttpClient是一个不错的选择。
Feign GitHub地址
添加依赖
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-coreartifactId>
<version>8.18.0version>
dependency>
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-gsonartifactId>
<version>8.18.0version>
dependency>
编写请求接口
public interface BaiQiShiRepository {
/**
* GET 请求
*/
@RequestLine("GET /api/v1/{secretKey}/tick/{tick}")
@Headers("Content-Type:charset=UTF-8")
JSONObject query(@Param("secretKey") String secretKey, @Param("tick") String tick);
/**
* POST 请求,JSON格式参数
*/
@RequestLine("POST /query")
@Headers("Content-Type:application/json,charset=UTF-8")
@Body("%7b\"partnerId\":\"{baiQiShi.partnerId}\",\"verifyKey\":\"{baiQiShi.verifyKey}\",\"platform\":\"{baiQiShi.platform}\",\"tokenKey\":\"{baiQiShi.tokenKey}\"%7d")
JSONObject query(BaiQiShi baiQiShi);
/**
* POST 请求,表单形式提交
*/
@RequestLine("POST /riskService")
@Headers("Content-Type:application/x-www-form-urlencoded,charset=UTF-8")
JSONObject riskService(@QueryMap Map<String, String> queryMap);
}
Feign使用
String url = "http:.....";
BaiQiShiRepository repository = Feign.builder().encoder(new GsonEncoder()).decoder(new GsonDecoder()).target(BaiQiShiRepository.class, url);
JSONObject result = repository.query("secretKey", "tick");
想使用Feign跟SpringCloud一样,使用的时候自动注入。
项目中添加依赖
编写调用接口,和上面第二步一样
定义一个注解,用于将对象注册到Spring
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignRepository {
String value() default "";
/**
* 配置文件定义的url的key,配置文件中定义请求的url
*/
String uriPropertyName() default "";
}
使用示例:
/**
* tong-dun-uri是配置文件中的key,定义了请求的接口
*/
@FeignRepository(uriPropertyName = "tong-dun-uri")
public interface TongDunRepository {
@RequestLine("POST /riskService")
@Headers("Content-Type:application/x-www-form-urlencoded,charset=UTF-8")
JSONObject riskService(@QueryMap Map<String, String> queryMap);
}
之后要处理这个注解,并注册到Spring容器中,首先要扫描到这个注解并处理
定义注解FeignRepositoryScan
使用@Import实现Bean的自动注入
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(FeignRepositoryScanner.class)
public @interface FeignRepositoryScan {
/**
* 扫描路径,为空就扫描当前对应的
*/
String[] basePackage() default {};
}
实现解析FeignRepositoryScan
注解的实现类:FeignRepositoryScanner
public class FeignRepositoryScanner implements ImportBeanDefinitionRegistrar {
private static final String BASE_PACKAGE_KEY = "basePackage";
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(FeignRepositoryScan.class.getName()));
if (attributes == null) {
return;
}
// 获取 FeignRepositoryScan 注解basePackage参数中的值
String[] basePackages = attributes.getStringArray(BASE_PACKAGE_KEY);
if (basePackages == null || basePackages.length == 0) {
basePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass()
.getPackage().getName()};
}
// 自定义包扫描类,用于扫描带有 FeignRepository 注解的类,并注册到容器中
FeignRepositoryClassPathScanHandle scanHandle = new FeignRepositoryClassPathScanHandle(beanDefinitionRegistry, false);
//扫描指定路径下的接口
scanHandle.doScan(basePackages);
}
}
自定义扫描类:FeignRepositoryClassPathScanHandle (继承ClassPathBeanDefinitionScanner
)
public class FeignRepositoryClassPathScanHandle extends ClassPathBeanDefinitionScanner {
FeignRepositoryClassPathScanHandle(BeanDefinitionRegistry registry, boolean useDefaultFilters {
super(registry, useDefaultFilters);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
addIncludeFilter(new AnnotationTypeFilter(FeignRepository.class));
Set<BeanDefinitionHolder> definitionHolders = super.doScan(basePackages);
if (!CollectionUtils.isEmpty(definitionHolders)) {
for (BeanDefinitionHolder holder : definitionHolders) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 自定义一个工程方法,用于创建自定义的Feign客户端对象,需要实现FactoryBean接口(主要的是getObject()方法)
definition.setBeanClass(FeignRepositoryFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
return definitionHolders;
}
/**
* 重写该方法是为了让接口可以被认为是获选组件,否则无法初始化扫描的对象
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
工厂类:FeignRepositoryFactoryBean(实现FactoryBean
)
public class FeignRepositoryFactoryBean<T> implements FactoryBean<T> {
@Resource
private Environment env;
private Class<T> clazz;
public FeignRepositoryFactoryBean(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T getObject() throws Exception {
FeignRepository feignRepository = clazz.getAnnotation(FeignRepository.class);
String propertyName = feignRepository.uriPropertyName();
if (StringUtils.isEmpty(propertyName)) {
throw new SystemException("Feign uriPropertyName is null!");
}
String uri = env.getProperty(propertyName, "");
if (StringUtils.isEmpty(uri)) {
throw new SystemException("Feign " + propertyName + " not set");
}
return Feign.builder().encoder(new GsonEncoder()).decoder(new GsonDecoder()).target(clazz, uri);
}
@Override
public Class<?> getObjectType() {
return clazz;
}
}
使用:启动类加入FeignRepositoryScan
注解
@SpringBootApplication
@FeignRepositoryScan(basePackage = "com.wangxiaonan.berry.repository")
public class BerryApplication {
public static void main(String[] args) {
SpringApplication.run(BerryApplication.class, args);
}
}
到此自动注入结束,启动就可以像其他服务一样使用@Resources
和@Autowired
自动注入了
Interface Annotations
Feign annotations define the Contract
between the interface and how the underlying client
should work. Feign’s default contract defines the following annotations:
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine |
Method | Defines the HttpMethod and UriTemplate for request. Expressions , values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters. |
@Param |
Parameter | Defines a template variable, whose value will be used to resolve the corresponding template Expression , by name. |
@Headers |
Method, Type | Defines a HeaderTemplate ; a variation on a UriTemplate . that uses @Param annotated values to resolve the corresponding Expressions . When used on a Type , the template will be applied to every request. When used on a Method , the template will apply only to the annotated method. |
@QueryMap |
Parameter | Defines a Map of name-value pairs, or POJO, to expand into a query string. |
@HeaderMap |
Parameter | Defines a Map of name-value pairs, to expand into Http Headers |
@Body |
Method | Defines a Template , similar to a UriTemplate and HeaderTemplate , that uses @Param annotated values to resolve the corresponding Expressions . |
其他插件的集成,例如,SLF4J日志,JSON格式,SOAP,编码解码等