【spring系列】一个袖珍版的spring

前言

​ spring源码是一个困扰我很久的问题,这里面水又深有浑,跟一会就蒙圈了,好不容易找到了一点思路,时间一久,概念又模糊了,然后再去跟源码的时候又是一个恶心的循环。万般无奈,高仿一个spring来帮助下次快速梳理springIOC源码;

​ 那就从第一次接触spring来梳理吧:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
        System.out.println(userInfo.getName());
    }

​ 第一次接触spring,就听说,对象不用new,直接从容器中拿就可以了,我想大多数人心中都会有一个这样的概念吧。就如上代码,spring加载完xml文件之后,直接getBean方法就可以获取对象了,省去了new UserInfo() 。但是new ClassPathXmlApplicationContext("spring.xml") 又帮我们做了什么呢?

至此,对于如上3行代码进行高仿袖珍版springIOC容器,只是最简单的springIOC容器,如有不对或者不严谨的地方,请大家即使指出,我会尽快修改,免得误导别人。

1.创建核心接口

​ 既然高仿就要像一点,第一步要创建心脏 BeanFactory 了。然后核心接口一定要有的。


/**
 * Created by yangman on 2020/5/8.
 * 精简版本也要高仿的像一点 BeanFactory 不能少。
 */
public interface BeanFactory {
    // 来两个核心方法得了,其他的暂时先不搞了。
    Object getBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException;
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws IllegalAccessException, NoSuchFieldException, InstantiationException, NoSuchMethodException, InvocationTargetException;
}

接着创建子接口,HierarchicalBeanFactoryListableBeanFactoryApplicationContext但是前两个用的不多,注重说ApplicationContext吧。

​ 网上对于ApplicationContext接口有一个这样的说法,就是说如果比喻BeanFactory是心脏,则ApplicationContext就是躯干。由于这里只是高仿,大部分功能是用不到,所以不写了。

/**
 * Created by yangman on 2020/5/8.
 * beanFactory只有一些简单的,核心的功能,
 * ApplicationContext的接口就是一个全面化的接口,集结所有功能为一身。
 *  1.可以装配管理bean
 *  2.源码中实现了ResourcePatternResolver->ResourceLoader 接口,可以加载资源文件
 *  3.可以进行国际化处理等等
 */
public interface ApplicationContext extends HierarchicalBeanFactory,ListableBeanFactory {

}

接口有了,进行实现吧。创建ClassPathXmlApplicationContext实现ApplicationContext接口,注:spring源码中ClassPathXmlApplicationContext类不是直接实现的ApplicationContext接口。

public class ClassPathXmlApplicationContext implements ApplicationContext {
  public ClassPathXmlApplicationContext(String configLocation) {
        this(new String[] {configLocation});
    }
   public ClassPathXmlApplicationContext(String[] configLocations) {
        // 源码中做了相应的处理, 这里进行简化
        this.configLocations = configLocations;
        synchronized (this.startupShutdownMonitor) {
            try{
                // 读取配置文件
                Document document = loadConfig();
                //生成BeanDefinition
                createBeanDefinition(document);
                // IOC注入
                IocAutoWrite();
            }catch (Exception ex){
                    ex.printStackTrace();
            }
        }
    }
}

2.读取配置文件

​ 通过loadConfig方法进行处理文件路径,并且加载配置。

​ 应该还记得classpath吧,在源码中PathMatchingResourcePatternResolver.getResources 可见真正的解析过程。

    private Document loadConfig() throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 这里其实是一个数组,只是为了演示,这里就不循环处理了。
        InputStream inputStream = this.getFileInputStream(configLocations[0]);
        // 获取doc对象
        Document document= docBuilder.parse(inputStream);
        return document;
    }
    // 获取并处理文件路径
    private InputStream getFileInputStream(String configLocation) {
        // 真正的源码中spring 有对classpath* 和 classpath 这两个开头做了处理  PathMatchingResourcePatternResolver.getResources 可见
        int prefixEnd =   (configLocation.startsWith("war:") ? configLocation.indexOf("*/") + 1 :
                configLocation.indexOf(':') + 1);
        // this.getClass().getClassLoader().getResourceAsStream 这个函数寻找文件的起点是JAVA项目编译之后的根目录 ,也就是/target/class目录啦
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        return inputStream;
    }

3.生成BeanDefinition

​ 可能有的小伙伴对与BeanDefinition这个东西有点陌生,BeanDefinition是用来存放bean的元数据的,比如bean的类名,属性,scope等信息。但是就疑问了?这些东西配置文件中(xml)原本就有呀,何必在存一份呢?这里可以打个比方,BeanDefinition有如手机配件,bean是手机,手机配件从工厂进行加工出来,需要进行检测才能放到手机上,xml中的信息就是刚刚加工出来的手机配件,BeanDefinition就是检验合格的手机配件。xml中的信息加载到BeanDefinition中,才可以人为xml配置的信息被成功解析。

xml文件读取之后,接下进行验证xml文件,并且初始化到BeanDefinition中了。

通过createBeanDefinition初始化BeanDefinition

3.1创建BeanDefinition

BeanDefinition是一个接口,这里用到AbstractBeanDefinition进行实现,现在存储xml文件内容

/**
 * Created by likuo on 2020/5/9.
 * 高仿spring AbstractBeanDefinition.java
 */
public class AbstractBeanDefinition implements BeanDefinition {
    // 类
    private volatile Object beanClass;
    // bean名称,用来.getBean()中的值
    private String beanName;
    // bean的属性值 主要用来存放  <property name="name" value="aa"></property> 这一段
    private MutablePropertyValues propertyValues;

    public Object getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(Object beanClass) {
        this.beanClass = beanClass;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }

    public void setPropertyValues(MutablePropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }
}

3.2读取配置文件

主要完成从xml读取,并且生成BeanDefinition,BeanDefinition中存入类名,bean名称,和属性值,相当于把配置文件完全读取到内存中。

// 源码中就比较复杂, 这里就从简了哈。
private void createBeanDefinition(Document document) throws ClassNotFoundException {
    // 获取根节点
    Element documentElement = document.getDocumentElement();
    //这个方法名字也是高仿的,下载源码搜索就能搜索到此方法
    doRegisterBeanDefinitions(documentElement);
}

xml文件中有很多种标签,,,等,这里进行解析。

private void doRegisterBeanDefinitions(Element documentElement) throws ClassNotFoundException {
        // 看到这两个判断就豁然开朗了吧,此内容高仿 DefaultBeanDefinitionDocumentReader.parseDefaultElement方法,有兴趣的看一下哈。
        // 如果是<beans> 则解析下子元素
        if(NESTED_BEANS_ELEMENT.equals(documentElement.getNodeName())){
            NodeList childNodes = documentElement.getChildNodes();
            for(int i = 0 ;i<childNodes.getLength();i++){
                Node item = childNodes.item(i);
                if(item instanceof Element) {
                    // beans下面应该是bean,这里进行循环解析
                    doRegisterBeanDefinitions((Element)item);
                }

            }
        }else if(BEAN_ELEMENT.equals(documentElement.getNodeName())){
            AbstractBeanDefinition beanDefinition = new AbstractBeanDefinition();
            // 解析id属性
            String beanName = documentElement.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
            // 解析class属性
            String className = documentElement.getAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE);
            // 设置值
            beanDefinition.setBeanClass(Class.forName(className));
            beanDefinition.setBeanName(beanName);
            // 如果有设置了值 property,则进行解析
            NodeList childNodes = documentElement.getChildNodes();
            if(childNodes!=null){
                List<PropertyValue> propertyValueList = new ArrayList<>();
                for(int i = 0 ;i<childNodes.getLength();i++){
                    Node item = childNodes.item(i);
                    if(item instanceof Element) {
                         Element property = (Element)item;
                        if(BeanDefinitionParserDelegate.PROPERTY_ELEMENT.equals(property.getNodeName())){
                            String propertyName = property.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE);
                            String propertyValue = property.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE);
                            propertyValueList.add(new PropertyValue(propertyName,propertyValue));
                        }
                    }
                }
                beanDefinition.setPropertyValues(new MutablePropertyValues(propertyValueList));
            }
            // 保存beanDefinition
            beanDefinitionMap.put(beanName,beanDefinition);
            // 获取的beanName放到集合中
            beanDefinitionNames.add(beanName);
        }
    }

4.IOC注入

通过IocAutoWrite()进行IOC注入,主要是通过BeanDefinition转换成bean对象,并且放到map中。原本bean是一个代理对象,但是这里就从简,直接就放真实对象了。

    // 这里进行注入
    private void IocAutoWrite() throws IllegalAccessException, NoSuchFieldException, InstantiationException {
        System.out.println("进行注入");
        for(String beanName:beanDefinitionNames){
            this.getBean(beanName);
        }
    }
 @Override
    public Object getBean(String name)   {
        // 如果存在则直接返回
        if(singletonObjects.containsKey(name)){
            return singletonObjects.get(name);
        }else{
            Object o = null;
            try {
                o = doCreateBean(name);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return o;
        }
    }

在spring中,一般do开头的方法都需要重点的看下, 里面会实现很多逻辑。这里也高仿真一下。

    // 高仿 DefaultListableBeanFactory
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    //高仿DefaultSingletonBeanRegistry 用来存放单例的对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);   


private Object doCreateBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        // 现在只处理单例情况
        AbstractBeanDefinition beanDefinition = beanDefinitionMap.get(name);
        // 这次不说代理的情况,原本是使用cglib进行代理,这里简化下
        // 创建对象
         Class clazz = (Class) beanDefinition.getBeanClass();
         Object o = clazz.newInstance();
        // 设置属性  注意, 真实spring并不是这么设置的。。
        if(beanDefinition.getPropertyValues()!=null){
            List<PropertyValue> propertyValueList = beanDefinition.getPropertyValues().getPropertyValueList();
            for(PropertyValue propertyValue:propertyValueList){
                // 获取set方法的名称
                String setMethod = this.getSetMethod(propertyValue.getName());
                // 通过调用set方法进行赋值
                // 
                Method method = clazz.getMethod(setMethod,String.class);
                method.invoke(o,propertyValue.getValue());
            }
        }
        // 用来存放单例bean的map
        singletonObjects.put(name,o);
        return o;
    }

至此IOC注入完成。

5.测试

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userInfo" class="com.base.entity.UserInfo">
        <property name="name" value="张三"></property>
    </bean>
</beans>

创建xml文件

public class UserInfo {
    public Long id;
    public String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
        System.out.println(userInfo.getName());
    }
}

输出:

加载配置文件
初始化BeanDefinition
进行注入
张三

git地址:https://gitee.com/likuoblog/spring_in_pocket

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章