前前后后写了几篇Spring的源码分析,看着寥寥无几的阅读量,我意识到了自己写的不够好,虽然我的流程图、源码注释花了不少时间,可阅读量摆在那,就是不够好。
思来想去,我决定再加一篇更加通俗、直观的文章:假如让我们来设计Spring框架,我们该怎么设计,我们会想到什么问题,又该如何解决这些问题。
好的开始吧。
让我们回到2008年7月11日的早晨。
最近我们在项目开发中,遇到了如下的问题,感觉很繁琐,我想搞一个框架出来,方便日后的项目开发:
- 项目中有很多服务类对象,对象间引用极其麻烦,都用单例模式的话,那方法都是public,这样设计很不好,不符合Java的封装特性。应该是哪个类被需要,就聚合哪个类,而不是都暴露出来。使用聚合的话,那构造对象就很麻烦,一开始需要聚合3个服务,过几天又加了两个服务依赖,那对象构建的逻辑又要改了,一两个对象无所谓,大型项目里,依赖关系错综复杂,维护起来甚是麻烦。
- 很多服务类对象其实都是单例的,不需要频繁创建,大量重复对象创建导致频繁GC,服务器受不了。
- 处理对象聚合的问题,给相互聚合的对象进行初始化时候,还得特殊处理,不然会进入死胡同。
- 有很多优秀的第三方框架,我想让他们能够无痕无感地接入到我的框架里,就好像是我自己的组件一样,让开发者很方便的调用,而不用去做很多配置,这又该如何做。
问题还有很多,但是以上问题便是我设计框架的立根之本,不把这些问题解决了,那这个框架也只是一个终将被时间遗忘的代码,把这些功能实现了,再去开枝散叶才是长久之计。
Bean我的框架要有一些自己的特点,我把对象都统称为Bean。(豆子?咖啡豆?Java本来就是和咖啡有半毛钱关系)
先搞个容器来存放Bean
项目中有这么多单例Bean,而且Bean构建过程可能也颇为复杂,那我就把他们存起来。用什么存呢?Map呗
private final MapsingletonObjects = new ConcurrentHashMap<>(256);
单例、非抽象、非懒加载
什么Bean都可以放进我的缓存里边么?没必要吧,我只需要对单例的&&非抽象的&&非懒加载的Bean给放进缓存里就行了。
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
......
}
拆解Bean的创建过程
前面提到相互聚合即循环依赖的问题,那我就将Bean的创建分成3个步骤:实例化、属性赋值、初始化。
这样子,循环依赖的Bean,就可以先实例化,然后把半成品Bean(未属性赋值和初始化的对象)赋值给当前Bean,这样流程就可以往下走,最后出来的肯定是完好的Bean。
if (instanceWrapper == null) {
// 实例化
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
try {
// 填充属性
populateBean(beanName, mbd, instanceWrapper);
// aware方法、初始化前、初始化、 初始化后
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
增加二三级缓存
Bean创建过程拆解成三个步骤后,那整个容器初始化过程中,就会出现一种半成品的Bean,这些Bean我是不是要先存起来。因为我初始化时会初始化很多的Bean,不可能针对某一个进行特殊处理。那就加一个二级缓存,这个二级缓存放的就是实例化后的半成品Bean,一级缓存放的是完整的Bean,对象都创建好了会去把二级缓存里的半成品给清掉。
二级缓存:
private final MapearlySingletonObjects = new ConcurrentHashMap<>(16);
这样子就可以解决循环依赖的问题,但是呢。我可能后面会对Bean做动态代理增强,那么循环依赖下Bean所依赖的对象就应该是代理Bean吧,不能去依赖这个实例化后的半成品。
所以,这个二级缓存可能会放代理Bean,也可能放简单的半成品,甚至还有其他可能。如此一来会显得逻辑不够清晰,要不再加一个三级缓存,把处理逻辑封装进三级缓存,二级缓存就是纯粹的可以被属性赋值的半成品。
三级缓存:
private final Map> singletonFactories = new HashMap<>(16);
@FunctionalInterface public interface ObjectFactory{ T getObject() throws BeansException; }
// 二级缓存也没有
if (singletonObject == null) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//把早期对象放置在二级缓存,
this.earlySingletonObjects.put(beanName, singletonObject);
//ObjectFactory 包装对象从三级缓存中删除掉
this.singletonFactories.remove(beanName);
}
}
三级缓存存的是函数接口,可以返回实例化后的半成品,也可以把动态代理的逻辑塞到这里。
对于二级缓存而言,他只需从三级缓存拿就行。
看看我是怎么使用三级缓存函数接口吧:
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//上述条件满足,允许早期暴露对象
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,
// 该方法内部调用getEarlyBeanReference方法,该方法再去调后置处理器
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
这里的bp.getEarlyBeanReference(exposedObject, beanName)意思就是后置处理器会对一些功能进行封装并返回所需的对象,如果没有后置处理器可以处理这个Bean,那就返回半成品的Bean咯。
比如Aop后置处理器AspectJAwareAdvisorAutoProxyCreator的处理逻辑如下:
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 这里会创建动态代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
依赖注入
现在有了缓存,那我聚合对象的时候是不是就可以不用自己去new了,直接从缓存里拿就好了,那我就定义一个注解@Autowired,每个Bean进行属性赋值时候都会去解析这个注解,然后从缓存里拿到Bean再注入进去,这样就大功告成了。
后置处理器
刚才提到了后置处理器,我正要说呢。框架中我肯定会定义很多注解,这些注解肯定要有对应的类去做解析吧,而且这些处理器也应该要被注册进我的容器里,这样方便复用。除了内置的处理器,开发人员也可以自行编写处理器,这些处理器统称为后置处理器。
由于后置处理器是插件化添加的,所以会在框架中看到很多类似结构的代码:
for (XXPostProcessor bp : getBeanPostProcessors()) {
bp.postProcess();
}
这些后置处理器的构建顺序有先后,每个处理器所要处理的对象也不一样,所以每个Bean创建时候都会循环遍历所有处理器,看适不适合自己,如果太粗太大就不适合。一开始可能只有一个处理器去遍历,越到后面越多。
这个思路需要想清楚,不然会觉得我写的框架怎么跳来跳去。
扩展接口
容器初始化过程中会有很多详尽的步骤,我希望给开发者足够多的扩展空间,让他们在Bean的整个生命周期中尽可能多的去做一些自己的扩展,或者说可以让开发者在初始化阶段去获取捕获一些自己需要的信息供系统使用。
比如Bean名字获取接口:
@Component
public class A implements BeanNameAware {
private String beanName;
@Autowired
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
@Override
public void setBeanName(String name) {
System.out.println("我拿到了bean名字,然后把他存起来");
this.beanName = name;
}
}
Bean定义
到了这里,似乎我们这个框架的轮廓已经出来了。但是,我们再想想,一个类上的注解或者方法上的注解,我要怎么获取?我获取不到的话该怎么解析呀。使用反射?如果开发人员没有显式的创建Bean.java,又或者说这个Bean我是从其他途径获取的,比如xml里读取的,那又该咋办。
那就定义一个Bean定义的类,不管这个Bean是怎么来的,都要封装成Bean定义对象,然后放进Map里,这样在容器初始化阶段,我拿到这个Bean定义对象,里边有各种元数据信息,利用这些信息就可以做很多事。
FactoryBean
那么还剩下一个问题,第三方框架怎么和我的框架整合,肯定得留一个口子吧,让他们把配置好的对象注入到我的容器中。
public interface FactoryBean{ String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; @Nullable T getObject() throws Exception; @Nullable Class> getObjectType(); default boolean isSingleton() { return true; } }
比方说,Mybatis会对Dao接口创建动态代理,开发人员想在项目中直接进行依赖注入Dao的实现类Mapper对象,这该咋做呢?
问题本质就是要让代理对象丢进容器里呗。
有两种思路,一种就是第三方框架把对应的对象都生成好,然后再导进去。但是呢,我目前没有打算开放这样的窗口,我希望进入容器的Bean都尽可能按我的方式来:从Bean定义开始并构建出来。可能在后面的版本会增加此功能,但是现在我并不想。
第二种思路就是想办法把这些Mapper信息封装成Bean定义,这样不就高度无感了!
那我就提供一些让开发人员注册Bean定义的接口呗!在注册Bean定义的时候,需要指定Bean类型是FactoryBean类型,我可不关心你们要怎么创建Bean,我在初始化时候直接调用getObject()就完事。
好了,我的框架初见雏形,当然还有很多细节没想清楚,毕竟才刚刚起床,现在我要去洗漱吃饭,然后码代码了。我的项目会在github开源,欢迎参与到我的项目来:
https://github.com/spring-projects/spring-framework



