前提介绍
- Feign是SpringCloud中服务消费端的调用框架,通常与ribbon,hystrix等组合使用。
- 由于遗留原因,某些项目中,整个系统并不是SpringCloud项目,甚至不是Spring项目,而使用者关注的重点仅仅是简化http调用代码的编写。
- *如果采用httpclient或者okhttp这样相对较重的框架,对初学者来说编码量与学习曲线都会是一个挑战,而使用spring中RestTemplate,又没有配置化的解决方案,由此想到是否可以脱离Spring cloud,独立使用Feign。
内容简介
Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于Retrofit、JAXRS-2.0和WebSocket。Feign最初是为了降低统一绑定Denominator到HTTP API的复杂度,不区分是否支持Restful。Feign旨在通过最少的资源和代码来实现和HTTP API的连接。通过可定制的解码器和错误处理,可以编写任意的HTTP API。
maven依赖
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-coreartifactId>
<version>8.18.0version>
dependency>
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-jacksonartifactId>
<version>8.18.0version>
dependency>
<dependency>
<groupId>io.github.lukehutchgroupId>
<artifactId>fast-classpath-scannerartifactId>
<version>2.18.1version>
dependency>
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-jacksonartifactId>
<version>8.18.0version>
dependency>
复制代码
定义配置类
RemoteService service = Feign.builder()
.options(new Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3))
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(RemoteService.class, "http://127.0.0.1:8085");
复制代码
- options方法指定连接超时时长及响应超时时长
- retryer方法指定重试策略
- target方法绑定接口与服务端地址。
- 返回类型为绑定的接口类型。
自定义接口
随机定义一个远程调用的服务接口,并且声明相关的接口参数和请求地址。
通过@RequestLine指定HTTP协议及URL地址
public class User{
String userName;
}
public interface RemoteService {
User getOwner(User user);
public User getOwner( User user,
String requestToken,
Long userId,
String userName);
}
复制代码
服务提供者
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
public class UserController {
public User list( User user) throws InterruptedException{
System.out.println(user.getUsername());
user.setId(100L);
user.setUsername(user.getUsername().toUpperCase());
return user;
}
}
复制代码
调用
与调用本地方法相同的方式调用feign包装的接口,直接获取远程服务提供的返回值。
String result = service.getOwner(new User("scott"));
复制代码
原生Feign的两个问题
- 原生Feign只能一次解析一个接口,生成对应的请求代理对象,如果一个包里有多个调用接口就要多次解析非常麻烦。
- Feign生成的调用代理只是一个普通对象,该如何注册到Spring中,以便于我们可以使用@Autowired随时注入。
解决方案:
- 针对多次解析的问题,可以通过指定扫描包路径,然后对包中的类依次解析。
- 实现BeanFactoryPostProcessor接口,扩展Spring容器功能。
定义一个注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public FeignApi {
String serviceUrl();
}
复制代码
生成Feign代理并注册到Spring实现类:
import feign.Feign;
import feign.Request;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.util.List;
public class FeignClientRegister implements BeanFactoryPostProcessor{
private String scanPath="com.xxx.api";
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
List classes = scan(scanPath);
if(classes==null){
return ;
}
System.out.println(classes);
Feign.Builder builder = getFeignBuilder();
if(classes.size()>0){
for (String claz : classes) {
Class targetClass = null;
try {
targetClass = Class.forName(claz);
String url=targetClass.getAnnotation(FeignApi.class).serviceUrl();
if(url.indexOf("http://")!=0){
url="http://"+url;
}
Object target = builder.target(targetClass, url);
beanFactory.registerSingleton(targetClass.getName(), target);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
public Feign.Builder getFeignBuilder(){
Feign.Builder builder = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.options(new Request.Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3));
return builder;
}
public List scan(String path){
ScanResult result = new FastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class aClass) -> {
}).scan();
if(result!=null){
return result.getNamesOfAllInterfaceClasses();
}
return null;
}
}
复制代码
调用接口编写示例:
import com.xiaokong.core.base.Result;
import com.xiaokong.domain.DO.DeptRoom;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import com.xiaokong.register.FeignApi;
import java.util.List;
public interface RoomApi {
Result selectById( String id);
Result> selectList();
}
复制代码
接口使用示例:
public class ServiceImpl{
private RoomApi roomApi;
public void demo(){
Result result = roomApi.selectById("1");
System.out.println(result);
}
}
复制代码
注意事项:
- 如果接口返回的是一个复杂的嵌套对象,那么一定要明确的指定泛型,因为Feign在解析复杂对象的时候,需要通过反射获取接口返回对象内部的泛型类型才能正确使用Jackson解析。如果不明确的指明类型,Jackson会将json对象转换成一个LinkedHashMap类型。
- 如果你使用的是的Spring,又需要通过http调用别人的接口,都可以使用这个工具来简化调用与解析的操作。
分享资源
获取以上资源请访问开源项目 点击跳转