实现自己的IOC容器——Winter (一)Bean加载
做了几年 Java 开发,每天和 Spring系列框架打交道。虽然各种注解、中间件用的飞起,但说实话,心里挺虚的。框架帮我们屏蔽了太多细节,舒服是舒服了,可久而久之,感觉自己像个被惯坏的孩子——离了脚手架就不会盖房了。
为了治好这份“技术焦虑症”,也为了满足那点该死的好奇心,我决定自己动手,从零开始撸一个简易版的 Spring 容器。不求功能强大,只求把那些神秘的面纱亲手揭开。
项目地址:https://gitee.com/sheevg/winter
目前还在持续更新中....
我是从《Spring源码深度解析第二版》这本书开始了解Spring框架,所以Winter容器也是从解析Xml配置实现Bean注册(后面会陆续更新注解驱动、AOP等等)。
一、解析Xml配置、注册BeanDefinition
参考方法:XmlBeanDefinitionReader.loadBeanDefinitions(resource)
Winter容器是Xml配置文件驱动,对配置的解析流程如下:
1. 加载xml文件成Resource对象
2. 将Resource转成Document,解析Document,将Bean的配置解析成BeanDefinition
3. 将解析完成的BeanDefinition放入容器WinterBeanFactory中
1. 加载xml文件,转成Document对象
定义ClassPathResource作为类路径下文件抽象,定义getInputStream方法用于读取。定义ResourceLoader用于统一加载文件。
import java.io.FileNotFoundException; import java.io.InputStream; /** * 用于解析 classpath 路径下的文件 */ public class ClassPathResource implements Resource{ // 文件路径 private final String path; // 用于加载文件的 private final ClassLoader classLoader; public ClassPathResource(String path){ this(path,null); } public ClassPathResource(String path,ClassLoader classLoader){ // classloader加载时,不能以/ 为开头,比如/config/xx.xml if(path.startsWith("/")){ path = path.substring(1); } this.path = path; this.classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader() ; } @Override public InputStream getInputStream() throws FileNotFoundException { InputStream inputStream = this.classLoader.getResourceAsStream(this.path); if(inputStream == null){ throw new FileNotFoundException("描述: 类路径下的资源 [" + this.path + "] 不存在,无法打开"); } return inputStream; } }2. 解析Document成BeanDefintion
定义BeanDefinitionReader用于将Resource的输入流转成Document对象,使用dom4j转换。
public int loadBeanDefinition(Resource resource) throws FileNotFoundException, DocumentException { // 获取输入流 InputStream is = resource.getInputStream(); SAXReader reader = new SAXReader(); // 将输入流转成 document Document document = reader.read(is); // 计算注册了多少 int countBefore = beanDefinitionRegistry.getBeanDefinitionCount(); doRegisterBeanDefinition(document.getRootElement()); return beanDefinitionRegistry.getBeanDefinitionCount() - countBefore; }在doRegisterBeanDefinition方法中,遍历bean element,在用解析配置的委托类BeanDefinitionParserDelegate进行具体解析。最终将bean配置解析为BeanDefinition对象。
public void doRegisterBeanDefinition(Element rootElement){ System.out.println(rootElement.getName()); BeanDefinitionParserDelegate parserDelegate = new BeanDefinitionParserDelegate(); // 获取所有 bean标签 List<Element> beanElements = rootElement.elements("bean"); beanElements.forEach(e -> { BeanDefinitionHolder bdHolder = parserDelegate.parseBeanDefinition(e); beanDefinitionRegistry.registerBeanDefinition(bdHolder.getBeanName(),bdHolder.getBeanDefinition()); }); }public BeanDefinitionHolder parseBeanDefinition(Element element) { String id = element.attributeValue("id"); String className = element.attributeValue("class"); String initMethod = element.attributeValue("init-method"); // 构造函数 List<ConstructorArg> argList = new ArrayList<>(); List<Element> constructorArgs = element.elements("constructor-arg"); for(int i=0; i<constructorArgs.size();i++){ Element constructorArg = constructorArgs.get(i); String argName = constructorArg.attributeValue("name"); String ref = constructorArg.attributeValue("ref"); String value = constructorArg.attributeValue("value"); ConstructorArg ca = ConstructorArg.builder() .index(i) .name(argName) .type(StringUtils.isNotBlank(ref)?ConstructorArgEnum.REF:ConstructorArgEnum.VALUE) .value(StringUtils.isNotBlank(ref)?ref:value) .build(); argList.add(ca); } BeanDefinition bd = BeanDefinition.builder() .id(id) .beanName(id) .className(className) .initMethodName(initMethod) .argList(argList) // 默认单例 .isSingleton(true) .build(); return new BeanDefinitionHolder(id,bd); }3. 注册BeanDefinition
需要在WinterBeanFactory中创建一个本地缓存ConcurrentHashMap,将创建好的BeanDefinition放进去即可。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64); @Override public void registerBeanDefinition(String beanName, BeanDefinition bd) { this.beanDefinitionMap.put(beanName,bd); }明天更新如何将配置中的Bean注册进容器....
