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標注 private Client client = new Client.Default(null, null);//網(wǎng)絡請求客戶端(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)絡請求客戶端的參數(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應用中,當我們要使用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和對應的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.如果設置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 默認配置 @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對應一個內部類或者方法返回的方法本地類的情形 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 {}; /** * 用戶定義一個默認的配置類,作用于所有FeignClient */ Class<?>[] defaultConfiguration() default {}; }
到此這篇關于Java中的Feign深入分析的文章就介紹到這了,更多相關Feign深入分析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot業(yè)務功能實戰(zhàn)之告別輪詢websocket的集成使用
WebSocket使得客戶端和服務器之間的數(shù)據(jù)交換變得更加簡單,允許服務端主動向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關于springboot業(yè)務功能實戰(zhàn)之告別輪詢websocket的集成使用,需要的朋友可以參考下2022-10-10Java SpringBoot快速集成SpringBootAdmin管控臺監(jiān)控服務詳解
這篇文章主要介紹了如何基于springboot-admin管控臺監(jiān)控服務,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2021-09-09解決springboot3:mybatis-plus依賴錯誤:org.springframework.beans.fac
這篇文章主要介紹了解決springboot3:mybatis-plus依賴錯誤:org.springframework.beans.factory.UnsatisfiedDependencyException問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07