Skip to content

Commit 2260657

Browse files
committed
Add BootstrapRegistry for long lived instances
Add a simple `BootstrapRegistry` that can be used to store and share object instances across `EnvironmentPostProcessors`. The registry can be injected into the constructor of any `EnvironmentPostProcessor`. Registrations can also perform additional actions when the `ApplicationContext` has been prepared. For example, they could register the the bootstrap instances as beans so that they become available to the application. See gh-22956
1 parent 167e31d commit 2260657

11 files changed

+542
-85
lines changed

spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ private Collection<ApplicationListener<?>> getListeners() {
7474
List<ApplicationListener<?>> listeners = new ArrayList<>();
7575
listeners.add(new AnsiOutputApplicationListener());
7676
listeners.add(new EnvironmentPostProcessorApplicationListener(
77-
EnvironmentPostProcessorsFactory.singleton(ConfigDataEnvironmentPostProcessor::new)));
77+
EnvironmentPostProcessorsFactory.of(ConfigDataEnvironmentPostProcessor.class)));
7878
listeners.add(new ClasspathLoggingApplicationListener());
7979
listeners.add(new LoggingApplicationListener());
8080
listeners.add(new RemoteUrlPropertyExtractor());
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.env;
18+
19+
import java.util.function.BiConsumer;
20+
import java.util.function.Supplier;
21+
22+
import org.springframework.boot.context.event.ApplicationPreparedEvent;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.context.ConfigurableApplicationContext;
25+
import org.springframework.core.env.Environment;
26+
27+
/**
28+
* A simple object registry that is available during {@link Environment} post-processing
29+
* up to the point that the {@link ApplicationContext} is prepared. The registry can be
30+
* used to store objects that may be expensive to create, or need to be shared by
31+
* different {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
32+
* <p>
33+
* The registry uses the object type as a key, meaning that only a single instance of a
34+
* given class can be stored.
35+
* <p>
36+
* Registered instances may optionally use
37+
* {@link Registration#onApplicationContextPrepared(BiConsumer)
38+
* onApplicationContextPrepared(...)} to perform an action when the
39+
* {@link ApplicationContext} is {@link ApplicationPreparedEvent prepared}. For example,
40+
* an instance may choose to register itself as a regular Spring bean so that it is
41+
* available for the application to use.
42+
*
43+
* @author Phillip Webb
44+
* @since 2.4.0
45+
* @see EnvironmentPostProcessor
46+
*/
47+
public interface BootstrapRegistry {
48+
49+
/**
50+
* Get an instance from the registry, creating one if it does not already exist.
51+
* @param <T> the instance type
52+
* @param type the instance type
53+
* @param instanceSupplier a supplier used to create the instance if it doesn't
54+
* already exist
55+
* @return the registered instance
56+
*/
57+
<T> T get(Class<T> type, Supplier<T> instanceSupplier);
58+
59+
/**
60+
* Get an instance from the registry, creating one if it does not already exist.
61+
* @param <T> the instance type
62+
* @param type the instance type
63+
* @param instanceSupplier a supplier used to create the instance if it doesn't
64+
* already exist
65+
* @param onApplicationContextPreparedAction the action that should be called when the
66+
* application context is prepared. This action is ignored if the registration already
67+
* exists.
68+
* @return the registered instance
69+
*/
70+
<T> T get(Class<T> type, Supplier<T> instanceSupplier,
71+
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction);
72+
73+
/**
74+
* Register an instance with the registry and return a {@link Registration} that can
75+
* be used to provide further configuration. This method will replace any existing
76+
* registration.
77+
* @param <T> the instance type
78+
* @param type the instance type
79+
* @param instanceSupplier a supplier used to create the instance if it doesn't
80+
* already exist
81+
* @return an instance registration
82+
*/
83+
<T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier);
84+
85+
/**
86+
* Return if a registration exists for the given type.
87+
* @param <T> the instance type
88+
* @param type the instance type
89+
* @return {@code true} if the type has already been registered
90+
*/
91+
<T> boolean isRegistered(Class<T> type);
92+
93+
/**
94+
* Return any existing {@link Registration} for the given type.
95+
* @param <T> the instance type
96+
* @param type the instance type
97+
* @return the existing registration or {@code null}
98+
*/
99+
<T> Registration<T> getRegistration(Class<T> type);
100+
101+
/**
102+
* A single registration contained in the registry.
103+
*
104+
* @param <T> the instance type
105+
*/
106+
interface Registration<T> {
107+
108+
/**
109+
* Get or crearte the registered object instance.
110+
* @return the object instance
111+
*/
112+
T get();
113+
114+
/**
115+
* Add an action that should run when the {@link ApplicationContext} has been
116+
* prepared.
117+
* @param action the action to run
118+
*/
119+
void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action);
120+
121+
}
122+
123+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.env;
18+
19+
import java.util.ArrayList;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.function.BiConsumer;
24+
import java.util.function.Supplier;
25+
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.ConfigurableApplicationContext;
28+
29+
/**
30+
* Default implementation of {@link BootstrapRegistry}.
31+
*
32+
* @author Phillip Webb
33+
* @since 2.4.0
34+
*/
35+
public class DefaultBootstrapRegisty implements BootstrapRegistry {
36+
37+
private final Map<Class<?>, DefaultRegistration<?>> registrations = new HashMap<>();
38+
39+
@Override
40+
public <T> T get(Class<T> type, Supplier<T> instanceSupplier) {
41+
return get(type, instanceSupplier, null);
42+
}
43+
44+
@Override
45+
public <T> T get(Class<T> type, Supplier<T> instanceSupplier,
46+
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction) {
47+
Registration<T> registration = getRegistration(type);
48+
if (registration != null) {
49+
return registration.get();
50+
}
51+
registration = register(type, instanceSupplier);
52+
registration.onApplicationContextPrepared(onApplicationContextPreparedAction);
53+
return registration.get();
54+
}
55+
56+
@Override
57+
public <T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier) {
58+
DefaultRegistration<T> registration = new DefaultRegistration<>(instanceSupplier);
59+
this.registrations.put(type, registration);
60+
return registration;
61+
}
62+
63+
@Override
64+
public <T> boolean isRegistered(Class<T> type) {
65+
return getRegistration(type) != null;
66+
}
67+
68+
@Override
69+
@SuppressWarnings("unchecked")
70+
public <T> Registration<T> getRegistration(Class<T> type) {
71+
return (Registration<T>) this.registrations.get(type);
72+
}
73+
74+
/**
75+
* Method to be called when the {@link ApplicationContext} is prepared.
76+
* @param applicationContext the prepared context
77+
*/
78+
public void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
79+
this.registrations.values()
80+
.forEach((registration) -> registration.applicationContextPrepared(applicationContext));
81+
}
82+
83+
/**
84+
* Default implementation of {@link Registration}.
85+
*/
86+
private static class DefaultRegistration<T> implements Registration<T> {
87+
88+
private Supplier<T> instanceSupplier;
89+
90+
private volatile T instance;
91+
92+
private List<BiConsumer<ConfigurableApplicationContext, T>> applicationContextPreparedActions = new ArrayList<>();
93+
94+
DefaultRegistration(Supplier<T> instanceSupplier) {
95+
this.instanceSupplier = instanceSupplier;
96+
}
97+
98+
@Override
99+
public T get() {
100+
T instance = this.instance;
101+
if (instance == null) {
102+
synchronized (this.instanceSupplier) {
103+
instance = this.instanceSupplier.get();
104+
this.instance = instance;
105+
}
106+
}
107+
return instance;
108+
}
109+
110+
@Override
111+
public void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action) {
112+
if (action != null) {
113+
this.applicationContextPreparedActions.add(action);
114+
}
115+
}
116+
117+
/**
118+
* Method called when the {@link ApplicationContext} is prepared.
119+
* @param applicationContext the prepared context
120+
*/
121+
void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
122+
this.applicationContextPreparedActions.forEach((consumer) -> consumer.accept(applicationContext, get()));
123+
}
124+
125+
}
126+
127+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,16 @@
3434
* if they wish to be invoked in specific order.
3535
* <p>
3636
* Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally
37-
* take a single {@link Log} or {@link DeferredLogFactory} instance as a constructor
38-
* argument. The injected {@link Log} instance will defer output until the application has
39-
* been full prepared to allow the environment itself to configure logging levels.
37+
* take the following constructor parameters:
38+
* <ul>
39+
* <li>{@link DeferredLogFactory} - A factory that can be used to create loggers with
40+
* output deferred until the application has been full prepared (allowing the environment
41+
* itself to configure logging levels).</li>
42+
* <li>{@link Log} - A log with output deferred until the application has been full
43+
* prepared (allowing the environment itself to configure logging levels).</li>
44+
* <li>{@link BootstrapRegistry} - A bootstrap registry that can be used to store objects
45+
* that may be expensive to create, or need to be shared.</li>
46+
* </ul>
4047
*
4148
* @author Andy Wilkinson
4249
* @author Stephane Nicoll

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
4444

4545
private final DeferredLogs deferredLogs;
4646

47+
private final DefaultBootstrapRegisty bootstrapRegistry;
48+
4749
private int order = DEFAULT_ORDER;
4850

4951
private final EnvironmentPostProcessorsFactory postProcessorsFactory;
@@ -63,13 +65,14 @@ public EnvironmentPostProcessorApplicationListener() {
6365
* @param postProcessorsFactory the post processors factory
6466
*/
6567
public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) {
66-
this(postProcessorsFactory, new DeferredLogs());
68+
this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty());
6769
}
6870

6971
EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory,
70-
DeferredLogs deferredLogs) {
72+
DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) {
7173
this.postProcessorsFactory = postProcessorsFactory;
7274
this.deferredLogs = deferredLogs;
75+
this.bootstrapRegistry = bootstrapRegistry;
7376
}
7477

7578
@Override
@@ -84,8 +87,11 @@ public void onApplicationEvent(ApplicationEvent event) {
8487
if (event instanceof ApplicationEnvironmentPreparedEvent) {
8588
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
8689
}
87-
if (event instanceof ApplicationPreparedEvent || event instanceof ApplicationFailedEvent) {
88-
onFinish();
90+
if (event instanceof ApplicationPreparedEvent) {
91+
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
92+
}
93+
if (event instanceof ApplicationFailedEvent) {
94+
onApplicationFailedEvent((ApplicationFailedEvent) event);
8995
}
9096
}
9197

@@ -97,14 +103,19 @@ private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPrepare
97103
}
98104
}
99105

100-
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() {
101-
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs);
106+
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
107+
this.deferredLogs.switchOverAll();
108+
this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext());
102109
}
103110

104-
private void onFinish() {
111+
private void onApplicationFailedEvent(ApplicationFailedEvent event) {
105112
this.deferredLogs.switchOverAll();
106113
}
107114

115+
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() {
116+
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry);
117+
}
118+
108119
@Override
109120
public int getOrder() {
110121
return this.order;

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package org.springframework.boot.env;
1818

19-
import java.util.Collections;
2019
import java.util.List;
21-
import java.util.function.Function;
2220

2321
import org.springframework.boot.logging.DeferredLogFactory;
2422
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -36,9 +34,11 @@ public interface EnvironmentPostProcessorsFactory {
3634
/**
3735
* Create all requested {@link EnvironmentPostProcessor} instances.
3836
* @param logFactory a deferred log factory
37+
* @param bootstrapRegistry a bootstrap registry
3938
* @return the post processor instances
4039
*/
41-
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory);
40+
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
41+
BootstrapRegistry bootstrapRegistry);
4242

4343
/**
4444
* Return a {@link EnvironmentPostProcessorsFactory} backed by
@@ -71,14 +71,4 @@ static EnvironmentPostProcessorsFactory of(String... classNames) {
7171
return new ReflectionEnvironmentPostProcessorsFactory(classNames);
7272
}
7373

74-
/**
75-
* Create a {@link EnvironmentPostProcessorsFactory} containing only a single post
76-
* processor.
77-
* @param factory the factory used to create the post processor
78-
* @return an {@link EnvironmentPostProcessorsFactory} instance
79-
*/
80-
static EnvironmentPostProcessorsFactory singleton(Function<DeferredLogFactory, EnvironmentPostProcessor> factory) {
81-
return (logFactory) -> Collections.singletonList(factory.apply(logFactory));
82-
}
83-
8474
}

0 commit comments

Comments
 (0)