Spring Cloud(4):Feign的使用
基于上一篇文章:https://www.cnblogs.com/xuyiqing/p/10867739.html
使用Ribbon实现了订单服务调用商品服务的Demo
下面介绍如何使用Feign实现这个Demo
Feign:伪RPC客户端,底层基于HTTP
在订单服务的POM中加入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
并在启动类中加入注解,这里已经加入的Ribbon可以保留
package org.dreamtech.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
Feign接口
package org.dreamtech.orderservice.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "product-service") public interface ProductClient { @RequestMapping("/api/product/find") String findById(@RequestParam(value = "id")int id); }
使用
package org.dreamtech.orderservice.service.impl; import com.fasterxml.jackson.databind.JsonNode; import org.dreamtech.orderservice.domain.ProductOrder; import org.dreamtech.orderservice.service.ProductClient; import org.dreamtech.orderservice.service.ProductOrderService; import org.dreamtech.orderservice.utils.JsonUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.Date; import java.util.Map; import java.util.UUID; @Service public class ProductOrderServiceImpl implements ProductOrderService { private final ProductClient productClient; @Autowired public ProductOrderServiceImpl(ProductClient productClient) { this.productClient = productClient; } @Override public ProductOrder save(int userId, int productId) { String response = productClient.findById(productId); JsonNode jsonNode = JsonUtils.str2JsonNode(response); ProductOrder productOrder = new ProductOrder(); productOrder.setCreateTime(new Date()); productOrder.setUserId(userId); productOrder.setTradeNo(UUID.randomUUID().toString()); productOrder.setProductName(jsonNode.get("name").toString()); productOrder.setPrice(Integer.parseInt(jsonNode.get("price").toString())); return productOrder; } }
工具类
package org.dreamtech.orderservice.utils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class JsonUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); public static JsonNode str2JsonNode(String str) { try { return objectMapper.readTree(str); } catch (IOException e) { return null; } } }
重启Eureka Server和多个product-service:
访问http://localhost:8781/api/order/save?user_id=1&product_id=1,成功
使用注意:
1.Feign的路由和服务的路由必须一致(如这里的/api/product/find)
2.注意服务名的一致
3.参数必须加入@RequestParam确保参数名和服务的一致
4.如果服务的参数加入了@RequestBody,Feign客户端也要加入@RequestBody并修改为POST方式
5.这里获取到JSON直接解析,实际情况可以抽取公共实体类为JAR包引入返回实体类
FeignClient原理:
查看Clien类可以发现,Feign的实现是通过HTTP形式:
HttpURLConnection convertAndSend(Request request, Options options) throws IOException { HttpURLConnection connection = (HttpURLConnection)(new URL(request.url())).openConnection(); if (connection instanceof HttpsURLConnection) { HttpsURLConnection sslCon = (HttpsURLConnection)connection; if (this.sslContextFactory != null) { sslCon.setSSLSocketFactory(this.sslContextFactory); } if (this.hostnameVerifier != null) { sslCon.setHostnameVerifier(this.hostnameVerifier); } }
Client的实现类中:
发现Feign调用了Ribbon做负载均衡
public Response execute(Request request, Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost); IClientConfig requestConfig = this.getClientConfig(options, clientName); return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse(); } catch (ClientException var8) { IOException io = this.findIOException(var8); if (io != null) { throw io; } else { throw new RuntimeException(var8); } } }
处理超时问题:实际情况服务和调用者之间的通信可能较慢,这时候Feign会报错
如下配置Feign超时实际为10秒
feign: client: config: default: connectTimeout: 10000 readTimeout: 10000
Feign默认超时为1秒
Feign和Ribbon的对比:
1.Feign里面包含Ribbon,Feign的负载均衡由Ribbon实现
2.Feign更方便,可以和Hystrix结合