1. Overview

In this tutorial, we're going to look at the ways to define dependencies for Spring @Bean methods.

2. Sample Application

Let's start with our sample application.

We have GreetingService and its implementation class, DefaultGreetingService:

public interface GreetingService {

    void greet();
}

public class DefaultGreetingService implements GreetingService {

    private final GreetingProperties greetingProperties;

    public DefaultGreetingService(GreetingProperties greetingProperties) {
        this.greetingProperties = greetingProperties;
    }

    @Override
    public void greet() {
        System.out.println(greetingProperties.getGreeting());
    }
}

Here, DefaultGreetingService depends on the GreetingProperties class:

public class GreetingProperties {

    private final String greeting = "Hi";

    public String getGreeting() {
        return greeting;
    }
}

3. Dependency as Method Parameter

Firstly, we'll examine how we can define dependency as a method parameter on the @Bean method. If the dependency is being used only on one @Bean method, this is the preferable approach:

@Configuration
public class ApplicationConfiguration {

    @Bean
    public DefaultGreetingService defaultGreetingService(GreetingProperties greetingProperties) {
        return new DefaultGreetingService(greetingProperties);
    }

    @Bean
    public GreetingProperties greetingProperties() {
        return new GreetingProperties();
    }
}

Here, we have two bean definitions: one for DefaultGreetingService and one for GreetingProperties. Note that the defaultGreetingService method defines GreetingProperties as a method parameter. As a result, Spring injects the GreetingProperties bean - also defined here - as the method parameter.

4. Dependency as Inter-Bean References

Next, we'll define a dependency using inter-bean references. This approach relies on CGLIB proxying on @Configuration classes. So Spring first checks the application context for an existing bean before creating a new one.

@Configuration
public class ApplicationConfiguration {

    @Bean
    public DefaultGreetingService defaultGreetingService() {
        return new DefaultGreetingService(greetingProperties());
    }

    @Bean
    public GreetingProperties greetingProperties() {
        return new GreetingProperties();
    }
}

Similar to the previous example, we have two bean definitions. But this time, we don't have any parameters on the @Bean methods. The defaultGreetingService method directly calls the greetingProperties method.

5. Dependency as Configuration Variable

Now, we'll define the dependency as an instance variable of the @Configuration class. We can prefer this approach when the dependency isn't defined in the current @Configuration class and multiple @Bean methods require it:

@Configuration
public class OtherApplicationConfiguration {

    @Bean
    public GreetingProperties greetingProperties() {
        return new GreetingProperties();
    }
}

Here, we have the GreetingProperties bean. The GreetingService beans, on the other hand, are defined in another @Configuration class.

@Configuration
@Import(OtherApplicationConfiguration.class)
public class ApplicationConfiguration {
    
    private final GreetingProperties greetingProperties;
    
    public ApplicationConfiguration(GreetingProperties greetingProperties) {
        this.greetingProperties = greetingProperties;
    }
    
    @Bean
    public DefaultGreetingService defaultGreetingService() {
        return new DefaultGreetingService(greetingProperties);
    }
    
    @Bean
    public DefaultGreetingService anotherGreetingService() {
        return new DefaultGreetingService(greetingProperties);
    } 
}

Here, we're declaring two DefaultGreetingService beans and both are using the greetingProperties variable. Since we're declaring the dependency in one place, it is easier to manage. Instead of this, we could have defined GreetingProperties dependency as a method parameter - in defaultGreetingService and anotherGreetingService.

6. Dependency as External Configuration Method

Lastly, we'll directly compose with a @Configuration class in order to acquire a dependency. This approach also benefits from CGLIB proxying.

@Configuration
public class OtherApplicationConfiguration {

    @Bean
    public GreetingProperties greetingProperties() {
        return new GreetingProperties();
    }
}

Here, we're defining the GreetingProperties bean. And in another @Configuration, we have the GreetingService bean:

@Configuration
@Import(OtherApplicationConfiguration.class)
public class ApplicationConfiguration {

    private final OtherApplicationConfiguration otherApplicationConfiguration;

    public ApplicationConfiguration(OtherApplicationConfiguration otherApplicationConfiguration) {
        this.otherApplicationConfiguration = otherApplicationConfiguration;
    }

    @Bean
    public DefaultGreetingService defaultGreetingService() {
        return new DefaultGreetingService(otherApplicationConfiguration.greetingProperties());
    }
}

Here, ApplicationConfiguration declares OtherApplicationConfiguration as an instance variable. Moreover, the @Bean method - defaultGreetingService - gets its dependency by calling the otherApplicationConfiguration.greetingProperties methodNote that this invocation doesn't follow the normal Java semantics. In other words, Spring first checks the application context for a GreetingProperties bean and returns it if one exists. If there is no existing bean, it calls the real method and returns the newly created bean.

7. Summary

In this tutorial, we've looked at how we can define the dependencies of Spring @Bean methods.

As always, the source code for all examples in this tutorial is available on Github.