Feign的目的是为了减少复杂度尽快地实现Http API的连接。在Spring项目中使用原生的Feign代替HttpClient是一个不错的选择。
一、Feign 客户端的使用
-
添加依赖
<!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-core --> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-core</artifactId> <version>8.18.0</version> </dependency> <!-- 不用Gson进行encoder可以换成其他的依赖,例如,使用Jackson --> <!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-gson</artifactId> <version>8.18.0</version> </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");
二、集成到Spring的项目中
想使用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
自动注入了
三、常用
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,编码解码等