攜程Apollo(阿波羅)配置中心在Spring Boot項目快速集成

前提:先搭建好本地的單機運行項目:http://www.cnblogs.com/EasonJim/p/7643630.htmlhtml

說明:下面的示例是基於Spring Boot搭建的,對於Spring項目基本通用。遷移舊項目的配置下一篇說明,這裏先就如何快速的集成Client和獲取配置的值進行實踐。java

0、下面的示例都是基於官方提供的教程去實踐的,能夠參考以下網址:git

https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97github

https://github.com/ctripcorp/apollo/wiki/Apollo%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97#23-java%E6%A0%B7%E4%BE%8B%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8web

一、先新建好Spring Boot項目,這裏使用了Web作測試,因此引用了Thymeleaf模板。spring

搭建參考:http://www.cnblogs.com/EasonJim/p/7519854.htmlspringboot

二、登陸Apollo上新建App和相關的配置項,能夠參考以下配置:app

三、在POM上引入Client的依賴,此時會有兩個包引入Client和Core。ide

        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>0.8.0</version>
        </dependency>

四、在項目上配置上面設置的app.id,注意此時是經過新建META-INF/app.properties文件實現的。函數

五、代碼實現

實現的功能是經過配置中心修改後,再次刷新頁面而不重啓應用的狀況下能看到值的改變。

實現方式主要幾種在兩種,1爲經過API的形式增長監聽回調函數來監聽值的改變後直接修改,2爲經過注入Bean的方式使用Bean下的專用監聽註解實現回調監聽。

5.一、經過API的方式

代碼實現以下:

package com.jsoft.springboottest.springboottest1.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;

@Controller
public class IndexController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    private Config config;    private int timeout; private int batch; private String url;

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model) {
        Entry entry = new Entry();
        entry.setText("Text");
        entry.setTitle("Title");
        model.addAttribute("entries", entry);
        model.addAttribute("entry", new Entry());
        
        model.addAttribute("url", url); model.addAttribute("timeout",timeout); model.addAttribute("batch",batch); logger.info("timeout:{}", timeout); logger.info("batch:{}", batch); logger.info("url:{}", url); return "index";
    }

    public IndexController() {
        config = ConfigService.getAppConfig(); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { logger.info("Changes for namespace {}", changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); logger.info("Change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()); if (key.equals("url")) { url = change.getNewValue(); } if(key.equals("batch")) { batch = Integer.valueOf(change.getNewValue()); } if (key.equals("timeout")) { timeout = Integer.valueOf(change.getNewValue()); } } } });
    }

}

說明:

經過新建Config對象,使用的是ConfigService.getAppConfig()獲取默認配置,也就是配置中心中的application.properties的,固然,getAppConfig能夠指定不能的namespage。

經過獲取Config對象增長監聽回調函數addChangeListener。監聽指定的值變化後,從新賦值變量。

這種方式應該是最簡單的,不用寫特殊的註解去實現,可是可能在運行時也會發現,程序在一啓動時不會去獲取默認的配置值,好比timeout這些是空的,要解決這個問題時須要在Class上增長@EnableApolloConfig的註解,而後在屬性上增長@Value的值便可,改動以下:

package com.jsoft.springboottest.springboottest1.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;

@Controller
@EnableApolloConfig public class IndexController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    private Config config;

    @Value("${timeout:200}") private int timeout;
    @Value("${batch:200}") private int batch;
    @Value("${url:http://easonjim.com}") private String url;

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model) {
        Entry entry = new Entry();
        entry.setText("Text");
        entry.setTitle("Title");
        model.addAttribute("entries", entry);
        model.addAttribute("entry", new Entry());
        
        model.addAttribute("url", url);
        model.addAttribute("timeout",timeout);
        model.addAttribute("batch",batch);

        logger.info("timeout:{}", timeout);
        logger.info("batch:{}", batch);
        logger.info("url:{}", url);

        return "index";
    }

    public IndexController() {
        config = ConfigService.getAppConfig();
        config.addChangeListener(new ConfigChangeListener() {
            @Override
            public void onChange(ConfigChangeEvent changeEvent) {
                logger.info("Changes for namespace {}", changeEvent.getNamespace());
                for (String key : changeEvent.changedKeys()) {
                    ConfigChange change = changeEvent.getChange(key);
                    logger.info("Change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType());

                    if (key.equals("url")) {
                        url = change.getNewValue();
                    }
                    if(key.equals("batch")) {
                        batch = Integer.valueOf(change.getNewValue());
                    }
                    if (key.equals("timeout")) {
                        timeout = Integer.valueOf(change.getNewValue());
                    }
                }                    
            }
        });
    }

}

說明:約定俗成,在@Value上獲取值時記得增長默認值,以防止獲取爲空。

上面代碼或許有些冗餘,能夠再提煉一下,由於config對象的值會實時更新,因此也不須要監聽onChange事件,也不須要本身建立一個變量,最後代碼實現以下:

package com.jsoft.springboottest.springboottest1.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;

@Controller
public class IndexController2 {

    private static final Logger logger = LoggerFactory.getLogger(IndexController2.class);

    private Config config = ConfigService.getAppConfig();

    @RequestMapping(value = "/index2", method = RequestMethod.GET)
    public String index(Model model) {
        Entry entry = new Entry();
        entry.setText("Text");
        entry.setTitle("Title");
        model.addAttribute("entries", entry);
        model.addAttribute("entry", new Entry());
        
        model.addAttribute("url", config.getProperty("url", ""));
        model.addAttribute("timeout",config.getProperty("timeout", ""));
        model.addAttribute("batch",config.getProperty("batch", ""));

        logger.info("timeout:{}", config.getProperty("timeout", ""));
        logger.info("batch:{}", config.getProperty("batch", ""));
        logger.info("url:{}", config.getProperty("url", ""));

        return "index";
    }

}

5.二、經過注入Bean的方式

5.2.一、新建的Bean以下:

package com.jsoft.springboottest.springboottest1.controller;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;

public class AnnotatedBean {
    private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);

    @Value("${timeout:200}")
    private int timeout;
    private int batch;

    @ApolloConfig
    private Config config;
    @ApolloConfig("FX.apollo")
    private Config anotherConfig;

    @PostConstruct
    void initialize() {
        logger.info("timeout is {}", timeout);
        logger.info("batch is {}", batch);

        logger.info("Keys for config: {}", config.getPropertyNames());
        logger.info("Keys for anotherConfig: {}", anotherConfig.getPropertyNames());
    }

    @Value("${batch:100}")
    public void setBatch(int batch) {
        this.batch = batch;
    }

    public int getBatch() {
        return batch;
    }

    public int getTimeout() {
        return timeout;
    }

    @ApolloConfigChangeListener("application") private void someChangeHandler(ConfigChangeEvent changeEvent) {
        logger.info("[someChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
        if (changeEvent.isChanged("timeout")) {
            refreshTimeout();
        }
        if (changeEvent.isChanged("batch")) {
            setBatch(Integer.valueOf(changeEvent.getChange("batch").getNewValue()));
        }
    }

    @ApolloConfigChangeListener({ "application", "FX.apollo" })
    private void anotherChangeHandler(ConfigChangeEvent changeEvent) {
        logger.info("[anotherChangeHandler]Changes for namespace {}", changeEvent.getNamespace());
        for (String key : changeEvent.changedKeys()) {
            ConfigChange change = changeEvent.getChange(key);
            logger.info("[anotherChangeHandler]Change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType());
        }
    }

    private void refreshTimeout() {
        // do some custom logic to update placeholder value
        timeout = config.getIntProperty("timeout", timeout);
        logger.info("Refreshing timeout to {}", timeout);
    }
}

5.2.二、經過@Configuration注入這個Bean,代碼以下:

package com.jsoft.springboottest.springboottest1.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;

@Configuration @EnableApolloConfig public class AnnotatedConfig {
    
    @Bean public AnnotatedBean annotatedBean() { return new AnnotatedBean(); }
}

注意:要增長@EnableApolloConfig和@Configuration,否則不會生效。而且留意到@Bean的註解,若是沒有這個時,@ApolloConfigChangeListener不會生效。這個是關鍵所在,@ApolloConfigChangeListener只能用於Bean注入上,這個和API的方式有明顯區別。

在代碼上使用:

package com.jsoft.springboottest.springboottest1.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    
    private static final Logger logger = LoggerFactory.getLogger(TestController.class);
    
    @Autowired private AnnotatedBean annotatedBean;
    
    @RequestMapping("/show")
    public String show(){
        
        logger.info("batch:{}",annotatedBean.getBatch()); logger.info("timeout:{}",annotatedBean.getTimeout()); return "Hello World"+" batch:"+annotatedBean.getBatch()+" timeout:"+annotatedBean.getTimeout();        
    }
}

說明:能夠看出,只要@Autowired注入剛纔的Bean就能夠直接使用。

六、運行,通常只須要在/opt/settings/server.properties中配置了env=DEV就能夠直接直接啓動(由於Client在本地倉庫的包上已經有了meta_server的信息),可是在IDE上也能夠經過指定VM的參數,增長系統屬性變量-D來實現調試,配置以下:

若是處處JAR運行,直接java -jar Spring-Boot-Demo.jar便可,不須要增長什麼參數(但前提是配置了/opt/settings/server.properties的env的值)。

 

測試代碼:https://github.com/easonjim/5_java_example/tree/master/apollotest/test1 

相關文章
相關標籤/搜索