一、理论基础
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;
}
}