Skip to content

Commit

Permalink
Improve ability to introspect bean context prior to start (#10805)
Browse files Browse the repository at this point in the history
The existing `InitializableBeanContext` is problematic because it still creates context scoped beans and runs event listeners. We need a way to configure the context and allow it to be introspected prior to start so this deprecates `InitializableBeanContext` and replaces it with `ConfigurableBeanContext` and `ConfigurableApplicationContext` interfaces.

These feature a `configure()` method that loads the bean definitions and state needed to start the context but doesn't actually start it.

The PR also adds logic to assert the context is running when resolving beans which was missing before and could lead to odd behaviour.
  • Loading branch information
graemerocher committed May 15, 2024
1 parent 3ad933b commit 1a8fc61
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.micronaut.inject.context

import io.micronaut.context.ApplicationContext
import spock.lang.Specification

class ApplicationContextConfigurerSpec
extends Specification {

void 'test context configurer registers bean'() {
given:
def ctx = ApplicationContext.run()

expect:"bean is registered"
ctx.getBean(Foo)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.micronaut.inject.context;

import io.micronaut.context.ApplicationContext;
import io.micronaut.context.ApplicationContextConfigurer;
import io.micronaut.context.RuntimeBeanDefinition;
import io.micronaut.context.annotation.ContextConfigurer;

@ContextConfigurer
public class TestContextConfigurer
implements ApplicationContextConfigurer {
@Override
public void configure(ApplicationContext applicationContext) {
applicationContext.registerBeanDefinition(
RuntimeBeanDefinition.of(new Foo())
);
}
}

class Foo {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*
* @author graemerocher
* @since 1.0
* @see ApplicationContextConfigurer
* @see ApplicationContext#builder()
*/
public interface ApplicationContextBuilder {

Expand All @@ -53,6 +55,7 @@ public interface ApplicationContextBuilder {
* @return The context builder
* @since 2.0
*/
@SuppressWarnings("unchecked")
default @NonNull ApplicationContextBuilder eagerInitSingletons(boolean eagerInitSingletons) {
if (eagerInitSingletons) {
return eagerInitAnnotated(Singleton.class);
Expand All @@ -77,6 +80,7 @@ public interface ApplicationContextBuilder {
* @return The context builder
* @since 2.0
*/
@SuppressWarnings("unchecked")
@NonNull ApplicationContextBuilder eagerInitAnnotated(Class<? extends Annotation>... annotations);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
*/
public interface ApplicationContextConfiguration extends BeanContextConfiguration {

/**
* The loaded {@link ApplicationContextConfigurer}.
* @return The context configurer.
* @since 4.5.0
*/
default Optional<ApplicationContextConfigurer> getContextConfigurer() {
return Optional.empty();
}

/**
* @return The environment names
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
* for configuring an application context before the
* application/function is started.
*
* Application context configurers must be registered
* <p>Application context configurers must be registered
* as services. Those services are automatically called
* whenever a new application context builder is created.
* whenever a new application context builder is created.</p>
*
* An application context annotated with
* <p>An application context annotated with
* {@link io.micronaut.context.annotation.ContextConfigurer}
* will automatically be registered as a service provider.
* will automatically be registered as a service provider.</p>
*
* @since 3.2
*/
Expand All @@ -45,9 +45,28 @@ public interface ApplicationContextConfigurer extends Ordered {

/**
* Configures the application context builder.
*
* <p>This method allows the programmatic enhancement of the context prior to construction.</p>
*
* <p>The {@link ApplicationContextBuilder} interface features methods to add environments, property sources etc.</p>
*
* @param builder the builder to configure
* @see ApplicationContextBuilder
*/
default void configure(@NonNull ApplicationContextBuilder builder) {
// no-op
}

/**
* Configures the application context.
*
* <p>This method will be called after the context is fully configured but
* prior to starting the context.</p>
*
* @param applicationContext The context.
* @since 4.5.0
*/
default void configure(@NonNull ApplicationContext applicationContext) {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.micronaut.context;

import io.micronaut.core.annotation.NonNull;

/**
* An interface for classes that provide an {@link ApplicationContext}.
*
Expand All @@ -28,5 +30,5 @@ public interface ApplicationContextProvider {
*
* @return The {@link ApplicationContext}
*/
ApplicationContext getApplicationContext();
@NonNull ApplicationContext getApplicationContext();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2017-2024 original 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 io.micronaut.context;

/**
* Extended version of the {@link ApplicationContext} that
* allows loading the context configuration without starting the context.
*
* @see ConfigurableBeanContext#configure()
* @since 4.5.0
*/
public interface ConfigurableApplicationContext
extends ApplicationContext, ConfigurableBeanContext {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2017-2024 original 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 io.micronaut.context;

/**
* Extends {@link BeanContext} interface and allows the bean context to be configured but not started.
*
* @since 4.5.0
*/
public interface ConfigurableBeanContext
extends BeanContext {

/**
* Configures the bean context loading all bean definitions
* required to perform successful startup without starting the context itself.
*
* <p>Once called the methods of the {@link BeanDefinitionRegistry} interface will
* return results allowing inspection of the context without needing to run the context.</p>
*
* @see io.micronaut.inject.BeanDefinition
* @see #getBeanDefinition(Class)
* @see #start()
*/
void configure();
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
* @author Graeme Rocher
* @since 1.0
*/
public class DefaultApplicationContext extends DefaultBeanContext implements ApplicationContext {
public class DefaultApplicationContext extends DefaultBeanContext implements ConfigurableApplicationContext {

private final ClassPathResourceLoader resourceLoader;
private final ApplicationContextConfiguration configuration;
Expand Down Expand Up @@ -131,6 +131,15 @@ public DefaultApplicationContext(@NonNull ApplicationContextConfiguration config
this.resourceLoader = configuration.getResourceLoader();
}

@Override
@Internal
final void configureContextInternal() {
super.configureContextInternal();
configuration.getContextConfigurer().ifPresent(configurer ->
configurer.configure(this)
);
}

@Override
public @NonNull
<T> ApplicationContext registerSingleton(@NonNull Class<T> type, @NonNull T singleton, @Nullable Qualifier<T> qualifier, boolean inject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class DefaultApplicationContextBuilder implements ApplicationContextBuild
private final List<PropertySource> propertySources = new ArrayList<>();
private final Collection<String> configurationIncludes = new HashSet<>();
private final Collection<String> configurationExcludes = new HashSet<>();
private final ApplicationContextConfigurer contextConfigurer;
private Boolean deduceEnvironments = null;
private boolean deduceCloudEnvironment = false;
private ClassLoader classLoader = getClass().getClassLoader();
Expand All @@ -76,11 +77,15 @@ public class DefaultApplicationContextBuilder implements ApplicationContextBuild
* Default constructor.
*/
protected DefaultApplicationContextBuilder() {
loadApplicationContextCustomizer(resolveClassLoader()).configure(this);
ApplicationContextConfigurer applicationContextConfigurer = loadApplicationContextCustomizer(resolveClassLoader());
applicationContextConfigurer.configure(this);
this.contextConfigurer = applicationContextConfigurer;
}

DefaultApplicationContextBuilder(ClassLoader classLoader) {
loadApplicationContextCustomizer(classLoader).configure(this);
ApplicationContextConfigurer applicationContextConfigurer = loadApplicationContextCustomizer(classLoader);
applicationContextConfigurer.configure(this);
this.contextConfigurer = applicationContextConfigurer;
this.classLoader = classLoader;
}

Expand All @@ -92,6 +97,11 @@ private ClassLoader resolveClassLoader() {
return DefaultApplicationContextBuilder.class.getClassLoader();
}

@Override
public Optional<ApplicationContextConfigurer> getContextConfigurer() {
return Optional.ofNullable(this.contextConfigurer);
}

@Override
public boolean isAllowEmptyProviders() {
return allowEmptyProviders;
Expand Down Expand Up @@ -449,6 +459,12 @@ public void configure(ApplicationContextBuilder builder) {
}
}

@Override
public void configure(ApplicationContext applicationContext) {
for (ApplicationContextConfigurer customizer : configurers) {
customizer.configure(applicationContext);
}
}
};
}
}

0 comments on commit 1a8fc61

Please sign in to comment.