1. 从零手搓类Spring框架:深入理解Spring核心原理
作为一名Java开发者,我最初接触Spring框架时,对那些"魔法"般的功能感到无比好奇:为什么一个简单的@Autowired注解就能自动注入依赖?AOP又是如何在不修改业务代码的情况下实现日志和事务的?为了彻底搞懂这些原理,我决定从零开始实现一个简化版的Spring框架——SpringWind。
1.1 SpringWind框架概述
SpringWind是一个教学性质的轻量级Java Web框架,核心代码约4000行,实现了Spring Framework中最关键的功能:
- IoC容器和依赖注入
- 组件扫描和生命周期管理
- AOP切面编程
- MVC请求处理
- JDBC模板
与Spring Framework相比,SpringWind去除了大量企业级特性,保留了最核心的设计思想和实现机制。通过这个项目,开发者可以:
- 深入理解Spring的底层工作原理
- 掌握框架设计的核心模式
- 学习如何解决循环依赖等复杂问题
- 为阅读Spring源码打下坚实基础
提示:SpringWind不是生产级框架,而是学习工具。理解它之后,你会发现自己阅读Spring源码时不再感到迷茫。
2. Spring核心设计理念解析
2.1 控制反转(IoC)与依赖注入(DI)
传统开发方式中,我们通常这样创建和使用对象:
java复制public class UserService {
// 直接new出依赖对象
private UserDao userDao = new UserDaoImpl();
public void saveUser(User user) {
userDao.save(user);
}
}
这种方式存在几个明显问题:
- 紧耦合:UserService直接依赖UserDaoImpl的具体实现
- 难以测试:无法轻松替换为Mock对象进行单元测试
- 管理困难:对象创建逻辑分散在各处
Spring的解决方案是通过IoC容器管理对象的生命周期,使用DI实现对象间的解耦:
java复制@Service
public class UserService {
@Autowired // 由容器注入
private UserDao userDao;
public void saveUser(User user) {
userDao.save(user);
}
}
这种方式的优势在于:
- 解耦:UserService不再关心UserDao的具体实现
- 可测试:可以轻松注入Mock对象进行测试
- 集中管理:所有对象的创建和依赖关系由容器统一管理
2.2 面向切面编程(AOP)
考虑一个常见的需求:为所有Service方法添加执行日志。传统做法是在每个方法中手动添加日志代码:
java复制public void saveUser(User user) {
log.info("开始执行saveUser");
long start = System.currentTimeMillis();
// 业务逻辑
userDao.save(user);
long end = System.currentTimeMillis();
log.info("saveUser执行完成,耗时:" + (end-start) + "ms");
}
这种方式会导致:
- 业务代码与非业务代码混杂
- 相同的日志代码需要重复编写
- 修改日志格式时需要改动所有方法
AOP的解决方案是将横切关注点(如日志、事务)从业务代码中分离:
java复制@Aspect
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint jp) {
log.info("开始执行:" + jp.getSignature().getName());
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint jp) {
log.info("执行完成:" + jp.getSignature().getName());
}
}
业务代码保持纯净:
java复制public void saveUser(User user) {
// 只关注业务逻辑
userDao.save(user);
}
3. SpringWind核心实现解析
3.1 IoC容器实现
SpringWind的核心是SpringWindApplicationContext类,它实现了以下关键功能:
- 组件扫描:通过反射查找带有
@Component、@Service等注解的类 - Bean定义注册:将类的元信息存储为BeanDefinition
- Bean实例化:通过反射创建对象实例
- 依赖注入:解析
@Autowired注解并注入依赖 - 生命周期管理:处理
@PostConstruct和@PreDestroy方法
java复制public class SpringWindApplicationContext {
// 三级缓存解决循环依赖
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 一级缓存
private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(); // 二级缓存
private Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(); // 三级缓存
public <T> T getBean(Class<T> requiredType) {
// 1. 检查一级缓存
// 2. 检查二级缓存
// 3. 检查三级缓存
// 4. 创建Bean实例
// 5. 放入三级缓存
// 6. 属性注入
// 7. 初始化
// 8. 放入一级缓存
}
}
3.2 循环依赖解决方案
Spring通过三级缓存机制解决循环依赖问题:
- 一级缓存(singletonObjects):存储完全初始化好的Bean
- 二级缓存(earlySingletonObjects):存储早期的Bean引用(已实例化但未完成依赖注入)
- 三级缓存(singletonFactories):存储ObjectFactory,用于延迟生成早期引用
解决循环依赖的关键代码:
java复制protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
// 1. 实例化Bean
Object bean = createBeanInstance(beanDefinition);
// 2. 将ObjectFactory放入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
// 3. 属性注入(可能触发循环依赖)
populateBean(beanName, beanDefinition, bean);
// 4. 初始化
initializeBean(beanName, bean);
return bean;
}
3.3 AOP实现机制
SpringWind支持两种代理方式:
- JDK动态代理:基于接口的代理
- CGLIB:基于继承的代理
代理创建的核心逻辑:
java复制public Object createProxy(Object target) {
// 查找匹配的切面
List<Advisor> advisors = findEligibleAdvisors(target.getClass());
if (target.getClass().getInterfaces().length > 0) {
// JDK动态代理
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkDynamicAopProxy(target, advisors));
} else {
// CGLIB代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new CglibAopProxy(target, advisors));
return enhancer.create();
}
}
4. 常见问题与解决方案
4.1 循环依赖的典型场景
java复制@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
解决方案:
- 使用setter注入而非字段注入
- 使用
@Lazy延迟初始化 - 重构代码消除循环依赖
4.2 AOP不生效的常见原因
- 目标方法不是public
- 方法被final修饰
- 自调用(同一个类中的方法调用)
- 切点表达式不匹配
4.3 Bean创建失败的可能原因
- 缺少无参构造器
- 依赖的Bean不存在
- 初始化方法抛出异常
- 循环依赖无法解决
5. 性能优化技巧
- 构造器缓存:缓存反射获取的构造器
- 注解缓存:缓存类和方法上的注解信息
- 切点预编译:预编译切点表达式
- 并发控制:使用ConcurrentHashMap代替同步集合
java复制// 构造器缓存示例
private final Map<Class<?>, Constructor<?>> constructorCache = new ConcurrentHashMap<>();
public Object createInstance(Class<?> clazz) {
return constructorCache.computeIfAbsent(clazz, c -> {
try {
return c.getDeclaredConstructor();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).newInstance();
}
6. 扩展与定制
SpringWind框架支持通过以下方式扩展:
- 实现BeanPostProcessor:干预Bean的创建过程
- 自定义注解:扩展框架功能
- 替换默认实现:如自定义AOP代理策略
示例:自定义BeanPostProcessor
java复制public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 初始化前处理
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 初始化后处理
return bean;
}
}
7. 从SpringWind到Spring Framework
理解SpringWind后,阅读Spring源码会更加容易。主要差异包括:
- 代码规模:Spring代码量超过50万行,SpringWind约4000行
- 功能范围:Spring提供全面的企业级特性
- 抽象层次:Spring有更多的抽象和扩展点
- 生产就绪:Spring经过大规模验证,SpringWind主要用于学习
建议的学习路径:
- 先通过SpringWind理解核心原理
- 再阅读Spring官方文档
- 最后深入Spring源码
8. 实践建议
- 边学边做:跟着SpringWind代码自己实现一遍
- 调试跟踪:通过断点观察Bean创建过程
- 对比学习:与Spring官方实现对比
- 循序渐进:先理解核心机制,再研究扩展点
通过这个项目,我不仅深入理解了Spring的工作原理,还掌握了框架设计的核心思想。现在阅读Spring源码时,那些曾经看似复杂的设计都变得清晰明了。如果你也想彻底搞懂Spring,不妨从实现一个简化版框架开始。
