提交 68034af4 编写于 作者: Y Yiming Liu

Merge pull request #20 from nobodyiam/refresh-config-merge

Add RefreshScope support
package com.ctrip.apollo.biz.service;
import com.ctrip.apollo.biz.entity.Version;
import com.ctrip.apollo.core.model.ApolloConfig;
/**
......@@ -15,4 +16,20 @@ public interface ConfigService {
* @return
*/
ApolloConfig loadConfig(long appId, String clusterName, String versionName);
/**
* Load Version by appId and versionName from database
* @param appId
* @param versionName
* @return
*/
Version loadVersionByAppIdAndVersionName(long appId, String versionName);
/**
* Load Config by version and clusterName from database
* @param version
* @param clusterName
* @return
*/
ApolloConfig loadConfigByVersionAndClusterName(Version version, String clusterName);
}
......@@ -32,10 +32,21 @@ public class ConfigServiceImpl implements ConfigService {
@Override
public ApolloConfig loadConfig(long appId, String clusterName, String versionName) {
Version version = versionRepository.findByAppIdAndName(appId, versionName);
Version version = loadVersionByAppIdAndVersionName(appId, versionName);
if (version == null) {
return null;
}
return loadConfigByVersionAndClusterName(version, clusterName);
}
@Override
public Version loadVersionByAppIdAndVersionName(long appId, String versionName) {
return versionRepository.findByAppIdAndName(appId, versionName);
}
@Override
public ApolloConfig loadConfigByVersionAndClusterName(Version version, String clusterName) {
ReleaseSnapShot releaseSnapShot =
releaseSnapShotRepository.findByReleaseIdAndClusterName(version.getReleaseId(), clusterName);
if (releaseSnapShot == null) {
......
......@@ -4,5 +4,5 @@ INSERT INTO Cluster (AppId, IsDeleted, Name) VALUES (101, 0, 'default');
INSERT INTO Version (AppId, IsDeleted, Name, ReleaseId) VALUES (101, 0, '1.0', 1);
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"}');
INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 2, '{"apollo.bar":"foo"}');
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"}');
......@@ -19,6 +19,7 @@ import java.io.IOException;
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.*;
......@@ -66,7 +67,38 @@ public class ConfigServiceImplTest {
assertEquals(someVersionName, result.getVersion());
assertEquals(someReleaseId, result.getReleaseId());
assertEquals(someMap, result.getConfigurations());
}
@Test
public void testLoadConfigWithVersionNotFound() throws Exception {
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersionName";
when(versionRepository.findByAppIdAndName(someAppId, someVersionName)).thenReturn(null);
ApolloConfig result = configService.loadConfig(someAppId, someClusterName, someVersionName);
assertNull(result);
verify(versionRepository, times(1)).findByAppIdAndName(someAppId, someVersionName);
}
@Test
public void testLoadConfigWithConfigNotFound() throws Exception {
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersionName";
long someReleaseId = 1;
Version someVersion = assembleVersion(someAppId, someVersionName, someReleaseId);
when(versionRepository.findByAppIdAndName(someAppId, someVersionName)).thenReturn(someVersion);
when(releaseSnapShotRepository.findByReleaseIdAndClusterName(someReleaseId, someClusterName)).thenReturn(null);
ApolloConfig result = configService.loadConfig(someAppId, someClusterName, someVersionName);
assertNull(result);
verify(versionRepository, times(1)).findByAppIdAndName(someAppId, someVersionName);
verify(releaseSnapShotRepository, times(1)).findByReleaseIdAndClusterName(someReleaseId, someClusterName);
}
private Version assembleVersion(long appId, String versionName, long releaseId) {
......
......@@ -29,6 +29,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<!-- end of spring -->
<dependency>
<groupId>com.google.guava</groupId>
......
......@@ -4,19 +4,23 @@ import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.loader.ConfigLoaderFactory;
import com.ctrip.apollo.client.util.ConfigUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MutablePropertySources;
/**
* Client side config
* Client side config manager
*
* @author Jason Song(song_s@ctrip.com)
*/
......@@ -48,9 +52,27 @@ public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor,
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registerDependentBeans(registry);
preparePropertySource();
}
/**
* Register beans needed for Apollo Config Client
* <li>
* - RefreshScope: used to refresh beans when configurations changes
* </li>
* <li>
* - PropertySourcesPlaceholderConfigurer: used to support placeholder configuration injection
* </li>
* @param registry
*/
private void registerDependentBeans(BeanDefinitionRegistry registry) {
BeanDefinition refreshScope = BeanDefinitionBuilder.genericBeanDefinition(RefreshScope.class).getBeanDefinition();
registry.registerBeanDefinition("refreshScope", refreshScope);
BeanDefinition propertySourcesPlaceholderConfigurer = BeanDefinitionBuilder.genericBeanDefinition(PropertySourcesPlaceholderConfigurer.class).getBeanDefinition();
registry.registerBeanDefinition("propertySourcesPlaceholderConfigurer", propertySourcesPlaceholderConfigurer);
}
/**
* This is executed after postProcessBeanDefinitionRegistry
*/
......@@ -58,11 +80,20 @@ public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor,
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
/**
* Make sure this bean is called before other beans
* @return
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
return Ordered.HIGHEST_PRECEDENCE;
}
/**
* Prepare property sources
* First try to load from remote
* If loading from remote failed, then fall back to local cached properties
*/
void preparePropertySource() {
MutablePropertySources currentPropertySources = applicationContext.getEnvironment().getPropertySources();
if (currentPropertySources.contains(APOLLO_PROPERTY_SOURCE_NAME)) {
......
package com.ctrip.apollo.client.loader;
import com.ctrip.apollo.client.loader.impl.MockConfigLoader;
import com.ctrip.apollo.client.loader.impl.RemoteConfigLoader;
/**
......@@ -16,10 +15,6 @@ public class ConfigLoaderFactory {
return configLoaderFactory;
}
public ConfigLoader getMockConfigLoader() {
return new MockConfigLoader();
}
public ConfigLoader getRemoteConfigLoader() {
return new RemoteConfigLoader();
}
......
package com.ctrip.apollo.client.loader.impl;
import com.ctrip.apollo.client.loader.ConfigLoader;
import com.google.common.collect.Maps;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import java.util.Map;
/**
* Mock config
* @author Jason Song(song_s@ctrip.com)
*/
public class MockConfigLoader implements ConfigLoader {
private static final String PROPERTY_SOURCE_NAME = "ApolloMockConfigProperties";
@Override
public CompositePropertySource loadPropertySource() {
CompositePropertySource composite = new CompositePropertySource(PROPERTY_SOURCE_NAME);
composite.addPropertySource(mock());
return composite;
}
private MapPropertySource mock() {
Map<String, Object> source = Maps.newHashMap();
source.put("apollo.foo" ,"bar");
MapPropertySource propertySource = new MapPropertySource("mock source", source);
return propertySource;
}
}
......@@ -7,6 +7,8 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
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.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource;
......@@ -32,10 +34,12 @@ public class ApolloConfigManagerTest {
private ConfigurableEnvironment env;
@Mock
private MutablePropertySources mutablePropertySources;
@Mock
private BeanDefinitionRegistry beanDefinitionRegistry;
@Before
public void setUp() {
apolloConfigManager = new ApolloConfigManager();
apolloConfigManager = spy(new ApolloConfigManager());
when(applicationContext.getEnvironment()).thenReturn(env);
when(env.getPropertySources()).thenReturn(mutablePropertySources);
......@@ -68,4 +72,13 @@ public class ApolloConfigManagerTest {
assertTrue(insertedPropertySource.getPropertySources().contains(somePropertySource));
}
@Test
public void testPostProcessBeanDefinitionRegistry() {
doNothing().when(apolloConfigManager).preparePropertySource();
apolloConfigManager.postProcessBeanDefinitionRegistry(beanDefinitionRegistry);
verify(beanDefinitionRegistry, times(2)).registerBeanDefinition(anyString(), any(BeanDefinition.class));
}
}
package com.ctrip.apollo.server.config;
package com.ctrip.apollo.configserver.config;
import org.h2.server.web.WebServlet;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
......
package com.ctrip.apollo.server.controller;
package com.ctrip.apollo.configserver.controller;
import com.ctrip.apollo.biz.entity.Version;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.model.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 javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -16,10 +20,33 @@ public class ConfigController {
@Resource(name = "configService")
private ConfigService configService;
@RequestMapping(value = "/{appId}/{clusterName}/{version:.*}")
@RequestMapping(value = "/{appId}/{clusterName}/{versionName:.*}")
public ApolloConfig queryConfig(@PathVariable long appId,
@PathVariable String clusterName,
@PathVariable String version) {
return configService.loadConfig(appId, clusterName, version);
@PathVariable String versionName,
@RequestParam(value = "releaseId", defaultValue = "-1") long clientSideReleaseId,
HttpServletResponse response) throws IOException {
Version version = configService.loadVersionByAppIdAndVersionName(appId, versionName);
if (version == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
String.format("Could not load version with appId: %d, versionName: %s", appId, versionName));
return null;
}
if (version.getReleaseId() == clientSideReleaseId) {
//Client side configuration is the same with server side, return 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return null;
}
ApolloConfig apolloConfig =
configService.loadConfigByVersionAndClusterName(version, clusterName);
if (apolloConfig == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
String.format("Could not load config with releaseId: %d, clusterName: %s", version.getReleaseId(), clusterName));
return null;
}
return apolloConfig;
}
}
package com.ctrip.apollo.configserver;
import com.ctrip.apollo.configserver.controller.ConfigControllerTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
ConfigControllerTest.class
})
public class AllTests {
......
package com.ctrip.apollo.configserver.controller;
import com.ctrip.apollo.biz.entity.Version;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.model.ApolloConfig;
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.test.util.ReflectionTestUtils;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfigControllerTest {
private ConfigController configController;
@Mock
private ConfigService configService;
@Before
public void setUp() throws Exception {
configController = new ConfigController();
ReflectionTestUtils.setField(configController, "configService", configService);
}
@Test
public void testQueryConfig() throws Exception {
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersion";
long someClientSideReleaseId = 1;
long someServerSideNewReleaseId = 2;
HttpServletResponse someResponse = mock(HttpServletResponse.class);
Version someVersion = mock(Version.class);
when(configService.loadVersionByAppIdAndVersionName(someAppId, someVersionName)).thenReturn(someVersion);
when(someVersion.getReleaseId()).thenReturn(someServerSideNewReleaseId);
when(configService.loadConfigByVersionAndClusterName(someVersion, someClusterName)).thenReturn(someApolloConfig);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, someVersionName, someClientSideReleaseId, someResponse);
assertEquals(someApolloConfig, result);
verify(configService, times(1)).loadVersionByAppIdAndVersionName(someAppId, someVersionName);
verify(configService, times(1)).loadConfigByVersionAndClusterName(someVersion, someClusterName);
}
@Test
public void testQueryConfigWithVersionNotFound() throws Exception {
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersion";
long someClientSideReleaseId = 1;
HttpServletResponse someResponse = mock(HttpServletResponse.class);
when(configService.loadVersionByAppIdAndVersionName(someAppId, someVersionName)).thenReturn(null);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, someVersionName, someClientSideReleaseId, someResponse);
assertNull(result);
verify(someResponse, times(1)).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
}
@Test
public void testQueryConfigWithApolloConfigNotFound() throws Exception {
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersion";
long someClientSideReleaseId = 1;
long someServerSideNewReleaseId = 2;
HttpServletResponse someResponse = mock(HttpServletResponse.class);
Version someVersion = mock(Version.class);
when(configService.loadVersionByAppIdAndVersionName(someAppId, someVersionName)).thenReturn(someVersion);
when(someVersion.getReleaseId()).thenReturn(someServerSideNewReleaseId);
when(configService.loadConfigByVersionAndClusterName(someVersion, someClusterName)).thenReturn(null);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, someVersionName, someClientSideReleaseId, someResponse);
assertNull(result);
verify(someResponse, times(1)).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
}
@Test
public void testQueryConfigWithApolloConfigNotModified() throws Exception {
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersionName = "someVersion";
long someClientSideReleaseId = 1;
long someServerSideReleaseId = someClientSideReleaseId;
HttpServletResponse someResponse = mock(HttpServletResponse.class);
Version someVersion = mock(Version.class);
when(configService.loadVersionByAppIdAndVersionName(someAppId, someVersionName)).thenReturn(someVersion);
when(someVersion.getReleaseId()).thenReturn(someServerSideReleaseId);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, someVersionName, someClientSideReleaseId, someResponse);
assertNull(result);
verify(someResponse, times(1)).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
verify(configService, never()).loadConfigByVersionAndClusterName(any(Version.class), anyString());
}
}
package com.ctrip.apollo.server;
import com.ctrip.apollo.server.controller.ConfigControllerTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
ConfigControllerTest.class
})
public class AllTests {
}
package com.ctrip.apollo.server.controller;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.model.ApolloConfig;
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.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfigControllerTest {
private ConfigController configController;
@Mock
private ConfigService configService;
@Before
public void setUp() throws Exception {
configController = new ConfigController();
ReflectionTestUtils.setField(configController, "configService", configService);
}
@Test
public void testQueryConfig() throws Exception {
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
long someAppId = 1;
String someClusterName = "someClusterName";
String someVersion = "someVersion";
when(configService.loadConfig(someAppId, someClusterName, someVersion)).thenReturn(someApolloConfig);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, someVersion);
assertEquals(someApolloConfig, result);
verify(configService, times(1)).loadConfig(someAppId, someClusterName, someVersion);
}
}
......@@ -4,7 +4,6 @@ import com.ctrip.apollo.client.ApolloConfigManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -17,8 +16,13 @@ public class AppConfig {
return new ApolloConfigManager();
}
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
// @Bean
// public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
// return new PropertySourcesPlaceholderConfigurer();
// }
//
// @Bean
// public static RefreshScope refreshScope() {
// return new RefreshScope();
// }
}
package com.ctrip.apollo.demo.controller;
import com.ctrip.apollo.client.ApolloConfigManager;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.demo.model.Config;
import com.ctrip.apollo.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -26,20 +26,32 @@ public class DemoController {
@Autowired
private Environment env;
@Autowired
private ApolloConfigManager apolloConfigManager;
//Apollo config client internal impl, not intended to be use by application, only for this test page
private DemoService demoService;
//Apollo config client internal impl, not intended to be used by application, only for this test page
private ConfigUtil configUtil = ConfigUtil.getInstance();
@Value("${apollo.foo}")
private String foo;
@Autowired
private RefreshScope scope;
@RequestMapping(value = "/config/{configName:.*}", method = RequestMethod.GET)
public Config queryConfig(@PathVariable String configName) {
return new Config(configName, env.getProperty(configName, "undefined"));
String value;
if (configName.equals("foo")) {
value = demoService.getFoo();
} else {
value = env.getProperty(configName, "undefined");
}
return new Config(configName, value);
}
@RequestMapping(value = "/client/registries", method = RequestMethod.GET)
public List<ApolloRegistry> loadApolloRegistries() throws IOException {
return configUtil.loadApolloRegistries();
}
@RequestMapping(value = "/refresh", method = RequestMethod.POST)
public String refreshBeans() {
this.scope.refreshAll();
return "ok";
}
}
package com.ctrip.apollo.demo.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Service
@RefreshScope
public class DemoService {
private String foo;
@Value("${apollo.foo}")
private void setFoo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册