From 2059922735fc157eac6cce6a5ab669e826f79bfb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 9 Jan 2018 16:33:24 +0000 Subject: [PATCH] Make ContextIdApplicationContextInitializer produce unique IDs Closes gh-11023 --- .../appendix-application-properties.adoc | 1 - ...ontextIdApplicationContextInitializer.java | 113 +++++++----------- ...itional-spring-configuration-metadata.json | 6 - ...tIdApplicationContextInitializerTests.java | 80 ++++++++----- 4 files changed, 94 insertions(+), 106 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 37a89f17c4..9b087f6712 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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). # IDENTITY ({sc-spring-boot}/context/ContextIdApplicationContextInitializer.{sc-ext}[ContextIdApplicationContextInitializer]) - spring.application.index= # Application index. spring.application.name= # Application name. # ADMIN ({sc-spring-boot-autoconfigure}/admin/SpringApplicationAdminJmxAutoConfiguration.{sc-ext}[SpringApplicationAdminJmxAutoConfiguration]) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/ContextIdApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/ContextIdApplicationContextInitializer.java index 9d791125a4..be1b8ce4c3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/ContextIdApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/ContextIdApplicationContextInitializer.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.context; +import java.util.concurrent.atomic.AtomicLong; + import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -24,73 +26,19 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.StringUtils; /** - * {@link ApplicationContextInitializer} that set the Spring - * {@link ApplicationContext#getId() ApplicationContext ID}. The following environment - * properties will be consulted to create the ID: - * - * If no property is set the ID 'application' will be used. - * - *

- * In addition the following environment properties will be consulted to append a relevant - * port or index: - * - *

+ * {@link ApplicationContextInitializer} that sets the Spring + * {@link ApplicationContext#getId() ApplicationContext ID}. The + * {@code spring.application.name} property is used to create the ID. If the property is + * not set {@code application} is used. * * @author Dave Syer + * @author Andy Wilkinson */ public class ContextIdApplicationContextInitializer implements ApplicationContextInitializer, Ordered { - /** - * Placeholder pattern to resolve for application name. The following order is used to - * find the name: - * - * 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: - * - * 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; - 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) { this.order = order; } @@ -102,21 +50,46 @@ public class ContextIdApplicationContextInitializer implements @Override 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) { - String name = environment.resolvePlaceholders(this.name); - String index = environment.resolvePlaceholders(INDEX_PATTERN); - String profiles = StringUtils - .arrayToCommaDelimitedString(environment.getActiveProfiles()); - if (StringUtils.hasText(profiles)) { - name = name + ":" + profiles; + String name = environment.getProperty("spring.application.name"); + return StringUtils.hasText(name) ? name : "application"; + } + + /** + * 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; + } + } } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 2924562836..47a050ed7d 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -143,12 +143,6 @@ "sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer", "description": "Application name." }, - { - "name": "spring.application.index", - "type": "java.lang.Integer", - "sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer", - "description": "Application index." - }, { "name": "spring.beaninfo.ignore", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ContextIdApplicationContextInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ContextIdApplicationContextInitializerTests.java index 0bedae323a..a795ce2093 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ContextIdApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ContextIdApplicationContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,11 @@ 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.springframework.context.ConfigurableApplicationContext; @@ -33,50 +38,67 @@ public class ContextIdApplicationContextInitializerTests { private final ContextIdApplicationContextInitializer initializer = new ContextIdApplicationContextInitializer(); + private List contexts = new ArrayList<>(); + + @After + public void closeContexts() { + Collections.reverse(this.contexts); + this.contexts.forEach(ConfigurableApplicationContext::close); + } + @Test - public void testDefaults() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); - this.initializer.initialize(context); + public void singleContextWithDefaultName() { + ConfigurableApplicationContext context = createContext(null); assertThat(context.getId()).isEqualTo("application"); } @Test - public void testNameAndPort() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, - "spring.application.name=foo", "PORT=8080"); - this.initializer.initialize(context); - assertThat(context.getId()).isEqualTo("foo:8080"); + public void singleContextWithCustomName() { + ConfigurableApplicationContext context = createContext(null, + "spring.application.name=test"); + assertThat(context.getId()).isEqualTo("test"); } @Test - public void testNameAndProfiles() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, - "spring.application.name=foo", "spring.profiles.active=spam,bar", - "spring.application.index=12"); - this.initializer.initialize(context); - assertThat(context.getId()).isEqualTo("foo:spam,bar:12"); + public void linearHierarchy() { + ConfigurableApplicationContext grandparent = createContext(null); + ConfigurableApplicationContext parent = createContext(grandparent); + ConfigurableApplicationContext child = createContext(parent); + assertThat(child.getId()).isEqualTo("application-1-1"); } @Test - public void testCloudFoundry() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, - "spring.config.name=foo", "PORT=8080", "vcap.application.name=bar", - "vcap.application.instance_index=2"); - this.initializer.initialize(context); - assertThat(context.getId()).isEqualTo("bar:2"); + public void complexHierarchy() { + ConfigurableApplicationContext grandparent = createContext(null); + ConfigurableApplicationContext parent1 = createContext(grandparent); + ConfigurableApplicationContext parent2 = createContext(grandparent); + ConfigurableApplicationContext child1_1 = createContext(parent1); + assertThat(child1_1.getId()).isEqualTo("application-1-1"); + 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 - 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(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, - "spring.application.name=spam", "spring.config.name=foo", "PORT=8080", - "vcap.application.name=bar", "vcap.application.instance_index=2"); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, properties); + if (parent != null) { + context.setParent(parent); + } this.initializer.initialize(context); - assertThat(context.getId()).isEqualTo("spam:2"); + context.refresh(); + this.contexts.add(context); + return context; } }