As you already know, Spring IoC provides a great way to manage beans declared with the help of the @Bean annotation. Spring initializes these beans at startup and automatically wires them 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.
As with beans, components are singletons by default — the same PasswordGenerator instance is shared everywhere it's injected.
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 placed in a few different spots, depending on how you write the component.
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
}@Autowired can also go on a constructor (as shown earlier in this topic) or on a setter method. If a component has only one constructor, Spring injects its dependencies automatically — no annotation needed there.
We'll compare all these approaches — and see why constructor injection is generally preferred — in detail in the Dependency injection in Spring topic.
Conclusion
Components are a powerful feature in Spring that lets you 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 generate passwords with a simple component like PasswordGenerator or interact 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 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!