Commit 20599227 authored by Andy Wilkinson's avatar Andy Wilkinson

Make ContextIdApplicationContextInitializer produce unique IDs

Closes gh-11023
parent f2b15ce5
...@@ -47,7 +47,6 @@ content into your application. Rather, pick only the properties that you need. ...@@ -47,7 +47,6 @@ content into your application. Rather, pick only the properties that you need.
spring.aop.proxy-target-class=true # Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false). spring.aop.proxy-target-class=true # Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).
# IDENTITY ({sc-spring-boot}/context/ContextIdApplicationContextInitializer.{sc-ext}[ContextIdApplicationContextInitializer]) # IDENTITY ({sc-spring-boot}/context/ContextIdApplicationContextInitializer.{sc-ext}[ContextIdApplicationContextInitializer])
spring.application.index= # Application index.
spring.application.name= # Application name. spring.application.name= # Application name.
# ADMIN ({sc-spring-boot-autoconfigure}/admin/SpringApplicationAdminJmxAutoConfiguration.{sc-ext}[SpringApplicationAdminJmxAutoConfiguration]) # ADMIN ({sc-spring-boot-autoconfigure}/admin/SpringApplicationAdminJmxAutoConfiguration.{sc-ext}[SpringApplicationAdminJmxAutoConfiguration])
......
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -24,73 +26,19 @@ import org.springframework.core.env.ConfigurableEnvironment; ...@@ -24,73 +26,19 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* {@link ApplicationContextInitializer} that set the Spring * {@link ApplicationContextInitializer} that sets the Spring
* {@link ApplicationContext#getId() ApplicationContext ID}. The following environment * {@link ApplicationContext#getId() ApplicationContext ID}. The
* properties will be consulted to create the ID: * {@code spring.application.name} property is used to create the ID. If the property is
* <ul> * not set {@code application} is used.
* <li>spring.application.name</li>
* <li>vcap.application.name</li>
* <li>spring.config.name</li>
* </ul>
* If no property is set the ID 'application' will be used.
*
* <p>
* In addition the following environment properties will be consulted to append a relevant
* port or index:
*
* <ul>
* <li>spring.application.index</li>
* <li>vcap.application.instance_index</li>
* <li>PORT</li>
* </ul>
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
public class ContextIdApplicationContextInitializer implements public class ContextIdApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
/**
* Placeholder pattern to resolve for application name. The following order is used to
* find the name:
* <ul>
* <li>{@code spring.application.name}</li>
* <li>{@code vcap.application.name}</li>
* <li>{@code spring.config.name}</li>
* </ul>
* This order allows the user defined name to take precedence over the platform
* defined name. If no property is defined {@code 'application'} will be used.
*/
private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";
/**
* Placeholder pattern to resolve for application index. The following order is used
* to find the name:
* <ul>
* <li>{@code vcap.application.instance_index}</li>
* <li>{@code spring.application.index}</li>
* <li>{@code server.port}</li>
* <li>{@code PORT}</li>
* </ul>
* This order favors a platform defined index over any user defined value.
*/
private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}";
private final String name;
private int order = Ordered.LOWEST_PRECEDENCE - 10; private int order = Ordered.LOWEST_PRECEDENCE - 10;
public ContextIdApplicationContextInitializer() {
this(NAME_PATTERN);
}
/**
* Create a new {@link ContextIdApplicationContextInitializer} instance.
* @param name the name of the application (can include placeholders)
*/
public ContextIdApplicationContextInitializer(String name) {
this.name = name;
}
public void setOrder(int order) { public void setOrder(int order) {
this.order = order; this.order = order;
} }
...@@ -102,21 +50,46 @@ public class ContextIdApplicationContextInitializer implements ...@@ -102,21 +50,46 @@ public class ContextIdApplicationContextInitializer implements
@Override @Override
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment())); ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(),
contextId);
}
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
ApplicationContext parent = applicationContext.getParent();
if (parent != null && parent.containsBean(ContextId.class.getName())) {
return parent.getBean(ContextId.class).createChildId();
}
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
} }
private String getApplicationId(ConfigurableEnvironment environment) { private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.resolvePlaceholders(this.name); String name = environment.getProperty("spring.application.name");
String index = environment.resolvePlaceholders(INDEX_PATTERN); return StringUtils.hasText(name) ? name : "application";
String profiles = StringUtils }
.arrayToCommaDelimitedString(environment.getActiveProfiles());
if (StringUtils.hasText(profiles)) { /**
name = name + ":" + profiles; * The ID of a context.
*/
class ContextId {
private final AtomicLong children = new AtomicLong(0);
private final String id;
ContextId(String id) {
this.id = id;
} }
if (!"null".equals(index)) {
name = name + ":" + index; ContextId createChildId() {
return new ContextId(this.id + "-" + this.children.incrementAndGet());
} }
return name;
String getId() {
return this.id;
}
} }
} }
...@@ -143,12 +143,6 @@ ...@@ -143,12 +143,6 @@
"sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer", "sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer",
"description": "Application name." "description": "Application name."
}, },
{
"name": "spring.application.index",
"type": "java.lang.Integer",
"sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer",
"description": "Application index."
},
{ {
"name": "spring.beaninfo.ignore", "name": "spring.beaninfo.ignore",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
......
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,11 @@ ...@@ -16,6 +16,11 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -33,50 +38,67 @@ public class ContextIdApplicationContextInitializerTests { ...@@ -33,50 +38,67 @@ public class ContextIdApplicationContextInitializerTests {
private final ContextIdApplicationContextInitializer initializer = new ContextIdApplicationContextInitializer(); private final ContextIdApplicationContextInitializer initializer = new ContextIdApplicationContextInitializer();
private List<ConfigurableApplicationContext> contexts = new ArrayList<>();
@After
public void closeContexts() {
Collections.reverse(this.contexts);
this.contexts.forEach(ConfigurableApplicationContext::close);
}
@Test @Test
public void testDefaults() { public void singleContextWithDefaultName() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); ConfigurableApplicationContext context = createContext(null);
this.initializer.initialize(context);
assertThat(context.getId()).isEqualTo("application"); assertThat(context.getId()).isEqualTo("application");
} }
@Test @Test
public void testNameAndPort() { public void singleContextWithCustomName() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); ConfigurableApplicationContext context = createContext(null,
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, "spring.application.name=test");
"spring.application.name=foo", "PORT=8080"); assertThat(context.getId()).isEqualTo("test");
this.initializer.initialize(context);
assertThat(context.getId()).isEqualTo("foo:8080");
} }
@Test @Test
public void testNameAndProfiles() { public void linearHierarchy() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); ConfigurableApplicationContext grandparent = createContext(null);
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, ConfigurableApplicationContext parent = createContext(grandparent);
"spring.application.name=foo", "spring.profiles.active=spam,bar", ConfigurableApplicationContext child = createContext(parent);
"spring.application.index=12"); assertThat(child.getId()).isEqualTo("application-1-1");
this.initializer.initialize(context);
assertThat(context.getId()).isEqualTo("foo:spam,bar:12");
} }
@Test @Test
public void testCloudFoundry() { public void complexHierarchy() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); ConfigurableApplicationContext grandparent = createContext(null);
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, ConfigurableApplicationContext parent1 = createContext(grandparent);
"spring.config.name=foo", "PORT=8080", "vcap.application.name=bar", ConfigurableApplicationContext parent2 = createContext(grandparent);
"vcap.application.instance_index=2"); ConfigurableApplicationContext child1_1 = createContext(parent1);
this.initializer.initialize(context); assertThat(child1_1.getId()).isEqualTo("application-1-1");
assertThat(context.getId()).isEqualTo("bar:2"); ConfigurableApplicationContext child1_2 = createContext(parent1);
assertThat(child1_2.getId()).isEqualTo("application-1-2");
ConfigurableApplicationContext child2_1 = createContext(parent2);
assertThat(child2_1.getId()).isEqualTo("application-2-1");
} }
@Test @Test
public void testExplicitNameIsChosenInFavorOfCloudFoundry() { public void contextWithParentWithNoContextIdFallsBackToDefaultId() {
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext();
this.contexts.add(parent);
parent.refresh();
assertThat(createContext(parent).getId()).isEqualTo("application");
}
private ConfigurableApplicationContext createContext(
ConfigurableApplicationContext parent, String... properties) {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, properties);
"spring.application.name=spam", "spring.config.name=foo", "PORT=8080", if (parent != null) {
"vcap.application.name=bar", "vcap.application.instance_index=2"); context.setParent(parent);
}
this.initializer.initialize(context); this.initializer.initialize(context);
assertThat(context.getId()).isEqualTo("spam:2"); context.refresh();
this.contexts.add(context);
return context;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment