Spring Cloud —— 动态创建FeignClient实例
2021-05-29 04:02
标签:without ide res ext stat 配置 config factor for 我们在介绍Spring Cloud —— OpenFeign 核心原理2.2节时候,举了一个生产者消费者的案例,消费者服务在去调用生产者服务提供的接口时,我们需要定义定义 FeignClient 消费服务接口: 说到这里,我们就会想到一个问题,如果我们有若干个消费者服务,那我们岂不是需要在每个消费者服务中都定义一次FeignClient 接口。因此为了解决这个问题,生产者服务通常需要提供一套API包,供各个消费者服务相互调用。 消费者服务想调用生产者提供的接口时只需要引入我们提供的API包,并做一些简单配置: 然后向Spring容器中,注入FeignClient实例: 由于API包中提供的feign接口,是依赖于Spring Boot的,因此要求外部系统必须是Spring Boot应用,同时由于feign接口配置中没有指定用户权限服务的url地址,而采用指定服务名的方式,导致外部系统如果想调用feign接口,必须要和用户权限服务注册在同一个注册中心上。 在上一节中我们曾经介绍过FeignClient的创建流程,其底层是通过FeignClientFactoryBean的getTarget创建了一个FeignClient接口的实例。既然利用FeignClientFactoryBean可以创建这么一个FeignClient接口的实例,那我们是否可以自己去手动创建这样一个实例呢,当然是可以的,spring-cloud-openfeign-core为我们提供了一个这样的类FeignClientBuild,采用FeignClientBuild可以动态创建FeignClient实例。同时为了摆脱对Spring Boot的依赖,我们可以自己创建一个Spring ApplicationContext,用来模拟@FeignClient实例的注入过程。 我们首先创建一个DemoResourceApi类,用来封装DemoFeignClient的实例: DemoFeignClient实例我们是通过FeignClientUtils工具类构建的。 这里我们通过ApplicationContextBuilder创建了一个Spring ApplicationContext,并且向该容器中手动注入了Feign、nacos自动装配时注入的bean。同时注入了FeignContext。因此我们才可以从容器中获取到FeignContext实例。然后通过FeignContextBuilder为每个FeignClient接口创建了一个子Spring ApplicationContext。并保存到FeignContext中。 最后我们调用FeignClientBuilder创建对应的FeignClient实例(实际上就是通过contextId拿到FeignContext中保存的子容器、然后去构建Feign.Builder的过程),具体创建过程参考上一篇博客。 以上只展示了部分代码,更多代码下载:https://github.com/Zhengyang550/jnu-feign-server。 Spring Cloud —— 动态创建FeignClient实例 标签:without ide res ext stat 配置 config factor for 原文地址:https://www.cnblogs.com/zyly/p/14771134.html一、Feign使用中存在的问题
@FeignClient(name= "nacos-produce",contextId="DemoFeignClient")
public interface DemoFeignClient {
@GetMapping("/hello")
String sayHello(@RequestParam("name") String name);
}
@Autowired private DemoFeignClient demoFeignClient;
二、动态创建FeignClient实例(SDK)
2.1、DemoResourceApi类
package com.jnu.example.feign.sdk.swagger;
import com.jnu.example.feign.sdk.service.DemoFeignClient;
import com.jnu.example.feign.sdk.swagger.client.ApiClient;
import com.jnu.example.feign.sdk.util.FeignClientUtils;
/**
* @Author: zy
* @Date:2021/4/15
* @Description:demo API
*/
public class DemoResourceApi {
/**
* demo client feign service
*/
private DemoFeignClient demoFeignClient;
/**
* api client
*/
private ApiClient apiClient;
/**
* constructor
* @param apiClient: api client
*/
public DemoResourceApi(ApiClient apiClient){
if(apiClient == null){
throw new NullPointerException("Api client NullPointerException");
}
this.apiClient = apiClient;
this.demoFeignClient = FeignClientUtils.build(apiClient,"DemoFeignClient", DemoFeignClient.class);
}
/**
* @Author: zy
* @Date:2021/4/15 14:51
* @Description:say hello
* @param name
* @return: User
*/
public String sayHello(String name) {
String res = demoFeignClient.sayHello(name);
return res;
}
}
2.2、FeignClientUtils
package com.jnu.example.feign.sdk.util;
import com.jnu.example.feign.sdk.ApplicationContextBuilder;
import com.jnu.example.feign.sdk.feign.FeignContextBuilder;
import com.jnu.example.feign.sdk.swagger.client.ApiClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: zy
* @Date:2021/4/15
* @Description:动态创建FeignClient实例 https://www.jianshu.com/p/172e002e0eb4
* 关于动态创建Feign Client的问题 https://blog.csdn.net/qq_37312208/article/details/112476051
*/
@Slf4j
public final class FeignClientUtils {
/**
* forbid instantiate
*/
private FeignClientUtils(){
throw new AssertionError();
}
/**
* Spring ApplicationContext
*/
private static ApplicationContext applicationContext;
/**
* save contextId -> feign client mapping
*/
private static final Map
private Map
2.3、FeignClientBuilder
package com.jnu.example.feign.sdk.feign;
import cn.hutool.core.util.ReflectUtil;
import com.jnu.example.feign.sdk.feign.factory.TokenFactory;
import com.jnu.example.feign.sdk.swagger.client.ApiClient;
import feign.Logger;
import feign.Request;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: zy
* @Date: 2021/4/18
* @Description:A builder for creating Application context for feign client.
*/
@Slf4j
@Getter
public class FeignContextBuilder {
/**
* A factory that creates instances of feign classes. It creates a Spring
* ApplicationContext per feign client, and extracts the beans that it needs from there.
*/
private FeignContext feignContext;
/**
* Unique Spring ApplicationContext identification for feign client
*/
private String contextId;
/*
* Spring ApplicationContext for feign client
*/
private ApplicationContext feignClientContext;
/**
* constructor
* @param feignContext:
* @param apiClient
* @param contextId
*/
public FeignContextBuilder(FeignContext feignContext, ApiClient apiClient, String contextId){
//save parameter
this.feignContext = feignContext;
this.contextId = contextId;
//create Spring ApplicationContext for feign client .
this.feignClientContext = createContext(feignContext,apiClient,contextId);
}
/**
* create Spring ApplicationContext for feign client
* FeignContext cannot find the Spring ApplicationContext we created based on the contextId, it will create Spring ApplicationContext for feign client
* @param apiClient: api client
* @param contextId : Unique Spring ApplicationContext identification for feign client
*/
private ApplicationContext createContext(FeignContext feignContext, ApiClient apiClient, String contextId){
//create Spring ApplicationContext for feign client
Method getContext = ReflectUtil.getMethod(feignContext.getClass(),"getContext",String.class);
getContext.setAccessible(true);
AnnotationConfigApplicationContext context= null;
try {
//Inject Feign request interceptor bean into the Spring ApplicationContext
context = (AnnotationConfigApplicationContext)getContext.invoke(feignContext,contextId);
//Inject Feign request interceptor bean into the Spring ApplicationContext
registerRequestInterceptor(context,apiClient.getLoginName(),apiClient.getPassword(),apiClient.getTokenFactory());
//Inject Feign request options bean into the Spring ApplicationContext
registerRequestOptions(context,apiClient.getConnectTimeoutMillis(),apiClient.getReadTimeoutMillis());
//Inject Feign logger level bean into the Spring ApplicationContext
registerLoggerLevel(context,apiClient.getLevel());
//Inject Feign decoder bean into the Spring ApplicationContext
registerDecoder(context);
//Inject Feign error decoder bean into the Spring ApplicationContext
registerErrorDecoder(context);
log.info("jnu-feign-server-sdk:" + contextId + " feign client context refresh success");
} catch (Exception e) {
log.error(contextId +": create sub context fail");
}
return context;
}
/**
* Inject Feign request interceptor bean into the Spring ApplicationContext
* @param context : Spring ApplicationContext for feign client
* @param loginName:loginName
* @param password: password
*/
private void registerRequestInterceptor(AnnotationConfigApplicationContext context, String loginName, String password
, TokenFactory tokenFactory){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignRequestInterceptor.class);
builder.addPropertyValue("loginName",loginName);
builder.addPropertyValue("password",password);
builder.addPropertyValue("tokenFactory",tokenFactory);
context.registerBeanDefinition("feignRequestInterceptor",builder.getBeanDefinition());
}
/**
* Inject Feign request options bean into the Spring ApplicationContext
* @param context : Spring ApplicationContext for feign client
* @param connectTimeoutMillis : connect timeout
* @param readTimeoutMillis: read timeout
*/
private void registerRequestOptions(AnnotationConfigApplicationContext context,int connectTimeoutMillis,int readTimeoutMillis){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Request.Options.class);
builder.addConstructorArgValue(connectTimeoutMillis);
builder.addConstructorArgValue(readTimeoutMillis);
builder.addConstructorArgValue(true);
context.registerBeanDefinition("feignRequestOptions",builder.getBeanDefinition());
}
/**
* Inject Feign decoder bean into the Spring ApplicationContext
* @param context : Spring ApplicationContext for feign client
*/
private void registerDecoder(AnnotationConfigApplicationContext context){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpringDecoder.class);
builder.addConstructorArgValue(feignHttpMessageConverter());
context.registerBeanDefinition("feignDecoder",builder.getBeanDefinition());
}
/**
* HttpMessageConverters factory
*/
private ObjectFactory
三、代码下载
文章标题:Spring Cloud —— 动态创建FeignClient实例
文章链接:http://soscw.com/essay/88942.html