关于feign:Feign的基础使用模板

Feign是一个申明式的http客户端,官网地址:https://github.com/OpenFeign/feign其作用就是帮忙咱们优雅的实现http申请的发送 一 根本应用1 在消费者中引入依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>2 减少启动注解在消费者启动类上增加 @EnableFeignClients 注解,提供者可不增加 3 编写 Feign 接口@FeignClient("userservice") //服务名称public interface UserClient { @GetMapping("/hello/feignTest/{id}") //地址必须与服务中的地址统一 String feignTest(@PathVariable("id") String id);}//提供者中代码 @RestController@RequestMapping("/hello")public class Hello { @GetMapping("/hello") private String hello() { return "userHello"; } @GetMapping("feignTest/{id}") private String feignTest(@PathVariable("id") String id){ return "feignTest, ID:"+id; }}4 注入调用//消费者中注入调用 @RestController@RequestMapping("/hello")public class Hello { @Autowired private UserClient userClient; @GetMapping("/hello") private String hello() { return "orderHello"; } @GetMapping("/feign/{id}") private String feign(@PathVariable("id")String id ){ return userClient.feignTest(id); }}二 自定义配置Feign能够反对很多的自定义配置,如下表所示: ...

April 6, 2023 · 2 min · jiezi

关于feign:open-feign-调用超时与重试

1. 前言在 spring cloud 各种组件中,我最早接触的就是 open feign,但素来没有讲过它。起因是因为感觉它简略,无非就是个服务调用,在代码层面上也很简略,没有啥可说的。 但为什么明天来讲呢: 服务调用看起来简略,但实则是微服务治理中很重要的一环。咱们当初微服务有上百个,如何进步微服务之间调用的稳定性,是老大难的问题。网络或高并发等起因,简直每天都有个别报错是 feign 调用的。open feign 其实是封装了负载平衡、熔断等其余组件的,把握它是有难度的。1. feign 与 openfeignfeign 是 netflix 公司写的,是 spring cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,是 spring cloud 中的第一代负载平衡客户端。起初 netflix 讲 feign 开源给了 spring cloud 社区,也随之停更。 openfeig 是 spring cloud 本人研发的,在 feign的根底上反对了 spring MVC 的注解,如 @RequesMapping 等等。是 spring cloud中的第二代负载平衡客户端。 尽管 feign 停更了,之前我也介绍过 dobbo 这类代替产品。但在服务调用这个畛域,open feign 还是有它的一席之地。 2. spring cloud 版本更迭先讲讲 spring cloud 的版本迭代吧。在2020年之前,spring cloud 等版本号是依照伦敦地铁站号命名的(ABCDEFGH): AngleBrixtonCamdenDalstonEdgwareFinchleyGreenWichHoxton但从2020年开始,版本号开始以年份命名,如:2020.0.1。 spring cloud 与 spring boot 版本的对应关系如下: ...

February 17, 2023 · 5 min · jiezi

关于feign:Spring-CloudFeign调用异常触发降级后如何捕获异常

一、问题背景在Spring Cloud的微服务架构中,通常微服务之间通过feign/openfeign来进行http调用,并且启用hystrix并配置降级策略fallback,能够在http调用异样时触发降级,代码如下 @FeignClient(name = "resourceFeign", fallback = ResourceFeignFallback.class)public interface ResourceFeign { @PostMapping("/resource/list") Map<String, Object> resourceList();}@Componentpublic class ResourceFeignFallback implements ResourceFeign { @Override public Map<String, Object> resourceList() { Map result = new HashMap<>(); result.put("code", 500); result.put("msg", "申请异样"); return result; }}然而这种形式在http调用异样时,会间接执行降级策略,而无奈捕捉到具体的异样信息,这种状况如何解决? 二、解决办法所以咱们通常应用另一种形式,通过配置fallbackFactory来捕捉异样信息,代码如下 @FeignClient(name = "resourceFeign", fallbackFactory = ResourceFeignFallbackFactory.class)public interface ResourceFeign { @PostMapping("/resource/list") Map<String, Object> resourceList();}@Component@Slf4jpublic class ResourceWebFeignFallbackFactory implements FallbackFactory<ResourceFeign> { @Override public ResourceFeign create(Throwable throwable) { // 捕捉异样 log.error(throwable.getMessage()); return new ResourceFeign() { @Override public Map<String, Object> resourceList() { Map result = new HashMap<>(); result.put("code", 500); result.put("msg", "申请异样"); return result; } }; }}这样就能够捕捉到http调用的异样信息 ...

January 30, 2023 · 1 min · jiezi

关于feign:107FeignClient调用携带登录用户信息

定义ContextHolderContextHolder用来寄存和获取线程变量中的 用户id、用户名称、Token等信息。 /** * 获取以后线程变量中的 用户id、用户名称、Token等信息 * 留神: 必须在网关通过申请头的办法传入,同时在Interceptor拦截器设置值。 否则这里无奈获取 * * */public class SecurityContextHolder{ private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>(); public static void set(String key, Object value) { Map<String, Object> map = getLocalMap(); map.put(key, value == null ? StringUtils.EMPTY : value); } public static String get(String key) { Map<String, Object> map = getLocalMap(); return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); } public static <T> T get(String key, Class<T> clazz) { Map<String, Object> map = getLocalMap(); return StringUtils.cast(map.getOrDefault(key, null)); } public static Map<String, Object> getLocalMap() { Map<String, Object> map = THREAD_LOCAL.get(); if (map == null) { map = new ConcurrentHashMap<String, Object>(); THREAD_LOCAL.set(map); } return map; } public static void setLocalMap(Map<String, Object> threadLocalMap) { THREAD_LOCAL.set(threadLocalMap); } public static String getUserId() { return get(SecurityConstants.DETAILS_USER_ID); } public static void setUserId(String account) { set(SecurityConstants.DETAILS_USER_ID, account); } public static String getUserName() { return get(SecurityConstants.DETAILS_USERNAME); } public static void setUserName(String username) { set(SecurityConstants.DETAILS_USERNAME, username); } public static String getUserKey() { return get(SecurityConstants.USER_KEY); } public static void setUserKey(String userKey) { set(SecurityConstants.USER_KEY, userKey); } public static void remove() { THREAD_LOCAL.remove(); } public static void setUserType(String userType) { set(SecurityConstants.USER_TYPE, userType); } public static String getUserType() { return get(SecurityConstants.USER_TYPE); }}利用模块的拦截器寄存登录信息@Componentpublic class SjcjInterceptor implements HandlerInterceptor { @Autowired private RemoteUserService remoteUserService; @Autowired private TokenService tokenService; //超管的id=1 @Value("${admin.userId:1}") private String adminUserId; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String userId = SecurityContextHolder.getUserId(); if(StringUtils.isEmpty(userId)){ userId = adminUserId; R<LoginUser> userResult = remoteUserService.getUserIdInfo(userId, SecurityConstants.INNER); if (R.FAIL == userResult.getCode()) { throw new ServiceException(userResult.getMsg()); } LoginUser userInfo = userResult.getData(); Map<String, Object> rspMap = tokenService.createToken(userInfo, 1); SecurityContextHolder.setUserId(userInfo.getUserid()); SecurityContextHolder.setUserName(userInfo.getUsername()); SecurityContextHolder.set(SecurityConstants.LOGIN_USER, userInfo); SecurityContextHolder.set(SecurityConstants.AUTHORIZATION_HEADER,rspMap.get("access_token")); } return true; } }feign 申请拦截器该拦截器从申请requst取用户信息,或者从SecurityContextHolder取登录信息,并保留到RequestTemplate。 ...

November 7, 2022 · 2 min · jiezi

关于feign:撸了一个-Feign-增强包-V20-升级版

前言大略在两年前我写过一篇 撸了一个 Feign 加强包,过后筹备是利用 SpringBoot + K8s 构建利用,这个库能够相似于 SpringCloud 那样联合 SpringBoot 应用申明式接口来达到服务间通信的目标。 但前期因为技术栈发生变化(改为 Go),导致该我的项目只实现了根本需要后就搁置了。 偶合的时最近外部有局部我的项目又打算采纳 SpringBoot + K8s 开发,于是便着手持续保护;现曾经外部迭代了几个版本比较稳定了,也减少了一些实用功能,在此分享给大家。 https://github.com/crossoverJie/feign-plus 首先是新增了一些 features: 更加对立的 API。对立的申请、响应、异样日志记录。自定义拦截器。Metric 反对。异样传递。示例联合下面提到的一些个性做一些简略介绍,对立的 API 次要是在应用层面: 在上一个版本中申明接口如下: @FeignPlusClient(name = "github", url = "${github.url}")public interface Github { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<GitHubRes> contributors(@Param("owner") String owner, @Param("repo") String repo);}其中的 @RequestLine 等注解都是应用 feign 包所提供的。 这次更新后改为如下形式: @RequestMapping("/v1/demo")@FeignPlusClient(name = "demo", url = "${feign.demo.url}", port = "${feign.demo.port}")public interface DemoApi { @GetMapping("/id") String sayHello(@RequestParam(value = "id") Long id); @GetMapping("/id/{id}") String id(@PathVariable(value = "id") Long id); @PostMapping("/create") Order create(@RequestBody OrderCreateReq req); @GetMapping("/query") Order query(@SpringQueryMap OrderQueryDTO dto);}相熟的滋味,根本都是 Spring 自带的注解,这样在应用上学习老本更低,同时与我的项目中本来的接口写法保持一致。 ...

May 6, 2022 · 2 min · jiezi

关于feign:feign请求返回值反序列LocalDateTime异常记录

前言最近项目组用feign调用近程服务,生产端报了如下一个异样从异样信息能够得出localdatime反序列化出了异样,而这个异样又是因为jackson无奈解决导致。因而咱们能够为jackson的ObjectMapper适配一下 解决办法1、在pom.xml引入 <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> <version>${jackson.version}</version> </dependency>注: jackson-datatype-jsr310这是用来反对jsr310标准的工夫,jackson-datatype-jdk8用来反对新的特定于JDK8的类型,例如Optional 2、替换默认的ObjectMapper@Configurationpublic class LocalDateTimeConfig { public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module()); objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT)); objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule()); return objectMapper; }疑难点:为什么替换了默认的ObjectMapper后,feign就能够解决LocalDateTime答案就在 @Configuration(proxyBeanMethods = false)public class FeignClientsConfiguration { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder( new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); }}而messageConverters默认的转换器是依据HttpMessageConvertersAutoConfiguration而来 ...

July 27, 2021 · 1 min · jiezi

关于feign:已解决Feign上传文件相关配置

服务构造很简略,消费者通过Feign调用服务提供者的服务。provider有一个上传文件性能,依据接口文档,参数类型为File,与其余参数并列搁置。 关键点:1,路由层File参数应用@RequestPart注解;2,接口层增加consumes = MediaType.MULTIPART_FORM_DATA_VALUE阐明。 路由层 @PostMapping(value = "/notify/upload") @ApiOperation("上传告诉音") public String upload ( AudioRecordScooperReq audioRecordScooperReq, @RequestPart("file") MultipartFile file) { audioRecordScooperReq.setToken(tokenValue); return audioRecordFeign.upload(audioRecordScooperReq, file); }Feign接口层 // 上传告诉音 - @PostMapping(value = "/scooper-record/data/notify/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) String upload(@SpringQueryMap AudioRecordScooperReq audioRecordScooperReq, @RequestBody() MultipartFile file);

July 6, 2021 · 1 min · jiezi

关于feign:springCould整合feign出现required-a-bean-of-type-xxx-not-be-found

一、问题springCould整合feign提醒required a bean of type xxx that could not be found Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.2021-05-23 11:51:02.777 ERROR 17160 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***************************APPLICATION FAILED TO START***************************Description:Field oAuth2FeignClient in com.quantsmart.service.impl.SysLoginServiceImpl required a bean of type 'com.quantsmart.feign.OAuth2FeignClient' that could not be found.The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true)Action:Consider defining a bean of type 'com.quantsmart.feign.OAuth2FeignClient' in your configuration.Process finished with exit code 1com/quantsmart/feign/OAuth2FeignClient.java ...

May 23, 2021 · 2 min · jiezi

关于feign:Feign系列

Feign - 源码剖析 先占个坑。。。当前再来说应用

December 24, 2020 · 1 min · jiezi

关于feign:Feign-源码分析

例子这次先不写了。。。间接源码走起。局部设计跟Ribbon一样,这边就不在累述,倡议先看Ribbon系列。仍然从spring.factories说起。留神到这里有这几个类:FeignAutoConfiguration、FeignRibbonClientAutoConfiguration。 启动FeignAutoConfiguration加载FeignContext,这里会赋值FeignClientSpecification的汇合,前面说FeignClientSpecification类怎么来的,其实跟Ribbon用的是同一个办法。FeignContext的defaultConfigType是FeignClientsConfiguration.class,这个和Ribboon用法一样,前面会加载。 @Autowired(required = false)private List<FeignClientSpecification> configurations = new ArrayList<>();@Beanpublic FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context;}另一个加载的是HystrixTargeter,尽管是HystrixTargeter,如果Feign.Builder不是feign.hystrix.HystrixFeign.Builder,前面调用的还是Feign.Builder的target办法。 @Configuration(proxyBeanMethods = false)@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")protected static class HystrixFeignTargeterConfiguration { @Bean @ConditionalOnMissingBean public Targeter feignTargeter() { return new HystrixTargeter(); }}FeignRibbonClientAutoConfiguration加载CachingSpringLoadBalancerFactory,这个类会注入SpringClientFactory,看过了Ribbon,是不是就晓得了他要做什么。没错,他会创立一个ILoadBalancer,用于负载平衡。 @Bean@Primary@ConditionalOnMissingBean@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")public CachingSpringLoadBalancerFactory cachingLBClientFactory( SpringClientFactory factory) { return new CachingSpringLoadBalancerFactory(factory);}FeignRibbonClientAutoConfiguration还import了DefaultFeignLoadBalancedConfiguration类,在这个类,会创立一个LoadBalancerFeignClient,前面须要的Client就是他了。 @Bean@ConditionalOnMissingBeanpublic Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);} @EnableFeignClients咱们应用feign的时候,都会应用这个注解,注解里Import了FeignClientsRegistrar这个类,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,调用registerBeanDefinitions办法。这个办法做了两件事,一件事是注册FeignClientSpecification类,前面会注入到下面的FeignContext中,这个用法跟Ribbon - 几种自定义负载平衡策略提的一样。另外一件事就是创立FactoryBean。 ...

December 24, 2020 · 3 min · jiezi

关于feign:Feignhystrix的配置有了Apollo还用Archaius吗

前言feign是一个杰出的Http申请客户端封装框架,feign-hystrix是整个框架体系里的其中一个模块,用来集成hystrix熔断器的,feign和hystrix这两个我的项目都是Netflix开源的(openfeign已独立迭代)。在spring boot我的项目中,能够应用spring-cloud-starter-openfeign模块,无缝集成feign和hystrix。然而,hystrix默认采纳的Archaius来驱动hystrix的配置零碎,无缝集成的同时,也会把archaius-core给引入进来。archaius是一个配置核心我的项目,相似spring cloud config和apollo,如果archaius只是作为hystrix配置的驱动,我的项目启动时会打印烦人的正告日志,提醒你没有配置任何动静配置源。当我的项目里曾经采纳了apollo时,能够间接剔除掉Archaius,他们的功能定位高度重合了。间接剔除依赖,会导致本来配置在spring中的配置不失效,博主也是在不小心剔除后,遇到了配置不失效的问题,才有了本篇博文,记录下过程。只有稍加改变,联合apollo配置动静下发能力,能够做到hystrix的配置实时动静失效。 feign:https://github.com/OpenFeign/...hystrix:https://github.com/Netflix/Hy...archaius:https://github.com/Netflix/ar...apollo:https://github.com/ctripcorp/...archaius正告日志2020-12-10 11:19:41.766 WARN 12835 --- [ main] c.n.c.sources.URLConfigurationSource : No URLs will be polled as dynamic configuration sources.2020-12-10 11:19:41.766 INFO 12835 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.2020-12-10 11:19:41.772 WARN 12835 --- [ main] c.n.c.sources.URLConfigurationSource : No URLs will be polled as dynamic configuration sources.2020-12-10 11:19:41.772 INFO 12835 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.咱们遇到的问题在一次系统优化重构中,博主给整个我的项目来了一个360的大瘦身,把所有的未应用的依赖通通给挪走了。其中就包含了spring-cloud-starter-openfeign模块的archaius-core依赖。因为咱们曾经应用了apollo配置核心,archaius在这个我的项目里显得很多余,而且还会打印烦人的正告日志。所以就间接排除了,如: ...

December 10, 2020 · 1 min · jiezi

关于feign:springcloud中feign的FeignClient应该写在哪里

前言最近项目组拿了友商的springcloud alibaba我的项目来进行革新,在翻阅他们的代码时候,发现他们把@FeignClient写在服务提供方的API上,他们这样的写法胜利的引起我的留神,于是抱着好学的心态求教友商的开发人员,于是一篇水文就这么诞生了 友商开发人员解惑友商服务提供方的API形如下 @FeignClient(name = "feign-provider",path = UserService.INTER_NAME,contextId = "user")public interface UserService { String INTER_NAME = "user"; @GetMapping(value = "/{id}") UserDTO getUserById(@PathVariable("id") Long id);}我过往的经验是@FeignClient是写在生产端上,就是在生产端上会写一个接口继承服务端API接口,再打上@FeignClient,并指明fallback,形如下 @FeignClient(name = "feign-provider",path = UserService.INTER_NAME,contextId = "user",fallback = UserServiceClientFallBack.class)public interface UserServiceClient extends UserService {}我将我过往的写法通知友商开发人员,友商的开发人员对我说,你生产端还要本人写接口啊,那么麻烦。咱们这种写法,生产端仅需pom文件引入API包,在调用方上打个 @Autowired标注,就能够调用服务提供方的接口。额,他们的说法真的很有情理,惋惜没压服我,于是我抛出第二个问题,你们间接把@FeignClient写在服务提供方的API上,那如果生产端要进行熔断降级,要怎么做? 友商给我答案是用sentinel啊,间接在sentinel的控制面板上配置熔断降级策略,形如下 触发的后果形如下看着曾经实现了熔断的成果,然而我这种成果还不是我想要的,于是我又问,如果在面板上进行熔断后,我要记录熔断日志,该怎么做?友商给我的答案是这时候你就得采纳分布式链路追踪组件啊比方skywalking,反正你记录日志,不也是为了排查问题不便,要懂得变通。额,好吧,最初我再抛出一个问题,既然你们间接把@FeignClient写在服务提供方的API上,那如果生产端想直连某台服务提供方进行本地联调,那要怎么做?友商的答复是他们开发的时候不会有这种场景,大家都是直连开发环境联调 如果是我来实现,我会把@FeignClient写在哪里?毋庸置疑的,我会把@FeignClient写在生产端上,因为从职责上,只有生产端能力明确晓得本人要调用哪个服务提供方,比方直连哪个服务提供方进行调试,如果间接把@FeignClient写在服务提供方的API上,生产端就很难按需定制。其次因为本人对sentinel也停留在据说过,也没理论用过,也是因为这次友商的我的项目了用springcloud alibaba的全家桶,才接触了下。前面在和友商探讨@FeignClient的搁置问题后,回来在尝试了一把,发现友商说的在sentinel配置熔断降级不全面,因为我后边尝试让服务提供方超时或者报错,此时拜访页面就会呈现和后边我就按本人的想法,在生产端上会写一个接口继承服务端API接口,再打上@FeignClient,并指明fallback,形如下 @FeignClient(name = "feign-provider",path = UserService.INTER_NAME,contextId = "user",fallback = UserServiceClientFallBack.class)public interface UserServiceClient extends UserService {}@Component@Slf4jpublic class UserServiceClientFallBack implements UserServiceClient{ @Override public UserDTO getUserById(Long id) { log.info("id:{} fallback",id); return UserDTO.builder().id(id).userName("fallback").build(); }}在application.yml激活sentinel对feign的反对 ...

November 28, 2020 · 1 min · jiezi

关于feign:FeignZuul为何不启用HystrixRibbon重试

Feign不举荐启用HystrixFeign 部署地位: 业务服务之间调用增加Hystrix会造成凌乱,故障点难以确认Zuul不举荐启用Ribbon重试Zuul 部署地位: 在最后面作为入口zuul如果启用重试,可能造成后盾多台服务器压力过大

September 30, 2020 · 1 min · jiezi

跟我学SpringCloud-第三篇服务的提供与Feign调用

Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 上一篇,我们介绍了注册中心的搭建,包括集群环境吓注册中心的搭建,这篇文章介绍一下如何使用注册中心,创建一个服务的提供者,使用一个简单的客户端去调用服务端提供的服务。 本篇文章中需要三个角色,分别是服务的提供者,服务的消费者,还有一个是上一篇文章的主角——注册中心Eureka(使用单机版本即可,本篇的示例也会使用单机版本的Eureka)。 整体流程为: 先启动注册中心Eureka启动服务的提供者将提供服务,并将服务注册到注册中心Eureka上启动服务的消费者,在注册中心中找到服务并完成消费1. 服务提供者1. pom.xml<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springcloud</groupId> <artifactId>producer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>producer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2. 配置文件application.ymlserver: port: 8080spring: application: name: spring-cloud-producereureka: client: service-url: defaultZone: http://localhost:8761/eureka/3. 启动类ProducerApplication.java增加@EnableEurekaClient,如果是其他注册中心可以使用注解@EnableDiscoveryClient来进行服务的注册 ...

September 10, 2019 · 2 min · jiezi

聊聊feign的RequestInterceptor

序本文主要研究一下feign的RequestInterceptor RequestInterceptorfeign-core-10.2.3-sources.jar!/feign/RequestInterceptor.java public interface RequestInterceptor { /** * Called for every request. Add data using methods on the supplied {@link RequestTemplate}. */ void apply(RequestTemplate template);}RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptorBasicAuthRequestInterceptorfeign-core-10.2.3-sources.jar!/feign/auth/BasicAuthRequestInterceptor.java public class BasicAuthRequestInterceptor implements RequestInterceptor { private final String headerValue; /** * Creates an interceptor that authenticates all requests with the specified username and password * encoded using ISO-8859-1. * * @param username the username to use for authentication * @param password the password to use for authentication */ public BasicAuthRequestInterceptor(String username, String password) { this(username, password, ISO_8859_1); } /** * Creates an interceptor that authenticates all requests with the specified username and password * encoded using the specified charset. * * @param username the username to use for authentication * @param password the password to use for authentication * @param charset the charset to use when encoding the credentials */ public BasicAuthRequestInterceptor(String username, String password, Charset charset) { checkNotNull(username, "username"); checkNotNull(password, "password"); this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset)); } /* * This uses a Sun internal method; if we ever encounter a case where this method is not * available, the appropriate response would be to pull the necessary portions of Guava's * BaseEncoding class into Util. */ private static String base64Encode(byte[] bytes) { return Base64.encode(bytes); } @Override public void apply(RequestTemplate template) { template.header("Authorization", headerValue); }}BasicAuthRequestInterceptor实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorization的headerBaseRequestInterceptorspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/encoding/BaseRequestInterceptor.java ...

July 16, 2019 · 3 min · jiezi

聊聊spring-cloud的FeignClientFactoryBean

序本文主要研究一下spring cloud的FeignClientFactoryBean FeignClientFactoryBeanspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientFactoryBean.java class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { /*********************************** * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some * lifecycle race condition. ***********************************/ private Class<?> type; private String name; private String url; private String contextId; private String path; private boolean decode404; private ApplicationContext applicationContext; private Class<?> fallback = void.class; private Class<?> fallbackFactory = void.class; @Override public void afterPropertiesSet() throws Exception { Assert.hasText(this.contextId, "Context id must be set"); Assert.hasText(this.name, "Name must be set"); } @Override public Object getObject() throws Exception { return getTarget(); } @Override public Class<?> getObjectType() { return this.type; } @Override public boolean isSingleton() { return true; } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.applicationContext = context; } <T> T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient) client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); } private String cleanPath() { String path = this.path.trim(); if (StringUtils.hasLength(path)) { if (!path.startsWith("/")) { path = "/" + path; } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } } return path; } //......}FeignClientFactoryBean实现了FactoryBean的getObject、getObjectType、isSingleton方法;实现了InitializingBean的afterPropertiesSet方法;实现了ApplicationContextAware的setApplicationContext方法getObject调用的是getTarget方法,它从applicationContext取出FeignContext,然后构造Feign.Builder并设置了logger、encoder、decoder、contract,之后通过configureFeign根据FeignClientProperties来进一步配置Feign.Builder的retryer、errorDecoder、request.Options、requestInterceptors、queryMapEncoder、decode404初步配置完Feign.Builder之后再判断是否需要loadBalance,如果需要则通过loadBalance方法来设置,不需要则在Client是LoadBalancerFeignClient的时候进行unwrapFeignClientPropertiesspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientProperties.java ...

July 16, 2019 · 4 min · jiezi

聊聊spring-cloud的FeignClientBuilder

序本文主要研究一下spring cloud的FeignClientBuilder FeignClientBuilderspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/FeignClientBuilder.java public class FeignClientBuilder { private final ApplicationContext applicationContext; public FeignClientBuilder(final ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public <T> Builder<T> forType(final Class<T> type, final String name) { return new Builder<>(this.applicationContext, type, name); } /** * Builder of feign targets. * * @param <T> type of target */ public static final class Builder<T> { private FeignClientFactoryBean feignClientFactoryBean; private Builder(final ApplicationContext applicationContext, final Class<T> type, final String name) { this.feignClientFactoryBean = new FeignClientFactoryBean(); this.feignClientFactoryBean.setApplicationContext(applicationContext); this.feignClientFactoryBean.setType(type); this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name)); this.feignClientFactoryBean.setContextId(FeignClientsRegistrar.getName(name)); // preset default values - these values resemble the default values on the // FeignClient annotation this.url("").path("").decode404(false).fallback(void.class) .fallbackFactory(void.class); } public Builder url(final String url) { this.feignClientFactoryBean.setUrl(FeignClientsRegistrar.getUrl(url)); return this; } public Builder contextId(final String contextId) { this.feignClientFactoryBean.setContextId(contextId); return this; } public Builder path(final String path) { this.feignClientFactoryBean.setPath(FeignClientsRegistrar.getPath(path)); return this; } public Builder decode404(final boolean decode404) { this.feignClientFactoryBean.setDecode404(decode404); return this; } public Builder fallback(final Class<T> fallback) { FeignClientsRegistrar.validateFallback(fallback); this.feignClientFactoryBean.setFallback(fallback); return this; } public Builder fallbackFactory(final Class<T> fallbackFactory) { FeignClientsRegistrar.validateFallbackFactory(fallbackFactory); this.feignClientFactoryBean.setFallbackFactory(fallbackFactory); return this; } /** * @param <T> the target type of the Feign client to be created * @return the created Feign client */ public <T> T build() { return this.feignClientFactoryBean.getTarget(); } }}FeignClientBuilder提供了forType静态方法用于创建Builder;Builder的构造器创建了FeignClientFactoryBean,其build方法使用FeignClientFactoryBean的getTarget()来创建目标feign client实例public class FeignClientBuilderTests { @Rule public ExpectedException thrown = ExpectedException.none(); private FeignClientBuilder feignClientBuilder; private ApplicationContext applicationContext; private static Object getDefaultValueFromFeignClientAnnotation( final String methodName) { final Method method = ReflectionUtils.findMethod(FeignClient.class, methodName); return method.getDefaultValue(); } private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder, final String fieldName, final Object expectedValue) { final Field factoryBeanField = ReflectionUtils .findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean"); ReflectionUtils.makeAccessible(factoryBeanField); final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils .getField(factoryBeanField, builder); final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class, fieldName); ReflectionUtils.makeAccessible(field); final Object value = ReflectionUtils.getField(field, factoryBean); assertThat(value).as("Expected value for the field '" + fieldName + "':") .isEqualTo(expectedValue); } @Before public void setUp() { this.applicationContext = Mockito.mock(ApplicationContext.class); this.feignClientBuilder = new FeignClientBuilder(this.applicationContext); } @Test public void safetyCheckForNewFieldsOnTheFeignClientAnnotation() { final List<String> methodNames = new ArrayList(); for (final Method method : FeignClient.class.getMethods()) { methodNames.add(method.getName()); } methodNames.removeAll( Arrays.asList("annotationType", "value", "serviceId", "qualifier", "configuration", "primary", "equals", "hashCode", "toString")); Collections.sort(methodNames); // If this safety check fails the Builder has to be updated. // (1) Either a field was removed from the FeignClient annotation and so it has to // be removed // on this builder class. // (2) Or a new field was added and the builder class has to be extended with this // new field. assertThat(methodNames).containsExactly("contextId", "decode404", "fallback", "fallbackFactory", "name", "path", "url"); } @Test public void forType_preinitializedBuilder() { // when: final FeignClientBuilder.Builder builder = this.feignClientBuilder .forType(FeignClientBuilderTests.class, "TestClient"); // then: assertFactoryBeanField(builder, "applicationContext", this.applicationContext); assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); assertFactoryBeanField(builder, "name", "TestClient"); assertFactoryBeanField(builder, "contextId", "TestClient"); // and: assertFactoryBeanField(builder, "url", getDefaultValueFromFeignClientAnnotation("url")); assertFactoryBeanField(builder, "path", getDefaultValueFromFeignClientAnnotation("path")); assertFactoryBeanField(builder, "decode404", getDefaultValueFromFeignClientAnnotation("decode404")); assertFactoryBeanField(builder, "fallback", getDefaultValueFromFeignClientAnnotation("fallback")); assertFactoryBeanField(builder, "fallbackFactory", getDefaultValueFromFeignClientAnnotation("fallbackFactory")); } @Test public void forType_allFieldsSetOnBuilder() { // when: final FeignClientBuilder.Builder builder = this.feignClientBuilder .forType(FeignClientBuilderTests.class, "TestClient").decode404(true) .fallback(Object.class).fallbackFactory(Object.class).path("Path/") .url("Url/"); // then: assertFactoryBeanField(builder, "applicationContext", this.applicationContext); assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); assertFactoryBeanField(builder, "name", "TestClient"); // and: assertFactoryBeanField(builder, "url", "http://Url/"); assertFactoryBeanField(builder, "path", "/Path"); assertFactoryBeanField(builder, "decode404", true); assertFactoryBeanField(builder, "fallback", Object.class); assertFactoryBeanField(builder, "fallbackFactory", Object.class); } @Test public void forType_build() { // given: Mockito.when(this.applicationContext.getBean(FeignContext.class)) .thenThrow(new ClosedFileSystemException()); // throw an unusual exception // in the // FeignClientFactoryBean final FeignClientBuilder.Builder builder = this.feignClientBuilder .forType(TestClient.class, "TestClient"); // expect: 'the build will fail right after calling build() with the mocked // unusual exception' this.thrown.expect(Matchers.isA(ClosedFileSystemException.class)); builder.build(); }}FeignClientBuilderTests验证了safetyCheckForNewFieldsOnTheFeignClientAnnotation、forType_preinitializedBuilder、forType_allFieldsSetOnBuilder、forType_build小结FeignClientBuilder提供了forType静态方法用于创建Builder;Builder的构造器创建了FeignClientFactoryBean,其build方法使用FeignClientFactoryBean的getTarget()来创建目标feign client ...

July 15, 2019 · 3 min · jiezi

聊聊spring-cloud的RetryableFeignLoadBalancer

序本文主要研究一下spring cloud的RetryableFeignLoadBalancer RetryableFeignLoadBalancerspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser { private final LoadBalancedRetryFactory loadBalancedRetryFactory; public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector, LoadBalancedRetryFactory loadBalancedRetryFactory) { super(lb, clientConfig, serverIntrospector); this.loadBalancedRetryFactory = loadBalancedRetryFactory; this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig)); } @Override public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) throws IOException { final Request.Options options; if (configOverride != null) { RibbonProperties ribbon = RibbonProperties.from(configOverride); options = new Request.Options(ribbon.connectTimeout(this.connectTimeout), ribbon.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory .createRetryPolicy(this.getClientName(), this); RetryTemplate retryTemplate = new RetryTemplate(); BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory .createBackOffPolicy(this.getClientName()); retryTemplate.setBackOffPolicy( backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); RetryListener[] retryListeners = this.loadBalancedRetryFactory .createRetryListeners(this.getClientName()); if (retryListeners != null && retryListeners.length != 0) { retryTemplate.setListeners(retryListeners); } retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy() : new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName())); return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() { @Override public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException { Request feignRequest = null; // on retries the policy will choose the server and set it in the context // extract the server and update the request being made if (retryContext instanceof LoadBalancedRetryContext) { ServiceInstance service = ((LoadBalancedRetryContext) retryContext) .getServiceInstance(); if (service != null) { feignRequest = ((RibbonRequest) request .replaceUri(reconstructURIWithServer( new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest(); } } if (feignRequest == null) { feignRequest = request.toRequest(); } Response response = request.client().execute(feignRequest, options); if (retryPolicy != null && retryPolicy.retryableStatusCode(response.status())) { byte[] byteArray = response.body() == null ? new byte[] {} : StreamUtils .copyToByteArray(response.body().asInputStream()); response.close(); throw new RibbonResponseStatusCodeException( RetryableFeignLoadBalancer.this.clientName, response, byteArray, request.getUri()); } return new RibbonResponse(request.getUri(), response); } }, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() { @Override protected RibbonResponse createResponse(Response response, URI uri) { return new RibbonResponse(uri, response); } }); } @Override public RequestSpecificRetryHandler getRequestSpecificRetryHandler( FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) { return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig); } @Override public ServiceInstance choose(String serviceId) { return new RibbonLoadBalancerClient.RibbonServer(serviceId, this.getLoadBalancer().chooseServer(serviceId)); }}RetryableFeignLoadBalancer继承了FeignLoadBalancer,实现了ServiceInstanceChooser接口其构造器根据clientConfig创建了DefaultLoadBalancerRetryHandler;其choose方法使用的是getLoadBalancer().chooseServer,最后通过RibbonLoadBalancerClient.RibbonServer包装返回其execute方法首先创建了LoadBalancedRetryPolicy,进而创建retryTemplate,最后通过retryTemplate.execute来实现重试功能;其RetryCallback的doWithRetry方法在retryContext是LoadBalancedRetryContext的条件下会切换下一个service实例来进行重试小结RetryableFeignLoadBalancer继承了FeignLoadBalancer,对execute方法使用retryTemplate来实现重试,其中在retryContext是LoadBalancedRetryContext的条件下会切换下一个service实例来进行重试 ...

July 13, 2019 · 2 min · jiezi

聊聊spring-cloud的FeignLoadBalancer

序本文主要研究一下spring cloud的FeignLoadBalancer FeignLoadBalancerspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancer.java public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> { private final RibbonProperties ribbon; protected int connectTimeout; protected int readTimeout; protected IClientConfig clientConfig; protected ServerIntrospector serverIntrospector; public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) { super(lb, clientConfig); this.setRetryHandler(RetryHandler.DEFAULT); this.clientConfig = clientConfig; this.ribbon = RibbonProperties.from(clientConfig); RibbonProperties ribbon = this.ribbon; this.connectTimeout = ribbon.getConnectTimeout(); this.readTimeout = ribbon.getReadTimeout(); this.serverIntrospector = serverIntrospector; } @Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; if (configOverride != null) { RibbonProperties override = RibbonProperties.from(configOverride); options = new Request.Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } Response response = request.client().execute(request.toRequest(), options); return new RibbonResponse(request.getUri(), response); } @Override public RequestSpecificRetryHandler getRequestSpecificRetryHandler( RibbonRequest request, IClientConfig requestConfig) { if (this.ribbon.isOkToRetryOnAllOperations()) { return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); } if (!request.toRequest().httpMethod().name().equals("GET")) { return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig); } else { return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); } } @Override public URI reconstructURIWithServer(Server server, URI original) { URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig, this.serverIntrospector, server); return super.reconstructURIWithServer(server, uri); } //......}FeignLoadBalancer继承了AbstractLoadBalancerAwareClient,它的构造器接收ILoadBalancer、IClientConfig、ServerIntrospector,设置的retryHandler为RetryHandler.DEFAULT其execute方法首先构造Request.Options,然后通过request.client().execute来获取Response,最后返回RibbonResponseFeignLoadBalancer还覆盖了getRequestSpecificRetryHandler方法,针对ribbon.isOkToRetryOnAllOperations()来构建不同的RequestSpecificRetryHandler;还覆盖了reconstructURIWithServer方法,它使用RibbonUtils的updateToSecureConnectionIfNeeded来构建URIRibbonRequestspring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancer.java ...

July 12, 2019 · 2 min · jiezi