当前位置: 首页> 教育> 大学 > 设计品牌企业logo_便宜网站建设价格_网络营销前景和现状分析_网络营销怎么做

设计品牌企业logo_便宜网站建设价格_网络营销前景和现状分析_网络营销怎么做

时间:2025/7/11 18:16:12来源:https://blog.csdn.net/joeyhhhhh/article/details/144439616 浏览次数:0次
设计品牌企业logo_便宜网站建设价格_网络营销前景和现状分析_网络营销怎么做

文章目录

  • 前言
  • 源码解读
  • 配置举例
    • 通过@EnableFeignClients#defaultConfiguration实现全局配置
    • 配置单个客户端组件
  • 总结

前言

在上一篇中我们介绍了springcloud_openfeign通过EnableFeignClients注解扫描并注册每个@FeignClient标识的接口对应的FeignClientFactoryBean到spring容器中, 本节我们来了解一下这个类的具体内容。

源码解读

FeignClientFactoryBean看名字它是一个工厂bean, 负责创建某个类型的对象, 注意FactoryBean与BeanFactory是不同的, 一个是具体的bean, 一个是工厂。FactoryBean 用于创建特定类型的 bean, 而BeanFactory 用于管理和创建各种类型的 bean。

FeignClientFactoryBean

public class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {//...BeanDefinition注入的一些属性们@Overridepublic Object getObject() {return getTarget();}@Overridepublic void afterPropertiesSet() {Assert.hasText(contextId, "Context id must be set");Assert.hasText(name, "Name must be set");}
}

FeignClientFactoryBean实现了bean生命周期中的三个接口, InitializingBean用来在初始化bean之后做处理(注意初始化和实例化的区别), ApplicationContextAware负责注入ApplicationContext容器上下文对象, BeanFactoryAware负责注入BeanFactory对象。

InitializingBean的afterPropertiesSet方法用来校验contextId不能为空, name不能为空, 回顾上一篇的介绍

  • contextId取值顺序为contextId > serviceId(默认没有) > name > value
  • name取值顺序为serviceId>name>value

FactoryBean接口提供的getObject方法就是真正用来创建feign接口对应的那个对象, 在之前的篇幅中了解到openFeign对接口是生成了一个代理对象。它仅仅是调用了getTarget方法。

getTarget

<T> T getTarget() {// 从上下文中获取FeignClientFactory对象FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class): applicationContext.getBean(FeignClientFactory.class);// 获取Feign.Builder对象Feign.Builder builder = feign(feignClientFactory);// 当前url为空, 并且没有contextId对应的客户端配置if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {if (LOG.isInfoEnabled()) {LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");}if (!name.startsWith("http://") && !name.startsWith("https://")) {url = "http://" + name;}else {url = name;}// 加上pathurl += cleanPath();// 1.设置请求客户端client  2.返回接口代理对象return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));}// 配置了url属性if (StringUtils.hasText(url) && !url.startsWith("http://") && !url.startsWith("https://")) {url = "http://" + url;}// 获取contextId对应的ClientClient client = getOptional(feignClientFactory, Client.class);if (client != null) {if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 使用容器中或者属性中的FeignBuilderCustomizer对`Feign.Builder`进行处理applyBuildCustomizers(feignClientFactory, builder);// 从父子容器中获取Targeter对象Targeter targeter = get(feignClientFactory, Targeter.class);// 解析TargeterHardCodedTarget<T> objectHardCodedTarget = resolveTarget(feignClientFactory, contextId, url);return targeter.target(this, builder, feignClientFactory, objectHardCodedTarget);
}

方法小结

  1. 从spring上下文中获取FeignClientFactory对象, 这个接口是feign客户端接口父子容器的关键
  2. 构建Feign.Builder; 这个是比较熟悉的面孔了, openFeign中的Feign.Builder对象
  3. 如果url为空(也就是@FeignClient注解里的url属性), 并且注入的属性类不存在或者url值为空
  • 从当前feign客户端上下文获取targeter对象, 然后生成接口代理对象返回
  1. 如果url不为空, 也就是@FeignClient注解指定了url参数
  • 设置请求客户端client, 如果自定义设置了client为FeignBlockingLoadBalancerClient或者FeignBlockingLoadBalancerClient对象, 也不会生效
  • 直接创建openFeign中的HardCodedTarget
  1. 如果url为空, 但是注入的属性类中url不为空
  • 如果需要刷新客户端, 那么创建的是RefreshableHardCodedTarget
  • 如果属性配置类中有url参数的话, 创建的是PropertyBasedTarget, 否则直接报错了
  1. 使用当前feign客户端和spring的上下文中的FeignBuilderCustomizerFeign.Builder进行增强
  2. 使用当前FactoryBean中的FeignBuilderCustomizerFeign.Builder进行增强
  3. 从当前feign客户端上下文获取targeter对象, 然后生成接口代理对象返回

总得来说就是根据不同的url配置, 会生成不同的Target对象

解释起来有点绕, 举几个例子

1、无默认url配置

@FeignClient(name = "url-demo", path = "/feign")
public interface UrlDemoRemote {
}

这种情况bean默认的url参数(FeignClient.url)为空, 并且没有指定配置类属性, 那么它将使用HardCodedTarget

此时的url为 http:// + name + path 也就是 http://url-demo/feign, 这里的{url-demo}可以被环境中的内容替换

如果开启info及以上的日志级别, 将可以看到 o.s.c.o.FeignClientFactoryBean - For ‘url-demo’ URL not provided. Will try picking an instance via load-balancing.

2、@FeignClient注解添加url属性

@FeignClient(url = "localhost:8081", name = "url-demo", path = "/feign")
public interface UrlDemoRemote {
}

这种情况, 将会使用HardCodedTarget对象

此时的url为 http://{localhost:localhost}/path, 也就是http://localhost/feign

3、属性配置url

@FeignClient(contextId = "urlDemoRemote", name = "url-demo", path = "/feign")
public interface UrlDemoRemote {
}spring:cloud:openfeign:client: # feign的组件配置defaultToProperties: true # 设置配置文件中的优先级高于容中的配置defaultConfig: default  # 指定默认配置是哪个, 也可以指定为下面写的urlDemoRemote; 默认指定的是defaultconfig:urlDemoRemote:url: http://localhost:8080

这种情况, 将会使用PropertyBasedTarget对象

此时的url为 http://localhost:8080/feign

url的顺序为: @FeignClient.url > spring文件中的url > 无配置

feign方法

protected Feign.Builder feign(FeignClientFactory context) {// 从父子容器中获取FeignLoggerFactory对象FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);// 创建loggerLogger logger = loggerFactory.create(type);// @formatter:offFeign.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;
}

方法小结

  1. 从父子容器中获取FeignLoggerFactory来创建Logger对象
  2. 然后就是从父子容器中获取Encoder, Decoder, Contract对象
  3. 构建Feign.Builder对象
  4. 添加配置到Feign.Builder

这个方法主要就是构建Feign.Builder对象

configureFeign

使用容器中配置的组件和属性配置填充Feign.Builder

protected void configureFeign(FeignClientFactory context, Feign.Builder builder) {// 配置的客户端属性FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class): applicationContext.getBean(FeignClientProperties.class);// 从父子容器中获取FeignClientConfigurer对象FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);// 默认是true, 继承父容器的配置setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());// 处理属性对象的配置if (properties != null && inheritParentContext) {// 判断是否为默认属性, 默认是tureif (properties.isDefaultToProperties()) {// 添加容器中获取的配置configureUsingConfiguration(context, builder);// 添加配置文件中的配置; 配置的默认配置 已经当前feign接口上下文的配置(全局配置和单个接口的配置)configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),properties.getConfig().get(contextId), builder);}else {// 添加配置文件中的配置; 配置的默认配置 已经当前feign接口上下文的配置(全局配置和单个接口的配置)configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),properties.getConfig().get(contextId), builder);// 添加容器中获取的配置configureUsingConfiguration(context, builder);}// 1.添加请求头 2.添加query参数(@QueryMap)configureDefaultRequestElements(properties.getConfig().get(properties.getDefaultConfig()),properties.getConfig().get(contextId), builder);}else {// 添加容器中获取的配置configureUsingConfiguration(context, builder);}
}

方法小结

  1. 获取配置的属性对象; 前缀为spring.cloud.openfeign.client
  2. 设置是否启用配置对象中的内容; inheritParentContext属性控制, 默认是true
  • 如果属性对象是默认配置, properties.isDefaultToProperties()属性控制, 默认是true
    • 先从容器中获取配置信息
    • 再从属性配置中获取, 它将覆盖容器中的配置
  • 如果属性对象不是默认配置, 那么先从加载属性中的配置, 再加载容器中的配置
  1. 添加属性对象中配置的请求头和请求行参数
  2. 如果没有配置属性对象内容, 那么直接从上下文中获取即可

这里通过对象 FeignClientConfigurer 来开启是否可以从父容器中获取配置对象

它默认的配置如下, FeignClientConfigurer是一个接口, 默认开启父容器配置

public interface FeignClientConfigurer {// 默认开启父容器配置default boolean inheritParentConfiguration() {return true;}
}
@Bean
@ConditionalOnMissingBean(FeignClientConfigurer.class)
public FeignClientConfigurer feignClientConfigurer() {return new FeignClientConfigurer() {};
}

configureUsingConfiguration

使用bean来配置

protected void configureUsingConfiguration(FeignClientFactory context, Feign.Builder builder) {// 1.容器中获取logger的级别Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);if (level != null) {builder.logLevel(level);}// 2.重试对象Retryer retryer = getInheritedAwareOptional(context, Retryer.class);if (retryer != null) {builder.retryer(retryer);}// 3.ErrorDecoderErrorDecoder errorDecoder = getInheritedAwareOptional(context, ErrorDecoder.class);if (errorDecoder != null) {builder.errorDecoder(errorDecoder);}else {// 直接从父子容器中获取, 这里不管有没有开启父容器了FeignErrorDecoderFactory errorDecoderFactory = getOptional(context, FeignErrorDecoderFactory.class);if (errorDecoderFactory != null) {ErrorDecoder factoryErrorDecoder = errorDecoderFactory.create(type);builder.errorDecoder(factoryErrorDecoder);}}// 4.Request.OptionsRequest.Options options = getInheritedAwareOptional(context, Request.Options.class);if (options == null) {options = getOptionsByName(context, contextId);}if (options != null) {builder.options(options);readTimeoutMillis = options.readTimeoutMillis();connectTimeoutMillis = options.connectTimeoutMillis();followRedirects = options.isFollowRedirects();}// 5.请求拦截器集合Map<String, RequestInterceptor> requestInterceptors = getInheritedAwareInstances(context,RequestInterceptor.class);if (requestInterceptors != null) {List<RequestInterceptor> interceptors = new ArrayList<>(requestInterceptors.values());AnnotationAwareOrderComparator.sort(interceptors);builder.requestInterceptors(interceptors);}// 6.响应拦截器ResponseInterceptor responseInterceptor = getInheritedAwareOptional(context, ResponseInterceptor.class);if (responseInterceptor != null) {builder.responseInterceptor(responseInterceptor);}// 7.@QueryMap和@HeaderMap参数编码器QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class);if (queryMapEncoder != null) {builder.queryMapEncoder(queryMapEncoder);}// 8.是否忽略404if (dismiss404) {builder.dismiss404();}// 9.重试异常传播策略ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(context,ExceptionPropagationPolicy.class);if (exceptionPropagationPolicy != null) {builder.exceptionPropagationPolicy(exceptionPropagationPolicy);}// 10.Capability(增强)Map<String, Capability> capabilities = getInheritedAwareInstances(context, Capability.class);if (capabilities != null) {capabilities.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE).forEach(builder::addCapability);}
}

方法小结

如果打开了父子容器开关(feignClientConfigurer.inheritParentConfiguration()为true), 那么这个方法中的配置对象都是从父子容器中获取的, 否则就只是从当前容器中获取, 为了方便, 以下容器就代表父子容器或者当前容器

  1. 从容器中获取日志等级(Logger.Level)添加到Feign.Builder
  2. 从容器中获取重试对象(Retryer)添加到Feign.Builder
  3. ErrorDecoder
  4. Request.Options
  5. 请求拦截器集合RequestInterceptor
  6. 响应拦截器ResponseInterceptor
  7. @QueryMap和@HeaderMap参数编码器QueryMapEncoder
  8. 是否忽略404
  9. 重试异常传播策略
  10. Capability(增强)

这里就从容器中获取了十个配置项设置到了Feign.Builder

configureUsingProperties

configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),
properties.getConfig().get(contextId), builder);

这里通过contextId与属性配置对应

例如

spring:cloud:openfeign:client: # feign的组件配置defaultToProperties: true # 默认使用配置文件中的配置defaultConfig: defaultconfig:default:	# 默认配置, 将对所有的feign客户端生效loggerLevel: HEADERSretryer: feign.Retryer.Defaultabc:  # 指定contextId的客户端connectTimeout: 5000readTimeout: 5000// 这个@FeignClient标识的客户端将对应上面abc的配置
@FeignClient(contextId = "abc")

方法解释

protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration baseConfig,FeignClientProperties.FeignClientConfiguration finalConfig, Feign.Builder builder) {// 添加默认配置configureUsingProperties(baseConfig, builder);// 添加项目最终配置configureUsingProperties(finalConfig, builder);// 添加是否忽略404; 如果客户端配置(finalConfig)存在就取它, 否则取全局配置(baseConfig), 否则为空Boolean dismiss404 = finalConfig != null && finalConfig.getDismiss404() != null ? finalConfig.getDismiss404(): (baseConfig != null && baseConfig.getDismiss404() != null ? baseConfig.getDismiss404() : null);if (dismiss404 != null) {if (dismiss404) {builder.dismiss404();}}
}
  1. 这里也是先调用的configureUsingProperties方法, 只是先配置的default配置项, 再配置的指定feign接口的客户端, 也就是说指定feign客户端的配置优先级更高

  2. 添加是否忽略404; 如果客户端配置(finalConfig)存在就取它, 否则取全局配置(baseConfig), 否则为空

配置举例

通过@EnableFeignClients#defaultConfiguration实现全局配置

添加配置application.yml

server:port: 8080logging:level:org.springframework.cloud.openfeign: DEBUG
spring:cloud:openfeign:cache:enabled: false  # 先关闭, 不然启动会报错okhttp:enabled: true

定义接口

@FeignClient(contextId = "urlDemoRemote", url = "localhost:8080", name = "url-demo", path = "/configDemo")
public interface UrlDemoRemote {//    @RequestLine("POST /getPerson")
//    @Headers("Content-Type:application/json")@PostMapping(value = "/getPerson", consumes = "application/json")Person getPerson(Person person);
}

注意openfeign-cloud中默认不支持openfeign的原始@RequestLine("POST")@Headers注解了, 改成了@ReqeustMapping那种,

@PostMapping也是@ReqeustMapping

服务端接口

@PostMapping("/configDemo/getPerson")
public Person getPerson(@RequestBody Person person) {System.out.println("uncleqiao 收到body:" + person);person.setName("小乔同学");person.setAge(20);return person;
}

全局配置

/*** 为全局客户端配置解码器*/
public class MyGlobalDecoder implements Decoder {private final JavaTimeModule javaTimeModule = new JavaTimeModule();private final Decoder decoder = new JacksonDecoder(List.of(javaTimeModule));@Overridepublic Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {System.out.println("MyGolbalDecoder ...");return decoder.decode(response, type);}
}/*** 为全局客户端配置编码器*/
public class MyGlobalEncode implements Encoder {private JavaTimeModule javaTimeModule = new JavaTimeModule();private JacksonEncoder encoder = new JacksonEncoder(List.of(javaTimeModule));@Overridepublic void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {System.out.println("MyGlobalEncode ...");encoder.encode(object, bodyType, template);}
}

启动类

@SpringBootApplication
@EnableFeignClients(basePackages = "per.qiao.feign.starter", defaultConfiguration = {MyGlobalEncode.class, MyGlobalDecoder.class})
public class FeignStudyApplication {public static void main(String[] args) {System.setProperty("spring.profiles.active", "urldemo");SpringApplication.run(FeignStudyApplication.class, args);}
}

这里注意defaultConfiguration的配置

测试

gav: 引入springboot的测试包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

测试类

@SpringBootTest
public class ConfigTest {@Autowiredprivate UrlDemoRemote urlDemoRemote;@Testvoid globlaConfigTest() {Person person = urlDemoRemote.getPerson(new Person("小乔同学", 18, 1, LocalDate.now()));System.out.println(person);}
}

结果

// 服务端打印
uncleqiao 收到body:Person(name=小乔同学, age=18, gender=1, birthday=2024-12-12)// 客户端打印
MyGlobalEncode ...
MyGolbalDecoder ...
Person(name=小乔同学, age=20, gender=1, birthday=2024-12-12)

配置单个客户端组件

@FeignClient(contextId = "urlDemoRemote", url = "localhost:8080", configuration = {MyContextDecoder.class, MyContextEncode.class}, name = "url-demo", path = "/configDemo")
public interface UrlDemoRemote {//    @RequestLine("POST /getPerson")
//    @Headers("Content-Type:application/json")@PostMapping(value = "/getPerson", consumes = "application/json")Person getPerson(Person person);
}

这里注意configuration的配置, 它给当前feign客户端专门配置组件用的

此时我们直接运行demo的话会报错, 原因是FeignClientFactoryBean#get方法会从父子容器中按照类型获取bean, 它会获取到多个, 然后就会抛异常, 原理如下

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));/*** 从父子容器中获取一个bean, 没有找到会抛异常*/
protected <T> T get(FeignClientFactory context, Class<T> type) {// 从contextId对应的上下文中或者spring上下文获取type对象(从父子容器中获取对象)T instance = context.getInstance(contextId, type);if (instance == null) {throw new IllegalStateException("No bean found of type " + type + " for " + contextId);}return instance;
}
// context#getInstance  这个方法获取多个直接返回null
public <T> T getInstance(String name, Class<T> type) {GenericApplicationContext context = getContext(name);try {// 容器中获取bean, 多个或者没有都会抛异常return context.getBean(type);}catch (NoSuchBeanDefinitionException e) {// ignore}return null;
}

所以@EnableFeignClients#defaultConfiguration@FeignClient#configuration中不要配置相同类型的组件

总结

本节主要介绍了springcloud_openfeign如何根据@EnableFeignClients注解和@FeignClient以及父子容器来构建Feign.Builder对象, 并处理Target需要的url, 最后通过Feign.Builder#target构建feign接口的代理对象。

本文没有具体介绍其中父子容器的相关细节, 以及配置的一些具体内容, 这些将会在后面的文章中介绍到

  1. @EnableFeignClients通过@Import注解给spring容器注入了FeignClientFactoryBean对象, 并且它是一个实现了FactoryBean接口的bean工厂
  2. FeignClientFactoryBean的getObject方法创建了我们实际需要和客户端对象
  3. Target对象根据url参数所在的位置会有不同的类型
  • 如果url在@FeignClient#url中, 它会生成HardCodedTarget
  • 如果url在配置文件中(对应一个属性类),
    • 如果可刷新客户端RefreshableHardCodedTarget(这里不考虑这种情况)
    • 否则生成PropertyBasedTarget
  • 如果以上两种情况都不是, 那么url就是@FeignClient#name属性, 它会生成HardCodedTarget, 这种没有配置url的就会做负载均衡, 有url的直接用url调用就行
  1. 父子容器中获取FeignLoggerFactory, Feign.Builder, Encoder, Decoder, Contract对象
  2. 可以通过FeignClientConfigurer对象控制是否让属性文件中的配置内容生效
  • 如果属性配置生效的话, 可以通过defaultToProperties为true, 设置属性配置优先级高于容器中配置的优先级, 为false则顺序相反
  1. 如果没有开启属性配置生效, 那么只从容器中获取组件设置给Feign.Builder
  2. 可以通过容器中的FeignBuilderCustomizer对象对Feign.Builder做最终调整
  3. 属性文件可配置的组件有以下10个
    1. Logger.Level: 日志级别
    1. 请求客户端的链接超时(connectTimeout), 读取超时(readTimeout), 跟随服务端重定向(followRedirects)
    1. Retryer: 重试对象
    1. ErrorDecoder 异常编码
    1. RequestInterceptor: 追加请求拦截器, 注意这里是追加
    1. ResponseInterceptor: 响应拦截器, 注意是追加
    1. Encoder: 编码器
    1. Decoder: 解码器
    1. Contract: 约定解析器(约定并解析注解、参数的对象)
    1. 重试异常传播策略
    1. Capability增强策略
    1. QueryMapEncoder: @QueryMap和@HeaderMap参数编码器
关键字:设计品牌企业logo_便宜网站建设价格_网络营销前景和现状分析_网络营销怎么做

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: