Blog Spring Boot

Demystifying @Autowired in Spring: More Than Just Dependency Injection

In the world of Spring, @Autowired is a superstar annotation that most developers use daily. But how much do we really understand it? Is it just a magical keyword that makes dependencies appear, or is there more to the story? Today, we’ll peel back the layers of @Autowired to truly grasp its power, versatility, and the Spring philosophy it embodies.

What is @Autowired, Really?

At its core, @Autowired is a feature in Spring that allows the framework to automatically inject dependencies into a Spring bean. But to see it as merely “dependency injection” is to miss the forest for the trees.

When I first started with Spring, someone told me, “@Autowired uses Dependency Injection.” When I asked for more details, there was often an awkward silence. It’s a correct statement, but it doesn’t capture the essence or the “why” behind @Autowired.

In simpler terms, Spring is telling us: “Hey developers, don’t think about creating objects. I’ll provide objects to you, so you can focus more on your core logic.” It’s like having a personal assistant who knows exactly what tools you need for a task and hands them to you without being asked.

But this raises a question: How does Spring know which objects a developer needs and in which class? This is where another annotation comes into play: @Component.

The Dance of @Component and @Autowired

Let’s move away from abstract cars and engines and dive into a real-world example. Imagine we’re building a COVID-19 data dashboard. We need to fetch data from a third-party API and then process it.

Java
@Component
public class CovidDataHelper {
    private final RestTemplate restTemplate;

    @Autowired
    public CovidDataHelper(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public CovidData getCovidData(String country) {
        String url = "https://api.covid19api.com/total/country/" + country;
        return restTemplate.getForObject(url, CovidData.class);
    }

    public ProcessedCovidData processCovidData(CovidData raw) {
        // Logic to calculate moving averages, growth rates, etc.
        return new ProcessedCovidData(raw);
    }
}

@RestController
public class DashboardController {
    @Autowired
    private CovidDataHelper helper;

    @GetMapping("/covid/{country}")
    public ProcessedCovidData getCountryData(@PathVariable String country) {
        CovidData raw = helper.getCovidData(country);
        return helper.processCovidData(raw);
    }
}

When we annotate our CovidDataHelper class with @Component, we’re not just tagging it. We’re telling Spring, “This class is special. When the application starts, create an object (or ‘bean’) for this class and keep it in your container.”

Now, in our DashboardController, there’s a dependency on CovidDataHelper. But how does the controller’s bean know that there’s a CovidDataHelper bean waiting in the container? That’s where @Autowired shines. When we write @Autowired above the helper variable, it’s like saying, “Spring, there’s a CovidDataHelper bean out there. Find it, and inject it here.” Spring then searches its container, finds the matching bean, and initializes the helper variable. Just like that, it’s ready to use.

Ref: https://medium.com/javarevisited/autowired-in-spring-boot-58fc8a598449

The Many Faces of @Autowired

@Autowired is versatile. It can be applied in three main ways:

  1. Field Injection:
Java
   @Component
   public class WeatherService {
       @Autowired
       private WeatherApiClient client;
   }

Quick and looks clean, but it’s harder to test and makes dependencies less obvious.

  1. Setter Injection:
Java
   @Component
   public class WeatherService {
       private WeatherApiClient client;

       @Autowired
       public void setClient(WeatherApiClient client) {
           this.client = client;
       }
   }

More flexible, as you can call the setter anytime. But it can lead to a partially constructed object if you use the dependency before setting it.

  1. Constructor Injection (The Star):
Java
   @Component
   public class WeatherService {
       private final WeatherApiClient client;

       @Autowired // Optional in Spring 4.3+
       public WeatherService(WeatherApiClient client) {
           this.client = client;
       }
   }

This is the gold standard. Why?

  • Immutability: The client is final, ensuring it’s set only once.
  • NullPointerException Prevention: The object is fully initialized after construction.
  • Clear Dependencies: By looking at the constructor, you instantly know what this class needs.
  • Easy Testing: In unit tests, you can simply new WeatherService(mockClient) without Spring.

The Great Debate: Autowiring vs. Constructor Injection

While constructor injection uses @Autowired, some argue it’s not really “autowiring.” After all, you’re explicitly listing dependencies in the constructor. So, let’s clarify:

  • Pure Autowiring (Field & Setter):
  • Pros: Concise, less code, dependencies injected magically.
  • Cons: Can lead to unclear dependencies, harder to test, potential for null values.
  • Constructor Injection:
  • Pros: Clear dependencies, immutability, easy testing, no null surprises.
  • Cons: More verbose, might seem redundant with @Autowired in newer Spring versions.

When to Use What:

  • Use constructor injection for mandatory, immutable dependencies.
  • Consider field autowiring for optional dependencies or in very simple scenarios.
  • Rarely use setter autowiring, mostly when dependencies must be swappable.

The Magic Behind @Autowired

  1. Component Scanning: When you start your Spring application, it scans the classpath for classes annotated with @Component (or its specializations like @Service, @Repository). It creates a bean for each.
  2. Dependency Resolution: For each bean, Spring looks at its fields, constructors, and setter methods marked with @Autowired.
  3. Type Matching: By default, Spring tries to find a bean whose type matches the dependency. If WeatherService needs a WeatherApiClient, Spring searches for a WeatherApiClient bean.
  4. Name Matching (Fallback): If multiple beans match by type, Spring looks at the property name. If you have @Autowired WeatherApiClient primaryClient, it’ll look for a bean named primaryClient.
  5. @Qualifier (Tiebreaker): If name matching doesn’t help, or you want to be explicit, use @Qualifier("mainWeatherClient") to specify exactly which bean you want.

Why Use @Autowired? The Spring Philosophy

  1. Focus on Business Logic: By saying, “Spring, handle object creation,” developers can focus more on what their code does rather than how objects are instantiated.
  2. Loose Coupling: Classes aren’t tightly bound to each other. Your WeatherService doesn’t need to know how WeatherApiClient is created.
  3. Single Responsibility: Each class focuses on its main task. The intricacies of object lifecycle and wiring are Spring’s responsibility.
  4. Easier Testing: With dependencies injected, it’s trivial to swap in mocks or stubs during tests.
  5. Consistency: With Spring managing instantiation, you ensure consistent object creation across your application.
  6. Readability: Annotations like @Autowired make dependencies explicit, improving code readability.
  7. DRY Principle: Instead of writing object instantiation code everywhere, you define it once in Spring’s configuration.

A Word on “Properties”

In our MySQLConfig, we use:

@ConfigurationProperties("spring.datasource.mysql")

This is Spring Boot’s way of saying, “Hey, I expect properties like spring.datasource.mysql.url in your configuration.” It’s part of Boot’s convention-over-configuration philosophy, making setups simpler and more standardized.

Conclusion: The Heart of Spring

@Autowired isn’t just a tool for dependency injection—it’s a reflection of Spring’s core philosophy. By taking over object creation and wiring, Spring encourages developers to think at a higher level. It’s about focusing on what your objects do, not how they come into being.

In our COVID-19 dashboard example, we didn’t worry about how RestTemplate is created or how CovidDataHelper is instantiated. We simply expressed our needs (@Component, @Autowired), and Spring, like a dutiful assistant, ensured everything was in place. This lets us concentrate on fetching data, processing it, and serving our users—the things that truly matter.

Whether you lean towards the magic of field autowiring or the clarity of constructor injection, the choice is yours. Spring’s flexibility supports various coding styles while always championing clean, maintainable, and modular code. So the next time you type @Autowired, remember: you’re not just injecting a dependency; you’re embracing a philosophy that has reshaped Java development.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over