Add RestartScope and Restart attributes

Add a "restart" Spring `Scope` that remains between application
restarts. Allows beans such as the livereload server to remain active
during restarts and not disconnect clients.

See gh-3085
This commit is contained in:
Phillip Webb
2015-06-01 13:22:51 -07:00
parent f09134180e
commit a9f69e86be
7 changed files with 268 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2015 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.boot.developertools.restart;
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;
/**
* Restart {@code @Scope} Annotation used to indicate that a bean shoul remain beteen
* restarts.
*
* @author Phillip Webb
* @since 1.3.0
* @see RestartScopeInitializer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope("restart")
public @interface RestartScope {
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2015 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.boot.developertools.restart;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Support for a 'restart' {@link Scope} that allows beans to remain between restarts.
*
* @author Phillip Webb
* @since 1.3.0
*/
public class RestartScopeInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
}
/**
* {@link Scope} that stores beans as {@link Restarter} attributes.
*/
private static class RestartScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return Restarter.getInstance().getOrAddAttribute(name, objectFactory);
}
@Override
public Object remove(String name) {
return Restarter.getInstance().removeAttribute(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
}

View File

@@ -22,6 +22,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -38,6 +39,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.developertools.restart.classloader.RestartClassLoader;
import org.springframework.boot.logging.DeferredLog;
@@ -89,6 +91,8 @@ public class Restarter {
private final UncaughtExceptionHandler exceptionHandler;
private final Map<String, Object> attributes = new HashMap<String, Object>();
private final BlockingDeque<LeakSafeThread> leakSafeThreads = new LinkedBlockingDeque<LeakSafeThread>();
private boolean finished = false;
@@ -344,6 +348,22 @@ public class Restarter {
}
}
public Object getOrAddAttribute(final String name,
final ObjectFactory<?> objectFactory) {
synchronized (this.attributes) {
if (!this.attributes.containsKey(name)) {
this.attributes.put(name, objectFactory.getObject());
}
return this.attributes.get(name);
}
}
public Object removeAttribute(String name) {
synchronized (this.attributes) {
return this.attributes.remove(name);
}
}
/**
* Return the initial set of URLs as configured by the {@link RestartInitializer}.
* @return the initial URLs or {@code null}

View File

@@ -1,3 +1,7 @@
# Application Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.developertools.restart.RestartScopeInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.developertools.restart.RestartApplicationListener