๐ช Reflection Layer โ How Spring Sees and Wires Your Code¶
Essence: The Reflection Layer is the foundation of Spring. Itโs how Spring discovers, creates, and connects your classes at runtime โ turning plain Java code into a self-managing system.
๐ฑ 1. Concept Overview¶
Javaโs Reflection API lets code inspect and manipulate itself at runtime.
Spring builds on that to power @ComponentScan, @Autowired, @Bean, and most of its container magic.
source (.java)
โ compiler
bytecode (.class)
โ ClassLoader
Class<?> object in Metaspace
โ Reflection
Spring inspects, instantiates, and injects beans
๐งฉ 2. Core Reflection API โ Javaโs Runtime Mirror¶
Everything starts with the Class<?> object.
๐ Inspecting a Class¶
clazz.getName(); // "com.app.PaymentService"
clazz.getDeclaredFields(); // all fields
clazz.getDeclaredMethods();
clazz.getSuperclass();
clazz.getAnnotations();
Spring uses this to find annotations like @Component, @Service, or @Bean.
๐ง 3. Creating Objects Reflectively¶
With parameters:
Constructor<?> ctor = clazz.getDeclaredConstructor(AuditService.class);
Object obj = ctor.newInstance(new AuditService());
Thatโs what Spring does when it creates beans from your classes โ it calls constructors reflectively based on metadata from scanning.
โ๏ธ 4. Accessing Private Members¶
Reflection can bypass visibility restrictions.
Field f = clazz.getDeclaredField("audit");
f.setAccessible(true);
f.set(instance, new AuditService());
This is the essence of dependency injection โ Spring sets fields without needing public setters.
๐งฌ 5. Invoking Methods¶
Method m = clazz.getDeclaredMethod("process", String.class);
m.setAccessible(true);
m.invoke(instance, "Alice");
Used internally for:
- Lifecycle hooks (
@PostConstruct,@PreDestroy) - Configuration methods (
@Bean) - Event listeners (
@EventListener)
โก 6. Annotations in Reflection¶
if (clazz.isAnnotationPresent(Service.class)) {
Annotation a = clazz.getAnnotation(Service.class);
}
Field or method level:
Field f = clazz.getDeclaredField("repo");
if (f.isAnnotationPresent(Autowired.class)) {
// inject dependency reflectively
}
Reflection lets Spring read annotation metadata and decide what to do with it.
๐ค 7. @Autowired โ Reflection Meets IoC¶
@Autowired is the declarative interface to reflection.
It tells Spring: โFind me something of this type and assign it here.โ
Field Injection¶
Spring finds a matching bean and executes:
Field f = PaymentService.class.getDeclaredField("audit");
f.setAccessible(true);
f.set(paymentServiceInstance, auditInstance);
Constructor Injection¶
If thereโs only one constructor, @Autowired is optional โ Spring assumes it.
Under the hood:
Constructor<?> ctor = clazz.getDeclaredConstructor(AuditService.class);
Object bean = ctor.newInstance(auditBean);
Setter Injection¶
Spring reflectively calls:
Method m = clazz.getDeclaredMethod("setAuditService", AuditService.class);
m.invoke(instance, auditBean);
๐งฎ 8. Dependency Resolution Rules¶
When resolving an @Autowired dependency:
- By Type โ find one matching bean in the ApplicationContext.
-
If multiple found:
-
Prefer
@Primary - Or match name with field/parameter
- Or use
@Qualifier("beanName") -
If none found:
-
Throw
NoSuchBeanDefinitionException - Unless
@Autowired(required = false)is used
Qualifiers and Primary¶
@Primary
@Service
public class EmailNotifier implements Notifier {}
@Service("smsNotifier")
public class SmsNotifier implements Notifier {}
@Service
public class PaymentService {
@Autowired
@Qualifier("smsNotifier")
private Notifier notifier;
}
Optional & Lazy Variants¶
@Autowired(required = false)
private Optional<AuditService> audit;
@Autowired
@Lazy
private HeavyService heavy; // proxy until first call
๐งญ 9. Internal Flow (Simplified Pseudocode)¶
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Object dependency = context.getBean(field.getType());
field.setAccessible(true);
field.set(beanInstance, dependency);
}
}
Spring does this reflection once during startup โ then caches results for efficiency.
๐งฑ 10. Reflection in Bean Lifecycle¶
Scan classes โ detect annotations โ create Class<?> objects
โ register BeanDefinitions โ construct beans via reflection
โ inject dependencies via reflection
โ invoke @PostConstruct โ ready
Reflection is used heavily during startup โ once the context is ready, execution runs as normal Java code.
โ๏ธ 11. Dynamic Proxies (Advanced Reflection)¶
Spring uses reflection again to generate proxies for beans with AOP features:
MyService proxy = (MyService) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[]{MyService.class},
(proxyObj, method, args) -> {
System.out.println("Before call");
return method.invoke(targetBean, args);
});
Used for:
- Transactions (
@Transactional) - Async execution (
@Async) - Caching (
@Cacheable) - Security (
@PreAuthorize)
๐งฉ 12. Common Pitfalls¶
| Symptom | Likely Cause | Fix |
|---|---|---|
ClassNotFoundException |
Wrong class path | Verify package name |
NoSuchFieldException |
Typo or renamed field | Check reflection target |
NoUniqueBeanDefinitionException |
Multiple beans of same type | Use @Qualifier or @Primary |
Field remains null |
Not in @ComponentScan path |
Fix scan base package |
@Autowired on static field |
Not supported | Avoid static injection |
๐งญ 13. Performance Considerations¶
- Reflection is slower than direct calls, but Spring caches all reflective lookups.
- The reflection cost happens only once โ at startup.
- During runtime, calls go through direct references (or proxies).
๐งฉ 14. Quick Reference Summary¶
| Task | API / Annotation | Used In Spring For |
|---|---|---|
| Discover class metadata | Class<?> |
Component scanning |
| Create instance | Constructor.newInstance() |
Bean instantiation |
| Inject dependency | Field.set() / Method.invoke() |
@Autowired wiring |
| Access annotations | isAnnotationPresent() |
Detect Spring stereotypes |
| Build proxies | Proxy.newProxyInstance() |
AOP wrapping |
| Resolve dependency | @Qualifier, @Primary |
Ambiguity handling |
| Delay creation | @Lazy |
Lazy bean proxies |
๐ Related¶
๐ช Core Takeaway¶
Reflection gives Spring its eyes;
@Autowiredgives it its hands. Together they let the framework see your classes, build your objects, and connect them into a living application โ all before your code ever runs.