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

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

3.1、骨架图

Feign服务调用深入研究_第1张图片

3.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.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 xmlRes = baseWebService.handleXmlToEntity(xmlType, xml, organCode);
   ....
}

3.4、BaseServiceImpl :

调用Fegin客户端

@Service
public class BaseServiceImpl implements BaseWebService {
  @Autowired
  private HandleXmlClient client;
  ....略
  //新增xml
  @Override
  public Map 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);
  }

3.5、Fegin回调:HandleXmlClientFallback

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

@Component
public class HandleXmlClientFallback  implements HandleXmlClient{

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

3.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 handleStrToEntity(XmlHandleData xmlData);
}

 

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

@Path("/healthfileshare/subscribe")
@Api(tags = "healthfileshare")
public class SubscribeUI extends BaseContextAwareResource {
   	/**
	 * 接收xml保存到业务表
	 * 
	 * @return
	 */
	@POST
	@Path("/handleXml")
	public Map handleStrToEntity(XmlHandleData xmlData) {
		Map res = new HashMap();
		Map map = new HashMap();
		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;
	}
}

 

你可能感兴趣的:(SpringCloud,项目实战)