无关微服务中,服务与服务如何通信,我曾经给大家介绍了 Ribbon 近程调用的相干常识,不晓得大家有没有发现 Ribbon 的问题呢?
Ribbon 的问题
在 Ribbon 中,如果咱们想要发动一个调用,是这样的:
@Resource
private RestTemplate restTemplate
String result = restTemplate.getForObject("http://my-goods/goods/get", String.class);
Goods goods = JSONObject.parseObject(result, Goods.class);
这就像一个一般的 http 申请一样,须要对入参和出参进行手动解决。
打一眼看上去如同没什么问题,但认真一想就不对劲了:这个被调用的接口都是咱们本人写的,入参和出参都是确定的,甚至写被调用的接口的人都是同一个 … 有没有一种更好的形式,比方像调用本地办法一样间接调用它呢?
比方这样:
Goods goods = goodsServer.get();
这个术语叫:近程办法调用
明天的配角
Feign
就为咱们实现了这样的性能,上面有请~
什么是 Feign
官网是这样介绍的:Feign 是受 Retrofit、JAXRS-2.0 和 WebSocket 启发的 Java 到 HTTP 客户端 binder(我也不晓得这里怎么翻译)。
为什么说是 binder?在看 Feign 所实现的性能及出发点来说,Feign 自身并未实现 HTPP 客户端,而且在其余组件的客户端的下层进行了加强,咱们能够先来感受一下 Feign 所带给咱们的性能
客户端
- java.net.URL
- Apache HTTP
- OK Http
- Ribbon
- Java 11 http2
- ….
标准
- Feign
- JAX-RS
- JAX-RS 2
- SOAP
- Spring 4
- ….
编解码
- GSON
- JAXB
- Jackson
- ….
其余
- Hystrix
- SLF4J
- Mock
更多内容查看 github: https://github.com/OpenFeign/…
根本应用
1. 在商品服务中编写 crud
小伙伴能够本人轻易找个我的项目写,次要就是搞几个接口来调用
@RestController
@RequestMapping("/goods")
public class GoodsController {@GetMapping("/get-goods")
public Goods getGoods(){return new Goods().setName("苹果")
.setPrice(1.1)
.setNumber(2);
}
@GetMapping("/list")
public List<Goods> list(){ArrayList<Goods> goodsList = new ArrayList<>();
Goods apple = new Goods().setName("苹果")
.setPrice(1.1)
.setNumber(2);
goodsList.add(apple);
Goods lemon = new Goods().setName("柠檬")
.setPrice(5.1)
.setNumber(3);
goodsList.add(lemon);
return goodsList;
}
@PostMapping("save")
public void save(@RequestBody Goods goods){System.out.println(goods);
}
@DeleteMapping
public void delete(String id){System.out.println(id);
}
}
2. 建设一个 demo,引入依赖
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<!-- 用于编解码 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
</dependencies>
3. 编写 feign 标准的接口
public interface GoodsApi {@RequestLine("GET /goods/get-goods")
Goods getGoods();
@RequestLine("GET /goods/list")
List<Goods> list();
@RequestLine("POST /goods/save")
@Headers("Content-Type: application/json")
void save(Goods goods);
@RequestLine("DELETE /goods?id={id}")
@Headers("Content-Type: application/json")
void delete(@Param("id") String id);
}
Feign 标准是什么置信小伙伴一看例子就懂了
4. 测试
public class FeignDemo {public static void main(String[] args) {
// 构建 feign 接口
GoodsApi goodsApi = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(GoodsApi.class, "http://localhost:8082");
// 调用测试
System.out.println(goodsApi.getGoods());
System.out.println(goodsApi.list());
goodsApi.save(new Goods().setName("banana"));
goodsApi.delete("1");
}
}
灵光一闪
看到这里,不晓得大家有没有灵光一闪呢?
我要是把构建的代码写这样
@Bean
public GoodsApi goodsApi(){return Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(GoodsApi.class, "http://localhost:8082");
}
而后应用时间接注入,woc,这不是爽翻天?
比拟罕用的应用形式
回顾 什么是 Feign章节,除了下面根本应用之外,feign 还反对 Spring 4 的标准,以及各种 http 客户端(如 okHttp),重试超时,日志等,我给大家介绍一个比拟罕用的形式
减少依赖
<!-- spring4 标准 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-spring4</artifactId>
<version>10.10.1</version>
</dependency>
<!-- ribbon 客户端 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-ribbon</artifactId>
</dependency>
<!-- okhttp 客户端 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
编写接口
public interface Spring4GoodsApi {@GetMapping("/goods/get-goods")
Goods getGoods();
@GetMapping("/goods/list")
List<Goods> list();
@PostMapping(value = "/goods/save", consumes = MediaType.APPLICATION_JSON_VALUE)
void save(Goods goods);
@DeleteMapping("/goods")
void delete(@RequestParam(value = "id") String id);
}
测试
public class Spring4FeignDemo {public static void main(String[] args) {Spring4GoodsApi goodsApi = Feign.builder()
// 应用 spring4 标准
.contract(new SpringContract())
// 应用 jackson 编解码
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
// okhttp 客户端
.client(new OkHttpClient())
// 申请失败重试,默认最大 5 次
.retryer(new Retryer.Default())
// 申请超时配置
.options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
// 日志配置,将在申请前后打印日志
.logger(new Slf4jLogger())
// 日志等级配置,BASIC: 只打印申请门路和响应状态码根本信息
.logLevel(Logger.Level.BASIC)
.target(Spring4GoodsApi.class, "http://localhost:8082");
System.out.println(goodsApi.getGoods());
System.out.println(goodsApi.list());
goodsApi.save(new Goods().setName("banana"));
goodsApi.delete("1");
}
}
拦截器
是个 http 客户端就会有拦截器机制,用于在申请前对立做一些操作:比方增加申请头
feign 的拦截器应用形式如下:
public class AuthFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {System.out.println("进入拦截器");
HttpServletRequest request =
((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
template.header("token", request.getHeader("token"));
}
}
实现 RequestInterceptor 即可
在 build 时退出
Feign.builder()
.requestInterceptor(new AuthFeignInterceptor())
.target(Spring4GoodsApi.class, "http://localhost:8082");
以上就是 Feign 的罕用形式了,学会之后,整合到 Spring Cloud 也是手到擒来。
整合 Spring Cloud
官网文档:https://docs.spring.io/spring…
该整合样例为订单服务调用商品服务
间接三板斧走起
1. 退出依赖
在 order-server 中引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 减少注解
在 Application 类中加上注解EnableFeignClients
@EnableFeignClients
@SpringBootApplication
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(GoodsApplication.class, args);
}
}
任何一个配置类都能够,然而举荐在启动类上加,因为注解默认扫描该注解类门路下的所有包,而后启动类又是在最顶端的,所以这样就能够扫描到所有的包了。
当然,你也能够间接扫描某个包,毕竟个别 feign 接口都放在一起
3. 编写配置
见配置章节
三板斧完结,开始编写样例
4. 编写样例
@FeignClient(name = "my-goods", path = "/goods", contextId = "goods")
public interface GoodsApi {@GetMapping("/get-goods")
Goods getGoods();
/**
* get 形式传加入上需 @SpringQueryMap 注解
*/
@GetMapping("/goods")
Goods getGoods(@SpringQueryMap Goods goods);
@GetMapping("/list")
List<Goods> list();
@PostMapping(value = "/save")
void save(Goods goods);
@DeleteMapping
void delete(String id);
}
FeignClient:
name: 调用的服务名称
path: 门路前缀,该类下的所有接口都会继承该门路
contextId: 用于辨别不同的 feign 接口,因为一般来说一个服务不止一个 feign 接口,比方还有个 GoodsDetailApi(商品详情), 然而他们的 name 属性是雷同的,都是商品服务,所有须要一个 contextId 来辨别不同的业务场景
其余的与罕用形式雷同
5. 测试
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private GoodsApi goodsApi;
@GetMapping("/get-goods")
public Goods getGoods(){return goodsApi.getGoods();
}
}
配置
在罕用形式中,咱们构建一个 feign 接口的各种属性,是通过硬编码实现的,整合到 spring 之后,能够通过配置的形式实现了,更加的灵便。
日志
feign:
client:
config:
# 全局配置, 配置类外面的属性名叫 defaultConfig, 值却是 default, 留神不要搞错
default:
loggerLevel: FULL
# 独自服务配置 对应的是 contextId 优先级更高
goods:
loggerLevel: BASIC
全局配置这里特地坑,不看源码基本不晓得怎么配,小伙伴肯定要留神
客户端
feign 默认应用的 HttpURLConnection
作为客户端,小伙伴也能够替换成其余的客户端
前言:所有的客户端都为 Client
接口的实现类,想要晓得是否替换胜利只需在对应的实现类中打个断点
应用 httpclient
引入依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.10.1</version>
</dependency>
这样就能够了,不必批改任何配置,这是因为当服务中蕴含 ApacheHttpClient
的 class 时,httpClient 的 feign 主动配置类就会失效,并且比默认的 HttpURLConnection
主动配置类优先级更高。此时服务就将注入 HttpClient 作为客户端。
源码如下:
@Import 导入的程序是 HttpClient, OkHttp, Default(HttpURLConnection)
该配置类的失效条件就是存在 ApacheHttpClient 类,而
feign.httpclient.enabled
配置默认不配也是失效。
应用 OkHttp
引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
因为咱们在上一步引入了 httpclient 依赖,而 httpclient 的优先级比 okhttp 高,并且是默认失效,所以想要 okhttp 失效有两种形式:
- 删除掉 httpclient 的依赖
- 显示敞开 httpclient
这里我应用第二种形式
feign:
# 将 httpclient 敞开
httpclient:
enabled: false
# 将 okhttp 开启
okhttp:
enabled: true
GZIP 压缩
有时当申请数据过大时,进行压缩数据能够无效的进步申请性能,feign 也提供这样的配置形式
留神:压缩只在非 okhttp 客户端时失效
feign:
httpclient:
enabled: true
# 配置压缩
compression:
request:
enabled: true
# 压缩的类型,默认就是这些
mime-types: text/xml, application/xml, application/json
# 压缩的字节最小阈值,超过该大小才进行压缩,默认 1024
min-request-size: 10
这里我应用 httpclient,不写也能够,我只是为了让大家晓得我没用 okhttp
容错重试
互联网利用无奈解决的问题之一:网络分区。当被服务提供者呈现这种状况,始终无奈响应状况,咱们也不可能让服务消费者始终傻傻的等着,所以咱们能够给服务配置一个超时工夫,超过肯定的工夫被调用的服务未响应,就把申请掐断。
feign 的配置:
feign:
client:
config:
# 全局配置
default:
# 连贯超时工夫 单位毫秒 默认 10 秒
connectTimeout: 1000
# 申请超时工夫 单位毫秒 默认 60 秒
readTimeout: 5000
互联网利用无奈解决的问题之二:网络抖动。当经验这种状况时,咱们只能让服务进行重试。
feign 配置:
feign:
client:
config:
# 全局配置
default:
# 重试 默认重试 5 次
retryer: feign.Retryer.Default
拦截器
拦截器与罕用形式雷同,实现 RequestInterceptor
接口即可
@Component
public class AuthFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {System.out.println("进入拦截器");
HttpServletRequest request =
((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
template.header("token", request.getHeader("token"));
}
}
加上 @Component 注解放到 spring 容器中,服务启动时会主动退出到 feign 的拦截器链
也能够应用配置的形式
feign:
client:
config:
# 全局配置
default:
requestInterceptors:
- com.my.micro.service.order.interceptor.AuthFeignInterceptor
小结
本篇具体介绍了一种近程办法调用形式:Feign,当初,带大家来简略的回顾一下。
什么是 Feign?
一种近程办法调用客户端,整合了 ribbon,httpclient, okhttp, 反对各种各样的标准,如 Spring4
Feign 的根本应用形式?
编辑接口,加上 Feign 反对的标准注解,应用 Feign.Builder 构建出代理类,发动调用。
如何整合 SpringCloud?
引入依赖,加上 @EnableFeignClients 注解,依据需要减少配置
本篇内容根本涵盖了 Feign 的罕用形式,心愿大家有所播种,咱们下期再见~
gittee: https://gitee.com/lzj960515/m…
集体博客空间:https://zijiancode.cn/archive…
看完之后想必有所播种吧~ 想要理解更多精彩内容,欢送关注公众号:程序员阿鉴,阿鉴在公众号欢送你的到来~