草庐IT

Spring Cloud 升级之路 - 2020.0.x - 5. 理解 NamedContextF

干货满满张哈希 2023-03-28 原文
spring-cloud-commons 中参考了 spring-cloud-netflix 的设计,引入了 NamedContextFactory 机制,一般用于对于不同微服务的客户端模块使用不同的 子 ApplicationContext 进行配置。

spring-cloud-commons 是 Spring Cloud 对于微服务基础组件的抽象。在一个微服务中,调用微服务 A 与调用微服务 B 的配置可能不同。比较简单的例子就是,A 微服务是一个简单的用户订单查询服务,接口返回速度很快,B 是一个报表微服务,接口返回速度比较慢。这样的话我们就不能对于调用微服务 A 和微服务 B 使用相同的超时时间配置。还有就是,我们可能对于服务 A 通过注册中心进行发现,对于服务 B 则是通过 DNS 解析进行服务发现,所以对于不同的微服务我们可能使用不同的组件,在 Spring 中就是使用不同类型的 Bean。

在这种需求下,不同微服务的客户端有不同的以及相同的配置有不同的 Bean,也有相同的 Bean。所以,我们可以针对每一个微服务将他们的 Bean 所处于 ApplicationContext 独立开来,不同微服务客户端使用不同的 ApplicationContext。NamedContextFactory 就是用来实现这种机制的。

通过实例了解 NamedContextFactory 的使用

编写源码:

package com.github.hashjang.spring.cloud.iiford.service.common; import org.junit.Assert; import org.junit.Test; import org.springframework.cloud.context.named.NamedContextFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import java.util.List; import java.util.Objects; public class CommonNameContextTest { private static final String PROPERTY_NAME = "test.context.name"; @Test public void test() { //创建 parent context AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); //添加 BaseConfig 相关配置 parent.register(BaseConfig.class); //初始化 parent parent.refresh(); //创建 testClient1,默认配置使用 ClientCommonConfig TestClient testClient1 = new TestClient(ClientCommonConfig.class); //创建 service1 与 service2 以及指定对应额外的配置类 TestSpec testSpec1 = new TestSpec("service1", new Class[]{Service1Config1.class, Service1Config2.class}); TestSpec testSpec2 = new TestSpec("service2", new Class[]{Service2Config.class}); //设置 parent ApplicationContext 为 parent testClient1.setApplicationContext(parent); //将 service1 与 service2 的配置加入 testClient1 testClient1.setConfigurations(List.of(testSpec1, testSpec2)); BaseBean baseBean = testClient1.getInstance("service1", BaseBean.class); System.out.println(baseBean); //验证正常获取到了 baseBean Assert.assertNotNull(baseBean); ClientCommonBean commonBean = testClient1.getInstance("service1", ClientCommonBean.class); System.out.println(commonBean); //验证正常获取到了 commonBean Assert.assertNotNull(commonBean); Service1Bean1 service1Bean1 = testClient1.getInstance("service1", Service1Bean1.class); System.out.println(service1Bean1); //验证正常获取到了 service1Bean1 Assert.assertNotNull(service1Bean1); Service1Bean2 service1Bean2 = testClient1.getInstance("service1", Service1Bean2.class); System.out.println(service1Bean2); //验证正常获取到了 service1Bean2 Assert.assertNotNull(service1Bean2); BaseBean baseBean2 = testClient1.getInstance("service2", BaseBean.class); System.out.println(baseBean2); //验证正常获取到了 baseBean2 并且 baseBean2 就是 baseBean Assert.assertEquals(baseBean, baseBean2); ClientCommonBean commonBean2 = testClient1.getInstance("service2", ClientCommonBean.class); System.out.println(commonBean2); //验证正常获取到了 commonBean2 并且 commonBean 和 commonBean2 不是同一个 Assert.assertNotNull(commonBean2); Assert.assertNotEquals(commonBean, commonBean2); Service2Bean service2Bean = testClient1.getInstance("service2", Service2Bean.class); System.out.println(service2Bean); //验证正常获取到了 service2Bean Assert.assertNotNull(service2Bean); } @Configuration(proxyBeanMethods = false) static class BaseConfig { @Bean BaseBean baseBean() { return new BaseBean(); } } static class BaseBean {} @Configuration(proxyBeanMethods = false) static class ClientCommonConfig { @Bean ClientCommonBean clientCommonBean(Environment environment, BaseBean baseBean) { //在创建 NamedContextFactory 里面的子 ApplicationContext 的时候,会指定 name,这个 name 对应的属性 key 即 PROPERTY_NAME return new ClientCommonBean(environment.getProperty(PROPERTY_NAME), baseBean); } } static class ClientCommonBean { private final String name; private final BaseBean baseBean; ClientCommonBean(String name, BaseBean baseBean) { this.name = name; this.baseBean = baseBean; } @Override public String toString() { return "ClientCommonBean{" + "name='" + name + '\'' + ", baseBean=" + baseBean + '}'; } } @Configuration(proxyBeanMethods = false) static class Service1Config1 { @Bean Service1Bean1 service1Bean1(ClientCommonBean clientCommonBean) { return new Service1Bean1(clientCommonBean); } } static class Service1Bean1 { private final ClientCommonBean clientCommonBean; Service1Bean1(ClientCommonBean clientCommonBean) { this.clientCommonBean = clientCommonBean; } @Override public String toString() { return "Service1Bean1{" + "clientCommonBean=" + clientCommonBean + '}'; } } @Configuration(proxyBeanMethods = false) static class Service1Config2 { @Bean Service1Bean2 service1Bean2() { return new Service1Bean2(); } } static class Service1Bean2 { } @Configuration(proxyBeanMethods = false) static class Service2Config { @Bean Service2Bean service2Bean(ClientCommonBean clientCommonBean) { return new Service2Bean(clientCommonBean); } } static class Service2Bean { private final ClientCommonBean clientCommonBean; Service2Bean(ClientCommonBean clientCommonBean) { this.clientCommonBean = clientCommonBean; } @Override public String toString() { return "Service2Bean{" + "clientCommonBean=" + clientCommonBean + '}'; } } static class TestSpec implements NamedContextFactory.Specification { private final String name; private final Class<?>[] configurations; public TestSpec(String name, Class<?>[] configurations) { this.name = name; this.configurations = configurations; } @Override public String getName() { return name; } @Override public Class<?>[] getConfiguration() { return configurations; } } static class TestClient extends NamedContextFactory<TestSpec> { public TestClient(Class<?> defaultConfigType) { super(defaultConfigType, "testClient", PROPERTY_NAME); } } } 结果输出为:

com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d ClientCommonBean{name='service1', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d} Service1Bean1{clientCommonBean=ClientCommonBean{name='service1', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}} com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$Service1Bean2@4648ce9 com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d ClientCommonBean{name='service2', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d} Service2Bean{clientCommonBean=ClientCommonBean{name='service2', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}} 代码中实现了这样一个 Context 结构:

图中的被包含的 ApplicationContext 可以看到外层 ApplicationContext 的 Bean,也就是通过对被包含的 ApplicationContext 调用 getBean(xxx) 可以获取到外层 ApplicationContext 的 Bean (其实外层就是 parent ApplicationContext),但是外层的看不到内层私有的 Bean。

在我们的测试代码中,首先,创建了一个 AnnotationConfigApplicationContext。这个其实就是模拟了我们平常使用 Spring 框架的时候的根核心 ApplicationContext,所以我们将其命名为 parent。我们向里面注册了 BaseConfigBaseConfig 里面的 BaseBean 会注册到 parent。之后我们 建 testClient1,默认配置使用 ClientCommonConfig。如果我们指定了 testClient1 的 parent ApplicationContext 为 parent,那么 parent 里面的 Bean 都能被 testClient1 里面的子 ApplicationContext 访问到。然后,我们创建 service1 与 service2 以及指定对应额外的配置类。service1 会创建 ClientCommonConfigService1Config1Service1Config2 里面配置的 Bean。service2 会创建 ClientCommonConfigService2Config 里面配置的 Bean。

NamedContextFactory 的基本原理以及源码

NamedContextFactory 的核心方法是 public <T> T getInstance(String name, Class<T> type),通过这个方法获取 NamedContextFactory 里面的子 ApplicationContext 里面的 Bean。源码是:

NamedContextFactory.java

/** * 获取某个 name 的 ApplicationContext 里面的某个类型的 Bean * @param name 子 ApplicationContext 名称 * @param type 类型 * @param <T> Bean 类型 * @return Bean */ public <T> T getInstance(String name, Class<T> type) { //获取或者创建对应名称的 ApplicationContext AnnotationConfigApplicationContext context = getContext(name); try { //从对应的 ApplicationContext 获取 Bean,如果不存在则会抛出 NoSuchBeanDefinitionException return context.getBean(type); } catch (NoSuchBeanDefinitionException e) { //忽略 NoSuchBeanDefinitionException } //没找到就返回 null return null; } protected AnnotationConfigApplicationContext getContext(String name) { //如果 map 中不存在,则创建 if (!this.contexts.containsKey(name)) { //防止并发创建多个 synchronized (this.contexts) { //再次判断,防止有多个等待锁 if (!this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } return this.contexts.get(name); } //根据名称创建对应的 context protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); //如果 configurations 中有对应名称的配置类,则注册之 if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name).getConfiguration()) { context.register(configuration); } } //如果 configurations 中有名称开头为 default. 的配置类,则注册之 for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<?> configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } //注册 PropertyPlaceholderAutoConfiguration,这样可以解析 spring boot 相关的 application 配置 //注册默认的配置类 defaultConfigType context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); //将当前 context 的名称,放入对应的属性中,在配置类中可能会用到 //我们上面举得例子,就是通过 environment.getProperty() 获取了这个属性 context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.<String, Object>singletonMap(this.propertyName, name))); if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); //spring boot 可以打包成一种 fatjar 的形式,将依赖的 jar 包都打入同一个 jar 包中 //fatjar 中的依赖,通过默认的类加载器是加载不正确的,需要通过定制的类加载器 //由于 JDK 11 LTS 相对于 JDK 8 LTS 多了模块化,通过 ClassUtils.getDefaultClassLoader() 有所不同 //在 JDK 8 中获取的就是定制的类加载器,JDK 11 中获取的是默认的类加载器,这样会有问题 //所以,这里需要手动设置当前 context 的类加载器为父 context 的类加载器 context.setClassLoader(this.parent.getClassLoader()); } //生成展示名称 context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; }

有关Spring Cloud 升级之路 - 2020.0.x - 5. 理解 NamedContextF的更多相关文章

  1. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

    我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

  4. ruby - 在不使用 RVM 的情况下在 Mac 上卸载和升级 Ruby - 2

    我最近决定从我的系统中卸载RVM。在thispage提出的一些论点说服我:实际上,我的决定是,我根本不想担心Ruby的多个版本。我只想使用1.9.2-p290版本而不用担心其他任何事情。但是,当我在我的Mac上运行ruby--version时,它告诉我我的版本是1.8.7。我四处寻找如何简单地从我的Mac上卸载这个Ruby,但奇怪的是我没有找到任何东西。似乎唯一想卸载Ruby的人运行linux,而使用Mac的每个人都推荐RVM。如何从我的Mac上卸载Ruby1.8.7?我想升级到1.9.2-p290版本,并且我希望我的系统上只有一个版本。 最佳答案

  5. Tomcat AJP 文件包含漏洞(CVE-2020-1938) - 2

    目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控

  6. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  7. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  8. ruby - 易于初学者理解的 Ruby 库 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭3年前。Improvethisquestion我正处于学习Ruby的阶段,我想查看一些小型库的源代码以了解它们是如何构建的。我不知道什么是小型图书馆,但希望SO能推荐一些易于理解的图书馆来学习。因此,如果有人知道一两个非常小的库,这是新手Rubyists学习的好例子,请推荐!我想使用Manveru'sInnatelib,因为它试图保持在2000LOC以下,但我还不熟悉其中经常使用的Ruby速记。也许大约100-5

  9. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  10. ruby - 无法理解 `puts{}.class` 和 `puts({}.class)` 之间的区别 - 2

    由于匿名block和散列block看起来大致相同。我正在玩它。我做了一些严肃的观察,如下所示:{}.class#=>Hash好的,这很酷。空block被视为Hash。print{}.class#=>NilClassputs{}.class#=>NilClass为什么上面的代码和NilClass一样,下面的代码又显示了Hash?puts({}.class)#Hash#=>nilprint({}.class)#Hash=>nil谁能帮我理解上面发生了什么?我完全不同意@Lindydancer的观点你如何解释下面几行:print{}.class#NilClassprint[].class#A

随机推荐