This project implements an APT plugin that converts @Configuration to functional bean registrations, which are known to be faster and also more amenable to AOT compilation (native image building).

Takes this:

@Configuration
public class SampleConfiguration {

    @Bean
    public Foo foo() {
        return new Foo();
    }

    @Bean
    public Bar bar(Foo foo) {
        return new Bar(foo);
    }

}

and transforms it to this:

public class SampleConfigurationInitializer 
      implements ApplicationContextInitializer<GenericApplicationContext> {

    @Override
    public void initialize(GenericApplicationContext context) {
        context.registerBean( SampleConfiguration.class);
        context.registerBean(Foo.class, () -> context.getBean(SampleConfiguration.class).foo());
        context.registerBean(Bar.class, () -> context.getBean(SampleConfiguration.class).bar(context.getBean(Foo.class)));
    }

}

You then need a Spring application bootstrap utility that recognizes the ApplicationContextInitializer and treats it in a special way. This is provided in the modules of this project, so just add them as dependencies:

        <dependency>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-init-core</artifactId>
            <version>${init.version}</version>
        </dependency>

and set the APT processor up as a compiler plugin:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.springframework.experimental</groupId>
                            <artifactId>spring-init-processor</artifactId>
                            <version>${init.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>

To use functional versions of Spring Boot autoconfiguration you need to include additional dependencies, for example (with generated.version=2.1.1.BUILD-SNAPSHOT for Spring Boot 2.1.1):

        <dependency>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-boot-autoconfigure-func</artifactId>
            <version>${generated.version}</version>
        </dependency>

The convention is that the artifact ID is the same as the Spring Boot dependency, but the group ID is different and the classifier is "func" (to make the jar file names unique). There is also a spring-init-library with some scraps of support for Spring Cloud.

You can use @Import or @ImportAutoConfiguration (more idiomatically) to include existing autoconfigurations in your app. Example (a simple, non-web application):

@SpringBootConfiguration
@ComponentScan
@ImportAutoConfiguration({ ConfigurationPropertiesAutoConfiguration.class,
        PropertyPlaceholderAutoConfiguration.class })
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(SampleApplication.class);
        app.run(args);
    }

}

Look at the samples for code to copy. If you are using Eclipse you will need the M2E APT plugin.

Build and run:

$ ./mvnw package
$ java -jar samples/application/target/*.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v2.1.0.RELEASE)

...
Bar: [email protected]

The command line runner output comes out on stdout after the Spring Boot app has started.

Building

Before you build, run the update.sh script in the "generated" directory. It will clone Spring Boot and all the source code needed to generate initializers from Spring Boot autoconfigurations.

You can then build and run everything from the command line, or from an IDE. If you are using Eclipse, you need to disable the Maven project nature for the "processor" project (or work with it in a different workspace). It only has 2 dependencies, so it's quite easy to manually set up the classpath for that project (then you have to build it on the command line before you refresh the sample apps).

You can debug the APT processor by running on the command line and attaching a debugger, e.g:

(cd modules/tests-lib/; MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" mvn clean compile)

Benchmarks

Benchmark startup time (seconds) with annotations, with Spring Init (slim), and with manual configuration:

class                              method  sample  beans    classes   heap   memory  median  mean   range
com.example.bench.SlimBenchmarkIT  annos   jlog    85.000   4185.000  6.227  43.667  0.724   0.740  0.017
com.example.bench.SlimBenchmarkIT  annos   demo    98.000   4962.000  8.255  50.530  0.837   0.855  0.028
com.example.bench.SlimBenchmarkIT  annos   actr    183.000  5224.000  9.812  53.737  0.986   1.009  0.025
com.example.bench.SlimBenchmarkIT  annos   conf    181.000  5601.000  8.201  55.619  1.179   1.242  0.057
com.example.bench.SlimBenchmarkIT  manual  jlog    56.000   4207.000  6.648  43.372  0.646   0.662  0.023
com.example.bench.SlimBenchmarkIT  manual  demo    56.000   4932.000  7.501  48.560  0.725   0.741  0.024
com.example.bench.SlimBenchmarkIT  manual  actr    103.000  5320.000  8.272  50.886  0.826   0.844  0.016
com.example.bench.SlimBenchmarkIT  manual  conf    74.000   4992.000  7.732  49.243  0.830   0.869  0.033
com.example.bench.SlimBenchmarkIT  slim    jlog    87.000   4416.000  6.334  44.230  0.729   0.737  0.018
com.example.bench.SlimBenchmarkIT  slim    demo    100.000  5207.000  8.482  51.243  0.841   0.855  0.020
com.example.bench.SlimBenchmarkIT  slim    actr    185.000  5734.000  8.515  53.691  1.007   1.022  0.021
com.example.bench.SlimBenchmarkIT  slim    conf    117.000  5290.000  9.215  52.639  0.977   0.993  0.024

It's not obvious that Spring Init is any faster or uses less memory than the straight annotation-based version with Spring Boot 2.3. It might be a better story in native images, but that remains to be seen.

N.B. the "conf" sample is probably not doing what it says on the can, so none of those measurements is reliable at present. The bean counts in the non-annos versions of that benchmark, for instance, suggest that this is not the whole set of functionality that is running with the annotations.