卧疾丰暇豫,翰墨时间作。这篇文章主要讲述SpringCloud技术专题打开Fegin之RPC技术的开端,你会使用原生态Fegin(上)相关的知识,希望能为你提供帮助。
前提介绍
- Feign是SpringCloud中服务消费端的调用框架,通常与ribbon,hystrix等组合使用。
- 由于遗留原因,某些项目中,整个系统并不是SpringCloud项目,甚至不是Spring项目,而使用者关注的重点仅仅是简化http调用代码的编写。
- 如果采用httpclient或者okhttp这样相对较重的框架,对初学者来说编码量与学习曲线都会是一个挑战,而使用spring中RestTemplate,又没有配置化的解决方案,由此想到是否可以脱离Spring cloud,独立使用Feign。
<
dependency>
<
groupId>
com.netflix.feign<
/groupId>
<
artifactId>
feign-core<
/artifactId>
<
version>
8.18.0<
/version>
<
/dependency>
<
dependency>
<
groupId>
com.netflix.feign<
/groupId>
<
artifactId>
feign-jackson<
/artifactId>
<
version>
8.18.0<
/version>
<
/dependency>
<
dependency>
<
groupId>
io.github.lukehutch<
/groupId>
<
artifactId>
fast-classpath-scanner<
/artifactId>
<
version>
2.18.1<
/version>
<
/dependency>
<
dependency>
<
groupId>
com.netflix.feign<
/groupId>
<
artifactId>
feign-jackson<
/artifactId>
<
version>
8.18.0<
/version>
<
/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 {
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("POST /users/list")
User getOwner(User user);
@RequestLine("POST /users/list2")
@Headers({
"Content-Type: application/json",
"Accept: application/json",
"request-token: {requestToken}",
"UserId: {userId}",
"UserName: {userName}"
})
public User getOwner(@RequestBody User user,
@Param("requestToken") String requestToken,
@Param("userId") Long userId,
@Param("userName") 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;
@Controller
@RequestMapping(value="https://www.songbingjia.com/android/users")
public class UserController {
@RequestMapping(value="https://www.songbingjia.com/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
@ResponseBody
public User list(@RequestBody 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;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignApi {
/**
* 调用的服务地址
* @return
*/
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;
@Component
public class FeignClientRegister implements BeanFactoryPostProcessor{//扫描的接口路径
private StringscanPath="com.xxx.api";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
List<
String>
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<
String>
scan(String path){
ScanResult result = new FastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class<
?>
aClass) ->
{
}).scan();
if(result!=null){
return result.getNamesOfAllInterfaceClasses();
}
returnnull;
}
}
调用接口编写示例:
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;
@FeignApi(serviceUrl = "http://localhost:8085")
public interface RoomApi {
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("GET /room/selectById?id={id}")
Result<
DeptRoom>
selectById(@Param(value="https://www.songbingjia.com/android/id") String id);
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("GET /room/test")
Result<
List<
DeptRoom>
>
selectList();
}
接口使用示例:
@Service
public class ServiceImpl{
//将接口注入要使用的bean中直接调用即可
@Autowired
private RoomApi roomApi;
@Test
public void demo(){
Result<
DeptRoom>
result = roomApi.selectById("1");
System.out.println(result);
}
}
注意事项:
- 如果接口返回的是一个复杂的嵌套对象,那么一定要明确的指定泛型,因为Feign在解析复杂对象的时候,需要通过反射获取接口返回对象内部的泛型类型才能正确使用Jackson解析。如果不明确的指明类型,Jackson会将json对象转换成一个LinkedHashMap类型。
- 如果你使用的是的Spring,又需要通过http调用别人的接口,都可以使用这个工具来简化调用与解析的操作。
推荐阅读
- #导入MD文档图片# 推荐一款阿里最新 Python 自动化开源工具!
- #导入MD文档图片#Cobra + Client-go实现K8s 自定义插件开发
- 百度BaikalDB在同程艺龙的成功应用实践剖析
- #导入MD文档图片#一步一步搭建Svn服务之windows
- 如何使用JavaScript从DOM元素捕获图像
- 如何创建自己的JavaScript库
- 使用javascript将highcharts图表导出为图像的3种方法(客户端解决方案)
- 如何在ReactJS中创建同步和异步自动完成输入
- 如何在Django中返回JSON响应