Feign的目的是为了减少复杂度尽快地实现Http API的连接。在Spring项目中使用原生的Feign代替HttpClient是一个不错的选择。
Feign GitHub地址
添加依赖
<!-- 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("{\"partnerId\":\"{baiQiShi.partnerId}\",\"verifyKey\":\"{baiQiShi.verifyKey}\",\"platform\":\"{baiQiShi.platform}\",\"tokenKey\":\"{baiQiShi.tokenKey}\"}") 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:
AnnotationInterface TargetUsage@RequestLineMethodDefines the HttpMethod and UriTemplate for request. Expressions, values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters.@ParamParameterDefines a template variable, whose value will be used to resolve the corresponding template Expression, by name.@HeadersMethod, TypeDefines 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.@QueryMapParameterDefines a Map of name-value pairs, or POJO, to expand into a query string.@HeaderMapParameterDefines a Map of name-value pairs, to expand into Http Headers@BodyMethodDefines a Template, similar to a UriTemplate and HeaderTemplate, that uses @Param annotated values to resolve the corresponding Expressions.其他插件的集成,例如,SLF4J日志,JSON格式,SOAP,编码解码等