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.
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 AutowiredAnnotationBeanPostProcessorBean 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;
} applyMergedBeanDefinitionPostProcessorsiterates 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
InjectionMetadataholds 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
