zoukankan      html  css  js  c++  java
  • 手撕spring(一):实现一个简单的IOC容器

    gitee地址:https://gitee.com/zr-zhang2021/easy-framework-spring/tree/v1-base-beanfactory

    前言

    为了更好地帮助自己阅读spring源码,学习源码中的思想,本次spring手撕系列参考了多方面的资料,目标从最简单的原理出发,不断地逼近spring源码,当然完全去模仿spring的难度太大,这里只是针对spring的核心主线,实现一些基本的功能,理解一些基本的抽象,方便以后能够把这些思想运用到实际工作中。

    万丈高楼平地起,一个简单的IOC容器,其本质就是实现:读取配置文件并提取bean;注册bean到容器;从容器里加载bean;本章实现基础的BeanFactory:支持xml方式配置bean,支持加载单例bean

    首先从最基本的IOC使用开始:

    (1) 定义spring-bean.xml:

    <bean id="bean1" class="com.rui.test.TestBean1" scope="singleton">
    
    </bean>
    

    (2) TestBean1.java:

    public class TestBean1 {
        public void test(){
            System.out.println("hello...");
        }
    }
    

    (3) 测试BeanFactoryTest.java:

    public class BeanFactoryTest {
        @Test
        public void testGetBean(){
            Resource resource=new ClassPathResource("spring-bean.xml");
            BeanFactory factory=new XmlBeanFactory(resource);
            TestBean1 bean1 = (TestBean1)factory.getBean("bean1");
            bean1.test();
        }
    

    (4) 结果:

    hello...
    

    开始设计:容器的类型?

    从前面定义xml文件中可以看出,一个bean中会包含idclassscope等属性,因此需要用用一个类(BeanDefinition)来定义,为了之后除了支持xml定义bean之外还支持注解方式,BeanDefinition被抽象成接口,最后,IOC容器被设计成一个:map<String,BeanDefinition>,下面为了简单展示,scope属性后面再添加。

    public interface BeanDefinition {
        //获取该bean的全类名
        String getBeanClassName();
    }
    
    

    GenericBeanDefinition为BeanDefinition最基本的实现类:

    public class GenericBeanDefinition implements BeanDefinition {
    
        private String beanId;
        private String beanClassName;
    
        public GenericBeanDefinition(String beanId,String beanClassName){
            this.beanId=beanId;
            this.beanClassName=beanClassName;
        }
    
        @Override
        public String getBeanClassName() {
            return this.beanClassName;
        }
    }
    

    抽象:实现Resource

    下面我们从(3)中一句一句地分析:首先是Resource resource=new ClassPathResource("spring-bean.xml");

    参考《Spring源码深度解析》里的说法,Resource接口抽象了所有Spring内部使用到的底层资源。把不同来源的资源文件(比如File,ClassPath)都封装成相应的Resource实现:FileSystemResource、ClassPathResource等。在这里,Resource接口主要是提供getInputStream()的方法,此外还提供getDescription()方法用来打印错误信息。

    public interface Resource {
    
         InputStream getInputStream() throws IOException;
    
         String getDesciption();
    }
    

    最最最核心的思路

    BeanFactory factory=new XmlBeanFactory(resource);这一句中到底做了什么?

    掌握最核心的思路,其他的抽象都是基于这个核心引申出来的。
    本章一开始有说,IOC容器本质上就是:(1)读取配置文件,把提取的bean信息注册到IOC容器;(2)需要使用bean时再从容器里加载bean。参考一下spring源码,根据单一职责原则,xmlBeanFactory中有两个非常核心的角色,XmlBeanDefinitionReader 的职责对应上面的(1),DefaultBeanFactory的职责对应(2),被xmlBeanFactory继承。

    public class XmlBeanFactory extends DefaultBeanFactory {
    
        private final XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(this);
    
        public XmlBeanFactory(Resource resource){
            this.reader.loadBeanDefinition(resource);
        }
    
    }
    
    • BeanFactory:提供注册、加载Bean接口
    • DefaultBeanFactory:实现注册Bean方法,加载Bean方法【非常重要】
    • XmlBeanDefinitionReader:解析xml文件,并利用DefaultBeanFactory的注册方法注册Bean

    XMLBeanFactory类图

    抽象:实现BeanDefinitionRegistry

    对于工厂BeanFactory而言,设计的初衷是获取关于bean对象的信息,BeanDefinition的注册与获取属于“内部”的操作,我们不希望将来BeanFactory接口暴露BeanDefinition注册与获取方法,而只提供关于bean的操作,因此BeanDefinitionRegistry的角色就出现了。

    在这里插入图片描述

    DefaultBeanFactory实现了BeanDefinitionRegistry接口的方法:

        @Override
        public void registerBeanDefinition(String beanId, BeanDefinition bd) {
            this.beanDefinitionMap.put(beanId,bd);
        }
    
        @Override
        public BeanDefinition getBeanDefinition(String beanId) {
            return this.beanDefinitionMap.get(beanId);
        }
    

    【重要】实现XmlBeanDefinitionReader的解析、注册

    public class XmlBeanDefinitionReader {
        //依赖于BeanDefinition注册器
        private BeanDefinitionRegistry registry;
        //构造函数,把注册器传进来
        public XmlBeanDefinitionReader(BeanDefinitionRegistry registry){
            this.registry=registry;
        }
        //解析资源
        public void loadBeanDefinition(Resource resource){
    
            try {
                InputStream inputStream = resource.getInputStream();
                SAXReader saxReader = new SAXReader();
                Document doc = saxReader.read(inputStream);//利用dom4j工具读取resource的输入流
                Element root= doc.getRootElement();//beans标签
                Iterator<Element> iterator = root.elementIterator();
                while(iterator.hasNext()){
                    Element element= iterator.next();//bean标签
                    String beanId = element.attributeValue("id");//bean标签里面的id属性
                    String beanClassName = element.attributeValue("class");//bean标签里面的class属性
                    //注册该bean
                    GenericBeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);//BeanDefinition的实现类
                    registry.registerBeanDefinition(beanId,bd);//利用注册器的注册方法注册BeanDefinition
                }
            } catch (Exception e) {
                throw new BeanDefinitionStoreException("IOException parsing XML document from " + resource.getDesciption(),e);
            }
        }
    }
    
    

    【重要】实现DefaultBeanFactory加载Bean

    TestBean1 bean1 = (TestBean1)factory.getBean("bean1");这一句到底经历了什么呢?

    前面也说过实质上是DefaultBeanFactory实现了getBean(String beanId)这个方法的。

    DefaultBeanFactory:

    public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry{
    
        private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap= new ConcurrentHashMap<String,BeanDefinition>(64);
    
        private ClassLoader classLoader;
    
        @Override
        public Object getBean(String beanId) {
            BeanDefinition bd = this.getBeanDefinition(beanId);
            if (bd==null){
                return null;
            }
            return this.createBean(bd);
        }
    
        private Object createBean(BeanDefinition bd){
            String beanClassName = bd.getBeanClassName();
            ClassLoader classLoader = this.getClassLoader();
            try {
                Class<?> clazz = classLoader.loadClass(beanClassName);
                return clazz.newInstance();
            } catch (Exception e) {
                throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
            }
        }
        public ClassLoader getClassLoader(){
            return this.classLoader!=null?this.classLoader: ClassUtils.getDefaultClassLoader();
        }
    
    

    到这里,一个简单的IOC容器的雏形就已经完成了,其支持了Bean的注册与加载。

    【重要】实现单例singleton

    singleton单例模式也就是说在xml声明bean时,scope属性设置为singleton(默认也是单例),此时该bean实例在IOC容器里只存在一个,而不会出现加载一次bean就产生一个新的对象,怎么实现呢?

    首先,在BeanDefinition接口中添加scope属性的设置和获取,然后还有单例状态(isSingleton)和多例状态(isPrototype)

    public interface BeanDefinition {
    
        public static final String SCOPE_SINGLETON="singleton";
        public static final String SCOPE_PROTOTYPE="prototype";
        public static final String SCOPE_DEFAULT="";
    
        boolean isSingleton();
        boolean isPrototype();
    
        String getScope();
        void setScope(String scope);
    
        String getBeanClassName();
    }
    

    其次,GenericBeanDefinition再实现这些方法,也就是说在reader解析注册bean时能够设置scope的属性和单例/多例状态。

    public class GenericBeanDefinition implements BeanDefinition {
    
        private String beanId;
        private String beanClassName;
        private boolean isSingleton=true;
        private boolean isPrototype=false;
        private String scope=SCOPE_DEFAULT;
    
        @Override
        public void setScope(String scope) {
            this.scope=scope;
            this.isSingleton=SCOPE_SINGLETON.equals(scope)||SCOPE_DEFAULT.equals(scope);
            this.isPrototype=SCOPE_PROTOTYPE.equals(scope);
        }
        ......
    }
    

    然后,需要在getBean时维护一个单例,也就是在每次加载bean时判断是否加载过该bean实例。这里抽象出SingletonBeanRegistry。

    在这里插入图片描述

    该实现类DefaultSingletonBeanDefinition里面用一个map(singletonBeanObjectMap)维护单例对象。

    public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
        public final ConcurrentHashMap<String,Object> singletonBeanObjectMap=new ConcurrentHashMap<>(64);
    
        @Override
        public void registerSingleton(String beanId, Object singletonObject) {
            singletonBeanObjectMap.put(beanId,singletonObject);
        }
    
        @Override
        public Object getSingleton(String beanId) {
            return this.singletonBeanObjectMap.get(beanId);
        }
    }
    

    最后,在DefaultBeanFactory里加载bean时就可以先获取singletonBeanObjectMap里获取这个singleton,如果没有,再createBean()。

    public class DefaultBeanFactory extends DefaultSingletonBeanRegistry
            implements BeanFactory,BeanDefinitionRegistry{
    
        @Override
        public Object getBean(String beanId) {
            BeanDefinition bd = this.getBeanDefinition(beanId);
            if (bd==null){
                return null;
            }
            if (bd.isSingleton()){
                Object singleton = this.getSingleton(beanId);
                if (singleton==null){
                    singleton = this.createBean(bd);
                    this.registerSingleton(beanId, singleton);
                }
                return singleton;
            }
    
            return this.createBean(bd);
        }
    

    小扩展:ApplicationContext的实现

    在实际的应用的,大部分使用的是ApplicationContext来取代BeanFactory,在spring源码中ApplicationContext继承了BeanFactory,并扩展了一些功能,具体扩展了哪些地方,在后面再详细说吧。在这里为了迎合ApplicationContext的使用,先简单设计并实现ApplicationContext。

    先写个测试用例:

    public class ApplicationContextTest {
        @Test
        public void testApplicationContext(){
            ApplicationContext context=new ClassPathXmlApplicationContext("spring-bean.xml");
            TestBean1 bean1=(TestBean1)context.getBean("bean1");
            bean1.test();
        }
    }
    

    ApplicationContext继承了BeanFactory的getBean功能:

    在这里插入图片描述

    ClassPathXmlApplicationContext实现类:

    public class ClassPathXmlApplicationContext implements ApplicationContext {
    
        private DefaultBeanFactory factory;
    
        public ClassPathXmlApplicationContext(String configFile) {
            this.factory=new DefaultBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            Resource resource = new ClassPathResource(configFile);
            reader.loadBeanDefinition(resource);
        }
    
        @Override
        public Object getBean(String beanId) {
            return factory.getBean(beanId);
        }
    }
    
    

    到这里,测试用例已经可以顺利运行了,但是会产生两个小问题需要优化。

    设计模式:模板方法模式的运用

    接着上面的,第一个问题是ApplicationContext的扩展问题,前面有提过我们的resource资源可以来自不同的来源,ResourceClassPathResource也有FileSystemResource

    在这里插入图片描述

    相应的ApplicationContextClassPathXmlApplicationContext也有FileSystemXmlApplicationContext,如果以后有其他ApplicationContext的实现类,例如FileSystemXmlApplicationContext,那么就要在里面重新写一遍ClassPathXmlApplicationContext的内容,不同的是把输入转化为Resource时用到FileSystemResource

    public class FileSystemXmlApplicationContext extends AbstractApplicationContext {
    
        private DefaultBeanFactory factory;
    
        public FileSystemXmlApplicationContext(String configFile) {
            this.factory=new DefaultBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            Resource resource = new FileSystemResource(configFile);
            reader.loadBeanDefinition(resource);
        }
    
        @Override
        public Object getBean(String beanId) {
            return factory.getBean(beanId);
        }
    }
    

    针对这个问题,首先需要抽象出新的类AbstractApplicationContext,来解决各实现类重复代码的问题,再者运用模板方法模式,也就是在父类定义好执行的模板/骨架(包含抽象方法),并在子类各自重写抽象方法。来解决对不同来源的文件转化为Resource时用不同Resource实现类(ClassPathResource和FileSystemResource)的问题。

    在这里插入图片描述

    AbstractApplicationContext抽象类:

    public abstract class AbstractApplicationContext implements ApplicationContext {
    
    	private DefaultBeanFactory factory = null;
    	
    	public AbstractApplicationContext(String configFile){
    		factory = new DefaultBeanFactory();
    		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);	
    		Resource resource = this.getResourceByPath(configFile);
    		reader.loadBeanDefinitions(resource);
    		
    	}
    	
    	public Object getBean(String beanID) {
    		
    		return factory.getBean(beanID);
    	}
    	
    	protected abstract Resource getResourceByPath(String path);
    
    }
    

    ClassPathXmlApplicationContext:

    public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
    	public ClassPathXmlApplicationContext(String configFile) {
    		super(configFile);
    		
    	}
    
    	@Override
    	protected Resource getResourceByPath(String path) {
    		
    		return new ClassPathResource(path);
    	}
    
    }
    

    抽象:实现ConfigurableBeanFactory

    第二个小问题是关于ClassLoader的问题,很多地方都用到getBeanClassLoader,比如说ClassPathResource获取流的时候,还有DefaultBeanFactory在createBean的时候,但目前的实现都是使用默认写死的(ClassUtils.getDefaultClassLoader()),我们希望支持ClassLoader的传入,因此抽象出ConfigurableBeanFactory接口。

    在这里插入图片描述

    本节类图全家福

    在这里插入图片描述

  • 相关阅读:
    [nodejs] 静态资源服务器
    [nodejs]fs 读数据流和写数据流
    [nodejs]fs文件模块-练习
    [nodejs] fs文件模块
    利用SqlServer触发器自动更新表updatetime字段值
    python发送邮件至多人
    mybatis-plus获取Timestamp类型,无法获取变量null
    1.iOS第一个简单APP
    Mysql浅析
    Nginx编译安装Lua模块
  • 原文地址:https://www.cnblogs.com/xxlad/p/15391461.html
Copyright © 2011-2022 走看看