Java中的Feign深入分析
一、使用方式:
1. java動態(tài)生成 :
使用Feign.Builder動態(tài)生成,可動態(tài)靈活生成不同的操作對象。整個Feign操作核心就是生成這樣的Feign.Builder對象。
1)示例:
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
.logLevel(Logger.Level.FULL)
.client(new OkHttpClient())
.requestInterceptor(new ForwardedForInterceptor())
.target(GitHub.class, https://api.github.com);2)源代碼分析:
public abstract class Feign {
//1. Feign.Builder屬性。
public static class Builder {
private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default(); //使用約解釋api標(biāo)注
private Client client = new Client.Default(null, null);//網(wǎng)絡(luò)請求客戶端(okHttp/ApacheHttpClient)
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options(); //
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
}
}
//2. 網(wǎng)絡(luò)請求客戶端的參數(shù)設(shè)置:(連接時間、讀取超過)
public static class Options {
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
private final boolean followRedirects;
}2. Spring Feign注解方式實現(xiàn)
通過注解方式提高生成Feign.Bulder的效率,簡化代碼。其核心最終還是生成不同的Feign.Builder實例對象。(見1. java動態(tài)生成 )在Spring cloud應(yīng)用中,當(dāng)我們要使用feign客戶端時,一般要做以下三件事情 :
- 使用注解@EnableFeignClients啟用feign客戶端: 掃描和注冊feign客戶端bean定義
- 使用注解@FeignClient 定義feign客戶端 : 定義了一個feign客戶端.
- 使用注解@Autowired使用上面所定義feign的客戶端 : 使用所定義feign的客戶端。
示例:
//1.使用注解@EnableFeignClients啟用feign客戶端。
@SpringBootApplication
@EnableFeignClients(basePackages= {"com.missuteam.onepiece.oauth.api"},
defaultConfiguration = defaultFeignConfig.class)
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
//2.FeignClient定義
@FeignClient(name = "choppe-oauth-server",
configuration = AuthServiceFeignConfig.class,
fallbackFactory = AuthServiceFallbackFactory.class,
path = "/authCenter",
decode404 = true)
public interface AuthServiceFeignClient {
//接口
@RequestMapping(value = "/echo", method = RequestMethod.GET)
TestModel echo(@RequestParam("parameter") String parameter);
}二、源代碼分析:
啟動流程:
1. 注冊所有feign客戶端的缺省配置@EnableFeignClients里指定的defaultConfiguration,生成一個FeignClientSpecification bean.
2. 注冊所有@FeignClient的configuration,生成一個配置FeignClientSpecification bean.
3. 所有的配置放置到FeignContext Bean里。
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}4. 生成@FeignClient的代理Bean,(FeignClientFactoryBean)FeignClientFactoryBean包括一個Fegin。
5. 配置FeignClientFactoryBean的屬性,可以從@EnableFeign里的defaultConfigure和各個@FeignClient里的configure,或application.yml配置屬性獲取。( feign.client.defaultToProperties = true yml里的配置(default和對應(yīng)的name)將覆蓋@EnableFeign和@FeignClient里的configure.)
protected void configureFeign(FeignContext context, Feign.Builder builder) {
1.獲取application.yml里的配置信息。
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
2.如果設(shè)置isDefaultToProperties=true,將使用application.yml里的default配置覆蓋@FeignClient或@EnableFeignClient里的defaultConfigure.
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
2.1@EnableFeignClient里的defaultConfigure.
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
2.2@FeignClient里的Configure.
configureUsingProperties(properties.getConfig().get(this.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
this.name, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
if (decode404) {
builder.decode404();
}
}
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
if (config == null) {
return;
}
if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}
if (config.getRetryer() != null) {
Retryer retryer = getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}
if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
// this will add request interceptor to builder, not replace existing
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
if (Objects.nonNull(config.getEncoder())) {
builder.encoder(getOrInstantiate(config.getEncoder()));
}
if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder()));
}
if (Objects.nonNull(config.getContract())) {
builder.contract(getOrInstantiate(config.getContract()));
}
}FeignClientsRegistrar.class
//注冊All feign 默認(rèn)配置
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
// 注冊feign客戶端的缺省配置,缺省配置信息來自注解元數(shù)據(jù)的屬性 defaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 獲取注解@EnableFeignClients的注解屬性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 下面是對所注冊的缺省配置的的命名,格式如下 :
// default.xxx.TestApplication
if (metadata.hasEnclosingClass()) {
// 針對注解元數(shù)據(jù)metadata對應(yīng)一個內(nèi)部類或者方法返回的方法本地類的情形
name = "default." + metadata.getEnclosingClassName();
}
else {
// name 舉例 : default.xxx.TestApplication
// 這里 xxx.TestApplication 是注解@EnableFeignClients所在配置類的長名稱
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}FeignClientFactoryBean.java封裝了,如何生成Feign
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
private Class<?> type;
private String name;
private String url;
private String path;
private boolean decode404;
private ApplicationContext applicationContext;
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
1.Feign.Builder
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
2. 配置Feign.Builder
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
3.最終生成Targeter
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}EnableFeignClients類:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* 指定掃描的包或類,不指定時,全部掃描.
*/
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] clients() default {};
/**
* 用戶定義一個默認(rèn)的配置類,作用于所有FeignClient
*/
Class<?>[] defaultConfiguration() default {};
}到此這篇關(guān)于Java中的Feign深入分析的文章就介紹到這了,更多相關(guān)Feign深入分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot業(yè)務(wù)功能實戰(zhàn)之告別輪詢websocket的集成使用
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于springboot業(yè)務(wù)功能實戰(zhàn)之告別輪詢websocket的集成使用,需要的朋友可以參考下2022-10-10
Java SpringBoot快速集成SpringBootAdmin管控臺監(jiān)控服務(wù)詳解
這篇文章主要介紹了如何基于springboot-admin管控臺監(jiān)控服務(wù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2021-09-09
Java?Rabbitmq中四種集群架構(gòu)的區(qū)別詳解
這篇文章主要為大家詳細(xì)介紹了Java?Rabbitmq中四種集群架構(gòu)的區(qū)別,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
解決springboot3:mybatis-plus依賴錯誤:org.springframework.beans.fac
這篇文章主要介紹了解決springboot3:mybatis-plus依賴錯誤:org.springframework.beans.factory.UnsatisfiedDependencyException問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

