Update devtools to use @Lazy(false)

Fixes gh-16184
This commit is contained in:
Madhura Bhave 2019-04-05 17:51:33 -07:00
parent ce0282406f
commit 3c203e9b0d
7 changed files with 223 additions and 67 deletions

View File

@ -1,66 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.devtools.autoconfigure;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
/**
* {@link EnableAutoConfiguration Auto-configuration} to ensure that DevTools' beans are
* eagerly initialized when the application is otherwise being initialized lazily.
*
* @author Andy Wilkinson
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
public class EagerInitializationAutoConfiguration {
@Bean
public static AlwaysEagerBeanFactoryPostProcessor alwaysEagerBeanFactoryPostProcessor() {
return new AlwaysEagerBeanFactoryPostProcessor();
}
private static final class AlwaysEagerBeanFactoryPostProcessor
implements BeanFactoryPostProcessor, Ordered {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
for (String name : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
String factoryBeanName = beanDefinition.getFactoryBeanName();
if (factoryBeanName != null && factoryBeanName
.startsWith("org.springframework.boot.devtools")) {
beanDefinition.setLazyInit(false);
}
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}
}

View File

@ -39,6 +39,7 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.ResolvableType;
@ -90,6 +91,7 @@ public class LocalDevToolsAutoConfiguration {
/**
* Local Restart Configuration.
*/
@Lazy(false)
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.devtools.restart", name = "enabled",
matchIfMissing = true)

View File

@ -9,7 +9,6 @@ org.springframework.boot.devtools.logger.DevToolsLogFactory.Listener
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.devtools.autoconfigure.EagerInitializationAutoConfiguration,\
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration,\
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\
org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration

View File

@ -22,10 +22,14 @@ import java.io.File;
* Launches an application with DevTools.
*
* @author Andy Wilkinson
* @author Madhura Bhave
*/
public interface ApplicationLauncher {
LaunchedApplication launchApplication(JvmLauncher javaLauncher, File serverPortFile)
throws Exception;
LaunchedApplication launchApplication(JvmLauncher jvmLauncher, File serverPortFile,
String... additionalArgs) throws Exception;
}

View File

@ -0,0 +1,183 @@
/*
* Copyright 2012-2019 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
*
* https://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.devtools.tests;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for DevTools with lazy initialization enabled.
*
* @author Madhura Bhave
*/
@RunWith(Parameterized.class)
public class DevToolsWithLazyInitializationIntegrationTests {
@ClassRule
public static final TemporaryFolder temp = new TemporaryFolder();
private static final BuildOutput buildOutput = new BuildOutput(
DevToolsIntegrationTests.class);
private LaunchedApplication launchedApplication;
private final File serverPortFile;
private final ApplicationLauncher applicationLauncher;
private String[] args;
@Rule
public JvmLauncher javaLauncher = new JvmLauncher();
public DevToolsWithLazyInitializationIntegrationTests(
ApplicationLauncher applicationLauncher) {
this.applicationLauncher = applicationLauncher;
this.serverPortFile = new File(buildOutput.getRootLocation(), "server.port");
}
@Before
public void launchApplication() throws Exception {
this.serverPortFile.delete();
this.launchedApplication = this.applicationLauncher.launchApplication(
this.javaLauncher, this.serverPortFile,
"--spring.main.lazy-initialization=true");
}
@After
public void stopApplication() throws InterruptedException {
this.launchedApplication.stop();
}
@Test
public void addARequestMappingToAnExistingControllerWhenLazyInit() throws Exception {
TestRestTemplate template = new TestRestTemplate();
String urlBase = "http://localhost:" + awaitServerPort();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode())
.isEqualTo(HttpStatus.NOT_FOUND);
controller("com.example.ControllerOne").withRequestMapping("one")
.withRequestMapping("two").build();
urlBase = "http://localhost:" + awaitServerPort();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForObject(urlBase + "/two", String.class))
.isEqualTo("two");
}
private int awaitServerPort() throws Exception {
Duration timeToWait = Duration.ofSeconds(40);
long end = System.currentTimeMillis() + timeToWait.toMillis();
System.out.println("Reading server port from '" + this.serverPortFile + "'");
while (this.serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(String.format(
"server.port file '" + this.serverPortFile
+ "' was not written within " + timeToWait.toMillis()
+ "ms. " + "Application output:%n%s%s",
FileCopyUtils.copyToString(new FileReader(
this.launchedApplication.getStandardOut())),
FileCopyUtils.copyToString(new FileReader(
this.launchedApplication.getStandardError()))));
}
Thread.sleep(100);
}
FileReader portReader = new FileReader(this.serverPortFile);
int port = Integer.valueOf(FileCopyUtils.copyToString(portReader));
this.serverPortFile.delete();
System.out.println("Got port " + port);
this.launchedApplication.restartRemote(port);
Thread.sleep(1000);
return port;
}
private ControllerBuilder controller(String name) {
return new ControllerBuilder(name,
this.launchedApplication.getClassesDirectory());
}
@Parameterized.Parameters(name = "{0}")
public static Object[] parameters() throws IOException {
Directories directories = new Directories(buildOutput, temp);
return new Object[] { new Object[] { new LocalApplicationLauncher(directories) },
new Object[] { new ExplodedRemoteApplicationLauncher(directories) },
new Object[] { new JarFileRemoteApplicationLauncher(directories) } };
}
private static final class ControllerBuilder {
private final List<String> mappings = new ArrayList<>();
private final String name;
private final File classesDirectory;
private ControllerBuilder(String name, File classesDirectory) {
this.name = name;
this.classesDirectory = classesDirectory;
}
public ControllerBuilder withRequestMapping(String mapping) {
this.mappings.add(mapping);
return this;
}
public void build() throws Exception {
DynamicType.Builder<Object> builder = new ByteBuddy().subclass(Object.class)
.name(this.name).annotateType(AnnotationDescription.Builder
.ofType(RestController.class).build());
for (String mapping : this.mappings) {
builder = builder.defineMethod(mapping, String.class, Visibility.PUBLIC)
.intercept(FixedValue.value(mapping)).annotateMethod(
AnnotationDescription.Builder.ofType(RequestMapping.class)
.defineArray("value", mapping).build());
}
builder.make().saveIn(this.classesDirectory);
}
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.devtools.tests;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm;
@ -45,6 +46,20 @@ public class LocalApplicationLauncher extends AbstractApplicationLauncher {
null);
}
@Override
public LaunchedApplication launchApplication(JvmLauncher jvmLauncher,
File serverPortFile, String... additionalArgs) throws Exception {
List<String> args = new ArrayList<>(
Arrays.asList("com.example.DevToolsTestApplication",
serverPortFile.getAbsolutePath(), "--server.port=0"));
args.addAll(Arrays.asList(additionalArgs));
LaunchedJvm jvm = jvmLauncher.launch("local", createApplicationClassPath(),
args.toArray(new String[] {}));
return new LaunchedApplication(getDirectories().getAppDirectory(),
jvm.getStandardOut(), jvm.getStandardError(), jvm.getProcess(), null,
null);
}
protected String createApplicationClassPath() throws Exception {
File appDirectory = getDirectories().getAppDirectory();
copyApplicationTo(appDirectory);

View File

@ -19,6 +19,7 @@ package org.springframework.boot.devtools.tests;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
@ -55,6 +56,24 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher {
remoteRestarter);
}
@Override
public LaunchedApplication launchApplication(JvmLauncher javaLauncher,
File serverPortFile, String... additionalArgs) throws Exception {
List<String> args = new ArrayList<>(Arrays.asList(
"com.example.DevToolsTestApplication", serverPortFile.getAbsolutePath(),
"--server.port=0", "--spring.devtools.remote.secret=secret"));
args.addAll(Arrays.asList(additionalArgs));
LaunchedJvm applicationJvm = javaLauncher.launch("app",
createApplicationClassPath(), args.toArray(new String[] {}));
int port = awaitServerPort(applicationJvm.getStandardOut(), serverPortFile);
BiFunction<Integer, File, Process> remoteRestarter = getRemoteRestarter(
javaLauncher);
return new LaunchedApplication(getDirectories().getRemoteAppDirectory(),
applicationJvm.getStandardOut(), applicationJvm.getStandardError(),
applicationJvm.getProcess(), remoteRestarter.apply(port, null),
remoteRestarter);
}
private BiFunction<Integer, File, Process> getRemoteRestarter(
JvmLauncher javaLauncher) {
return (port, classesDirectory) -> {