Feign服务调用深入研究

一、理论基础

1.1、Feign的作用

  • 支持可插拔的HTTP编码器和解码器;
  • 支持Hystrix和它的Fallback;
  • 支持Ribbon的负载均衡;
  • 支持HTTP请求和响应的压缩

1.2、FeignClient的实现原理

参考:https://www.jianshu.com/p/50fd582b739f

(1)在启动类添加@EnableFeignClients注解。其主要功能是初始化FeignClient的配置动态执行client的请求

(2)该注解的源码中@Import(FeignClientsRegistrar.class)是用来初始化FeignClient配置的

(3)FeignClientsRegistrar源码有个中药房方法,该方法有两个方法:

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
  • 第一个方法:用来加载@EnableFeignClients中的defaultConfiguration和@FeignClient中的configuration配置文件;
  • 第二个方法:用来加载@EnableFeignClients中的其他配和@FeignClient中的其他配置。

 

1.3、Feign请求超时问题

注意Feign默认集成了Hystrix容错器

Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。

  • 方法1:该配置是让Hystrix的超时时间改为5秒——hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
  • 方法2:禁用Hystrix的超时时间——hystrix.command.default.execution.timeout.enabled: false

.....待更新

1.4、引入FeignClientsConfiguration支持Hystrix配置:

FeignClientsConfiguration类中定义了Feign.Builder:

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {

        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled")
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }

    }

HystrixFeign.builder加载:

配置feign.hystrix.enabled=true

 1.5、FeignClient的功能定制

(1)使用Apache的Httpclient替换Ribbon/loadbalance配置:

理由:有时候,我们的Feignclient没有启用注册中心

配置:启用FeignClient的url属性来标明被调用方。此时,启用Httpclient的连接池方式可能会比Ribbon的客户端loadbalance方式更好。

参考:https://www.jianshu.com/p/50fd582b739f

二、源码分析

2.1、注册FeignClient配置类和FeignClient BeanDefinition

 在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean

2.2、实例化Feign上下文对象FeignContext

2.3、创建 Feign.builder 对象

2.4、生成负载均衡代理类

Feign调用方发起请求,发送至hystrix的HystrixInvocationHandler,通过服务名称,找到对应方法的methodHandler,methodHandler中封装了loadBalanceClient、retryer、RequestInterceptor等组件,如果引入了sleuth,这几个组件均是sleuth的包装类。然后通过以上组件构造 http请求完成整个过程

2.5、生成默认代理类

2.6、注入到spring容器

具体源码分析参考:https://blog.csdn.net/forezp/article/details/83896098

三、实战

3.1、案例1:普通的服务间调用

@FeignClient(value = "${url.mapping.SYGATEWAY:SYGATEWAY}", path = "/daweb/daportal/", configuration = {
    DataOpenHeaderInterceptor.class
})
public interface RemoteDataOpenCaller {

    @RequestMapping(value = "/infController/startFlow", method = RequestMethod.POST)
    DataOpenResponse startFlow(@RequestHeader("headerInfo") DataOpenHeaderInfo headerInfo, @RequestBody Map params);

}

.......

// 设置你fegin调用服务的鉴权,比如账号密码这些,这里直接从请求头拿
public class DataOpenHeaderInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        Map<String, Collection<String>> headers = template.headers();
        if (headers.get("headerInfo") != null) {
            String headerInfoStr = headers.get("headerInfo").iterator().next();
            DataOpenHeaderInfo headerInfo = JSONObject.parseObject(headerInfoStr, DataOpenHeaderInfo.class);
            template.header("accountCode", headerInfo.getAccountCode());
            template.header("accountPasswd", headerInfo.getAccountPasswd());
            template.header("ssoType", headerInfo.getSsoType());
        }
    }
}

如果是想在本机测试时,测试需要调用的是哪个服务,可以添加URL参数(提交代码时记得删除):

说明:url = "http://10.45.xx.xx:8486"    是你需要调用的服务所属的IP+该服务的网关端口号,端口号可以去8900看gateway的端口号

@FeignClient(url = "http://10.45.xx.xx:8486", value = "${url.mapping.SYGATEWAY:SYGATEWAY}", path = "/daweb/daportal/", configuration = {
    DataOpenHeaderInterceptor.class
})
public interface RemoteDataOpenCaller {

    @RequestMapping(value = "/infController/startFlow", method = RequestMethod.POST)
    DataOpenResponse startFlow(@RequestHeader("headerInfo") DataOpenHeaderInfo headerInfo, @RequestBody Map params);

}

得注意的是,启用本地调试时,注意Redis连接的是哪个环境的,不然可能会提示登录信息错误

3.2、案例2:通过WebService+Feign实现XML保存到业务表(跨服务调用)

(1)骨架图

(2)WebService配置:WebServiceConfig

什么是WebService参考:https://blog.csdn.net/RuiKe1400360107/article/details/83063644

@Configuration
@EnableWebMvc
public class WebServiceConfig extends WsConfigurerAdapter {
  @Value("${webservice.url}")
  private String baseUrl;

  @Bean
  public HospitalStaffService hospitalStaffService() {
    return new HospitalStaffServiceImpl();
  }
  /**
   * @Description: HospitalStaffService
   */
  @Bean
  public Endpoint endpoint1() {
    EndpointImpl endpoint = new EndpointImpl(springBus(), hospitalStaffService());
    endpoint.setPublishedEndpointUrl(
        "http://" + baseUrl + "/registermanage/registermanage/hospitalStaffService");
    endpoint.publish("/hospitalStaffService");
    return endpoint;
  }

}

(3)serviceImpl:

@WebService注解参数说明:

  • serviceName: 对外发布的服务名;
  • endpointInterface: 服务接口全路径;
  • targetNamespace:指定你想要的名称空间,是使用接口实现类的包名的反缀
@WebService(serviceName = "HospitalStaffService", targetNamespace = "http://service.webService.registerManage.gl.ms.xx.com", endpointInterface = "com.xx.ms.gl.registerManage.webService.service.HospitalStaffService")
@Service
public class HospitalStaffServiceImpl implements HospitalStaffService {

	@Autowired
	BaseWebService baseWebService;
    ......略
    /**
	 * 新增
	 */
	@Override
	public String handleXMLInfo(String xml, String organCode) {
    ......略
    // xml保存到业务表
	Map<String, String> xmlRes = baseWebService.handleXmlToEntity(xmlType, xml, organCode);
   ....
}

(4)BaseServiceImpl :

调用Fegin客户端

@Service
public class BaseServiceImpl implements BaseWebService {
  @Autowired
  private HandleXmlClient client;
  ....略
  //新增xml
  @Override
  public Map<String, String> handleXmlToEntity(String xmlType, String xml, String orgId) {
    XmlHandleData xmlData = new XmlHandleData();
    xmlData.setXml(xml);
    xmlData.setXmlType(xmlType);
    xmlData.setOrgId(orgId);

    // 解析xml
    return client.handleStrToEntity(xmlData);
  }

(5)Fegin回调:HandleXmlClientFallback

注意:在使用fallback属性时,需要使用@Component注解保证fallback类被Spring容器扫描到

@Component
public class HandleXmlClientFallback  implements HandleXmlClient{

   	@Override
	public Map<String, String> handleStrToEntity(XmlHandleData xmlData) {
		Map<String, String> res=new HashMap<String, String>();
		res.put("code", "-1");
		res.put("message", "feign调用xml插入实体表方法失败");
		return res;
	}
}

(6)Fegin客户端调用:HandleXmlClient

Fegin是分布式架构服务之间,各子模块系统内部通信的核心。

下面是@FeignClient注解的参数说明:

  • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现;
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址——也可以不指定;
  • configuration: Feign配置类;
  • fallback: 定义容错的处理类,服务降级;——比如这里的是HandleXmlClientFallback

注意:这里的Path路径也是需要调用的接口的全路径

@FeignClient(name = "healthfileshare", configuration = {
		FeignClientsConfigurationCustom.class },fallback = HandleXmlClientFallback.class)
@Produces("application/json")
@Consumes("application/json")
public interface HandleXmlClient {

 	@POST
	@Path(value = "/healthfileshare/subscribe/handleXml")
	public Map<String,String> handleStrToEntity(XmlHandleData xmlData);
}

 

(7)Fegin调用的服务的UI控制层(也是@FeignClient注解的name属性值的服务

@Path("/healthfileshare/subscribe")
@Api(tags = "healthfileshare")
public class SubscribeUI extends BaseContextAwareResource {
   	/**
	 * 接收xml保存到业务表
	 * 
	 * @return
	 */
	@POST
	@Path("/handleXml")
	public Map<String, String> handleStrToEntity(XmlHandleData xmlData) {
		Map<String, String> res = new HashMap<String, String>();
		Map<String, String> map = new HashMap<String, String>();
		String result = "";
		try {
			map = xml2DbService.handleXml(xmlData.getXmlType(), xmlData.getXml(), xmlData.getOrgId());
		} catch (Exception e) {
			StringWriter sw = new StringWriter();
			e.printStackTrace(new PrintWriter(sw, true));
			result = sw.getBuffer().toString();
		}
		if (result.equals("") && map.get("code").equals("1")) {
			// 成功
			res.put("code", "0");
			res.put("message", "xml保存到业务表成功");
		} else {
			res.put("code", "-1");
			res.put("message", "xml保存到业务表失败," + result+","+map.get("message"));
		}
		return res;
	}
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Feign是一个声明式的Web服务客户端,它使得编写Web服务客户端变得更加简单。通过使用Feign,我们可以以类似于编写控制器的方式来调用远程服务。 在使用Feign进行服务调用时,我们需要按照以下步骤进行: 1. 导入Feign依赖:在项目的pom.xml文件中添加Feign的依赖,例如: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> ``` 2. 启用Feign:在主应用程序类上添加`@EnableFeignClients`注解,例如: ```java @SpringBootApplication @EnableFeignClients public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } ``` 3. 创建Feign客户端接口:定义一个接口,使用`@FeignClient`注解指定要调用的目标服务名称,例如: ```java @FeignClient(name = "target-service") public interface TargetServiceClient { @GetMapping("/api/resource") ResponseEntity<Resource> getResource(); } ``` 4. 调用远程服务:在需要调用远程服务的地方,通过自动注入方式注入Feign客户端接口,并直接调用其中的方法,例如: ```java @RestController public class MyController { private final TargetServiceClient targetServiceClient; public MyController(TargetServiceClient targetServiceClient) { this.targetServiceClient = targetServiceClient; } @GetMapping("/my-endpoint") public ResponseEntity<Resource> myEndpoint() { return targetServiceClient.getResource(); } } ``` 通过上述步骤,我们可以使用Feign来简化服务间的调用,并且可以像调用本地方法一样调用远程服务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值