Java中的Feign深入分析
一、使用方式:
1. java動(dòng)態(tài)生成 :
使用Feign.Builder動(dòng)態(tài)生成,可動(dòng)態(tài)靈活生成不同的操作對(duì)象。整個(gè)Feign操作核心就是生成這樣的Feign.Builder對(duì)象。
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ò)請(qǐng)求客戶(hù)端(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ò)請(qǐng)求客戶(hù)端的參數(shù)設(shè)置:(連接時(shí)間、讀取超過(guò)) public static class Options { private final int connectTimeoutMillis; private final int readTimeoutMillis; private final boolean followRedirects; }
2. Spring Feign注解方式實(shí)現(xiàn)
通過(guò)注解方式提高生成Feign.Bulder的效率,簡(jiǎn)化代碼。其核心最終還是生成不同的Feign.Builder實(shí)例對(duì)象。(見(jiàn)1. java動(dòng)態(tài)生成 )在Spring cloud應(yīng)用中,當(dāng)我們要使用feign客戶(hù)端時(shí),一般要做以下三件事情 :
- 使用注解@EnableFeignClients啟用feign客戶(hù)端: 掃描和注冊(cè)feign客戶(hù)端bean定義
- 使用注解@FeignClient 定義feign客戶(hù)端 : 定義了一個(gè)feign客戶(hù)端.
- 使用注解@Autowired使用上面所定義feign的客戶(hù)端 : 使用所定義feign的客戶(hù)端。
示例:
//1.使用注解@EnableFeignClients啟用feign客戶(hù)端。 @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); }
二、源代碼分析:
啟動(dòng)流程:
1. 注冊(cè)所有feign客戶(hù)端的缺省配置@EnableFeignClients里指定的defaultConfiguration,生成一個(gè)FeignClientSpecification bean.
2. 注冊(cè)所有@FeignClient的configuration,生成一個(gè)配置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包括一個(gè)Fegin。
5. 配置FeignClientFactoryBean的屬性,可以從@EnableFeign里的defaultConfigure和各個(gè)@FeignClient里的configure,或application.yml配置屬性獲取。( feign.client.defaultToProperties = true yml里的配置(default和對(duì)應(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
//注冊(cè)All feign 默認(rèn)配置 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } // 注冊(cè)feign客戶(hù)端的缺省配置,缺省配置信息來(lái)自注解元數(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; // 下面是對(duì)所注冊(cè)的缺省配置的的命名,格式如下 : // default.xxx.TestApplication if (metadata.hasEnclosingClass()) { // 針對(duì)注解元數(shù)據(jù)metadata對(duì)應(yīng)一個(gè)內(nèi)部類(lèi)或者方法返回的方法本地類(lèi)的情形 name = "default." + metadata.getEnclosingClassName(); } else { // name 舉例 : default.xxx.TestApplication // 這里 xxx.TestApplication 是注解@EnableFeignClients所在配置類(lèi)的長(zhǎng)名稱(chēng) 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類(lèi):
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { /** * 指定掃描的包或類(lèi),不指定時(shí),全部掃描. */ String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] clients() default {}; /** * 用戶(hù)定義一個(gè)默認(rèn)的配置類(lèi),作用于所有FeignClient */ Class<?>[] defaultConfiguration() default {}; }
到此這篇關(guān)于Java中的Feign深入分析的文章就介紹到這了,更多相關(guān)Feign深入分析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot自動(dòng)裝配Import示例詳解
SpringBoot中@Import注解的使用可以幫助開(kāi)發(fā)者將指定的Bean或配置類(lèi)導(dǎo)入到IOC容器中,該注解支持四種用法:導(dǎo)入Bean、導(dǎo)入配置類(lèi)、實(shí)現(xiàn)ImportSelector接口和實(shí)現(xiàn),感興趣的朋友一起看看吧2024-09-09springboot業(yè)務(wù)功能實(shí)戰(zhàn)之告別輪詢(xún)websocket的集成使用
WebSocket使得客戶(hù)端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶(hù)端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于springboot業(yè)務(wù)功能實(shí)戰(zhàn)之告別輪詢(xún)websocket的集成使用,需要的朋友可以參考下2022-10-10Java SpringBoot快速集成SpringBootAdmin管控臺(tái)監(jiān)控服務(wù)詳解
這篇文章主要介紹了如何基于springboot-admin管控臺(tái)監(jiān)控服務(wù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09Java?Rabbitmq中四種集群架構(gòu)的區(qū)別詳解
這篇文章主要為大家詳細(xì)介紹了Java?Rabbitmq中四種集群架構(gòu)的區(qū)別,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02解決springboot3:mybatis-plus依賴(lài)錯(cuò)誤:org.springframework.beans.fac
這篇文章主要介紹了解決springboot3:mybatis-plus依賴(lài)錯(cuò)誤:org.springframework.beans.factory.UnsatisfiedDependencyException問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成
這篇文章主要介紹了深入理解JVM之Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪(fǎng)問(wèn)定位,結(jié)合實(shí)例形式詳細(xì)分析了Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪(fǎng)問(wèn)定位相關(guān)概念、原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2021-07-07