How Does Spring’s @Autowired Really Work? A Deep Dive into Its Implementation

This article provides a comprehensive analysis of Spring's @Autowired annotation, covering its usage, the bean lifecycle stages where it operates, the core classes and methods that implement it, and a comparison with other injection annotations such as @Resource, all illustrated with runnable code examples.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
How Does Spring’s @Autowired Really Work? A Deep Dive into Its Implementation

Overview of @Autowired

The @Autowired annotation is the primary way to express dependency injection (DI) in Spring. It can be placed on constructors, fields, methods, parameters, and even on custom annotations (meta‑annotations). By default the dependency is required; setting required=false suppresses the NoSuchBeanDefinitionException that is thrown when no matching bean is found.

Typical Usage

Bean definition

public class BeanConfiguration {
    @Bean
    public User user() {
        return new User("markus", 24);
    }
}

Demo class

package com.example.spring.injection;

import com.example.spring.bean.User;
import com.example.spring.configuration.BeanConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Collection;
import java.util.Map;

@Import({BeanConfiguration.class})
public class AutowiredDemo {

    @Autowired
    private User user;

    @Autowired
    private Map<String, User> userMap;

    @Autowired
    private Collection<User> userCollection;

    private User userFromCtor;

    public AutowiredDemo(@Autowired User user) {
        this.userFromCtor = user;
    }

    private User userFromMethod;

    @Autowired
    public void setUser(User user) {
        this.userFromMethod = user;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AutowiredDemo.class);
        ctx.refresh();
        AutowiredDemo demo = ctx.getBean(AutowiredDemo.class);
        System.out.println("user: " + demo.user);
        System.out.println("userMap: " + demo.userMap);
        System.out.println("userCollection: " + demo.userCollection);
        System.out.println("userFromCtor: " + demo.userFromCtor);
        System.out.println("userFromMethod: " + demo.userFromMethod);
        ctx.close();
    }
}

Qualifying beans when multiple candidates exist

public class SameTypeConfig {
    @Bean
    public User user1() { return new User("markus", 24); }

    @Bean
    public User user2() { return new User("luna", 23); }
}

@Import({SameTypeConfig.class})
public class QualifierDemo {
    @Autowired
    @Qualifier("user1")
    private User user;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(QualifierDemo.class);
        ctx.refresh();
        QualifierDemo demo = ctx.getBean(QualifierDemo.class);
        System.out.println("qualified user: " + demo.user);
        ctx.close();
    }
}

Internal Mechanics of @Autowired

Bean lifecycle hook

During bean creation Spring executes the following simplified steps:

Register bean definition.

Instantiate the bean.

Populate properties – this is where @Autowired is processed.

Initialize bean (post‑processors, init methods).

Destroy bean on container shutdown.

The injection occurs in the *populate* phase.

Key classes

AbstractAutowiredCapableBeanFactory
AutowiredAnnotationBeanPostProcessor

Bean creation flow

The factory method doCreateBean orchestrates the process:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
        }
    }
    try {
        populateBean(beanName, mbd, instanceWrapper);
    } catch (Throwable ex) {
        // error handling omitted
    }
    return exposedObject;
}
applyMergedBeanDefinitionPostProcessors

iterates over all BeanPostProcessor instances, invoking those that implement MergedBeanDefinitionPostProcessor. The AutowiredAnnotationBeanPostProcessor builds injection metadata at this point. populateBean performs property injection and invokes the postProcessProperties method of each InstantiationAwareBeanPostProcessor. This is where the actual @Autowired injection happens.

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                if (!((InstantiationAwareBeanPostProcessor) bp)
                        .postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }
    }
    PropertyValues pvs = mbd.getPropertyValues();
    // invoke AutowiredAnnotationBeanPostProcessor#postProcessProperties
    // further property handling omitted for brevity
}

AutowiredAnnotationBeanPostProcessor

This post‑processor implements both InstantiationAwareBeanPostProcessor and MergedBeanDefinitionPostProcessor. It creates an InjectionMetadata object during the merged‑definition phase and later uses it to inject dependencies.

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDef, Class<?> beanType, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDef);
}

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

InjectionMetadata structure

InjectionMetadata

holds a list of InjectedElement objects – one for each field or method annotated with @Autowired. Its inject method simply iterates over these elements.

public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
    for (InjectedElement element : injectedElements) {
        element.inject(target, beanName, pvs);
    }
}

The two concrete subclasses are: AutowiredFieldElement – injects a bean directly into a field via reflection. AutowiredMethodElement – resolves method parameters and invokes the method.

// field injection
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value = getResourceToInject(bean, beanName);
    ReflectionUtils.makeAccessible(field);
    field.set(bean, value);
}

// method injection
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    Method method = (Method) this.member;
    Object[] args = resolveArguments(bean, beanName);
    ReflectionUtils.makeAccessible(method);
    method.invoke(bean, args);
}

Resolving a dependency – DefaultListableBeanFactory#resolveDependency

The actual bean lookup is performed by DefaultListableBeanFactory.resolveDependency. It handles special cases ( Optional, ObjectFactory, JSR‑330 @Inject) and then delegates to doResolveDependency for the standard resolution logic.

public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName,
                               Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
    // handle Optional, ObjectFactory, JSR‑330 …
    Object result = getAutowireCandidateResolver()
            .getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
    if (result == null) {
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }
    return result;
}

protected Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
                                    Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
    // 1. @Value placeholder handling (omitted for brevity)
    // 2. Resolve collections, maps, arrays when the injection point expects multiple beans
    // 3. Find candidate beans of the required type
    // 4. If multiple candidates exist, apply @Primary or @Qualifier rules to select a single bean
    // 5. Convert the resolved bean to the required type and return it
    // (implementation details are in the source code; the steps are listed here for clarity)
}

Both AutowiredFieldElement and AutowiredMethodElement ultimately call beanFactory.resolveDependency to obtain the object that will be injected.

Other injection annotations

Spring also supports @Resource (JSR‑250) and @Value. @Resource combines name‑based lookup with a type‑based fallback, which differs from @Autowired that prefers type‑first lookup.

@Resource example

package com.example.spring.injection;

import com.example.spring.bean.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;

@Import({SameTypeConfig.class})
public class ResourceDemo {

    @Resource
    private List<User> users;

    @Resource
    private User user;

    @Resource(name = "user2")
    private User userFromName;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(ResourceDemo.class);
        ctx.refresh();
        ResourceDemo demo = ctx.getBean(ResourceDemo.class);
        System.out.println("users: " + demo.users);
        System.out.println("user: " + demo.user);
        System.out.println("userFromName: " + demo.userFromName);
        ctx.close();
    }
}

Key differences: @Resource first attempts a name‑based lookup; if no bean with that name exists it falls back to type‑based lookup. @Autowired always performs type‑first lookup and uses @Qualifier only when multiple candidates are found.

Both annotations are processed during the same property‑population stage.

Summary

The @Autowired annotation drives dependency injection during Spring's bean population phase. The process can be traced as follows:

Spring creates a bean via AbstractAutowiredCapableBeanFactory#doCreateBean.

During merged‑definition processing, AutowiredAnnotationBeanPostProcessor builds InjectionMetadata for all @Autowired points.

When populateBean runs, the post‑processor's postProcessProperties method retrieves the metadata and invokes its inject method.

Each InjectedElement (field or method) calls DefaultListableBeanFactory#resolveDependency to obtain the required bean, applying @Primary, @Qualifier, and collection handling as needed.

The resolved bean is finally injected via reflection.

Understanding these steps helps developers diagnose injection problems, customize bean post‑processors, or implement their own DI annotations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaspringdependency-injectionAutowiredAnnotation Processingbean-lifecycle
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.