๐Ÿชž 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.

Class<?> clazz = PaymentService.class;
Class<?> clazz2 = Class.forName("com.app.PaymentService");

๐Ÿ” 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

Constructor<?> ctor = clazz.getDeclaredConstructor();
Object instance = ctor.newInstance();

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

@Autowired
private AuditService audit;

Spring finds a matching bean and executes:

Field f = PaymentService.class.getDeclaredField("audit");
f.setAccessible(true);
f.set(paymentServiceInstance, auditInstance);

Constructor Injection

@Autowired
public PaymentService(AuditService audit) {
    this.audit = audit;
}

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

@Autowired
public void setAuditService(AuditService audit) {
    this.audit = audit;
}

Spring reflectively calls:

Method m = clazz.getDeclaredMethod("setAuditService", AuditService.class);
m.invoke(instance, auditBean);

๐Ÿงฎ 8. Dependency Resolution Rules

When resolving an @Autowired dependency:

  1. By Type โ€” find one matching bean in the ApplicationContext.
  2. If multiple found:

  3. Prefer @Primary

  4. Or match name with field/parameter
  5. Or use @Qualifier("beanName")
  6. If none found:

  7. Throw NoSuchBeanDefinitionException

  8. 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


๐Ÿชž Core Takeaway

Reflection gives Spring its eyes; @Autowired gives 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.