Spring Framework Core Components Deep Dive

Understanding Spring's internal architecture begins with its foundational abstractions. This article explores the essential interfaces and classes that form the backbone of Spring’s IoC container, clarifying their responsibilities, relationships, and practical usage patterns — without relying on high-level abstractions like @SpringBootApplication.

BeanDefinition: The Blueprint for Beans

A BeanDefinition encapsulates metadata required to instantiate, configure, and manage a bean. Its not the bean itself, but rather a configuration contract containing:

  • beanClass: Target class type (e.g., UserService.class)
  • scope: Lifecycle scope (singleton, prototype, etc.)
  • lazyInit: Whether instantiation should be deferred until first access
  • dependsOn: Names of beans that must be initialized before this one
  • initMethodName / destroyMethodName: Lifecycle callback method names

Both declarative definitions (e.g., @Component, @Bean) and programmatic registration ultimately produce BeanDefinition instances. For example:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
AbstractBeanDefinition def = BeanDefinitionBuilder
    .genericBeanDefinition(UserService.class)
    .getBeanDefinition();
def.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
ctx.registerBeanDefinition("userSvc", def);

BeanDefinitionReader: Configuration Loader Abstraction

This interface standardizes how external configuration sources are parsed in to BeanDefinitions and registered in a BeanDefinitionRegistry. Though rarely used directly by application code, it underpins all configuration loading mechanisms.

XmlBeanDefinitionReader

Loads bean definitions from XML resources:

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
int loaded = reader.loadBeanDefinitions("classpath:spring-config.xml");

AnnotatedBeanDefinitionReader

Registers classes annotated with stereotype annotations or @Bean-producing methods programmatically:

AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(ctx);
reader.register(UserService.class, OrderService.class);

It processes annotations such as @Scope, @Lazy, @Primary, and @Conditional, enabling conditional registration logic.

ClassPathBeanDefinitionScanner

Performs classpath scanning to detect and register candidate components:

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(ctx);
scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));
scanner.scan("com.example.app");

Filters support custom matching rules beyond built-in annotations like @Component, @Named, or @ManagedBean.

BeanFactory: Core Container Interface

The BeanFactory interface defines the minimal contract for a factory capable of managing named objects (beans), including:

  • getBean(String name) and getBean(Class<T> type)
  • containsBean(String name)
  • isSingleton(String name), isPrototype(String name)
  • getType(String name), getAliases(String name)

It serves as the foundation for all Spring-managed object resolution — no dependency injection or lifecycle management happens outside this contract.

DefaultListableBeanFactory: The Default Implementation

This concrete implementation provides full support for:

  • Registration and retrieval of BeanDefinitions via an internal ConcurrentHashMap<String, BeanDefinition>
  • Dependency resolution using doResolveDependency()
  • Autowiring candidate selection (e.g., honoring @Primary)
  • Eager singleton instantiation via preInstantiateSingletons()

It implements multiple interfaces (ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory), making it central to most non-web Spring contexts.

AbstractBeanFactory & AbstractAutowireCapableBeanFactory

AbstractBeanFactory introduces the core doGetBean() algorithm — handling scope resolution, prototype creation, and circular reference detection. Its subclass AbstractAutowireCapableBeanFactory adds full instantiation and initialization logic:

  • createBeanInstance(): Instantiates via constructor inference or factory method
  • populateBean(): Injects dependencies (by name or type)
  • initializeBean(): Invokes InitializingBean, @PostConstruct, and custom init methods
  • applyBeanPostProcessorsBeforeInitialization() and AfterInitialization()

These layers separate concerns: resolution → instantiation → population → initialization → post-processing.

HierarchicalBeanFactory

This interface enables nested container hierarchies. A child BeanFactory can delegate unresolved bean lookups to its parent, allowing shared infrastructure beans (e.g., data sources) while isolating application-specific beans. Merged bean definitions rely on this delegation to resolve overridden properties.

ApplicationContext: Enhanced BeanFactory

An ApplicationContext extends BeanFactory with enterprise features:

  • Event publishing (ApplicationEventPublisher)
  • Internationalization (MessageSource)
  • Resource loading (ResourcePatternResolver)
  • Environment-aware configuration (EnvironmentCapable)

Concrete implementations include:

  • AnnotationConfigApplicationContext: Bootstraps from Java config classes and performs component scanning
  • ClassPathXmlApplicationContext: Loads XML-based configurations

Extension Points: Post-Processors and Factories

BeanPostProcessor

Allows interception of bean initialization phases. Multiple processors are ordered and executed before/after initialization:

public class LoggingBeanPostProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String name) {
    System.out.println("Initializing: " + name);
    return bean;
  }
}

BeanFactoryPostProcessor

Modifies the BeanFactory itself before any beans are instantiated — commonly used by frameworks to alter bean definitions globally (e.g., PropertySourcesPlaceholderConfigurer):

public class CustomBeanFactoryPP implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
    // Modify bean definitions here
  }
}

FactoryBean

Provides fine-grained control over bean creation. Unlike regular beans, FactoryBean instances are managed by Spring but produce arbitrary objects via getObject():

@Component
public class DataSourceFactoryBean implements FactoryBean<DataSource> {
  @Override
  public DataSource getObject() { return buildCustomDataSource(); }
  @Override
  public Class<?> getObjectType() { return DataSource.class; }
}

Note: Objects returned by FactoryBean.getObject() bypass standard Spring lifecycle callbacks and dependency injection unless explicitly applied.

Metadata Reading with ASM

To avoid expensive classloading during startup, Spring uses MetadataReader (backed by ASM) to read class and annotation metadata directly from bytecode:

MetadataReaderFactory factory = new SimpleMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader("com.example.service.UserService");
System.out.println(reader.getClassMetadata().getClassName());
System.out.println(reader.getAnnotationMetadata().getAnnotationTypes());

This mechanism powers annotation-based configuration without triggering full class initialization.

Tags: spring-framework bean-definition beanfactory ASM metadata-reader

Posted on Thu, 05 Nov 2026 16:46:11 +0000 by gj6kings