A minimal Spring Framework implementation for learning purposes, built with Java 8 and Maven.
spring-simple reproduces the core internals of Spring Framework from scratch, including IoC container, Bean lifecycle, circular dependency resolution (three-level cache), AOP dynamic proxy, and declarative transaction management. The module structure mirrors the official Spring project.
55 tests, all passing.
spring-simple/
├── spring-core/ # Utilities: Resource loading, ReflectionUtils, ClassUtils, StringUtils, Assert
├── spring-beans/ # IoC core: BeanFactory hierarchy, BeanDefinition, three-level cache, lifecycle
├── spring-context/ # ApplicationContext, @ComponentScan, annotation-driven, event mechanism
├── spring-aop/ # JDK dynamic proxy, CGLIB proxy, AspectJ expression pointcut, interceptor chain
├── spring-tx/ # @Transactional, DataSourceTransactionManager, transaction propagation
└── spring-test/ # Integration tests
spring-core
└── spring-beans
└── spring-context
└── spring-aop
└── spring-tx
Full BeanFactory hierarchy — DefaultListableBeanFactory, AbstractAutowireCapableBeanFactory, AbstractBeanFactory — with annotation-driven configuration via AnnotationConfigApplicationContext.
@Configuration
@ComponentScan("com.example")
public class AppConfig {}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
UserService svc = ctx.getBean(UserService.class);Supported annotations: @Component, @Service, @Repository, @Controller, @Configuration, @Bean, @Autowired, @Qualifier, @Value, @Scope, @Lazy.
Full lifecycle pipeline:
Instantiation
→ populateBean (@Autowired / PropertyValues)
→ Aware callbacks (BeanNameAware, BeanFactoryAware, ApplicationContextAware)
→ BeanPostProcessor#postProcessBeforeInitialization
→ InitializingBean#afterPropertiesSet / init-method
→ BeanPostProcessor#postProcessAfterInitialization ← AOP proxy created here
→ [Bean ready in singletonObjects]
→ DisposableBean#destroy / destroy-method ← on container close
Setter/field injection circular dependencies are resolved transparently.
| Cache | Type | Purpose |
|---|---|---|
singletonObjects |
Map<String, Object> |
Fully initialized singletons |
earlySingletonObjects |
Map<String, Object> |
Early-exposed references (may be AOP proxy) |
singletonFactories |
Map<String, ObjectFactory<?>> |
Factory lambdas — allows proxy creation on first early access |
The third level exists specifically for AOP: when bean A is circularly referenced, ObjectFactory.getObject() is invoked to potentially wrap A in a proxy before the full initialization completes, ensuring all references point to the same proxy instance.
// A depends on B, B depends on A — works out of the box
@Service
public class ServiceA {
@Autowired private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired private ServiceA serviceA;
}ProxyFactory automatically picks the proxy strategy:
| Condition | Strategy |
|---|---|
| Target class implements interface(s) | JDK dynamic proxy (java.lang.reflect.Proxy) |
Target class has no interface / proxyTargetClass=true |
CGLIB bytecode proxy |
Advice types supported: @Before, @After, @AfterReturning, @AfterThrowing, @Around.
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void before(JoinPoint jp) {
System.out.println("Before: " + jp.getSignature());
}
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
System.out.println("Elapsed: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
}@Transactional is implemented on top of AOP — TransactionInterceptor wraps each annotated method with begin / commit / rollback logic. TransactionSynchronizationManager binds the JDBC Connection to the current thread via ThreadLocal.
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
orderDao.insert(order);
inventoryDao.deduct(order.getSkuId(), order.getQty());
// Any RuntimeException triggers automatic rollback
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String msg) {
// Runs in an independent transaction regardless of the caller's state
auditDao.insert(msg);
}
}Supported propagation behaviors: REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTED.
Requirements: JDK 8+, Maven 3.6+
# Build all modules (skip tests)
mvn clean package -DskipTests -Dsort.skip=true
# Run all tests
mvn clean test -Dsort.skip=true
# Run a specific module's tests
mvn clean test -pl spring-test -Dsort.skip=true
# Run a specific test class
mvn clean test -pl spring-test -Dtest=CircularDependencyTest -Dsort.skip=true| Test Class | Module | What It Covers |
|---|---|---|
IocContainerTest |
spring-test | Bean registration, @Autowired, @Value, prototype scope |
ApplicationContextTest |
spring-test | AnnotationConfigApplicationContext, lifecycle callbacks, events |
CircularDependencyTest |
spring-test | A→B→A circular dependency with and without AOP |
JdkProxyTest |
spring-test | JDK dynamic proxy, @Before / @After / @Around advice |
CglibProxyTest |
spring-test | CGLIB proxy for concrete classes |
TransactionTest |
spring-test | Commit, rollback, REQUIRED, REQUIRES_NEW propagation |
spring-beans/
└── factory/support/
├── DefaultSingletonBeanRegistry.java # Three-level cache
├── AbstractAutowireCapableBeanFactory.java # createBean / populateBean / initializeBean
└── DefaultListableBeanFactory.java # Full BeanFactory implementation
spring-context/
└── support/
├── AbstractApplicationContext.java # refresh() 12-step lifecycle
├── AnnotationConfigApplicationContext.java
└── ClassPathBeanDefinitionScanner.java # @ComponentScan implementation
spring-aop/
└── framework/
├── ProxyFactory.java # JDK vs CGLIB selection
├── JdkDynamicAopProxy.java
├── CglibAopProxy.java
└── ReflectiveMethodInvocation.java # Interceptor chain execution
spring-tx/
└── interceptor/
└── TransactionInterceptor.java # @Transactional AOP interceptor
└── datasource/
└── DataSourceTransactionManager.java # JDBC transaction manager
└── support/
└── TransactionSynchronizationManager.java # ThreadLocal connection binding
MIT