Initial commit

This commit is contained in:
Dave Syer
2014-06-13 14:44:18 +01:00
commit c6cee8a8cc
36 changed files with 3181 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
*~
.#*
*#
target
bin
.classpath
.project
.settings
.springBeans
.DS_Store
*.sw*

85
pom.xml Normal file
View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Platform Config</name>
<description>Spring Platform Config</description>
<url>http://projects.spring.io/spring-platform/</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.2.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>
<scm>
<url>https://github.com/spring-platform/spring-platform-config</url>
</scm>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at gopivotal.com</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>http://www.spring.io</organizationUrl>
<roles>
<role>Project lead</role>
</roles>
</developer>
</developers>
<prerequisites>
<maven>3.0.0</maven>
</prerequisites>
<properties>
<main.basedir>${basedir}</main.basedir>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>1.5.3</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>spring-platform-config-client</module>
<module>spring-platform-config-server</module>
</modules>
</profile>
<profile>
<id>full</id>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,126 @@
# Spring Platform Components
This project contains boilerplate patterns for distributed application
components, making those patterns easy to implement with Spring (and
Spring Boot in particular).
## Spring Boot Features
**Bootstrap Listener** - A `SpringApplication` with the platform
components on its classpath will have an extended bootstrap phase,
allowing new strategies for configuration management and other
non-functional features to be added to the application before it is
initialized. The implementation is similar to
`@EnableAutoConfiguration`, allowing new jars to contribute additional
features by defining `BootstrapConfiguration` classes in the form of
`@Configuration`. Instead of being added to the application's context
they are added to a special "bootstrap" context, which is then used to
extract `ApplicationContextInitializers` to apply to the application
before it is created. A typical use case would be to locate and
install additional environment properties so that the application can
adapt to its environment in a completely flexible way.
**Autoconfiguration** - An application using
`@EnableAutoConfiguration` will pick up beans for the components
below, notably `EnvironmentManager` and `RefreshScope`.
## EnvironmentManager and EnvironmentManagerEndpoint
The `EnvironmentManager` allows you to modify the Spring `Environment`
in a running application. It adds a `PropertySource` with high
priority to the `Environment` (so properties set there will take
precedence). If you have the Actuator and are in a webapp then the
`EnvironmentManagerEndpoint` will also expose "/env" with POST for
updating the `EnvironmentManager` remotely. E.g. run the app locally:
```
@Configuration
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
then interact with the `Environment`:
```
$ curl localhost:8080/env/message
Hello
$ curl localhost:8080/env -d message=Foo
$ curl localhost:8080/env/message
Foo
```
Doing this (or the equivalent operation over JMX) will trigger an event
that leads to rebinding of `@ConfigurationProperties` (see below).
## ConfigurationPropertiesRebinder
Spring beans that are `@ConfigurationProperties` can be rebound if the `Environment` changes. Example:
```
@ConfigurationProperties
protected static class ServiceProperties {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
```
then inject `ServiceProperties` into an application component using `@Autowired` and use it to govern the behaviour.
When the `Environment` changes do this
```
@Autowired
private ConfigurationPropertiesRebinder rebinder;
...
rebinder.rebind();
```
The `ConfigurationPropertiesRebinder` is also a `@ManagedResource` so you can ping the `rebind()` operation remotely.
## RefreshScope
Spring beans in scope="refresh" are re-initialized on the next method call after a refresh. Example:
```
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@EnableAutoConfiguration
protected static class TestConfiguration {
@Autowired
private TestProperties properties;
@Bean
@RefreshScope
public ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage(properties.getMessage());
service.setDelay(properties.getDelay());
return service;
}
}
```
then update the `TestProperties` (e.g. via JMX or something), and
```
@Autowired
private RefreshScope scope;
...
// ...and then refresh, so the bean is re-initialized:
scope.refresh("service");
```

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-platform-config-client</artifactId>
<packaging>jar</packaging>
<name>Spring Platform Config Client</name>
<parent>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<url>http://spring.io</url>
<description>
<![CDATA[
This project is a Spring configuration client.
]]>
</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.platform.autoconfigure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.platform.context.environment.EnvironmentManager;
import org.springframework.platform.context.environment.EnvironmentManagerMvcEndpoint;
import org.springframework.platform.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.platform.context.scope.refresh.RefreshScope;
@Configuration
@ConditionalOnClass(RefreshScope.class)
@AutoConfigureAfter(EndpointAutoConfiguration.class)
public class RefreshAutoConfiguration {
@Autowired
private ConfigurationPropertiesBindingPostProcessor binder;
@Autowired
private ConfigurationBeanFactoryMetaData metaData;
@Bean
@ConditionalOnMissingBean
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
public EnvironmentManager environmentManager(ConfigurableEnvironment environment) {
return new EnvironmentManager(environment);
}
@Bean
@ConditionalOnMissingBean
public ConfigurationPropertiesRebinder configurationPropertiesRebinder() {
ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(binder);
rebinder.setBeanMetaDataStore(metaData);
return rebinder;
}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(EnvironmentEndpoint.class)
@ConditionalOnExpression("${endpoints.env.enabled:true}")
@ConditionalOnBean(EnvironmentEndpoint.class)
protected static class EnvironmentEndpointConfiguration {
@Bean
public EnvironmentManagerMvcEndpoint environmentManagerEndpoint(EnvironmentEndpoint delegate, EnvironmentManager environment) {
return new EnvironmentManagerMvcEndpoint(delegate, environment);
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* A listener that prepares a SpringApplication (e.g. populating its Environment) by
* delegating to {@link ApplicationContextInitializer} beans in a separate bootstrap
* context. The bootstrap context is a SpringApplication created from sources defined in
* spring.factories as {@link BootstrapConfiguration}, and initialized with external
* config taken from "bootstrap.properties" (or yml), instead of the normal
* "application.properties".
*
* @author Dave Syer
*
*/
public class BootstrapApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
private int order = DEFAULT_ORDER;
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Environment environment = event.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment configurable = (ConfigurableEnvironment) environment;
// don't listen to events in a bootstrap context
if (configurable.getPropertySources().contains("bootstrap")) {
return;
}
ConfigurableApplicationContext context = bootstrapServiceContext(
configurable, event.getSpringApplication());
apply(context, event.getSpringApplication(), configurable);
}
}
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
for (PropertySource<?> source : environment.getPropertySources()) {
bootstrapProperties.addLast(source);
}
bootstrapProperties.addFirst(new MapPropertySource("bootstrap",
Collections.<String, Object> singletonMap("spring.config.name",
"bootstrap")));
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = SpringFactoriesLoader.loadFactoryNames(
BootstrapConfiguration.class, classLoader);
// TODO: is it possible or sensible to share a ResourceLoader?
SpringApplicationBuilder builder = new SpringApplicationBuilder().showBanner(
false).environment(bootstrapEnvironment).web(false).properties(
"spring.application.name:bootstrap");
builder.sources(names.toArray());
final ConfigurableApplicationContext context = builder.run();
// Shutdown the bootstrap context when the app closes
// TODO: maybe make the bootstrap context a parent of the app context
application.addListeners(new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
context.close();
}
});
return context;
}
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
@SuppressWarnings("rawtypes")
List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
application.addInitializers(initializers.toArray(new ApplicationContextInitializer[0]));
}
private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context, Class<T> type) {
List<T> result = new ArrayList<T>();
for (String name : context.getBeanNamesForType(type)) {
result.add(context.getBean(name, type));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.platform.bootstrap;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BootstrapConfiguration {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
*/
Class<?>[] exclude() default {};
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.PropertySource;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static Log logger = LogFactory.getLog(ConfigServiceBootstrapConfiguration.class);
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<PropertySourceLocator>();
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource("bootstrap");
AnnotationAwareOrderComparator.sort(propertySourceLocators);
boolean empty = true;
for (PropertySourceLocator locator : propertySourceLocators) {
PropertySource<?> source = null;
try {
source = locator.locate();
} catch (Exception e) {
logger.error("Could not locate PropertySource: " + e.getMessage());
}
if (source == null) {
continue;
}
composite.addPropertySource(source);
empty = false;
}
if (!empty) {
applicationContext.getEnvironment().getPropertySources().addFirst(composite);
}
}
@Bean
@ConfigurationProperties("spring.platform.config")
public ConfigServicePropertySourceLocator configServicePropertySource() {
return new ConfigServicePropertySourceLocator();
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Dave Syer
*
*/
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
private String env = "development";
@Value("${spring.application.name:'application'}")
private String name;
private String label = "master";
private String url = "http://localhost:8888";
private RestTemplate restTemplate = new RestTemplate();
@Override
public PropertySource<?> locate() {
CompositePropertySource composite = new CompositePropertySource("configService");
List<SerializableMapPropertySource> result = restTemplate.exchange(
url + "/{name}/{env}/{label}", HttpMethod.GET,
new HttpEntity<Void>((Void) null),
new ParameterizedTypeReference<List<SerializableMapPropertySource>>() {
}, name, env, label).getBody();
for (SerializableMapPropertySource source : result) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source.getSource();
composite.addPropertySource(new MapPropertySource(source.getName(), map));
}
return composite;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEnv() {
return env;
}
public void setEnv(String env) {
this.env = env;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
protected static class SerializableMapPropertySource {
private String name;
private Map<?, ?> source;
@JsonCreator
public SerializableMapPropertySource(@JsonProperty("name") String name,
@JsonProperty("source") Map<?, ?> source) {
this.name = name;
this.source = source;
}
public String getName() {
return name;
}
public Map<?, ?> getSource() {
return source;
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
import org.springframework.core.env.PropertySource;
/**
* @author Dave Syer
*
*/
public interface PropertySourceLocator {
PropertySource<?> locate();
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.config;
/**
* A helper interface providing optional decoration of bean instances and their
* destruction callbacks. Users can supply custom implementations of this
* strategy if they want tighter control over method invocation on the bean or
* its destruction callback.
*
* @param <T>
* the type of auxiliary context object that can be passed between
* methods. Implementations can choose what type of data to supply as
* it is passed around unchanged by the caller.
*
* @author Dave Syer
*
*/
public interface BeanLifecycleDecorator<T> {
/**
* Optionally decorate and provide a new instance of a compatible bean for
* the caller to use instead of the input.
*
* @param bean
* the bean to optionally decorate
* @param context
* the context as created by
* {@link #decorateDestructionCallback(Runnable)}
* @return the replacement bean for the caller to use
*/
Object decorateBean(Object bean, Context<T> context);
/**
* Optionally decorate the destruction callback provided, and also return
* some context that can be used later by the
* {@link #decorateBean(Object, Context)} method.
*
* @param callback
* the destruction callback that will be used by the container
* @return a context wrapper
*/
Context<T> decorateDestructionCallback(Runnable callback);
static class Context<T> {
private final T auxiliary;
private final Runnable callback;
public Context(Runnable callback, T auxiliary) {
this.callback = callback;
this.auxiliary = auxiliary;
}
public Runnable getCallback() {
return callback;
}
public T getAuxiliary() {
return auxiliary;
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.config;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
/**
* A {@link BeanLifecycleDecorator} that tries to protect against concurrent access to a bean during its own destruction.
* A read-write lock is used, and method access is protected using the read lock, while the destruction callback is
* protected more strictly with the write lock. In this way concurrent access is possible to the bean as long as it is
* not being destroyed, in which case only one thread has access. If the bean has no destruction callback the lock and
* associated proxies are never created.
*
* @author Dave Syer
*
*/
public class StandardBeanLifecycleDecorator implements BeanLifecycleDecorator<ReadWriteLock> {
private final boolean proxyTargetClass;
public StandardBeanLifecycleDecorator(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
public Object decorateBean(Object bean, Context<ReadWriteLock> context) {
if (context != null) {
bean = getDisposalLockProxy(bean, context.getAuxiliary().readLock());
}
return bean;
}
public Context<ReadWriteLock> decorateDestructionCallback(final Runnable callback) {
if (callback == null) {
return null;
}
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
return new Context<ReadWriteLock>(new Runnable() {
public void run() {
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
callback.run();
} finally {
lock.unlock();
}
}
}, readWriteLock);
}
/**
* Apply a lock (preferably a read lock allowing multiple concurrent access) to the bean. Callers should replace the
* bean input with the output.
*
* @param bean the bean to lock
* @param lock the lock to apply
* @return a proxy that locks while its methods are executed
*/
private Object getDisposalLockProxy(Object bean, final Lock lock) {
ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(proxyTargetClass);
factory.addAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
lock.lock();
try {
return invocation.proceed();
} finally {
lock.unlock();
}
}
});
return factory.getProxy();
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.environment;
import java.util.Set;
import org.springframework.context.ApplicationEvent;
/**
* @author Dave Syer
*
*/
@SuppressWarnings("serial")
public class EnvironmentChangeEvent extends ApplicationEvent {
private Set<String> keys;
public EnvironmentChangeEvent(Set<String> keys) {
super(keys);
this.keys = keys;
}
/**
* @return the keys
*/
public Set<String> getKeys() {
return keys;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.environment;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;
/**
* @author Dave Syer
*
*/
@Component
@ManagedResource
public class EnvironmentManager implements ApplicationEventPublisherAware {
private static final String MANAGER_PROPERTY_SOURCE = "manager";
private Map<String, Object> map = new LinkedHashMap<String, Object>();
private ConfigurableEnvironment environment;
private ApplicationEventPublisher publisher;
public EnvironmentManager(ConfigurableEnvironment environment) {
this.environment = environment;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@ManagedOperation
public void reset() {
if (!map.isEmpty()) {
Set<String> keys = map.keySet();
map.clear();
publish(new EnvironmentChangeEvent(keys));
}
}
@ManagedOperation
public void setProperty(String name, String value) {
if (!environment.getPropertySources().contains(MANAGER_PROPERTY_SOURCE)) {
synchronized (map) {
if (!environment.getPropertySources().contains(MANAGER_PROPERTY_SOURCE)) {
MapPropertySource source = new MapPropertySource(
MANAGER_PROPERTY_SOURCE, map);
environment.getPropertySources().addFirst(source);
}
}
}
if (!value.equals(environment.getProperty(name))) {
map.put(name, value);
publish(new EnvironmentChangeEvent(Collections.singleton(name)));
}
}
@ManagedOperation
public Object getProperty(String name) {
return environment.getProperty(name);
}
private void publish(EnvironmentChangeEvent environmentChangeEvent) {
if (publisher !=null ) {
publisher.publishEvent(environmentChangeEvent);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.environment;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Dave Syer
*
*/
public class EnvironmentManagerMvcEndpoint implements MvcEndpoint {
private EnvironmentManager environment;
private EnvironmentEndpoint delegate;
public EnvironmentManagerMvcEndpoint(EnvironmentEndpoint delegate, EnvironmentManager enviroment) {
this.delegate = delegate;
environment = enviroment;
}
@RequestMapping(value = "", method = RequestMethod.POST)
@ResponseBody
public Object value(@RequestParam Map<String,String> params) {
for (String name : params.keySet()) {
environment.setProperty(name, params.get(name));
}
return params;
}
public void setEnvironmentManager(EnvironmentManager environment) {
this.environment = environment;
}
@Override
public String getPath() {
return "/" + this.delegate.getId();
}
@Override
public boolean isSensitive() {
return this.delegate.isSensitive();
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return this.delegate.getClass();
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.properties;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.context.environment.EnvironmentChangeEvent;
import org.springframework.stereotype.Component;
/**
* @author Dave Syer
*
*/
@Component
@ManagedResource
public class ConfigurationPropertiesRebinder implements BeanPostProcessor, ApplicationListener<EnvironmentChangeEvent> {
private ConfigurationBeanFactoryMetaData metaData;
private ConfigurationPropertiesBindingPostProcessor binder;
public ConfigurationPropertiesRebinder(
ConfigurationPropertiesBindingPostProcessor binder) {
this.binder = binder;
}
private Map<String, Object> beans = new HashMap<String, Object>();
/**
* @param beans the bean meta data to set
*/
public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
this.metaData = beans;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
beans.put(beanName, bean);
}
else if (metaData != null) {
annotation = this.metaData.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
beans.put(beanName, bean);
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@ManagedOperation
public void rebind() {
for (String name : beans.keySet()) {
rebind(name);
}
}
@ManagedOperation
public void rebind(String name) {
binder.postProcessBeforeInitialization(beans.get(name), name);
}
@ManagedAttribute
public Set<String> getBeanNames() {
return new HashSet<String>(beans.keySet());
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
rebind();
}
}

View File

@@ -0,0 +1,490 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.platform.context.scope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.platform.context.config.BeanLifecycleDecorator;
import org.springframework.platform.context.config.BeanLifecycleDecorator.Context;
import org.springframework.platform.context.config.StandardBeanLifecycleDecorator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* <p>
* A generic Scope implementation.
* </p>
*
* @author Dave Syer
*
* @since 3.1
*
*/
public class GenericScope implements Scope, BeanFactoryPostProcessor,
DisposableBean {
private static final Log logger = LogFactory.getLog(GenericScope.class);
private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
new StandardScopeCache());
private String name = "generic";
private boolean proxyTargetClass = true;
private boolean autoProxy = true;
private ConfigurableListableBeanFactory beanFactory;
private StandardEvaluationContext evaluationContext;
private String id;
private BeanLifecycleDecorator<?> lifecycle;
/**
* Manual override for the serialization id that will be used to identify
* the bean factory. The default is a unique key based on the bean names in
* the bean factory.
*
* @param id
* the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* The name of this scope. Default "refresh".
*
* @param name
* the name value to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Flag to indicate that proxies should be created for the concrete type,
* not just the interfaces, of the scoped beans.
*
* @param proxyTargetClass
* the flag value to set
*/
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
/**
* Flag to indicate that all scoped beans should automatically be proxied.
* If true then scoped beans can be injected as dependencies of another
* component and the concrete target will only be instantiated when it is
* used. Proxying is a huge advantage if the context storage for the scope
* cache is not available at configuration time (e.g. for thread-based, or
* other transient scopes). If this flag is false you can expect maybe to
* have to add extra meta-data to the bean definitions individually (e.g.
* &lt;aop:scoped-proxy/&gt; for an XML configuration).
*
* @param autoProxy
* the flag value to set, default is true
*/
public void setAutoProxy(boolean autoProxy) {
this.autoProxy = autoProxy;
}
/**
* The cache implementation to use for bean instances in this scope.
*
* @param cache
* the cache to use
*/
public void setScopeCache(ScopeCache cache) {
this.cache = new BeanLifecycleWrapperCache(cache);
}
/**
* Helper to manage the creation and destruction of beans.
*
* @param lifecycle
* the bean lifecycle to set
*/
public void setBeanLifecycleManager(BeanLifecycleDecorator<?> lifecycle) {
this.lifecycle = lifecycle;
}
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
wrapper.destroy();
} catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
}
protected void destroy(String name) {
BeanLifecycleWrapper wrapper = cache.remove(name);
if (wrapper != null) {
wrapper.destroy();
}
}
public Object get(String name, ObjectFactory<?> objectFactory) {
if (lifecycle == null) {
lifecycle = new StandardBeanLifecycleDecorator(proxyTargetClass);
}
BeanLifecycleWrapper value = cache.put(name, new BeanLifecycleWrapper(
name, objectFactory, lifecycle));
return value.getBean();
}
public String getConversationId() {
return name;
}
public void registerDestructionCallback(String name, Runnable callback) {
BeanLifecycleWrapper value = cache.get(name);
if (value == null) {
return;
}
value.setDestroyCallback(callback);
}
public Object remove(String name) {
BeanLifecycleWrapper value = cache.remove(name);
if (value == null) {
return null;
}
// Someone might have added another object with the same key, but we
// keep the method contract by removing the
// value we found anyway
return value.getBean();
}
public Object resolveContextualObject(String key) {
Expression expression = parseExpression(key);
return expression.getValue(evaluationContext, beanFactory);
}
private Expression parseExpression(String input) {
if (StringUtils.hasText(input)) {
ExpressionParser parser = new SpelExpressionParser();
try {
return parser.parseExpression(input);
} catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse expression: "
+ input, e);
}
} else {
return null;
}
}
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(name, this);
setSerializationId(beanFactory);
this.beanFactory = beanFactory;
evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new BeanFactoryAccessor());
if (!autoProxy) {
// No need to try and create proxies
return;
}
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so RefreshScope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
// Replace this or any of its inner beans with scoped proxy if it
// has this scope
boolean scoped = name.equals(definition.getScope());
Scopifier scopifier = new Scopifier(registry, name,
proxyTargetClass, scoped);
scopifier.visitBeanDefinition(definition);
if (scoped) {
createScopedProxy(beanName, definition, registry,
proxyTargetClass);
}
}
}
/**
* If the bean factory is a DefaultListableBeanFactory then it can serialize
* scoped beans and deserialize them in another context (even in another
* JVM), as long as the ids of the bean factories match. This method sets up
* the serialization id to be either the id provided to the scope instance,
* or if that is null, a hash of all the bean names.
*
* @param beanFactory
* the bean factory to configure
*/
private void setSerializationId(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof DefaultListableBeanFactory) {
String id = this.id;
if (id == null) {
String names = Arrays.asList(
beanFactory.getBeanDefinitionNames()).toString();
logger.debug("Generating bean factory id from names: " + names);
id = UUID.nameUUIDFromBytes(names.getBytes()).toString();
}
logger.info("BeanFactory id=" + id);
((DefaultListableBeanFactory) beanFactory).setSerializationId(id);
} else {
logger.warn("BeanFactory was not a DefaultListableBeanFactory, so RefreshScope beans "
+ "cannot be serialized reliably and passed to a remote JVM.");
}
}
static RuntimeException wrapIfNecessary(Throwable throwable) {
if (throwable instanceof RuntimeException) {
return (RuntimeException) throwable;
}
if (throwable instanceof Error) {
throw (Error) throwable;
}
return new IllegalStateException(throwable);
}
private static BeanDefinitionHolder createScopedProxy(String beanName,
BeanDefinition definition, BeanDefinitionRegistry registry,
boolean proxyTargetClass) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(
new BeanDefinitionHolder(definition, beanName), registry,
proxyTargetClass);
registry.registerBeanDefinition(beanName,
proxyHolder.getBeanDefinition());
return proxyHolder;
}
/**
* Helper class to scan a bean definition hierarchy and force the use of
* auto-proxy for scoped beans.
*
* @author Dave Syer
*
*/
private static class Scopifier extends BeanDefinitionVisitor {
private final boolean proxyTargetClass;
private final BeanDefinitionRegistry registry;
private final String scope;
private final boolean scoped;
public Scopifier(BeanDefinitionRegistry registry, String scope,
boolean proxyTargetClass, boolean scoped) {
super(new StringValueResolver() {
public String resolveStringValue(String value) {
return value;
}
});
this.registry = registry;
this.proxyTargetClass = proxyTargetClass;
this.scope = scope;
this.scoped = scoped;
}
@Override
protected Object resolveValue(Object value) {
BeanDefinition definition = null;
String beanName = null;
if (value instanceof BeanDefinition) {
definition = (BeanDefinition) value;
beanName = BeanDefinitionReaderUtils.generateBeanName(
definition, registry);
} else if (value instanceof BeanDefinitionHolder) {
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
definition = holder.getBeanDefinition();
beanName = holder.getBeanName();
}
if (definition != null) {
boolean nestedScoped = scope.equals(definition.getScope());
boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
if (scopeChangeRequiresProxy) {
// Exit here so that nested inner bean definitions are not
// analysed
return createScopedProxy(beanName, definition, registry,
proxyTargetClass);
}
}
// Nested inner bean definitions are recursively analysed here
value = super.resolveValue(value);
return value;
}
}
private static class BeanLifecycleWrapperCache {
private final ScopeCache cache;
public BeanLifecycleWrapperCache(ScopeCache cache) {
this.cache = cache;
}
public BeanLifecycleWrapper remove(String name) {
return (BeanLifecycleWrapper) cache.remove(name);
}
public Collection<BeanLifecycleWrapper> clear() {
Collection<Object> values = cache.clear();
Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>();
for (Object object : values) {
wrappers.add((BeanLifecycleWrapper) object);
}
return wrappers;
}
public BeanLifecycleWrapper get(String name) {
return (BeanLifecycleWrapper) cache.get(name);
}
public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
return (BeanLifecycleWrapper) cache.put(name, (Object) value);
}
}
/**
* Wrapper for a bean instance and any destruction callback (DisposableBean
* etc.) that is registered for it. Also decorates the bean to optionally
* guard it from concurrent access (for instance).
*
* @author Dave Syer
*
*/
private static class BeanLifecycleWrapper {
private Object bean;
private Context<?> context;
private final String name;
@SuppressWarnings("rawtypes")
private final BeanLifecycleDecorator lifecycle;
private final ObjectFactory<?> objectFactory;
@SuppressWarnings("rawtypes")
public BeanLifecycleWrapper(String name,
ObjectFactory<?> objectFactory, BeanLifecycleDecorator lifecycle) {
this.name = name;
this.objectFactory = objectFactory;
this.lifecycle = lifecycle;
}
public void setDestroyCallback(Runnable callback) {
this.context = lifecycle.decorateDestructionCallback(callback);
}
@SuppressWarnings("unchecked")
public Object getBean() {
if (bean == null) {
bean = lifecycle.decorateBean(objectFactory.getObject(),
context);
}
return bean;
}
public void destroy() {
if (context==null) {
return;
}
Runnable callback = context.getCallback();
if (callback!=null) {
callback.run();
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope;
import java.util.Collection;
/**
* A special purpose cache interface specifically for the {@link GenericScope} to use to manage cached bean instances.
* Implementations generally fall into two categories: those that store values "globally" (i.e. one instance per key),
* and those that store potentially multiple instances per key based on context (e.g. via a thread local). All
* implementations should be thread safe.
*
* @author Dave Syer
*
*/
public interface ScopeCache {
/**
* Remove the object with this name from the cache.
*
* @param name the object name
* @return the object removed or null if there was none
*/
Object remove(String name);
/**
* Clear the cache and return all objects in an unmodifiable collection.
*
* @return all objects stored in the cache
*/
Collection<Object> clear();
/**
* Get the named object from the cache.
*
* @param name the name of the object
* @return the object with that name or null if there is none
*/
Object get(String name);
/**
* Put a value in the cache if the key is not already used. If one is already present with the name provided, it is
* not replaced, but is returned to the caller.
*
* @param name the key
* @param value the new candidate value
* @return the value that is in the cache at the end of the operation
*/
Object put(String name, Object value);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A simple cache implementation backed by a concurrent map.
*
* @author Dave Syer
*
*/
public class StandardScopeCache implements ScopeCache {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
public Object remove(String name) {
return cache.remove(name);
}
public Collection<Object> clear() {
Collection<Object> values = new ArrayList<Object>(cache.values());
cache.clear();
return values;
}
public Object get(String name) {
return cache.get(name);
}
public Object put(String name, Object value) {
Object result = cache.putIfAbsent(name, value);
if (result!=null) {
return result;
}
return value;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.platform.context.scope.refresh;
import java.io.Serializable;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.context.scope.GenericScope;
/**
* <p>
* A Scope implementation that allows for beans to be refreshed dynamically at runtime (see {@link #refresh(String)} and
* {@link #refreshAll()}). If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a
* new instance is created. All lifecycle methods are applied to the bean instances, so any destruction callbacks that
* were registered in the bean factory are called when it is refreshed, and then the initialization callbacks are
* invoked as normal when the new instance is created. A new bean instance is created from the original bean definition,
* so any externalized content (property placeholders or expressions in string literals) is re-evaluated when it is
* created.
* </p>
*
* <p>
* Note that all beans in this scope are <em>only</em> initialized when first accessed, so the scope forces lazy
* initialization semantics. The implementation involves creating a proxy for every bean in the scope, so there is a
* flag {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy creation, defaulting to JDK
* dynamic proxies and therefore only exposing the interfaces implemented by a bean. If callers need access to other
* methods then the flag needs to be set (and CGLib present on the classpath). Because this scope automatically proxies
* all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean definitions.
* </p>
*
* <p>
* The scoped proxy approach adopted here has a side benefit that bean instances are automatically {@link Serializable},
* and can be sent across the wire as long as the receiver has an identical application context on the other side. To
* ensure that the two contexts agree that they are identical they have to have the same serialization id. One will be
* generated automatically by default from the bean names, so two contexts with the same bean names are by default able
* to exchange beans by name. If you need to override the default id then provide an explicit {@link #setId(String) id}
* when the Scope is declared.
* </p>
*
* @author Dave Syer
*
* @since 3.1
*
*/
@ManagedResource
public class RefreshScope extends GenericScope {
/**
* Create a scope instance and give it the default name: "refresh".
*/
public RefreshScope() {
super();
super.setName("refresh");
}
@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
public void refresh(String name) {
if (!name.startsWith("scopedTarget.")) {
// User wants to refresh the bean with this name but that isn't the one in the cache...
name = "scopedTarget." + name;
}
// Ensure lifecycle is finished if bean was disposable
super.destroy(name);
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope.thread;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.platform.context.scope.ScopeCache;
/**
* @author Dave Syer
*
*/
public class ThreadLocalScopeCache implements ScopeCache {
private ThreadLocal<ConcurrentMap<String, Object>> data = new ThreadLocal<ConcurrentMap<String, Object>>() {
protected ConcurrentMap<String, Object> initialValue() {
return new ConcurrentHashMap<String, Object>();
}
};
public Object remove(String name) {
return data.get().remove(name);
}
public Collection<Object> clear() {
ConcurrentMap<String, Object> map = data.get();
Collection<Object> values = new ArrayList<Object>(map.values());
map.clear();
return values;
}
public Object get(String name) {
return data.get().get(name);
}
public Object put(String name, Object value) {
Object result = data.get().putIfAbsent(name, value);
if (result!=null) {
return result;
}
return value;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.platform.context.scope.thread;
import org.springframework.platform.context.scope.GenericScope;
/**
*
* @author Dave Syer
*
* @since 3.1
*
*/
public class ThreadScope extends GenericScope {
/**
* Create a scope instance and give it the default name: "thread".
*/
public ThreadScope() {
super();
super.setName("thread");
super.setScopeCache(new ThreadLocalScopeCache());
}
}

View File

@@ -0,0 +1,12 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.platform.autoconfigure.RefreshAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.platform.bootstrap.BootstrapApplicationListener
# Bootstrap components
org.springframework.platform.bootstrap.BootstrapConfiguration=\
org.springframework.platform.bootstrap.config.ConfigServiceBootstrapConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.platform.bootstrap.config.PropertySourceLocator;
/**
* @author Dave Syer
*
*/
public class BootstrapConfigurationTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (context != null) {
context.close();
}
}
@Test
public void picksUpAdditionalPropertySource() {
context = new SpringApplicationBuilder().web(false).sources(
BareConfiguration.class).run();
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
assertTrue(context.getEnvironment().getPropertySources().contains("bootstrap"));
}
@Test
public void environmentEnrichedOnce() {
context = new SpringApplicationBuilder().sources(BareConfiguration.class).environment(
new StandardEnvironment()).child(BareConfiguration.class).web(false).run();
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
assertEquals(context.getEnvironment(), context.getParent().getEnvironment());
assertTrue(context.getEnvironment().getPropertySources().contains("bootstrap"));
}
@Test
public void environmentEnrichedInParent() {
context = new SpringApplicationBuilder().sources(BareConfiguration.class).child(
BareConfiguration.class).web(false).run();
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
assertNotSame(context.getEnvironment(), context.getParent().getEnvironment());
assertTrue(context.getEnvironment().getPropertySources().contains("bootstrap"));
assertTrue(((ConfigurableEnvironment) context.getParent().getEnvironment()).getPropertySources().contains(
"bootstrap"));
}
@Configuration
protected static class BareConfiguration {
}
@Configuration
protected static class PropertySourceConfiguration implements PropertySourceLocator {
@Override
public PropertySource<?> locate() {
return new MapPropertySource("testBootstrap",
Collections.<String, Object> singletonMap("bootstrap.foo", "bar"));
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.environment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import javax.servlet.ServletException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.platform.context.environment.EnvironmentManagerIntegrationTests.TestConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class EnvironmentManagerIntegrationTests {
@Autowired
private TestProperties properties;
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void testRefresh() throws Exception {
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
this.mvc.perform(post("/env").param("message", "Foo")).andExpect(status().isOk()).andExpect(
content().string("{\"message\":\"Foo\"}"));
assertEquals("Foo", properties.getMessage());
}
@Test
public void testRefreshFails() throws Exception {
try {
this.mvc.perform(post("/env").param("delay", "foo")).andExpect(
status().is5xxServerError());
fail("expected ServletException");
} catch (ServletException e) {
// The underlying BindException is not handled by the dispatcher servlet
}
assertEquals(0, properties.getDelay());
}
public static void main(String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
@Bean
protected TestProperties properties() {
return new TestProperties();
}
}
@ConfigurationProperties
protected static class TestProperties {
private String message;
private int delay;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.properties;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.platform.autoconfigure.RefreshAutoConfiguration;
import org.springframework.platform.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationPropertiesRebinderIntegrationTests {
@Autowired
private TestProperties properties;
@Autowired
private ConfigurationPropertiesRebinder rebinder;
@Autowired
private ConfigurableEnvironment environment;
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", properties.getMessage());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
rebinder.rebind();
assertEquals("Foo", properties.getMessage());
}
@Configuration
@EnableConfigurationProperties
@Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
protected static class TestConfiguration {
@Bean
protected TestProperties properties() {
return new TestProperties();
}
}
@ConfigurationProperties
protected static class TestProperties {
private String message;
private int delay;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.autoconfigure.RefreshAutoConfiguration;
import org.springframework.platform.context.config.annotation.RefreshScope;
import org.springframework.platform.context.scope.refresh.MoreRefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MoreRefreshScopeIntegrationTests {
@Autowired
private TestService service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.platform.context.scope.refresh.RefreshScope scope;
@Autowired
private ConfigurableEnvironment environment;
@Before
public void init() {
TestService.reset();
}
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertTrue(service instanceof Advised);
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", service.getMessage());
assertEquals(1, TestService.getInitCount());
assertEquals(0, TestService.getDestroyCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, TestService.getInitCount());
assertEquals(1, TestService.getDestroyCount());
assertNotSame(id1, id2);
}
@Test
@DirtiesContext
public void testRefreshFails() throws Exception {
assertEquals("Hello scope!", service.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo", "delay:foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
try {
// If a refresh fails (e.g. a binding error in this case) the application is
// basically hosed.
assertEquals("Hello scope!", service.getMessage());
fail("expected BeanCreationException");
} catch (BeanCreationException e) {
}
// But we can fix it by fixing the binding error:
EnvironmentTestUtils.addEnvironment(environment, "delay:0");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
assertEquals("Foo", service.getMessage());
}
public static class TestService implements InitializingBean, DisposableBean {
private static Log logger = LogFactory.getLog(TestService.class);
private volatile static int initCount = 0;
private volatile static int destroyCount = 0;
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
initCount++;
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
destroyCount++;
message = null;
}
public static void reset() {
initCount = 0;
destroyCount = 0;
}
public static int getInitCount() {
return initCount;
}
public static int getDestroyCount() {
return destroyCount;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties
@Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static class TestConfiguration {
@Bean
@RefreshScope
protected TestProperties properties() {
return new TestProperties();
}
@Bean
@RefreshScope
public TestService service() {
TestService service = new TestService();
service.setMessage(properties().getMessage());
service.setDelay(properties().getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.autoconfigure.RefreshAutoConfiguration;
import org.springframework.platform.context.config.annotation.RefreshScope;
import org.springframework.platform.context.scope.refresh.RefreshScopeConcurrencyTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class RefreshScopeConcurrencyTests {
private static Log logger = LogFactory.getLog(RefreshScopeConcurrencyTests.class);
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Autowired
private Service service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.platform.context.scope.refresh.RefreshScope scope;
@Test
@Repeat(10)
@DirtiesContext
public void testConcurrentRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
properties.setMessage("Foo");
properties.setDelay(500);
final CountDownLatch latch = new CountDownLatch(1);
Future<String> result = executor.submit(new Callable<String>() {
public String call() throws Exception {
logger.debug("Background started.");
try {
latch.countDown();
return service.getMessage();
} finally {
logger.debug("Background done.");
}
}
});
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
logger.info("Refreshing");
scope.refreshAll();
assertEquals("Foo", service.getMessage());
/*
* This is the most important assertion: we don't want a null value because that means the bean was destroyed
* and not re-initialized before we accessed it.
*/
assertNotNull(result.get());
assertEquals("Hello scope!", result.get());
}
public static interface Service {
String getMessage();
}
public static class ExampleService implements Service, InitializingBean, DisposableBean {
private static Log logger = LogFactory.getLog(ExampleService.class);
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
message = null;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
protected static class TestConfiguration {
@Autowired
private TestProperties properties;
@Bean
@RefreshScope
public ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage(properties.getMessage());
service.setDelay(properties.getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.autoconfigure.RefreshAutoConfiguration;
import org.springframework.platform.context.config.annotation.RefreshScope;
import org.springframework.platform.context.scope.refresh.RefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class RefreshScopeIntegrationTests {
@Autowired
private Service service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.platform.context.scope.refresh.RefreshScope scope;
@Before
public void init() {
ExampleService.reset();
}
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertTrue(service instanceof Advised);
// Change the dynamic property source...
properties.setMessage("Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", service.getMessage());
assertEquals(1, ExampleService.getInitCount());
assertEquals(0, ExampleService.getDestroyCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
properties.setMessage("Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, ExampleService.getInitCount());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
}
@Test
@DirtiesContext
public void testRefreshBean() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
properties.setMessage("Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refresh("service");
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, ExampleService.getInitCount());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
}
public static interface Service {
String getMessage();
}
public static class ExampleService implements Service, InitializingBean, DisposableBean {
private static Log logger = LogFactory.getLog(ExampleService.class);
private volatile static int initCount = 0;
private volatile static int destroyCount = 0;
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
initCount++;
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
destroyCount++;
message = null;
}
public static void reset() {
initCount = 0;
destroyCount = 0;
}
public static int getInitCount() {
return initCount;
}
public static int getDestroyCount() {
return destroyCount;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
protected static class TestConfiguration {
@Autowired
private TestProperties properties;
@Bean
@RefreshScope
public ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage(properties.getMessage());
service.setDelay(properties.getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,3 @@
message: Hello scope!
delay: 0
# debug: true

View File

@@ -0,0 +1 @@
spring.main.sources: org.springframework.platform.bootstrap.BootstrapConfigurationTests.PropertySourceConfiguration

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-platform-config-server</artifactId>
<packaging>jar</packaging>
<name>spring-platform-config-server</name>
<description>spring-platform-config-server</description>
<parent>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.springframework.platform.config.server.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,81 @@
package org.springframework.platform.config.server;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.StandardServletEnvironment;
@Configuration
@ComponentScan
@EnableAutoConfiguration
@RestController
public class Application {
@Autowired
private ConfigurableEnvironment environment;
private Set<String> standardSources = new HashSet<String>(Arrays.asList(
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
@RequestMapping("/{name}/{env}")
public List<SerializableMapPropertySource> master(@PathVariable String env) {
return properties(env, "master");
}
@RequestMapping("/{name}/{env}/{label}")
public List<SerializableMapPropertySource> properties(@PathVariable String env, @PathVariable String label) {
List<SerializableMapPropertySource> result = new ArrayList<Application.SerializableMapPropertySource>();
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (!standardSources .contains(name) && source instanceof MapPropertySource) {
result.add(new SerializableMapPropertySource(name, (Map<?,?>)source.getSource()));
}
}
return result;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
protected static class SerializableMapPropertySource {
private String name;
private Map<?, ?> source;
public SerializableMapPropertySource(String name, Map<?,?> source) {
this.name = name;
this.source = source;
}
public String getName() {
return name;
}
public Map<?, ?> getSource() {
return source;
}
}
}

View File

@@ -0,0 +1,7 @@
info:
component: Config Server
spring:
application:
name: configserver
server:
port: 8888

View File

@@ -0,0 +1,18 @@
package org.springframework.platform.config.server;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class ApplicationTests {
@Test
public void contextLoads() {
}
}