TSE的Apollo配置中心托管上线了!Java 代码教学

2021-12-04 21:29:46 浏览数 (2)

配置中心是大部分服务的强需求。自己运维一套会各种踩坑。现在云上的TSE已经支持了Apollo的托管,一键部署~ 还不心动么?这篇文章,我来介绍一下如何使用Java Spring Framework接入Apollo

入门教学Demo

场景描述

  1. 从Apollo读取简单的Key-Value配置
  2. 从Apollo读取给予yaml/yml格式的Key-Value配置
  3. 从Apollo读取Key-Value配置,并映射到Java Class的属性中

Apollo配置中心状态

请在Apollo配置中心做如下配置:

  1. 在默认namespace - "application"下 创建简单的Key-Value配置
  2. 在同一个App.ID下创建另一个私有的namespace 并且是yaml/yml格式。并写一个简单的yaml/yml格式的Key-Value配置

Pom 依赖列表

代码语言:javascript复制
      <dependencies>
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                  <groupId>com.ctrip.framework.apollo</groupId>
                  <artifactId>apollo-client</artifactId>
                  <version>1.8.0</version>
            </dependency>
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-configuration-processor</artifactId>
                  <optional>true</optional>
            </dependency>
            <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <optional>true</optional>
            </dependency>
            <dependency>
                  <groupId>com.ctrip.framework.apollo</groupId>
                  <artifactId>apollo-mockserver</artifactId>
                  <version>1.8.0</version>
                  <scope>test</scope>
            </dependency>
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
            </dependency>
      </dependencies>

spring.properties 属性文件内容

代码语言:javascript复制
app.id=100001
apollo.meta=http://172.17.16.14:8080
apollo.cluster=default
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces = name.yaml
# namespace application will always be loaded by default

创建测试程序运行时的环境变量配置

代码语言:javascript复制
export ENV=dev
export APOLLO_ACCESSKEY_SECRET=**************ac407c7

代码片段

  1. 直接用@Value读取Key-Value配置,和Spring Framework的标准玩法一致。
代码语言:javascript复制
// Java Config
@Configuration
@EnableApolloConfig
public class ApolloConfig {

@Value("${app.key01:unknown}")
    String hello01;
    @Value("${app.key02:unknown}")
    String hello02;
    @Value("${app.key03:unknown}")
    String hello03;
    @Value("${app.key04:unknown}")
    String hello04;

    @PostConstruct // after the values are initialized, print their values
    void print() {
        System.out.println(this.getClass().getName()   " - "   hello01);
        System.out.println(this.getClass().getName()   " - "   hello02);
        System.out.println(this.getClass().getName()   " - "   hello03);
        System.out.println(this.getClass().getName()   " - "   hello04);
    }
}
  1. 使用Bean的方式,并且用@Value对属性进行注入
代码语言:javascript复制
@Component
@RefreshScope
public class ValuesDemoBean {

@Value("${app.key01:unknown}")
    String hello01;
    @Value("${app.key02:unknown}")
    String hello02;
    @Value("${app.key03:unknown}")
    String hello03;
    @Value("${app.key04:unknown}")
    String hello04;

    @PostConstruct // after the values are initialized, print their values
    void print() {
        System.out.println(this.getClass().getName()   " - "   hello01);
        System.out.println(this.getClass().getName()   " - "   hello02);
        System.out.println(this.getClass().getName()   " - "   hello03);
        System.out.println(this.getClass().getName()   " - "   hello04);
    }
}
  1. 映射到Java Class的属性里
代码语言:javascript复制
@Component
@RefreshScope
@ConfigurationProperties(prefix = "app") // map object to keys with prefix
public class ObjectDemoBean {

private String key01;
    private String key02;
    private String key03;
    private String key04;

    public void setKey01(String key01) {
        this.key01 = key01;
    }

    public void setKey02(String key02) {
        this.key02 = key02;
    }

    public void setKey03(String key03) {
        this.key03 = key03;
    }

    public void setKey04(String key04) {
        this.key04 = key04;
    }

    @PostConstruct // after the values are initialized, print their values
    void print() {
        System.out.println(this.getClass().getName()   " - "   key01);
        System.out.println(this.getClass().getName()   " - "   key02);
        System.out.println(this.getClass().getName()   " - "   key03);
        System.out.println(this.getClass().getName()   " - "   key04);
    }

}

代码效果

所有的键值都拿到了~ Voila!

从多个App ID获取配置信息

配置服务需要允许一个服务同时从多个AppID(配置源)获取配置信息, 比如通用信息. DNS 服务器地址, 数据库连接地址. 在下面的案例中,我们演示如何从多个AppID获取配置信息。

准备一个Global可见的namespace

  1. 首先在另外一个AppID(100002)下,创建一个叫global-demo的namespace

  1. 关联新创建的namespace到之前已经存在AppID(100001)下

  1. 之后切换到100001 AppID的界面,可以看到配置已经被显示出来了

私有Namespace vs 公有Namespace

私有的Namespace只能对当前的AppID可见,而公有的Namespace可以被关联到多个AppID下。所以生命周期是不同的。

改进排期

请注意

  1. 公有namespace当前还不支持yaml/json/properties等格式,只有最基础的Key-Value Pair格式。近期会排期支持。
  2. @ApolloConfigChangeListener 这个Annotation必须列出所有相关的Namespace。 比如: @ApolloConfigChangeListener({ "name.yaml", "application", "TEST1.global-demo" })

代码片段

代码语言:javascript复制
@Component
@RefreshScope
@ConfigurationProperties(prefix = "global.url")
public class GlobalConfigDemoBean {

    private String mysql;
    private String dns;

    public void setMysql(String mysql) {
        this.mysql = mysql;
    }

    public void setDns(String dns) {
        this.dns = dns;
    }

    @PostConstruct // after the values are initialized, print their values
    void print() {
        System.out.println(this.getClass().getName()   " - "   mysql);
        System.out.println(this.getClass().getName()   " - "   dns);
    }
}

代码效果

就出来了!Fantastic!

配置自动刷新

@Value Annotation

基于@Value注入的消息会被自动更新,比如下面这个代码

代码语言:javascript复制
@Configuration
@EnableApolloConfig
public class HelloConfig {

      @Value("${app.key01:unknown}")
      String hello01;
      @Value("${app.key02:unknown}")
      String hello02;

      @PostConstruct // after the values are initialized, print their values
      void print() {
            System.out.println(this.getClass().getName()   " - "   hello01);
            System.out.println(this.getClass().getName()   " - "   hello02);
      }
}

@ConfigurationProperties注入的代码

  1. 需要添加一个新的依赖
代码语言:javascript复制
<dependency>
<groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-context</artifactId>
      <version>2.0.3.RELEASE</version>
</dependency>
  1. 然后在Bean的定义添加@RefreshScope注释
代码语言:javascript复制
@Component
@RefreshScope
@ConfigurationProperties(prefix = "global.url")
  1. 添加如下的代码(@ApolloConfigChangeListener)
代码语言:javascript复制
@Configuration
public class ApolloConfigRefreshBean implements ApplicationContextAware {

private ApplicationContext applicationContext;

    @Autowired
    RefreshScope refreshScope;

    // all related namespaces
    @ApolloConfigChangeListener({ "name.yaml", "application", "TEST1.global-demo" })
    private void someChangeHandler(ConfigChangeEvent changeEvent) {
        System.out.println("Apollo Auto-refresh Started");

        for (String changedKey : changeEvent.changedKeys()) {

            ConfigChange configChange = changeEvent.getChange(changedKey);
            String oldValue = configChange.getOldValue();
            String newValue = configChange.getNewValue();
            System.out.printf("[changedKey:%s,oldValue=%s, newValue:%s]", changedKey, oldValue, newValue);
        }

        refreshProperties(changeEvent);

        System.out.println("Apollo Auto-refresh done");
    }

    public void refreshProperties(ConfigChangeEvent changeEvent) {
        //all refreshScoped and annotated with @ConfigurationProperties
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
        refreshScope.refreshAll();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

代码效果

键值也自动刷新了 - Ja, voll!

如何运行这个Demo

手动配置项

  1. 使用正确的用户名密码登录Apollo
  2. 创建app.id 100001。
  3. 创建app.id 100002。
  4. 在100001 appid下创建key-value键值对,比如:app.key01:hey01 - 注意创建之后,需要发布才会生效
  5. 创建一个叫 "name"的私有namespace,并使用yaml格式。写一些yaml格式的内容,如下。
代码语言:javascript复制
app:
  key01: hello01
  key02: hello02
  1. 在appid 100002下创建一个叫"global-demo"的公有 namespace,并且把它关联到appid 100001。

启动Java程序

下面的配置会加载所有在app.id 100001的namespace application下的键值。

代码语言:javascript复制
app.id=100001
apollo.meta=http://[apollo-meta-server]:8080
apollo.cluster=default
apollo.bootstrap.enabled=true

如果你想加载更多的namespace,比如yaml等,需要写如下的配置 apollo.bootstrap.namespaces = name.yaml,TEST1.global-demo

代码语言:javascript复制
app.id=100001
apollo.meta=http://[apollo-meta-server]:8080
apollo.cluster=default
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces = name.yaml,TEST1.global-demo
# 这里关联namespace格式是[original-app.id].[namespace]

如何Mock本地Unit Test

使用这个文档 来构建Mock Unit Test

0 人点赞