SpringCloud——Feign实例及原理

一、实例


SpringCloud——Feign实例及原理


1、配置feign

添加依赖

在maven的pom中添加feign

<code>   <dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-feign/<artifactid>
/<dependency>/<code>

配置启用

在Application启动类中添加@EnableFeignClients注解。

<code>@EnableEurekaClient
@EnableFeignClients
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}


}/<code>

2、新建feign

<code>@FeignClient(name = "SERVICE-NAME", url = "${***}")
public interface TestFeign {
}/<code>

其中: name:微服务的名称,一定要以eureka后台配置的保持一致。 url:可以手动指定feign的调用地址 fallback:标记容错后执行的类

在feign中定义接口的方式与正常接口并无差异,需注意参数名称等保持一致。如:

<code> @RequestMapping(value = "/user/message", method = RequestMethod.POST)
JSONObject sendMessage(@RequestParam("userId") String userId, @RequestParam("content") String content);/<code>

3、调用feign

在ServiceImpl中注入feign接口,正常使用即可。

<code> @Autowired
TestFeign testFeign;/<code>

二、原理

如果不使用springcloud的相关组件,调用服务需要要走http,配置请求head、body,然后才能发起请求。获得响应体后,还需解析等操作,十分繁琐。

那为什么feign的使用如此简单轻量呢?

1、如何启用

启动配置上检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描为@FeignClient注解接口。扫描出该注解后,通过beanDefinition注入到IOC容器中,方便后续被调用使用。

在FeignClientsRegistrar中,registerFeignClients()完成了注册feign的操作。

<code>public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
Map<string> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
......

//遍历该项目所需调用的服务
Iterator var17 = ((Set)basePackages).iterator();

while(var17.hasNext()) {
String basePackage = (String)var17.next();
Set<beandefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
Iterator var21 = candidateComponents.iterator();

while(var21.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var21.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
//获取feign中的详细信息
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map<string> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = this.getClientName(attributes);
//注册配置信息
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
//注册feign客户端
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}/<string>/<beandefinition>/<string>/<code>

注册feign客户端,包括使用注解时配置的所有信息。

<code>private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<string> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(2);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = ((Boolean)attributes.get("primary")).booleanValue();
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}

BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}/<string>/<code>

2、如何发起请求

ReflectiveFeign内部使用了jdk的动态代理为目标接口生成了一个动态代理类,这里会生成一个InvocationHandler(jdk动态代理原理)统一的方法拦截器,同时为接口的每个方法生成一个SynchronousMethodHandler拦截器,并解析方法上的 元数据,生成一个http请求模板。

在SynchronousMethodHandler类中生成RequestTemplate发起请求。

<code>public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}/<code>
<code>Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}

}/<code>

发送http请求

<code> @Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final HttpURLConnection
connection =
(HttpURLConnection) new URL(request.url()).openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());

Collection<string> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean
gzipEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean
deflateEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {

connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}

if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}/<string>/<code>

三、小结

约定大于配置

JAVA进阶架构程序员福利:我这里还总结整理了比较全面的JAVA相关的面试资料,都已经整理成了

PDF版,这些都可以分享给大家,关注私信我:【806】,免费领取!


分享到:


相關文章: