Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
wrr-cat
apollo
提交
bde0a061
apollo
项目概览
wrr-cat
/
apollo
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
apollo
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
bde0a061
编写于
3月 23, 2016
作者:
Y
Yiming Liu
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #28 from nobodyiam/client-refresh-merge
Add refresh config support and refactor
上级
51978dd6
49c1b308
变更
23
显示空白变更内容
内联
并排
Showing
23 changed file
with
866 addition
and
201 deletion
+866
-201
apollo-biz/src/main/resources/import.sql
apollo-biz/src/main/resources/import.sql
+1
-0
apollo-client/src/main/java/com/ctrip/apollo/client/ApolloConfigManager.java
...ain/java/com/ctrip/apollo/client/ApolloConfigManager.java
+38
-15
apollo-client/src/main/java/com/ctrip/apollo/client/enums/PropertyChangeType.java
...ava/com/ctrip/apollo/client/enums/PropertyChangeType.java
+21
-0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoader.java
...ain/java/com/ctrip/apollo/client/loader/ConfigLoader.java
+5
-6
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java
...a/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java
+22
-1
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderManager.java
...a/com/ctrip/apollo/client/loader/ConfigLoaderManager.java
+190
-0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/AbstractConfigLoader.java
...ctrip/apollo/client/loader/impl/AbstractConfigLoader.java
+35
-0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/InMemoryConfigLoader.java
...ctrip/apollo/client/loader/impl/InMemoryConfigLoader.java
+15
-0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/LocalFileConfigLoader.java
...trip/apollo/client/loader/impl/LocalFileConfigLoader.java
+15
-0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java
...m/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java
+38
-93
apollo-client/src/main/java/com/ctrip/apollo/client/model/ApolloRegistry.java
...in/java/com/ctrip/apollo/client/model/ApolloRegistry.java
+11
-0
apollo-client/src/main/java/com/ctrip/apollo/client/model/PropertyChange.java
...in/java/com/ctrip/apollo/client/model/PropertyChange.java
+52
-0
apollo-client/src/main/java/com/ctrip/apollo/client/model/PropertySourceReloadResult.java
...ctrip/apollo/client/model/PropertySourceReloadResult.java
+44
-0
apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java
...rc/main/java/com/ctrip/apollo/client/util/ConfigUtil.java
+1
-1
apollo-client/src/test/java/com/ctrip/apollo/client/AllTests.java
...lient/src/test/java/com/ctrip/apollo/client/AllTests.java
+3
-1
apollo-client/src/test/java/com/ctrip/apollo/client/ApolloConfigManagerTest.java
...java/com/ctrip/apollo/client/ApolloConfigManagerTest.java
+49
-10
apollo-client/src/test/java/com/ctrip/apollo/client/loader/ConfigLoaderManagerTest.java
...m/ctrip/apollo/client/loader/ConfigLoaderManagerTest.java
+198
-0
apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java
...rip/apollo/client/loader/impl/RemoteConfigLoaderTest.java
+65
-37
apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java
...rip/apollo/configservice/controller/ConfigController.java
+2
-5
apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java
...src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java
+23
-1
apollo-demo/src/main/java/com/ctrip/apollo/demo/controller/DemoController.java
...java/com/ctrip/apollo/demo/controller/DemoController.java
+8
-5
apollo-demo/src/main/webapp/s/scripts/app.js
apollo-demo/src/main/webapp/s/scripts/app.js
+21
-19
apollo-demo/src/main/webapp/s/templates/list.html
apollo-demo/src/main/webapp/s/templates/list.html
+9
-7
未找到文件。
apollo-biz/src/main/resources/import.sql
浏览文件 @
bde0a061
...
...
@@ -6,3 +6,4 @@ INSERT INTO Version (AppId, IsDeleted, Name, ReleaseId) VALUES (102, 0, '1.0', 2
INSERT
INTO
RELEASESNAPSHOT
(
ClusterName
,
IsDeleted
,
ReleaseId
,
Configurations
)
VALUES
(
'default'
,
0
,
1
,
'{"apollo.foo":"bar", "apollo.bar":"foo"}'
);
INSERT
INTO
RELEASESNAPSHOT
(
ClusterName
,
IsDeleted
,
ReleaseId
,
Configurations
)
VALUES
(
'default'
,
0
,
2
,
'{"demo.foo":"demo1", "demo.bar":"demo2"}'
);
INSERT
INTO
RELEASESNAPSHOT
(
ClusterName
,
IsDeleted
,
ReleaseId
,
Configurations
)
VALUES
(
'default'
,
0
,
3
,
'{"apollo.foo":"another bar", "apollo.bar_new":"foo"}'
);
apollo-client/src/main/java/com/ctrip/apollo/client/ApolloConfigManager.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client
;
import
com.ctrip.apollo.client.loader.ConfigLoader
;
import
com.ctrip.apollo.client.loader.ConfigLoaderFactory
;
import
com.ctrip.apollo.client.loader.ConfigLoaderManager
;
import
com.ctrip.apollo.client.model.PropertyChange
;
import
com.ctrip.apollo.client.model.PropertySourceReloadResult
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.BeansException
;
import
org.springframework.beans.factory.config.BeanDefinition
;
import
org.springframework.beans.factory.config.ConfigurableListableBeanFactory
;
...
...
@@ -19,6 +23,7 @@ import org.springframework.core.PriorityOrdered;
import
org.springframework.core.env.CompositePropertySource
;
import
org.springframework.core.env.MutablePropertySources
;
import
java.util.List
;
import
java.util.concurrent.atomic.AtomicReference
;
/**
...
...
@@ -27,19 +32,18 @@ import java.util.concurrent.atomic.AtomicReference;
* @author Jason Song(song_s@ctrip.com)
*/
public
class
ApolloConfigManager
implements
BeanDefinitionRegistryPostProcessor
,
PriorityOrdered
,
ApplicationContextAware
{
p
ublic
static
final
String
APOLLO_PROPERTY_SOURCE_NAME
=
"ApolloConfigProperties"
;
p
rivate
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
ApolloConfigManager
.
class
)
;
private
static
AtomicReference
<
ApolloConfigManager
>
singletonProtector
=
new
AtomicReference
<
ApolloConfigManager
>();
private
ConfigLoader
configLoad
er
;
private
ConfigLoader
Manager
configLoaderManag
er
;
private
ConfigurableApplicationContext
applicationContext
;
private
CompositePropertySource
currentPropertySource
;
private
RefreshScope
scope
;
public
ApolloConfigManager
()
{
if
(!
singletonProtector
.
compareAndSet
(
null
,
this
))
{
throw
new
IllegalStateException
(
"There should be only one ApolloConfigManager instance!"
);
}
this
.
configLoader
=
ConfigLoaderFactory
.
getInstance
().
getRemoteConfigLoad
er
();
this
.
configLoader
Manager
=
ConfigLoaderFactory
.
getInstance
().
getConfigLoaderManag
er
();
}
@Override
...
...
@@ -97,23 +101,42 @@ public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor,
}
/**
* Prepare property sources
* First try to load from remote
* If loading from remote failed, then fall back to local cached properties
* Initialize property sources
*/
void
initializePropertySource
()
{
currentPropertySource
=
loadPropertySource
();
//TODO stop application from starting when config cannot be loaded?
CompositePropertySource
result
=
this
.
configLoaderManager
.
loadPropertySource
();
updateEnvironmentPropertySource
(
result
);
}
private
void
updateEnvironmentPropertySource
(
CompositePropertySource
currentPropertySource
)
{
MutablePropertySources
currentPropertySources
=
applicationContext
.
getEnvironment
().
getPropertySources
();
if
(
currentPropertySources
.
contains
(
currentPropertySource
.
getName
()))
{
currentPropertySources
.
remove
(
currentPropertySource
.
getName
());
currentPropertySources
.
replace
(
currentPropertySource
.
getName
(),
currentPropertySource
);
return
;
}
currentPropertySources
.
addFirst
(
currentPropertySource
);
}
CompositePropertySource
loadPropertySource
()
{
CompositePropertySource
compositePropertySource
=
new
CompositePropertySource
(
APOLLO_PROPERTY_SOURCE_NAME
);
compositePropertySource
.
addPropertySource
(
configLoader
.
loadPropertySource
());
return
compositePropertySource
;
public
List
<
PropertyChange
>
updatePropertySource
()
{
PropertySourceReloadResult
result
=
this
.
configLoaderManager
.
reloadPropertySource
();
if
(
result
.
hasChanges
())
{
updateEnvironmentPropertySource
(
result
.
getPropertySource
());
refreshBeans
();
}
return
result
.
getChanges
();
}
private
void
refreshBeans
()
{
if
(
this
.
scope
==
null
)
{
this
.
scope
=
applicationContext
.
getBean
(
"refreshScope"
,
RefreshScope
.
class
);
}
if
(
this
.
scope
==
null
)
{
logger
.
error
(
"Could not get refresh scope object, skip refresh beans"
);
}
this
.
scope
.
refreshAll
();
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/enums/PropertyChangeType.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.enums
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
enum
PropertyChangeType
{
NEW
(
"New"
),
MODIFIED
(
"Modified"
),
DELETED
(
"Deleted"
);
private
String
type
;
PropertyChangeType
(
String
type
)
{
this
.
type
=
type
;
}
public
String
getType
()
{
return
type
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoader.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader
;
import
org.springframework.core.env.CompositePropertySource
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
/**
* @author Jason Song(songs_ctrip.com)
*/
public
interface
ConfigLoader
{
/**
* Load property source for client use
* @return property source
*/
CompositePropertySource
loadPropertySource
();
ApolloConfig
loadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
);
void
setFallBackLoader
(
ConfigLoader
configLoader
);
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader
;
import
com.ctrip.apollo.client.loader.impl.InMemoryConfigLoader
;
import
com.ctrip.apollo.client.loader.impl.LocalFileConfigLoader
;
import
com.ctrip.apollo.client.loader.impl.RemoteConfigLoader
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
org.springframework.web.client.RestTemplate
;
/**
* @author Jason Song(song_s@ctrip.com)
...
...
@@ -15,7 +19,24 @@ public class ConfigLoaderFactory {
return
configLoaderFactory
;
}
public
ConfigLoader
getLocalFileConfigLoader
()
{
ConfigLoader
configLoader
=
new
LocalFileConfigLoader
();
return
configLoader
;
}
public
ConfigLoader
getInMemoryConfigLoader
()
{
ConfigLoader
inMemoryConfigLoader
=
new
InMemoryConfigLoader
();
inMemoryConfigLoader
.
setFallBackLoader
(
getLocalFileConfigLoader
());
return
inMemoryConfigLoader
;
}
public
ConfigLoader
getRemoteConfigLoader
()
{
return
new
RemoteConfigLoader
();
ConfigLoader
remoteConfigLoader
=
new
RemoteConfigLoader
(
new
RestTemplate
(),
ConfigUtil
.
getInstance
());
// remoteConfigLoader.setFallBackLoader(getInMemoryConfigLoader());
return
remoteConfigLoader
;
}
public
ConfigLoaderManager
getConfigLoaderManager
()
{
return
new
ConfigLoaderManager
(
getRemoteConfigLoader
(),
ConfigUtil
.
getInstance
());
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderManager.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader
;
import
com.ctrip.apollo.client.enums.PropertyChangeType
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.client.model.PropertyChange
;
import
com.ctrip.apollo.client.model.PropertySourceReloadResult
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
import
com.google.common.collect.Lists
;
import
com.google.common.collect.Maps
;
import
com.google.common.collect.Sets
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.core.env.CompositePropertySource
;
import
org.springframework.core.env.MapPropertySource
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.concurrent.*
;
import
java.util.concurrent.atomic.AtomicLong
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
class
ConfigLoaderManager
{
public
static
final
String
APOLLO_PROPERTY_SOURCE_NAME
=
"ApolloConfigProperties"
;
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
ConfigLoaderManager
.
class
);
private
ConfigLoader
configLoader
;
private
ConfigUtil
configUtil
;
private
final
ExecutorService
executorService
;
private
final
AtomicLong
counter
;
private
Map
<
ApolloRegistry
,
ApolloConfig
>
currentApolloRegistryConfigCache
;
private
Map
<
ApolloRegistry
,
ApolloConfig
>
previousApolloRegistryConfigCache
;
private
List
<
ApolloRegistry
>
apolloRegistries
;
public
ConfigLoaderManager
(
ConfigLoader
configLoader
,
ConfigUtil
configUtil
)
{
this
.
configLoader
=
configLoader
;
this
.
configUtil
=
configUtil
;
this
.
counter
=
new
AtomicLong
();
this
.
executorService
=
Executors
.
newFixedThreadPool
(
5
,
new
ThreadFactory
()
{
@Override
public
Thread
newThread
(
Runnable
r
)
{
Thread
thread
=
new
Thread
(
r
,
"ConfigLoaderManager-"
+
counter
.
incrementAndGet
());
return
thread
;
}
});
this
.
currentApolloRegistryConfigCache
=
Maps
.
newConcurrentMap
();
}
public
CompositePropertySource
loadPropertySource
()
{
try
{
apolloRegistries
=
configUtil
.
loadApolloRegistries
();
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"Load apollo config registry failed"
,
e
);
}
return
loadPropertySourceWithApolloRegistries
(
apolloRegistries
);
}
public
PropertySourceReloadResult
reloadPropertySource
()
{
CompositePropertySource
composite
=
loadPropertySourceWithApolloRegistries
(
apolloRegistries
);
List
<
ApolloConfig
>
previous
=
Lists
.
newArrayList
(
this
.
previousApolloRegistryConfigCache
.
values
());
List
<
ApolloConfig
>
current
=
Lists
.
newArrayList
(
this
.
currentApolloRegistryConfigCache
.
values
());
return
new
PropertySourceReloadResult
(
composite
,
calcPropertyChanges
(
previous
,
current
));
}
/**
* Load property source with apollo registries provided
* Should not be invoked in parallel since there are some operations like create/destroy cache,
* writing to files etc.
* @param apolloRegistries
* @return
*/
private
synchronized
CompositePropertySource
loadPropertySourceWithApolloRegistries
(
List
<
ApolloRegistry
>
apolloRegistries
)
{
resetApolloRegistryConfigCache
();
CompositePropertySource
composite
=
new
CompositePropertySource
(
APOLLO_PROPERTY_SOURCE_NAME
);
if
(
apolloRegistries
==
null
||
apolloRegistries
.
isEmpty
())
{
logger
.
warn
(
"No Apollo Registry found!"
);
return
composite
;
}
try
{
List
<
ApolloConfig
>
apolloConfigList
=
loadApolloConfigs
(
apolloRegistries
);
Collections
.
sort
(
apolloConfigList
);
for
(
ApolloConfig
apolloConfig
:
apolloConfigList
)
{
composite
.
addPropertySource
(
new
MapPropertySource
(
assemblePropertySourceName
(
apolloConfig
),
apolloConfig
.
getConfigurations
()));
}
return
composite
;
}
catch
(
Throwable
throwable
)
{
throw
new
RuntimeException
(
"Load apollo configs failed"
,
throwable
);
}
}
List
<
PropertyChange
>
calcPropertyChanges
(
List
<
ApolloConfig
>
previous
,
List
<
ApolloConfig
>
current
)
{
Map
<
String
,
Object
>
previousMap
=
collectConfigurations
(
previous
);
Map
<
String
,
Object
>
currentMap
=
collectConfigurations
(
current
);
Set
<
String
>
previousKeys
=
previousMap
.
keySet
();
Set
<
String
>
currentKeys
=
currentMap
.
keySet
();
Set
<
String
>
commonKeys
=
Sets
.
intersection
(
previousKeys
,
currentKeys
);
Set
<
String
>
newKeys
=
Sets
.
difference
(
currentKeys
,
commonKeys
);
Set
<
String
>
removedKeys
=
Sets
.
difference
(
previousKeys
,
commonKeys
);
List
<
PropertyChange
>
changes
=
Lists
.
newArrayList
();
for
(
String
newKey
:
newKeys
)
{
changes
.
add
(
new
PropertyChange
(
newKey
,
null
,
currentMap
.
get
(
newKey
),
PropertyChangeType
.
NEW
));
}
for
(
String
removedKey
:
removedKeys
)
{
changes
.
add
(
new
PropertyChange
(
removedKey
,
previousMap
.
get
(
removedKey
),
null
,
PropertyChangeType
.
DELETED
));
}
for
(
String
commonKey
:
commonKeys
)
{
if
(
previousMap
.
get
(
commonKey
).
equals
(
currentMap
.
get
(
commonKey
)))
{
continue
;
}
changes
.
add
(
new
PropertyChange
(
commonKey
,
previousMap
.
get
(
commonKey
),
currentMap
.
get
(
commonKey
),
PropertyChangeType
.
MODIFIED
));
}
return
changes
;
}
Map
<
String
,
Object
>
collectConfigurations
(
List
<
ApolloConfig
>
apolloConfigs
)
{
Collections
.
sort
(
apolloConfigs
);
Map
<
String
,
Object
>
configMap
=
Maps
.
newHashMap
();
for
(
int
i
=
apolloConfigs
.
size
()
-
1
;
i
>
-
1
;
i
--)
{
configMap
.
putAll
(
apolloConfigs
.
get
(
i
).
getConfigurations
());
}
return
configMap
;
}
List
<
ApolloConfig
>
loadApolloConfigs
(
List
<
ApolloRegistry
>
apolloRegistries
)
throws
Throwable
{
List
<
Future
<
ApolloConfig
>>
futures
=
Lists
.
newArrayList
();
for
(
final
ApolloRegistry
apolloRegistry
:
apolloRegistries
)
{
futures
.
add
(
executorService
.
submit
(
new
Callable
<
ApolloConfig
>()
{
@Override
public
ApolloConfig
call
()
throws
Exception
{
return
loadSingleApolloConfig
(
apolloRegistry
);
}
}));
}
List
<
ApolloConfig
>
apolloConfigList
=
Lists
.
newArrayList
();
for
(
Future
<
ApolloConfig
>
future
:
futures
)
{
try
{
ApolloConfig
result
=
future
.
get
();
if
(
result
==
null
)
{
continue
;
}
apolloConfigList
.
add
(
result
);
}
catch
(
ExecutionException
e
)
{
throw
e
.
getCause
();
}
}
return
apolloConfigList
;
}
ApolloConfig
loadSingleApolloConfig
(
ApolloRegistry
apolloRegistry
)
{
ApolloConfig
result
=
configLoader
.
loadApolloConfig
(
apolloRegistry
,
getPreviousApolloConfig
(
apolloRegistry
));
if
(
result
==
null
)
{
logger
.
error
(
"Loaded config null..."
);
return
null
;
}
logger
.
info
(
"Loaded config: {}"
,
result
);
updateCurrentApolloConfigCache
(
apolloRegistry
,
result
);
return
result
;
}
void
resetApolloRegistryConfigCache
()
{
this
.
previousApolloRegistryConfigCache
=
currentApolloRegistryConfigCache
;
this
.
currentApolloRegistryConfigCache
=
Maps
.
newConcurrentMap
();
}
ApolloConfig
getPreviousApolloConfig
(
ApolloRegistry
apolloRegistry
)
{
return
previousApolloRegistryConfigCache
.
get
(
apolloRegistry
);
}
void
updateCurrentApolloConfigCache
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
apolloConfig
)
{
currentApolloRegistryConfigCache
.
put
(
apolloRegistry
,
apolloConfig
);
}
private
String
assemblePropertySourceName
(
ApolloConfig
apolloConfig
)
{
return
String
.
format
(
"%d-%s-%s-%d"
,
apolloConfig
.
getAppId
(),
apolloConfig
.
getCluster
(),
apolloConfig
.
getVersion
(),
apolloConfig
.
getReleaseId
());
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/AbstractConfigLoader.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader.impl
;
import
com.ctrip.apollo.client.loader.ConfigLoader
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
abstract
class
AbstractConfigLoader
implements
ConfigLoader
{
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
AbstractConfigLoader
.
class
);
private
ConfigLoader
fallback
;
@Override
public
ApolloConfig
loadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
)
{
try
{
return
doLoadApolloConfig
(
apolloRegistry
,
previous
);
}
catch
(
Throwable
e
)
{
if
(
this
.
fallback
==
null
)
{
throw
new
RuntimeException
(
String
.
format
(
"Load Apollo Config failed - %s"
,
apolloRegistry
.
toString
()),
e
);
}
logger
.
error
(
"Load Config via {} failed, try to use its fallback {} to load"
,
getClass
().
getSimpleName
(),
fallback
.
getClass
().
getSimpleName
(),
e
);
return
this
.
fallback
.
loadApolloConfig
(
apolloRegistry
,
previous
);
}
}
protected
abstract
ApolloConfig
doLoadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
);
@Override
public
void
setFallBackLoader
(
ConfigLoader
configLoader
)
{
this
.
fallback
=
configLoader
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/InMemoryConfigLoader.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader.impl
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
class
InMemoryConfigLoader
extends
AbstractConfigLoader
{
@Override
protected
ApolloConfig
doLoadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
)
{
return
null
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/LocalConfigLoader.java
→
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/Local
File
ConfigLoader.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader.impl
;
import
com.ctrip.apollo.client.
loader.ConfigLoader
;
import
org.springframework.core.env.CompositePropertySource
;
import
com.ctrip.apollo.client.
model.ApolloRegistry
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
/**
* Load config from local backup file
* @author Jason Song(song_s@ctrip.com)
*/
public
class
LocalConfigLoader
implements
ConfigLoader
{
private
static
final
String
PROPERTY_SOURCE_NAME
=
"ApolloLocalConfigProperties"
;
public
class
LocalFileConfigLoader
extends
AbstractConfigLoader
{
@Override
public
CompositePropertySource
loadPropertySource
(
)
{
public
ApolloConfig
doLoadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
)
{
return
null
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader.impl
;
import
com.ctrip.apollo.client.loader.ConfigLoader
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
import
com.google.common.collect.
List
s
;
import
com.google.common.collect.
Map
s
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.core.env.CompositePropertySource
;
import
org.springframework.core.env.MapPropertySource
;
import
org.springframework.http.HttpEntity
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.HttpStatus
;
...
...
@@ -16,126 +13,74 @@ import org.springframework.http.ResponseEntity;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.client.RestTemplate
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.concurrent.*
;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.Map
;
/**
* Load config from remote config server
*
* @author Jason Song(song_s@ctrip.com)
*/
public
class
RemoteConfigLoader
implements
ConfigLoader
{
private
static
final
String
PROPERTY_SOURCE_NAME
=
"ApolloRemoteConfigProperties"
;
public
class
RemoteConfigLoader
extends
AbstractConfigLoader
{
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
RemoteConfigLoader
.
class
);
private
final
RestTemplate
restTemplate
;
private
final
ConfigUtil
configUtil
;
private
final
ExecutorService
executorService
;
private
final
AtomicLong
counter
;
public
RemoteConfigLoader
()
{
this
(
new
RestTemplate
(),
ConfigUtil
.
getInstance
());
}
public
RemoteConfigLoader
(
RestTemplate
restTemplate
,
ConfigUtil
configUtil
)
{
this
.
restTemplate
=
restTemplate
;
this
.
configUtil
=
configUtil
;
this
.
counter
=
new
AtomicLong
();
this
.
executorService
=
Executors
.
newFixedThreadPool
(
5
,
new
ThreadFactory
()
{
@Override
public
Thread
newThread
(
Runnable
r
)
{
Thread
thread
=
new
Thread
(
r
,
"RemoteConfigLoader-"
+
counter
.
incrementAndGet
());
return
thread
;
}
});
}
@Override
public
CompositePropertySource
loadPropertySource
()
{
CompositePropertySource
composite
=
new
CompositePropertySource
(
PROPERTY_SOURCE_NAME
);
List
<
ApolloRegistry
>
apolloRegistries
;
try
{
apolloRegistries
=
configUtil
.
loadApolloRegistries
();
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"Load apollo config registry failed"
,
e
);
}
if
(
apolloRegistries
==
null
||
apolloRegistries
.
isEmpty
())
{
logger
.
warn
(
"No Apollo Registry found!"
);
return
composite
;
}
try
{
doLoadRemoteApolloConfig
(
apolloRegistries
,
composite
);
}
catch
(
Throwable
throwable
)
{
throw
new
RuntimeException
(
"Load remote property source failed"
,
throwable
);
}
return
composite
;
}
ApolloConfig
getRemoteConfig
(
RestTemplate
restTemplate
,
String
uri
,
String
cluster
,
ApolloRegistry
apolloRegistry
,
ApolloConfig
previousConfig
)
{
long
appId
=
apolloRegistry
.
getAppId
();
String
version
=
apolloRegistry
.
getVersion
();
void
doLoadRemoteApolloConfig
(
List
<
ApolloRegistry
>
apolloRegistries
,
CompositePropertySource
compositePropertySource
)
throws
Throwable
{
List
<
Future
<
MapPropertySource
>>
futures
=
Lists
.
newArrayList
();
for
(
final
ApolloRegistry
apolloRegistry
:
apolloRegistries
)
{
futures
.
add
(
executorService
.
submit
(
new
Callable
<
MapPropertySource
>()
{
@Override
public
MapPropertySource
call
()
throws
Exception
{
return
loadSingleApolloConfig
(
apolloRegistry
.
getAppId
(),
apolloRegistry
.
getVersion
());
}
}));
}
for
(
Future
<
MapPropertySource
>
future
:
futures
)
{
try
{
MapPropertySource
result
=
future
.
get
();
if
(
result
==
null
)
{
continue
;
}
compositePropertySource
.
addPropertySource
(
result
);
}
catch
(
ExecutionException
e
)
{
throw
e
.
getCause
();
}
}
}
MapPropertySource
loadSingleApolloConfig
(
long
appId
,
String
version
)
{
ApolloConfig
result
=
this
.
getRemoteConfig
(
restTemplate
,
configUtil
.
getConfigServerUrl
(),
appId
,
configUtil
.
getCluster
(),
version
);
if
(
result
==
null
)
{
logger
.
error
(
"Loaded config null..."
);
return
null
;
}
logger
.
info
(
"Loaded config: {}"
,
result
);
return
new
MapPropertySource
(
assemblePropertySourceName
(
result
),
result
.
getConfigurations
());
}
private
String
assemblePropertySourceName
(
ApolloConfig
apolloConfig
)
{
return
String
.
format
(
"%d-%s-%s-%d"
,
apolloConfig
.
getAppId
(),
apolloConfig
.
getCluster
(),
apolloConfig
.
getVersion
(),
apolloConfig
.
getReleaseId
());
}
ApolloConfig
getRemoteConfig
(
RestTemplate
restTemplate
,
String
uri
,
long
appId
,
String
cluster
,
String
version
)
{
logger
.
info
(
"Loading config from {}, appId={}, cluster={}, version={}"
,
uri
,
appId
,
cluster
,
version
);
String
path
=
"config/{appId}/{cluster}"
;
Object
[]
args
=
new
String
[]
{
String
.
valueOf
(
appId
),
cluster
};
String
path
=
"/config/{appId}/{cluster}"
;
Map
<
String
,
Object
>
paramMap
=
Maps
.
newHashMap
();
paramMap
.
put
(
"appId"
,
appId
);
paramMap
.
put
(
"cluster"
,
cluster
);
if
(
StringUtils
.
hasText
(
version
))
{
args
=
new
String
[]
{
String
.
valueOf
(
appId
),
cluster
,
version
};
path
=
path
+
"/{version}"
;
paramMap
.
put
(
"version"
,
version
);
}
ResponseEntity
<
ApolloConfig
>
response
=
null
;
if
(
previousConfig
!=
null
)
{
path
=
path
+
"?releaseId={releaseId}"
;
paramMap
.
put
(
"releaseId"
,
previousConfig
.
getReleaseId
());
}
ResponseEntity
<
ApolloConfig
>
response
;
try
{
// TODO retry
response
=
restTemplate
.
exchange
(
uri
+
path
,
HttpMethod
.
GET
,
new
HttpEntity
<
Void
>((
Void
)
null
),
ApolloConfig
.
class
,
args
);
}
catch
(
Exception
e
)
{
+
path
,
HttpMethod
.
GET
,
new
HttpEntity
<
Void
>((
Void
)
null
),
ApolloConfig
.
class
,
paramMap
);
}
catch
(
Throwable
e
)
{
throw
e
;
}
if
(
response
==
null
||
response
.
getStatusCode
()
!=
HttpStatus
.
OK
)
{
if
(
response
==
null
)
{
throw
new
RuntimeException
(
"Load apollo config failed, response is null"
);
}
if
(
response
.
getStatusCode
()
==
HttpStatus
.
NOT_MODIFIED
)
{
return
null
;
}
if
(
response
.
getStatusCode
()
!=
HttpStatus
.
OK
)
{
throw
new
RuntimeException
(
String
.
format
(
"Load apollo config failed, response status %s"
,
response
.
getStatusCode
()));
}
ApolloConfig
result
=
response
.
getBody
();
return
result
;
}
@Override
protected
ApolloConfig
doLoadApolloConfig
(
ApolloRegistry
apolloRegistry
,
ApolloConfig
previous
)
{
ApolloConfig
result
=
this
.
getRemoteConfig
(
restTemplate
,
configUtil
.
getConfigServerUrl
(),
configUtil
.
getCluster
(),
apolloRegistry
,
previous
);
//When remote server return 304, we need to return the previous result
return
result
==
null
?
previous
:
result
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/model/ApolloRegistry.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.model
;
import
com.google.common.base.MoreObjects
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
...
...
@@ -22,4 +24,13 @@ public class ApolloRegistry {
public
void
setVersion
(
String
version
)
{
this
.
version
=
version
;
}
@Override
public
String
toString
()
{
return
MoreObjects
.
toStringHelper
(
this
)
.
omitNullValues
()
.
add
(
"appId"
,
appId
)
.
add
(
"version"
,
version
)
.
toString
();
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/model/PropertyChange.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.model
;
import
com.ctrip.apollo.client.enums.PropertyChangeType
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
class
PropertyChange
{
private
String
propertyName
;
private
Object
oldValue
;
private
Object
newValue
;
private
PropertyChangeType
changeType
;
public
PropertyChange
(
String
propertyName
,
Object
oldValue
,
Object
newValue
,
PropertyChangeType
changeType
)
{
this
.
propertyName
=
propertyName
;
this
.
oldValue
=
oldValue
;
this
.
newValue
=
newValue
;
this
.
changeType
=
changeType
;
}
public
String
getPropertyName
()
{
return
propertyName
;
}
public
void
setPropertyName
(
String
propertyName
)
{
this
.
propertyName
=
propertyName
;
}
public
Object
getOldValue
()
{
return
oldValue
;
}
public
void
setOldValue
(
Object
oldValue
)
{
this
.
oldValue
=
oldValue
;
}
public
Object
getNewValue
()
{
return
newValue
;
}
public
void
setNewValue
(
Object
newValue
)
{
this
.
newValue
=
newValue
;
}
public
PropertyChangeType
getChangeType
()
{
return
changeType
;
}
public
void
setChangeType
(
PropertyChangeType
changeType
)
{
this
.
changeType
=
changeType
;
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/model/PropertySourceReloadResult.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.model
;
import
com.google.common.collect.Lists
;
import
org.springframework.core.env.CompositePropertySource
;
import
java.util.List
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
class
PropertySourceReloadResult
{
private
CompositePropertySource
propertySource
;
private
List
<
PropertyChange
>
changes
;
public
PropertySourceReloadResult
(
CompositePropertySource
propertySource
)
{
this
.
propertySource
=
propertySource
;
changes
=
Lists
.
newArrayList
();
}
public
PropertySourceReloadResult
(
CompositePropertySource
propertySource
,
List
<
PropertyChange
>
changes
)
{
this
.
propertySource
=
propertySource
;
this
.
changes
=
changes
;
}
public
CompositePropertySource
getPropertySource
()
{
return
propertySource
;
}
public
void
setPropertySource
(
CompositePropertySource
propertySource
)
{
this
.
propertySource
=
propertySource
;
}
public
List
<
PropertyChange
>
getChanges
()
{
return
changes
;
}
public
void
setChanges
(
List
<
PropertyChange
>
changes
)
{
this
.
changes
=
changes
;
}
public
boolean
hasChanges
()
{
return
!
changes
.
isEmpty
();
}
}
apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java
浏览文件 @
bde0a061
...
...
@@ -33,7 +33,7 @@ public class ConfigUtil {
public
String
getConfigServerUrl
()
{
// TODO return the meta server url based on different environments
return
"http://localhost
:8888
"
;
return
"http://localhost"
;
}
public
String
getCluster
()
{
...
...
apollo-client/src/test/java/com/ctrip/apollo/client/AllTests.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client
;
import
com.ctrip.apollo.client.loader.ConfigLoaderManagerTest
;
import
com.ctrip.apollo.client.loader.impl.RemoteConfigLoaderTest
;
import
com.ctrip.apollo.client.util.ConfigUtilTest
;
import
org.junit.runner.RunWith
;
...
...
@@ -8,7 +9,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith
(
Suite
.
class
)
@SuiteClasses
({
ApolloConfigManagerTest
.
class
,
RemoteConfigLoaderTest
.
class
,
ConfigUtilTest
.
class
ApolloConfigManagerTest
.
class
,
ConfigLoaderManagerTest
.
class
,
RemoteConfigLoaderTest
.
class
,
ConfigUtilTest
.
class
})
public
class
AllTests
{
...
...
apollo-client/src/test/java/com/ctrip/apollo/client/ApolloConfigManagerTest.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.client
;
import
com.ctrip.apollo.client.loader.ConfigLoader
;
import
com.ctrip.apollo.client.loader.ConfigLoaderManager
;
import
com.ctrip.apollo.client.model.PropertyChange
;
import
com.ctrip.apollo.client.model.PropertySourceReloadResult
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.BeforeClass
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.mockito.ArgumentCaptor
;
...
...
@@ -11,6 +12,7 @@ import org.mockito.Mock;
import
org.mockito.runners.MockitoJUnitRunner
;
import
org.springframework.beans.factory.config.BeanDefinition
;
import
org.springframework.beans.factory.support.BeanDefinitionRegistry
;
import
org.springframework.cloud.context.scope.refresh.RefreshScope
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.ConfigurableApplicationContext
;
import
org.springframework.core.env.CompositePropertySource
;
...
...
@@ -18,10 +20,11 @@ import org.springframework.core.env.ConfigurableEnvironment;
import
org.springframework.core.env.MutablePropertySources
;
import
org.springframework.test.util.ReflectionTestUtils
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.atomic.AtomicReference
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
mockito
.
Mockito
.*;
/**
...
...
@@ -31,7 +34,7 @@ import static org.mockito.Mockito.*;
public
class
ApolloConfigManagerTest
{
private
ApolloConfigManager
apolloConfigManager
;
@Mock
private
ConfigLoader
configLoad
er
;
private
ConfigLoader
Manager
configLoaderManag
er
;
@Mock
private
ConfigurableApplicationContext
applicationContext
;
@Mock
...
...
@@ -40,6 +43,8 @@ public class ApolloConfigManagerTest {
private
MutablePropertySources
mutablePropertySources
;
@Mock
private
BeanDefinitionRegistry
beanDefinitionRegistry
;
@Mock
private
RefreshScope
scope
;
@Before
public
void
setUp
()
{
...
...
@@ -49,7 +54,8 @@ public class ApolloConfigManagerTest {
when
(
env
.
getPropertySources
()).
thenReturn
(
mutablePropertySources
);
apolloConfigManager
.
setApplicationContext
(
applicationContext
);
ReflectionTestUtils
.
setField
(
apolloConfigManager
,
"configLoader"
,
configLoader
);
ReflectionTestUtils
.
setField
(
apolloConfigManager
,
"configLoaderManager"
,
configLoaderManager
);
ReflectionTestUtils
.
setField
(
apolloConfigManager
,
"scope"
,
scope
);
}
@After
...
...
@@ -66,21 +72,20 @@ public class ApolloConfigManagerTest {
}
@Test
public
void
test
Prepar
ePropertySourceSuccessfully
()
{
public
void
test
Initializ
ePropertySourceSuccessfully
()
{
CompositePropertySource
somePropertySource
=
mock
(
CompositePropertySource
.
class
);
final
ArgumentCaptor
<
CompositePropertySource
>
captor
=
ArgumentCaptor
.
forClass
(
CompositePropertySource
.
class
);
when
(
configLoader
.
loadPropertySource
()).
thenReturn
(
somePropertySource
);
when
(
configLoader
Manager
.
loadPropertySource
()).
thenReturn
(
somePropertySource
);
apolloConfigManager
.
initializePropertySource
();
verify
(
configLoader
,
times
(
1
)).
loadPropertySource
();
verify
(
configLoader
Manager
,
times
(
1
)).
loadPropertySource
();
verify
(
mutablePropertySources
,
times
(
1
)).
addFirst
(
captor
.
capture
());
final
CompositePropertySource
insertedPropertySource
=
captor
.
getValue
();
assertEquals
(
ApolloConfigManager
.
APOLLO_PROPERTY_SOURCE_NAME
,
insertedPropertySource
.
getName
());
assertTrue
(
insertedPropertySource
.
getPropertySources
().
contains
(
somePropertySource
));
assertEquals
(
insertedPropertySource
,
somePropertySource
);
}
@Test
...
...
@@ -92,4 +97,38 @@ public class ApolloConfigManagerTest {
verify
(
beanDefinitionRegistry
,
times
(
2
)).
registerBeanDefinition
(
anyString
(),
any
(
BeanDefinition
.
class
));
}
@Test
public
void
testUpdatePropertySourceWithChanges
()
throws
Exception
{
PropertySourceReloadResult
somePropertySourceReloadResult
=
mock
(
PropertySourceReloadResult
.
class
);
CompositePropertySource
somePropertySource
=
mock
(
CompositePropertySource
.
class
);
List
<
PropertyChange
>
someChanges
=
mock
(
List
.
class
);
when
(
somePropertySourceReloadResult
.
hasChanges
()).
thenReturn
(
true
);
when
(
somePropertySourceReloadResult
.
getPropertySource
()).
thenReturn
(
somePropertySource
);
when
(
somePropertySourceReloadResult
.
getChanges
()).
thenReturn
(
someChanges
);
when
(
configLoaderManager
.
reloadPropertySource
()).
thenReturn
(
somePropertySourceReloadResult
);
List
<
PropertyChange
>
result
=
apolloConfigManager
.
updatePropertySource
();
assertEquals
(
someChanges
,
result
);
verify
(
scope
,
times
(
1
)).
refreshAll
();
}
@Test
public
void
testUpdatePropertySourceWithNoChange
()
throws
Exception
{
PropertySourceReloadResult
somePropertySourceReloadResult
=
mock
(
PropertySourceReloadResult
.
class
);
CompositePropertySource
somePropertySource
=
mock
(
CompositePropertySource
.
class
);
List
<
PropertyChange
>
emptyChanges
=
Collections
.
emptyList
();
when
(
somePropertySourceReloadResult
.
hasChanges
()).
thenReturn
(
false
);
when
(
somePropertySourceReloadResult
.
getPropertySource
()).
thenReturn
(
somePropertySource
);
when
(
somePropertySourceReloadResult
.
getChanges
()).
thenReturn
(
emptyChanges
);
when
(
configLoaderManager
.
reloadPropertySource
()).
thenReturn
(
somePropertySourceReloadResult
);
List
<
PropertyChange
>
result
=
apolloConfigManager
.
updatePropertySource
();
assertEquals
(
emptyChanges
,
result
);
verify
(
scope
,
never
()).
refreshAll
();
}
}
apollo-client/src/test/java/com/ctrip/apollo/client/loader/ConfigLoaderManagerTest.java
0 → 100644
浏览文件 @
bde0a061
package
com.ctrip.apollo.client.loader
;
import
com.ctrip.apollo.client.enums.PropertyChangeType
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.client.model.PropertyChange
;
import
com.ctrip.apollo.client.model.PropertySourceReloadResult
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
import
com.google.common.base.Function
;
import
com.google.common.collect.FluentIterable
;
import
com.google.common.collect.Lists
;
import
com.google.common.collect.Maps
;
import
com.google.common.collect.ObjectArrays
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.mockito.Mock
;
import
org.mockito.runners.MockitoJUnitRunner
;
import
org.springframework.core.env.CompositePropertySource
;
import
org.springframework.core.env.MapPropertySource
;
import
org.springframework.core.env.PropertySource
;
import
org.springframework.test.util.ReflectionTestUtils
;
import
java.util.List
;
import
java.util.Map
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
mockito
.
Mockito
.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith
(
MockitoJUnitRunner
.
class
)
public
class
ConfigLoaderManagerTest
{
private
ConfigLoaderManager
configLoaderManager
;
@Mock
private
ConfigLoader
configLoader
;
@Mock
private
ConfigUtil
configUtil
;
@Before
public
void
setUp
()
{
configLoaderManager
=
spy
(
new
ConfigLoaderManager
(
configLoader
,
configUtil
));
}
@Test
public
void
testLoadPropertySource
()
throws
Exception
{
long
someAppId
=
100
;
long
anotherAppId
=
101
;
ApolloRegistry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
ApolloRegistry
anotherApolloRegistry
=
assembleSomeApolloRegistry
(
anotherAppId
,
"anotherVersion"
);
ApolloConfig
someApolloConfig
=
mock
(
ApolloConfig
.
class
);
ApolloConfig
anotherApolloConfig
=
mock
(
ApolloConfig
.
class
);
Map
<
String
,
Object
>
someMap
=
mock
(
Map
.
class
);
Map
<
String
,
Object
>
anotherMap
=
mock
(
Map
.
class
);
when
(
someApolloConfig
.
getAppId
()).
thenReturn
(
someAppId
);
when
(
someApolloConfig
.
getAppId
()).
thenReturn
(
anotherAppId
);
when
(
configUtil
.
loadApolloRegistries
()).
thenReturn
(
Lists
.
newArrayList
(
someApolloRegistry
,
anotherApolloRegistry
));
doReturn
(
someApolloConfig
).
when
(
configLoaderManager
).
loadSingleApolloConfig
(
someApolloRegistry
);
doReturn
(
anotherApolloConfig
).
when
(
configLoaderManager
).
loadSingleApolloConfig
(
anotherApolloRegistry
);
when
(
someApolloConfig
.
getConfigurations
()).
thenReturn
(
someMap
);
when
(
anotherApolloConfig
.
getConfigurations
()).
thenReturn
(
anotherMap
);
CompositePropertySource
result
=
configLoaderManager
.
loadPropertySource
();
assertEquals
(
2
,
result
.
getPropertySources
().
size
());
List
<
Map
<
String
,
Object
>>
resultMaps
=
FluentIterable
.
from
(
result
.
getPropertySources
()).
transform
(
new
Function
<
PropertySource
<?>,
Map
<
String
,
Object
>>()
{
@Override
public
Map
<
String
,
Object
>
apply
(
PropertySource
<?>
input
)
{
return
(
Map
<
String
,
Object
>)
input
.
getSource
();
}
}).
toList
();
assertTrue
(
resultMaps
.
containsAll
(
Lists
.
newArrayList
(
someMap
,
anotherMap
)));
}
@Test
(
expected
=
RuntimeException
.
class
)
public
void
testLoadPropertySourceWithError
()
throws
Exception
{
Exception
someException
=
mock
(
Exception
.
class
);
long
someAppId
=
100
;
ApolloRegistry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
when
(
configUtil
.
loadApolloRegistries
()).
thenReturn
(
Lists
.
newArrayList
(
someApolloRegistry
));
doThrow
(
someException
).
when
(
configLoaderManager
).
loadSingleApolloConfig
(
someApolloRegistry
);
configLoaderManager
.
loadPropertySource
();
}
@Test
public
void
testLoadApolloConfigsWithNoApolloRegistry
()
throws
Exception
{
when
(
configUtil
.
loadApolloRegistries
()).
thenReturn
(
null
);
CompositePropertySource
result
=
configLoaderManager
.
loadPropertySource
();
assertTrue
(
result
.
getPropertySources
().
isEmpty
());
}
@Test
public
void
testLoadSingleApolloConfig
()
throws
Exception
{
ApolloConfig
someApolloConfig
=
mock
(
ApolloConfig
.
class
);
Map
<
String
,
Object
>
someMap
=
Maps
.
newHashMap
();
long
someAppId
=
100
;
ApolloRegistry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
ApolloConfig
previousConfig
=
null
;
doReturn
(
null
).
when
(
configLoaderManager
).
getPreviousApolloConfig
(
someApolloRegistry
);
when
(
someApolloConfig
.
getConfigurations
()).
thenReturn
(
someMap
);
when
(
configLoader
.
loadApolloConfig
(
someApolloRegistry
,
previousConfig
)).
thenReturn
(
someApolloConfig
);
ApolloConfig
result
=
configLoaderManager
.
loadSingleApolloConfig
(
someApolloRegistry
);
assertEquals
(
someMap
,
result
.
getConfigurations
());
}
@Test
public
void
testReloadPropertySource
()
throws
Exception
{
long
someAppId
=
100
;
ApolloRegistry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
ApolloConfig
someApolloConfig
=
mock
(
ApolloConfig
.
class
);
Map
<
String
,
Object
>
someMap
=
mock
(
Map
.
class
);
List
<
PropertyChange
>
someChanges
=
mock
(
List
.
class
);
ReflectionTestUtils
.
setField
(
configLoaderManager
,
"apolloRegistries"
,
Lists
.
newArrayList
(
someApolloRegistry
));
doReturn
(
someApolloConfig
).
when
(
configLoaderManager
).
loadSingleApolloConfig
(
someApolloRegistry
);
when
(
someApolloConfig
.
getAppId
()).
thenReturn
(
someAppId
);
when
(
someApolloConfig
.
getConfigurations
()).
thenReturn
(
someMap
);
doReturn
(
someChanges
).
when
(
configLoaderManager
).
calcPropertyChanges
(
anyList
(),
anyList
());
PropertySourceReloadResult
result
=
configLoaderManager
.
reloadPropertySource
();
assertEquals
(
1
,
result
.
getPropertySource
().
getPropertySources
().
size
());
assertEquals
(
someChanges
,
result
.
getChanges
());
List
<
Map
<
String
,
Object
>>
resultMaps
=
FluentIterable
.
from
(
result
.
getPropertySource
().
getPropertySources
()).
transform
(
new
Function
<
PropertySource
<?>,
Map
<
String
,
Object
>>()
{
@Override
public
Map
<
String
,
Object
>
apply
(
PropertySource
<?>
input
)
{
return
(
Map
<
String
,
Object
>)
input
.
getSource
();
}
}).
toList
();
assertTrue
(
resultMaps
.
containsAll
(
Lists
.
newArrayList
(
someMap
)));
}
@Test
public
void
testCalcPropertyChanges
()
throws
Exception
{
long
someAppId
=
1
;
Map
<
String
,
Object
>
someConfig
=
Maps
.
newHashMap
();
someConfig
.
put
(
"key1"
,
"val1"
);
someConfig
.
put
(
"key2"
,
"val2"
);
Map
<
String
,
Object
>
anotherConfig
=
Maps
.
newHashMap
();
anotherConfig
.
put
(
"key1"
,
"val11"
);
anotherConfig
.
put
(
"key3"
,
"val3"
);
List
<
ApolloConfig
>
previous
=
Lists
.
newArrayList
(
assembleApolloConfig
(
someAppId
,
someConfig
));
List
<
ApolloConfig
>
current
=
Lists
.
newArrayList
(
assembleApolloConfig
(
someAppId
,
anotherConfig
));
List
<
PropertyChange
>
changes
=
configLoaderManager
.
calcPropertyChanges
(
previous
,
current
);
assertEquals
(
3
,
changes
.
size
());
List
<
String
>
changeResult
=
FluentIterable
.
from
(
changes
).
transform
(
new
Function
<
PropertyChange
,
String
>()
{
@Override
public
String
apply
(
PropertyChange
input
)
{
return
String
.
format
(
"%s-%s"
,
input
.
getPropertyName
(),
input
.
getChangeType
());
}
}).
toList
();
assertTrue
(
changeResult
.
containsAll
(
Lists
.
newArrayList
(
"key1-"
+
PropertyChangeType
.
MODIFIED
,
"key2-"
+
PropertyChangeType
.
DELETED
,
"key3-"
+
PropertyChangeType
.
NEW
)));
}
ApolloConfig
assembleApolloConfig
(
long
appId
,
Map
<
String
,
Object
>
configurations
)
{
String
someCluster
=
"someCluster"
;
String
someVersion
=
"someVersion"
;
long
someReleaseId
=
1
;
ApolloConfig
config
=
new
ApolloConfig
(
appId
,
someCluster
,
someVersion
,
someReleaseId
);
config
.
setConfigurations
(
configurations
);
return
config
;
}
private
ApolloRegistry
assembleSomeApolloRegistry
(
long
someAppId
,
String
someVersion
)
{
ApolloRegistry
someApolloRegistry
=
new
ApolloRegistry
();
someApolloRegistry
.
setAppId
(
someAppId
);
someApolloRegistry
.
setVersion
(
someVersion
);
return
someApolloRegistry
;
}
}
apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java
浏览文件 @
bde0a061
...
...
@@ -12,11 +12,16 @@ import org.mockito.Mock;
import
org.mockito.runners.MockitoJUnitRunner
;
import
org.springframework.core.env.CompositePropertySource
;
import
org.springframework.core.env.MapPropertySource
;
import
org.springframework.http.HttpEntity
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.web.client.RestTemplate
;
import
java.util.Map
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
mockito
.
Mockito
.*;
...
...
@@ -29,6 +34,8 @@ public class RemoteConfigLoaderTest {
@Mock
private
RestTemplate
restTemplate
;
private
ConfigUtil
configUtil
;
@Mock
private
ResponseEntity
<
ApolloConfig
>
someResponse
;
@Before
public
void
setUp
()
{
...
...
@@ -37,57 +44,78 @@ public class RemoteConfigLoaderTest {
}
@Test
public
void
testLoad
PropertySource
()
throws
Exception
{
long
someAppId
=
100
;
long
anotherAppId
=
101
;
Apollo
Registry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
ApolloRegistry
anotherApolloRegistry
=
assembleSomeApolloRegistry
(
anotherAppId
,
"anotherVersion"
)
;
MapPropertySource
somePropertySource
=
mock
(
MapPropertySource
.
class
);
MapPropertySource
anotherPropertySource
=
mock
(
MapPropertySource
.
class
)
;
doReturn
(
Lists
.
newArrayList
(
someApolloRegistry
,
anotherApolloRegistry
)).
when
(
configUtil
).
loadApolloRegistries
(
);
doReturn
(
somePropertySource
).
when
(
remoteConfigLoader
).
loadSingleApolloConfig
(
someApolloRegistry
.
getAppId
(),
someApolloRegistry
.
getVersion
()
);
doReturn
(
a
notherPropertySource
).
when
(
remoteConfigLoader
).
loadSingleApolloConfig
(
anotherApolloRegistry
.
getAppId
(),
anotherApolloRegistry
.
getVersion
());
CompositePropertySource
result
=
remoteConfigLoader
.
loadPropertySource
();
assertEquals
(
2
,
result
.
getPropertySources
().
size
());
assert
True
(
result
.
getPropertySources
().
containsAll
(
Lists
.
newArrayList
(
somePropertySource
,
anotherPropertySource
))
);
public
void
testLoad
ApolloConfig
()
throws
Exception
{
String
someServerUrl
=
"http://someUrl"
;
String
someCluster
=
"some cluster"
;
Apollo
Config
apolloConfig
=
mock
(
ApolloConfig
.
class
);
long
someAppId
=
1
;
ApolloRegistry
apolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
ApolloConfig
previousConfig
=
null
;
when
(
configUtil
.
getConfigServerUrl
()).
thenReturn
(
someServerUrl
);
when
(
configUtil
.
getCluster
()).
thenReturn
(
someCluster
);
doReturn
(
a
polloConfig
).
when
(
remoteConfigLoader
)
.
getRemoteConfig
(
restTemplate
,
someServerUrl
,
someCluster
,
apolloRegistry
,
previousConfig
);
ApolloConfig
result
=
remoteConfigLoader
.
loadApolloConfig
(
apolloRegistry
,
previousConfig
);
assert
Equals
(
apolloConfig
,
result
);
}
@Test
public
void
testLoadPropertySourceWithNoApolloRegistry
()
throws
Exception
{
doReturn
(
null
).
when
(
configUtil
).
loadApolloRegistries
();
public
void
testGetRemoteConfig
()
throws
Exception
{
long
someAppId
=
1
;
String
someServerUrl
=
"http://someServer"
;
String
someClusterName
=
"someCluster"
;
String
someVersionName
=
"someVersion"
;
ApolloConfig
someApolloConfig
=
mock
(
ApolloConfig
.
class
);
ApolloRegistry
apolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
someVersionName
);
ApolloConfig
previousConfig
=
null
;
CompositePropertySource
result
=
remoteConfigLoader
.
loadPropertySource
();
when
(
someResponse
.
getStatusCode
()).
thenReturn
(
HttpStatus
.
OK
);
when
(
someResponse
.
getBody
()).
thenReturn
(
someApolloConfig
);
when
(
restTemplate
.
exchange
(
anyString
(),
eq
(
HttpMethod
.
GET
),
any
(
HttpEntity
.
class
),
eq
(
ApolloConfig
.
class
),
anyMap
())).
thenReturn
(
someResponse
);
assertTrue
(
result
.
getPropertySources
().
isEmpty
());
ApolloConfig
result
=
remoteConfigLoader
.
getRemoteConfig
(
restTemplate
,
someServerUrl
,
someClusterName
,
apolloRegistry
,
previousConfig
);
assertEquals
(
someApolloConfig
,
result
);
}
@Test
(
expected
=
RuntimeException
.
class
)
public
void
testLoadPropertySourceWithError
()
throws
Exception
{
Exception
someException
=
mock
(
Exception
.
class
);
long
someAppId
=
100
;
ApolloRegistry
someApolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
"someVersion"
);
doReturn
(
Lists
.
newArrayList
(
someApolloRegistry
)).
when
(
configUtil
).
loadApolloRegistries
();
doThrow
(
someException
).
when
(
remoteConfigLoader
).
loadSingleApolloConfig
(
someApolloRegistry
.
getAppId
(),
someApolloRegistry
.
getVersion
());
remoteConfigLoader
.
loadPropertySource
();
public
void
testGetRemoteConfigWithServerError
()
throws
Exception
{
long
someAppId
=
1
;
String
someServerUrl
=
"http://someServer"
;
String
someClusterName
=
"someCluster"
;
String
someVersionName
=
"someVersion"
;
ApolloRegistry
apolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
someVersionName
);
ApolloConfig
previousConfig
=
null
;
HttpStatus
someErrorCode
=
HttpStatus
.
INTERNAL_SERVER_ERROR
;
when
(
someResponse
.
getStatusCode
()).
thenReturn
(
someErrorCode
);
when
(
restTemplate
.
exchange
(
anyString
(),
eq
(
HttpMethod
.
GET
),
any
(
HttpEntity
.
class
),
eq
(
ApolloConfig
.
class
),
anyMap
())).
thenReturn
(
someResponse
);
remoteConfigLoader
.
getRemoteConfig
(
restTemplate
,
someServerUrl
,
someClusterName
,
apolloRegistry
,
previousConfig
);
}
@Test
public
void
testLoadSingleApolloConfig
()
throws
Exception
{
ApolloConfig
someApolloConfig
=
mock
(
ApolloConfig
.
class
);
Map
<
String
,
Object
>
someMap
=
Maps
.
newHashMap
();
public
void
testGetRemoteConfigWith304Response
()
throws
Exception
{
long
someAppId
=
1
;
String
someServerUrl
=
"http://someServer"
;
String
someClusterName
=
"someCluster"
;
String
someVersionName
=
"someVersion"
;
ApolloRegistry
apolloRegistry
=
assembleSomeApolloRegistry
(
someAppId
,
someVersionName
);
ApolloConfig
previousConfig
=
null
;
when
(
someResponse
.
getStatusCode
()).
thenReturn
(
HttpStatus
.
NOT_MODIFIED
);
when
(
restTemplate
.
exchange
(
anyString
(),
eq
(
HttpMethod
.
GET
),
any
(
HttpEntity
.
class
),
eq
(
ApolloConfig
.
class
),
anyMap
())).
thenReturn
(
someResponse
);
when
(
someApolloConfig
.
getConfigurations
()).
thenReturn
(
someMap
);
doReturn
(
someApolloConfig
).
when
(
remoteConfigLoader
).
getRemoteConfig
(
any
(
RestTemplate
.
class
),
anyString
(),
anyLong
(),
anyString
(),
anyString
());
ApolloConfig
result
=
remoteConfigLoader
.
getRemoteConfig
(
restTemplate
,
someServerUrl
,
someClusterName
,
apolloRegistry
,
previousConfig
);
long
someAppId
=
100
;
MapPropertySource
result
=
remoteConfigLoader
.
loadSingleApolloConfig
(
someAppId
,
"someVersion"
);
assertNull
(
result
);
assertEquals
(
someMap
,
result
.
getSource
());
}
private
ApolloRegistry
assembleSomeApolloRegistry
(
long
someAppId
,
String
someVersion
)
{
...
...
apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java
浏览文件 @
bde0a061
...
...
@@ -3,10 +3,7 @@ package com.ctrip.apollo.configservice.controller;
import
com.ctrip.apollo.biz.entity.Version
;
import
com.ctrip.apollo.biz.service.ConfigService
;
import
com.ctrip.apollo.core.dto.ApolloConfig
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RequestParam
;
import
org.springframework.web.bind.annotation.RestController
;
import
org.springframework.web.bind.annotation.*
;
import
javax.annotation.Resource
;
import
javax.servlet.http.HttpServletResponse
;
...
...
@@ -21,7 +18,7 @@ public class ConfigController {
@Resource
(
name
=
"configService"
)
private
ConfigService
configService
;
@RequestMapping
(
value
=
"/{appId}/{clusterName}/{versionName:.*}"
)
@RequestMapping
(
value
=
"/{appId}/{clusterName}/{versionName:.*}"
,
method
=
RequestMethod
.
GET
)
public
ApolloConfig
queryConfig
(
@PathVariable
long
appId
,
@PathVariable
String
clusterName
,
@PathVariable
String
versionName
,
...
...
apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java
浏览文件 @
bde0a061
...
...
@@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
com.google.common.base.MoreObjects
;
import
java.util.Comparator
;
import
java.util.Map
;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public
class
ApolloConfig
{
public
class
ApolloConfig
implements
Comparable
<
ApolloConfig
>
{
private
long
appId
;
...
...
@@ -21,6 +22,8 @@ public class ApolloConfig {
private
long
releaseId
;
private
int
order
;
@JsonCreator
public
ApolloConfig
(
@JsonProperty
(
"appId"
)
long
appId
,
@JsonProperty
(
"cluster"
)
String
cluster
,
...
...
@@ -57,6 +60,14 @@ public class ApolloConfig {
return
releaseId
;
}
public
int
getOrder
()
{
return
order
;
}
public
void
setOrder
(
int
order
)
{
this
.
order
=
order
;
}
@Override
public
String
toString
()
{
return
MoreObjects
.
toStringHelper
(
this
)
...
...
@@ -68,4 +79,15 @@ public class ApolloConfig {
.
add
(
"configurations"
,
configurations
)
.
toString
();
}
@Override
public
int
compareTo
(
ApolloConfig
o
)
{
if
(
o
==
null
||
this
.
getOrder
()
>
o
.
getOrder
())
{
return
1
;
}
if
(
o
.
getOrder
()
>
this
.
getOrder
())
{
return
-
1
;
}
return
0
;
}
}
apollo-demo/src/main/java/com/ctrip/apollo/demo/controller/DemoController.java
浏览文件 @
bde0a061
package
com.ctrip.apollo.demo.controller
;
import
com.ctrip.apollo.client.ApolloConfigManager
;
import
com.ctrip.apollo.client.model.ApolloRegistry
;
import
com.ctrip.apollo.client.model.PropertyChange
;
import
com.ctrip.apollo.client.util.ConfigUtil
;
import
com.ctrip.apollo.demo.model.Config
;
import
com.ctrip.apollo.demo.service.DemoService
;
...
...
@@ -27,11 +29,12 @@ public class DemoController {
private
Environment
env
;
@Autowired
private
DemoService
demoService
;
//Apollo config client internal impl, not intended to be used by application, only for this test page
//Apollo config client internal impl, not intended to be used by application, only for this test page
!
private
ConfigUtil
configUtil
=
ConfigUtil
.
getInstance
();
//ApolloConfigManager, not intended to be used by application, only for this test page!
@Autowired
private
RefreshScope
scope
;
private
ApolloConfigManager
apolloConfigManager
;
@RequestMapping
(
value
=
"/config/{configName:.*}"
,
method
=
RequestMethod
.
GET
)
public
Config
queryConfig
(
@PathVariable
String
configName
)
{
...
...
@@ -49,8 +52,8 @@ public class DemoController {
}
@RequestMapping
(
value
=
"/refresh"
,
method
=
RequestMethod
.
POST
)
public
String
refreshBeans
()
{
this
.
scope
.
refreshAll
();
return
"ok"
;
public
List
<
PropertyChange
>
refreshBeans
()
{
List
<
PropertyChange
>
changes
=
this
.
apolloConfigManager
.
updatePropertySource
();
return
changes
;
}
}
apollo-demo/src/main/webapp/s/scripts/app.js
浏览文件 @
bde0a061
...
...
@@ -7,11 +7,11 @@
]);
app
.
controller
(
'
DemoController
'
,
function
(
$scope
,
$http
,
$modal
,
toastr
)
{
//
var NONE = "none";
var
NONE
=
"
none
"
;
this
.
registries
=
{};
this
.
configQuery
=
{};
//
this.refreshResult = NONE;
this
.
refreshResult
=
NONE
;
this
.
injectedConfigValue
=
''
;
var
self
=
this
;
...
...
@@ -46,24 +46,26 @@
});
};
//this.refreshConfig = function() {
// $http.post("refresh")
// .success(function(data) {
// self.assembleRefreshResult(data);
// })
// .error(function(data, status) {
// toastr.error((data && data.msg) || 'Refresh config failed');
// });
//
//};
this
.
refreshConfig
=
function
()
{
$http
.
post
(
"
demo/refresh
"
)
.
success
(
function
(
data
)
{
self
.
assembleRefreshResult
(
data
);
})
.
error
(
function
(
data
,
status
)
{
toastr
.
error
((
data
&&
data
.
msg
)
||
'
Refresh config failed
'
);
});
//this.assembleRefreshResult = function(changedPropertyArray) {
// if(!changedPropertyArray || !changedPropertyArray.length) {
// this.refreshResult = NONE;
// return;
// }
// this.refreshResult = changedPropertyArray.join(',');
//};
};
this
.
assembleRefreshResult
=
function
(
changedPropertyArray
)
{
if
(
!
changedPropertyArray
||
!
changedPropertyArray
.
length
)
{
this
.
refreshResult
=
NONE
;
return
;
}
this
.
refreshResult
=
_
.
map
(
changedPropertyArray
,
function
(
propertyChange
)
{
return
propertyChange
.
propertyName
+
'
(
'
+
propertyChange
.
changeType
+
'
)
'
;
});
};
this
.
loadRegistries
();
...
...
apollo-demo/src/main/webapp/s/templates/list.html
浏览文件 @
bde0a061
...
...
@@ -70,12 +70,14 @@
</form>
</div>
<!--<div id="refresh-config-wrapper">-->
<!--<h3>Refresh Config:</h3>-->
<!--<button type="button" class="btn btn-primary" ng-click="demoCtrl.refreshConfig()">Refresh Config</button>-->
<!--<div id="refresh-result">-->
<!--<strong>Changed Properties:</strong> {{demoCtrl.refreshResult}}-->
<!--</div>-->
<!--</div>-->
<div
id=
"refresh-config-wrapper"
>
<h3>
Refresh Config:
</h3>
<button
type=
"button"
class=
"btn btn-primary"
ng-click=
"demoCtrl.refreshConfig()"
>
Refresh
Config
</button>
<div
id=
"refresh-result"
>
<strong>
Changed Properties:
</strong>
{{demoCtrl.refreshResult}}
</div>
</div>
</div>
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录