一、简介
基于bilibili up主“学java的生生”的手写spring教程,实现一个简单的spring框架。mini-spring包含的核心功能有:包扫描,BeanDefinition封装,IOC容器,依赖注入,生命周期管理,按类型/名称获取Bean等功能。后续还会继续基于mini-spring实现mvc。
二、项目框架
mini-spring
├── .idea
├── .mvn
└── src└── main├── java│ └── tech│ └── insight│ └── minispring│ ├── annotation│ │ ├── Autowired│ │ ├── Component│ │ └── PostConstruct│ ├── entity│ ├── ApplicationContext│ ├── BeanDefinition│ ├── BeanPostProcessor│ ├── Main│ └── MyBeanPostProcessor└── resources└── application.properties
三、核心代码解析
1.BeanDefinition
package tech.insight.minispring;import tech.insight.minispring.sub.Autowired;
import tech.insight.minispring.sub.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;public class BeanDefinition {private final String name;private final Constructor<?> constructor;private final Method postConstructMethod;private final List<Field> autowiredFields;private final Class<?> beanType;public BeanDefinition(Class<?> type){this.beanType = type;Component component = type.getDeclaredAnnotation(Component.class);this.name = component.name().isEmpty() ? type.getSimpleName() : component.name();try {this.constructor = type.getConstructor();this.postConstructMethod = Arrays.stream(type.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(PostConstruct.class)).findFirst().orElse(null);this.autowiredFields = Arrays.stream(type.getDeclaredFields()).filter(m -> m.isAnnotationPresent(Autowired.class)).toList();} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public String getName(){return name;}public Constructor<?> getConstructor(){return constructor;}public Method getPostConstructMethod(){return postConstructMethod;}public List<Field> getAutowiredFields(){return autowiredFields;}public Class<?> getBeanType(){return beanType;}
}
为什么要定义BeanDefinition,而不是直接定义Bean然后进行实例化?
BeanDefinition存储的是Bean的元信息,用来描述怎么创建一个Bean。BeanDefinition像是配方,而Bean是具体做出来的菜。
使用BeanDefinition有以下优势:
- 延迟实例化(Lazy Init)
如果你直接new,那一启动就得把所有对象都创建出来。而通过BeanDefinition,可以先将Bean的配方存储,在真正需要使用时再创建Bean,这样就能控制实例化的时机。
- 生命周期管理
直接使用new,只能拿到对象本身,无法管理对象的生命周期。因为BeanDefinition记录了Bean的元数据,所以可以在合适的时机执行生命周期方法,比如PostConstruct,PreDestory等。
- 依赖注入
BeanDefinition中保存了类中带有@Autowired的字段,创建Bean时能够通过反射注入对应的依赖对象。
- 代理增强
可以通过BeanDefinition的配置,决定返回一个原始对象还是增强后的代理对象,可以额外实现日志、事务等功能,这也是SpringAOP的基础。
2.ApplicationContext
- 包扫描函数
*** 包扫描函数* @param packageName* @return*/
public List<Class<?>> scanPackage(String packageName) throws Exception {List<Class<?>> classList = new ArrayList<>();// 需要a.b.cURL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));Path path = Paths.get(resource.toURI());Files.walkFileTree(path,new SimpleFileVisitor<>(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Path absolutePath = file.toAbsolutePath();if (absolutePath.toString().endsWith(".class")) {String replaceStr = absolutePath.toString().replace(File.separator, ".");int index = replaceStr.indexOf(packageName);String className = replaceStr.substring(index,replaceStr.length() - ".class".length());try {classList.add(Class.forName(className));} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}return FileVisitResult.CONTINUE;}});return classList;
resource:URL对象,包所在的文件目录的定位地址
URI:更通用的统一资源标识符,避免URL中的特殊符号产生的起义
Path:NIO引入的新文件系统的抽象,比File更强大,能够把URI转化为操作系统本地文件路径
Files.walkFileTree():递归遍历包目录及其子目录
这一个方法其实是访问者模式的体现:把堆某种结构的访问操作,从结构本身分离出来,交给访问者去完成。在不改变数据结构的前提下,灵活扩展对结构的操作。
在这里文件是被访问者,SimpleFileVisitor子类是访问者,visitFile()是访问者逻辑,Files.walkFileTree是调度器,遍历目录将文件交给visitor处理。
visitFile()的逻辑是将后缀为.class的文件的绝对路径转化为类的全限定类名,然后通过全限定类名使用反射的方式加载对象(并不是实例化对象,只是生成字节码文件,存储对象的元信息),最后加入classList中。classList中存储的是对象的元信息,之后会被封装成BeanDefinition。
- wrapper映射函数:将字节码对象封装为BeanDefinition
protected BeanDefinition wrapper(Class<?> type){// 1. 用 Class<?> 创建一个 BeanDefinition(即“配方”)BeanDefinition beanDefinition = new BeanDefinition(type);// 2. 校验:容器里不能有重复的 beanNameif (beanDefinitionsMap.containsKey(beanDefinition.getName())){throw new RuntimeException("Duplicate bean name: " + beanDefinition.getName());}// 3. 注册:把 BeanDefinition 放进 beanDefinitionsMap(配方容器)beanDefinitionsMap.put(beanDefinition.getName(), beanDefinition);// 4. 返回 BeanDefinition(有时候可能还要进一步处理)return beanDefinition;
}
- createBean&&doCreateBean
protected Object createBean(BeanDefinition beanDefinition) {String name = beanDefinition.getName();if (ioc.containsKey(name)){return ioc.get(name);}if (loadingIoc.containsKey(name)){return loadingIoc.get(name);}return doCreateBean(beanDefinition);
}
先查询单例池IOC中是否有bean,如果有说明已经创建过了,直接返回。再检查loadingIoc中是否有正在创建的bean,如果有,先返回半成品用于解决循环依赖。如果两个容器中都没有,则进行doCreateBean()。
private Object doCreateBean(BeanDefinition beanDefinition) {Constructor constructor = beanDefinition.getConstructor();Object bean = null;try {// 1. 调用构造器,实例化对象,得到一个裸对象bean = constructor.newInstance();// 2. 放入临时集合,标记“正在创建”loadingIoc.put(beanDefinition.getName(), bean);// 3. 自动注入依赖(相当于 Spring 的依赖注入阶段)autowiredBean(bean, beanDefinition);// 4. 初始化(调用初始化方法、执行 Aware 接口、BeanPostProcessor 等)bean = initializeBean(bean, beanDefinition);// 5. 创建完成,从 loadingIoc 移除loadingIoc.remove(beanDefinition.getName());// 6. 放入单例池 ioc,表示 Bean 已经创建完成ioc.put(beanDefinition.getName(), bean);} catch (Exception e){throw new RuntimeException(e);}return bean;
Spring三级缓存解决循环依赖
在Spring的DefaultSingletonBeanRegistry中有三个Map:
1)singletonObjects:相当于ioc单例池,存放已经初始化好的bean。
2)earlySingletonObjects:相当于loadingIoc,存放已实例化但还未初始化完成的bean。
3)singletonFactories:存放ObjectFactory,一个生成bean的工厂对象
Spring三级缓存相较于本文代码的优势是:在循环依赖时,B拿到的A是工厂返回的代理对象,既可以返回原始对象,也能返回增强后的代理对象;而本文的实现是直接返回裸对象(使用BeanDefinition的构造器进行创建对象),在创建对象后无法再进行增强。因此,Spring的三级缓存具有更强大,更灵活的AOP功能。
图论环检测角度解决循环依赖
提供一个另外思考的视角,Spring 解决循环依赖的做法是 【图论环检测】 的经典实践。 依赖关系可以表示为一个有向图,Cat -> Dog -> Cat 的依赖关系可以表示为一个【有向图】 ,Bean是怕【顶点】, 依赖关系是【边】。Spring在createBean时会递归构建依赖图 (深度优先遍历DFS) , 如果发现当前路径中某个Bean正在创建中(即存在“正在创建”的顶点被重复访问),则判定存在环 【环检测】 。简化做法是采用二级缓存来获取 Cat 的引用 【在图中引入一个虚拟顶点】, 将环拆解为链式依赖 ,从而避免了无限递归。当所有依赖注入完成,再将真实顶点替换虚拟顶点。故视频中的loadingIoc缓存是用于存放虚拟顶点进行环检测的集合容器,ioc是存放真实顶点的集合容器。
- autowiredBean 自动注入方法
private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {for (Field autowiredField : beanDefinition.getAutowiredFields()) {autowiredField.setAccessible(true);autowiredField.set(bean,getBean(autowiredField.getType()));}
}
- BeanPostProcessor
private Object initializeBean(Object bean,BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {for (BeanPostProcessor postProcessor : postProcessors) {postProcessor.beforeInitializeBean(bean,beanDefinition.getName());}Method postConstructMethod = beanDefinition.getPostConstructMethod();if (postConstructMethod != null){postConstructMethod.invoke(bean);}for (BeanPostProcessor postProcessor : postProcessors) {postProcessor.afterInitializeBean(bean,beanDefinition.getName());}return bean;
}
/*** 将实现BeanPostProcessor接口的Bean实例化并放入postProcessor列表*/
private void initBeanPostProcessor() {beanDefinitionsMap.values().stream().filter(bd ->BeanPostProcessor.class.isAssignableFrom(bd.getBeanType())).map(this::createBean).map((bean) -> (BeanPostProcessor)bean).forEach(postProcessors::add);
}
@PostConstruct:在依赖注入完成后,Bean可用前的初始化钩子。
PostProcessor:处理器。所有实现BeanPostProcessor接口的Bean都会被注册成为一个处理器。所有postProcessors列表中的处理器都可以参与生命周期的增强。在创建新的bean时调用initializeBean方法,执行处理器中的逻辑:
先执行 MyBeanLogger.beforeInitializeBean()
再执行 @PostConstruct
方法(如果有)
最后执行 MyBeanLogger.afterInitializeBean()
所以postProcessor的作用就是:定义一系列处理器,在自动注入完成后,对半成品bean进行生命周期的增强
- getBean
/*** 通过名字获取对象* @param name* @return*/
public Object getBean(String name) {if (name == null) {return null;}Object bean = ioc.get(name);//如果bean不为空,直接返回if (bean != null){return bean;}//如果map中有但还未初始化,则将其初始化后返回//这样做是为了确保map中存在beanDefinition但还未在ioc容器中创建的对象也被创建if (beanDefinitionsMap.containsKey(name)){return createBean(beanDefinitionsMap.get(name));}//ioc中确实没有return null;
}/*** 通过类型获取对象* @param beanType* @return* @param <T>*/
public <T> T getBean(Class<T> beanType){String beanName = this.beanDefinitionsMap.values().stream().filter(bd -> beanType.isAssignableFrom(bd.getBeanType())).map(BeanDefinition::getName).findFirst().orElse(null);return (T)getBean(beanName);
}/**** @param beanType* @return* @param <T>*/
public <T> List<T> getBeans(Class<T> beanType){return this.beanDefinitionsMap.values().stream().filter(bd -> beanType.isAssignableFrom(bd.getBeanType())).map(BeanDefinition::getName).map(this::getBean).map(bean -> (T)bean).toList();
}
Spring在通过类型获取bean时,为什么需要通过遍历IoC容器获取,而不是通过维护一个Map<Class,Bean>来获取?
因为要支持多态、代理、多实现的场景。如果传入一个接口或一个父类,其具有多个实现,就不能保证Class和Bean的一一映射关系,无法准确获取bean。因此在通过名称获取时依靠beanNname -> Bean的唯一映射,通过Map来获取;而在通过类型获取时,需要遍历ioc容器获取bean,再匹配类型,以支持多态、代理、多实现的场景。