Skip to content
This repository has been archived by the owner on Feb 23, 2023. It is now read-only.

Commit

Permalink
Add basic support for parsing the bean definitions at build time
Browse files Browse the repository at this point in the history
  • Loading branch information
snicoll committed Sep 28, 2021
1 parent a271618 commit a8201b7
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 3 deletions.
5 changes: 2 additions & 3 deletions spring-aot/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ HELP.md
cache/
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
!**/src/main/**/build/
!**/src/test/**/build/
hs_err_pid*

### STS ###
Expand All @@ -28,7 +28,6 @@ hs_err_pid*
/dist/
/nbdist/
/.nb-gradle/
build/

### VS Code ###
.vscode/
4 changes: 4 additions & 0 deletions spring-aot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader-tools</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.springframework.context.annotation;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* A {@link GenericBeanDefinition} that also provides the {@link AnnotatedBeanDefinition}
* contract using ASM only.
*
* @author Stephane Nicoll
*/
public class AnnotatedMetadataGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {

private final AnnotationMetadata metadata;

public AnnotatedMetadataGenericBeanDefinition(MetadataReader metadataReader) {
Assert.notNull(metadataReader, "MetadataReader must not be null");
this.metadata = metadataReader.getAnnotationMetadata();
setBeanClassName(this.metadata.getClassName());
setResource(metadataReader.getResource());
}

@Override
public final AnnotationMetadata getMetadata() {
return this.metadata;
}

@Override
@Nullable
public MethodMetadata getFactoryMethodMetadata() {
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.springframework.context.build;

import java.io.IOException;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotatedMetadataGenericBeanDefinition;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.classreading.TypeDescriptor;
import org.springframework.core.type.classreading.TypeSystem;

/**
* A prototype of {@link GenericApplicationContext} that is intended to be used at build
* time.
*
* @author Stephane Nicoll
*/
public class BuildTimeBeanFactoryProvider {

private final GenericApplicationContext context;

private final TypeSystem typeSystem;

private final MetadataReaderFactory metadataReaderFactory;

private final BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;

public BuildTimeBeanFactoryProvider(GenericApplicationContext context, TypeSystem typeSystem) {
this.context = context;
this.typeSystem = typeSystem;
this.metadataReaderFactory = new SimpleMetadataReaderFactory();
}

/**
* Register root component classes that are meant to bootstrap the context.
* @param componentClasses the root component classes
*/
public void register(String... componentClasses) {
for (String componentClass : componentClasses) {
TypeDescriptor componentTypeDescriptor = this.typeSystem.resolve(componentClass);
if (componentTypeDescriptor != null) {
register(componentTypeDescriptor);
}
}
}

/**
* Process bean definitions without creating any instance and return the
* {@link ConfigurableListableBeanFactory bean factory}.
* @return the bean factory with the result of the processing
*/
public ConfigurableListableBeanFactory processBeanDefinitions() {
parseConfigurationClasses();
return this.context.getBeanFactory();
}

private void parseConfigurationClasses() {
ConfigurationClassPostProcessor configurationClassPostProcessor = new ConfigurationClassPostProcessor();
configurationClassPostProcessor.setApplicationStartup(this.context.getApplicationStartup());
configurationClassPostProcessor.setBeanClassLoader(this.context.getClassLoader());
configurationClassPostProcessor.setEnvironment(this.context.getEnvironment());
configurationClassPostProcessor.setResourceLoader(this.context);
configurationClassPostProcessor.postProcessBeanFactory(this.context.getBeanFactory());
}

private void register(TypeDescriptor typeDescriptor) {
MetadataReader metadataReader = getMetadataReader(typeDescriptor);
AnnotatedBeanDefinition beanDefinition = new AnnotatedMetadataGenericBeanDefinition(metadataReader);
String beanName = this.beanNameGenerator.generateBeanName(beanDefinition, this.context);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.context);
}

private MetadataReader getMetadataReader(TypeDescriptor typeDescriptor) {
try {
return this.metadataReaderFactory.getMetadataReader(typeDescriptor.getTypeName());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(String.format("Failure while reading metadata for %s.", typeDescriptor), ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.springframework.context.build;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.classreading.TypeSystem;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link BuildTimeBeanFactoryProvider}.
*
* @author Stephane Nicoll
*/
class BuildTimeBeanFactoryProviderTests {

private static final String CONFIGURATION_ONE = "org.springframework.context.build.samples.simple.ConfigurationOne";

private static final String CONFIGURATION_TWO = "org.springframework.context.build.samples.simple.ConfigurationTwo";

private final TypeSystem typeSystem = TypeSystem.getTypeSystem(new DefaultResourceLoader());

@Test
void refreshWithRegisteredConfigurationClasses() {
BuildTimeBeanFactoryProvider beanFactoryProvider = forApplicationContext(new GenericApplicationContext());
beanFactoryProvider.register(CONFIGURATION_ONE, CONFIGURATION_TWO);
ConfigurableListableBeanFactory beanFactory = beanFactoryProvider.processBeanDefinitions();
assertThat(beanFactory.getBeanDefinitionNames()).containsOnly("configurationOne",
"configurationTwo", "beanOne", "beanTwo");
}

@Test
void refreshWithRegisteredConfigurationClassWithImport() {
BuildTimeBeanFactoryProvider context = forApplicationContext(new GenericApplicationContext());
context.register("org.springframework.context.build.samples.compose.ImportConfiguration");
ConfigurableListableBeanFactory beanFactory = context.processBeanDefinitions();
assertThat(beanFactory.getBeanDefinitionNames()).containsOnly("importConfiguration",
CONFIGURATION_ONE, CONFIGURATION_TWO, "beanOne", "beanTwo");
}

private BuildTimeBeanFactoryProvider forApplicationContext(GenericApplicationContext context) {
return new BuildTimeBeanFactoryProvider(context, this.typeSystem);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.springframework.context.build.samples.compose;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.build.samples.simple.ConfigurationOne;
import org.springframework.context.build.samples.simple.ConfigurationTwo;

@Configuration(proxyBeanMethods = false)
@Import({ ConfigurationOne.class, ConfigurationTwo.class })
public class ImportConfiguration {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.springframework.context.build.samples.simple;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ConfigurationOne {

@Bean
String beanOne() {
return "one";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.springframework.context.build.samples.simple;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ConfigurationTwo {

@Bean
String beanTwo() {
return "two";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.springframework.context.build.samples.simple;

import org.springframework.stereotype.Component;

@Component
public class SimpleComponent {

}

0 comments on commit a8201b7

Please sign in to comment.