As you already know, Spring IoC provides a great way to manage beans declared with the help of the @Bean annotation. These beans are initialized at startup and automatically wired to other beans when needed. However, this is not the only way to declare container-managed objects in a Spring application. In many cases, you will adopt a component-based approach.
Components
In Spring, a component is a class managed by Spring IoC. Marked with the @Component annotation (from org.springframework.stereotype), these classes become eligible for automatic detection and dependency injection.
Spring also offers specialized stereotype annotations, each clearly indicating the role of the component:
@Service: Indicates that the class holds business logic.
@Repository: Denotes that the class is responsible for data access and may provide exception translation.
@Controller / @RestController: Marks classes that handle web requests and responses.
These annotations internally use @Component, allowing Spring to autodetect them during component scanning.
Here's a simple PasswordGenerator component example:
Java
import org.springframework.stereotype.Component;
import java.util.Random;
@Component
public class PasswordGenerator {
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
private static final Random random = new Random();
public String generate(int length) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = random.nextInt(CHARACTERS.length());
result.append(CHARACTERS.charAt(index));
}
return result.toString();
}
}Kotlin
import org.springframework.stereotype.Component
import java.util.Random
@Component
class PasswordGenerator {
companion object {
private const val CHARACTERS = "abcdefghijklmnopqrstuvwxyz"
private val random = Random()
}
fun generate(length: Int): String {
val result = StringBuilder()
for (i in 0 until length) {
val index: Int = random.nextInt(CHARACTERS.length)
result.append(CHARACTERS[index])
}
return result.toString()
}
}An object of this class needs no special initialization and can be created via the default constructor. When Spring Boot starts an application, it looks for all classes annotated with @Component (or its specialized stereotypes) and creates corresponding managed beans.
There's no need to worry if you haven't gotten a chance to use the Random or StringBuilder classes yet. All you need to know is that the first class can help us get random numbers, and the second creates a string by appending new elements.
Interacting with the command line
Before moving on, let's look at one facility of the Spring Framework and learn a bit more about components. To simplify the following explanation, we will declare a special component allowing us to interact with the standard I/O.
To achieve this, the component should implement the CommandLineRunner interface and override the run method. It is just an equivalent of the main method in console applications. You can write any piece of code there, and it will be executed once the Spring application starts.
Java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class Runner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Hello, Spring!");
}
}Kotlin
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class Runner : CommandLineRunner {
override fun run(vararg args: String) {
println("Hello, Spring!")
}
}The Spring framework will automatically invoke the run method. If you start an application that contains this component, you will see the result in the log:
Hello, Spring!You don't need to use CommandLineRunner in every Spring application, but this component can be used as a temporary solution when debugging or studying new features of the framework.
Autowiring components
Components managed by Spring can be injected into each other using the @Autowired annotation. This dependency injection mechanism works similarly for both components and beans defined via @Bean-annotated methods.
Consider a modified version of the Runner component that uses the PasswordGenerator component:
Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class Runner implements CommandLineRunner {
private final PasswordGenerator generator;
@Autowired
public Runner(PasswordGenerator generator) {
this.generator = generator;
}
@Override
public void run(String... args) {
System.out.println("A short password: " + generator.generate(5));
System.out.println("A long password: " + generator.generate(10));
}
}Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class Runner @Autowired constructor(private val generator: PasswordGenerator) : CommandLineRunner {
override fun run(vararg args: String) {
println("A short password: " + generator.generate(5))
println("A long password: " + generator.generate(10))
}
}Here we use the @Autowired annotation to tell Spring Boot that we need a PasswordGenerator object from the container. If you start this application, you will see output similar to the following:
A short password: bqtik
A long password: tjgdpswzbdThat's it! We ensured an object was put in the container with a @Component annotation and then took it to another object with @Autowired above its constructor.
It is important to know that a bean created with @Component is a singleton by default. If you declare another component and inject PasswordGenerator there, it will be the same object, not a copy. This default behavior can be modified. You will learn how to do so in the following lessons.
Spring has an important restriction: you cannot declare circular dependencies between beans (including components). If you do so nonetheless, your application will not start, and you will get an error: The dependencies of some of the beans in the application context form a cycle.
Where to put the @Autowired annotation
The @Autowired annotation can be used in several ways:
1. Constructor Injection:
Java
@Component
public class Runner implements CommandLineRunner {
private final PasswordGenerator generator;
@Autowired
public Runner(PasswordGenerator generator) {
this.generator = generator;
}
// run method
}Kotlin
@Component
class Runner(@Autowired private val generator: PasswordGenerator) : CommandLineRunner {
// run method
}2. Field Injection:
Java
@Component
public class Runner implements CommandLineRunner {
@Autowired
private PasswordGenerator generator;
// run method
}Kotlin
@Component
class Runner : CommandLineRunner {
@Autowired
private lateinit var generator: PasswordGenerator
// run method
}3. Omitting @Autowired on the Constructor:
Java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class Runner implements CommandLineRunner {
private final PasswordGenerator generator;
// No @Autowired needed here since there's only one constructor.
public Runner(PasswordGenerator generator) {
this.generator = generator;
}
@Override
public void run(String... args) {
System.out.println("A short password: " + generator.generate(5));
System.out.println("A long password: " + generator.generate(10));
}
}Kotlin
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class Runner(private val generator: PasswordGenerator) : CommandLineRunner {
override fun run(vararg args: String) {
println("A short password: " + generator.generate(5))
println("A long password: " + generator.generate(10))
}
}If a component has only one constructor, Spring can automatically inject the dependencies without explicitly annotating the constructor.
While field injection may be simpler, constructor injection is generally recommended as it clearly defines the dependencies, improves thread safety, and simplifies testing.
Unlike beans, you can use @Autowired annotation in multiple places for @Component classes, including constructor parameters, fields, setter methods, configuration methods annotated with @Bean.
When you use constructor injection, the @Autowired annotation can be omitted, but it is required when you use field injection. Otherwise, your fields will be null. We will continue explicitly using the annotation to make the learning process easier.
Conclusion
Components are a powerful feature in Spring that allows you to build well-decomposed, flexible applications. By marking a class with @Component (or one of its specialized stereotypes such as @Service, @Repository, or @Controller), you let Spring IoC manage its lifecycle and dependencies. This component-based approach not only enhances clarity and decoupling within your codebase but also simplifies configuration and testing. Whether you are generating passwords with a simple component like PasswordGenerator or interacting with the command line via CommandLineRunner, Spring’s dependency injection ensures that your components work together seamlessly.
As a bonus, we developed a small but useful Spring Boot application that generates random passwords using only two components. One of those components implemented the CommandLineRunner interface to be able to interact with the standard I/O. We hope this component-based approach will encourage you to build flexible and well-decomposed applications in the future!