一、关于feigin
feigin是一种模板化,声明式的http客户端,feign可以通过注解绑定到接口上来简化Http请求访问。当然我们也可以在创建Feign对象时定制自定义解码器(xml或者json等格式解析)和错误处理。
二、添加SpringCloud对feign的支持
gradle配置:
compile('org.springframework.cloud:spring-cloud-starter-feign')
feigin最基本使用方法:
1 interface GitHub { 2 @RequestLine("GET /repos/{owner}/{repo}/contributors") 3 List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); 4 } 5 6 static class Contributor { 7 String login; 8 int contributions; 9 } 10 11 public static void main(String... args) { 12 GitHub github = Feign.builder() 13 .decoder(new GsonDecoder()) 14 .target(GitHub.class, "https://api.github.com"); 15 16 // Fetch and print a list of the contributors to this library. 17 List<Contributor> contributors = github.contributors("OpenFeign", "feign"); 18 for (Contributor contributor : contributors) { 19 System.out.println(contributor.login + " (" + contributor.contributions + ")"); 20 } 21 }
feign发送json与xml的格式的http请求:
1 interface LoginClient { 2 3 @RequestLine("POST /") 4 @Headers("Content-Type: application/xml") 5 @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>") 6 void xml(@Param("user_name") String user, @Param("password") String password); 7 8 @RequestLine("POST /") 9 @Headers("Content-Type: application/json") 10 // json curly braces must be escaped! 11 @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") 12 void json(@Param("user_name") String user, @Param("password") String password); 13 }
注意示例中需要添加对gson的支持
feign发送https信任所有证书的代码:
1 final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { 2 @Override 3 public void checkClientTrusted( 4 java.security.cert.X509Certificate[] chain, 5 String authType) { 6 } 7 8 @Override 9 public void checkServerTrusted( 10 java.security.cert.X509Certificate[] chain, 11 String authType) { 12 } 13 14 @Override 15 public java.security.cert.X509Certificate[] getAcceptedIssuers() { 16 return null; 17 } 18 }}; 19 final SSLContext sslContext = SSLContext.getInstance("TLSv1"); 20 sslContext.init(null, trustAllCerts, 21 new java.security.SecureRandom()); 22 // Create an ssl socket factory with our all-trusting manager 23 final SSLSocketFactory sslSocketFactory = sslContext 24 .getSocketFactory(); 25 Feign.builder().client(new Client.Default(sslSocketFactory, (s, sslSession) -> true));
三、在SpringCloud中使用Feign
比如说注册中心有如下服务:
1)application.yml的配置:
spring: application: name: demo-consumer eureka: client: service-url: defaultZone: http://localhost:8080/eureka,http://localhost:8081/eureka server: port: 8090
2)创建接口
1 package com.bdqn.lyrk.consumer.demo.api; 2 3 import org.springframework.cloud.netflix.feign.FeignClient; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 6 @FeignClient("demo") 7 public interface DemoConfigService { 8 9 @RequestMapping("/demo.do") 10 String demoService(); 11 }
注意在接口上加上注解:@FeignClient("demo") 注解里的参数是在eureka注册的服务名
3)编写启动类:
package com.bdqn.lyrk.consumer.demo; import com.bdqn.lyrk.consumer.demo.api.DemoConfigService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.ConfigurableApplicationContext; @EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class DemoConsumerProvider { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoConsumerProvider.class, args); DemoConfigService demoConfigService = applicationContext.getBean(DemoConfigService.class); System.out.println(demoConfigService.demoService()); } }
注意在启动类上加上@EnableFeignClients来开启Feign功能
运行后输出:
此时我们可以发现使用feign客户端我们访问服务的代码简洁了好多
4) feign多参数设置方法
service-api模块的接口定义: public interface IBillService { @PostMapping("/queryBill") List<BillDTO> queryOrders(@RequestBody BillsVO billsVO); } 相关服务实现类: @RestController public class BillServiceImpl implements IBillService { @Autowired private BillMapper billMapper; @PostMapping("/queryBill") @Override public List<BillDTO> queryOrders(@RequestBody BillsVO billsVO) { return billMapper.query(BeanMap.create(billsVO)); } } 注意 需要在接口定义与实现类的参数上加@RequestBody注解
四、feign中的使用Hystrix
1) 在@FeignClient中有两个属性我们值得关注一下,它们分别是fallBack和fallBackFactory,当然我们系统里要添加Hystrix支持并且在属性文件里设置:
feign.hystrix.enabled=true
同样我们要在启动类里加上@EnableCircuitBreaker注解打开Hystrix保护
2) fallBack属性很简单,用来设置降级方法,当feign请求服务失败时所调用的方法, 这里我给出接口的例子:
首先定义一个接口:IOrderService
package com.bdqn.lyrk.service.api; import com.bdqn.lyrk.service.dto.OrderDTO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; public interface IOrderService { @GetMapping("/orderId/{orderId}") OrderDTO getOrderById(@PathVariable("orderId") Integer orderId); @GetMapping("/errorOrder") OrderDTO getErrorOrder(); }
其次定义Feign的接口OrderServiceClient继承IOrderService
package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.api.IOrderService; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Primary; @Primary @FeignClient(value = "ORDER-SERVER", fallBack="FailedOrderServiceClientImpl.class") public interface OrderServiceClient extends IOrderService { }
由于IOrderService不在同一个项目里,而且SpringCloud不推荐服务端和客户端使用同一个接口,所以我采用继承的方式,注意加上@Primary注解以免使用@Autowired时注入失败
在定义实现类:
package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.dto.OrderDTO; import org.springframework.stereotype.Component; @Component public class FailedOrderServiceClientImpl implements OrderServiceClient { @Override public OrderDTO getOrderById(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(orderId); orderDTO.setOrderName("服务中失败的订单,id为:" + orderId); return null; } @Override public OrderDTO getErrorOrder() { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName("服务中失败的订单"); orderDTO.setId(-1); return orderDTO; } }
最后@FeignClient中设置属性fallBack="FailedOrderServiceClientImpl.class" 就可以了
3) 当我们需要封装服务端的异常信息时,可以指定fallbackFactory属性,请看下面的例子:
package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.dto.OrderDTO; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class OrderServiceFallbackFactoryImpl implements FallbackFactory<OrderServiceClient> { @Override public OrderServiceClient create(Throwable cause) { return new OrderServiceClient() { @Override public OrderDTO getOrderById(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName(cause.getMessage()); return orderDTO; } @Override public OrderDTO getErrorOrder() { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName(cause.getMessage()); return orderDTO; } }; } }
注意:FallbackFactory的泛型参数一定要指定为@FeignClient修饰的接口,同时不建议fallback与fallbackFactory同时使用
最后 我贴一下服务端的实现代码:
package com.bdqn.lyrk.springcloud.order.service; import com.bdqn.lyrk.service.api.IOrderService; import com.bdqn.lyrk.service.dto.OrderDTO; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; @RestController public class OrderServiceImpl implements IOrderService { @HystrixCommand(fallbackMethod = "errorDTO", commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")} ) @GetMapping("/orderId/{orderId}") @Override public OrderDTO getOrderById(@PathVariable("orderId") Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(orderId); orderDTO.setOrderName("订单ID为" + orderId + "的订单"); try { TimeUnit.SECONDS.sleep(orderId); } catch (InterruptedException e) { throw new RuntimeException(e); } return orderDTO; } @Override public OrderDTO getErrorOrder() { System.out.println(1 / 0); return null; } public OrderDTO errorDTO(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(-1); orderDTO.setOrderName("错误的订单,请重试"); return orderDTO; } }
对于Hystrix可以参考:SpringCloud学习之Hystrix