手写Spring最基础模块---<bean>标签是如何变成具体对象的

Spring是Java开发中最常见的框架之一。这里不讲其具体的理论原理之类的,给我印象最深的就是在配置文件ApplicationContext.xml中的<bean>标签下配置好id、class等属性,不需要显式地在代码中new一个对象,通过getBean()方法便能获得我们想要的对象实例。那么,我们不通过new是怎么构造出对象的呢?这其中的原理主要还是应用到“反射”特性。一步步来手动实现这一最基础的spring框架吧。

我们把所需要的类分为四个部分:

1.pojo类,主要用于封装<bean>标签、<property>标签中的属性。

2.registry类,Map作为成员属性,用Map将<bean>标签代表的实体类装起来

3.reader类,registry是reader中的成员属性,reader通过DOM解析.xml的配置文件,并且创建出<bean>标签代表的实体类,放入registry的Map中

4.IoC容器类,reader是容器类的成员属性,通过它的方法获得最终需要的对象实例。这里采用非延迟加载的方式,即创建容器类的时候就已经创建好配置文件中所有的对象实例,并放入容器类的Map中。

所有需要的类如图所示:

1 pojo类

1.1 PropertyValue 封装了<property>标签的属性:name、value、ref

1.2 MutablePropertyValues 一个<bean>标签下有多个<property>标签,在该类中定义成员变量List管理多个PropertyValue类

1.3 BeanDefinition 封装了<bean>标签的属性:id、class,其内部的<property>标签用MutablePropertyValues作为成员变量

2 registry类 包括了一个接口 一个实现类

接口定义了功能:注册BeanDefinition对象到注册表、删除指定BeanDefinition、获取指定BeanDefinition、判断是否存在指定BeanDefinition、获取BeanDefinition对象的个数、获取所有BeanDefinition的名称

实现类实现了如上功能,并且用Map作为成员变量,键是<bean>标签的id属性,值是BeanDefinition对象,代码如下

public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();

    //注册BeanDefinition对象到注册表
    @Override
    public void registryBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    //删除指定BeanDefinition
    @Override
    public void deleteBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    //获取指定BeanDefinition
    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    //判断是否存在指定BeanDefinition
    @Override
    public boolean contains(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    //获取BeanDefinition对象的个数
    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    //获取所有BeanDefinition的名称
    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

3 reader类 包括了一个接口,一个实现类

接口定义的功能有:获取成员变量注册表registry、加载xml配置文件并且在注册表registry中注册对象,即将BeanDefiniton保存在SimpleBeanDefinitionRegistry类中的Map里。

实现类实现具体功能,将SimpleBeanDefinitionRegistry作为自己的成员变量。主要特点在于利用了dom解析获得了xml配置文件中的<bean>标签,并且进一步按标签层级获得每一个标签的属性值。

    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        SAXReader reader = new SAXReader();   // maven中先配置dom4j
        Document document = reader.read(is);  // 获得dom对象
        Element rootElement = document.getRootElement();  // 获得根标签<beans>
        
        // 解析每一个<bean>
        List<Element> elements = rootElement.elements();
        for (Element element : elements) { // 一个element代表一个<bean>标签
            String id = element.attributeValue("id"); // 获得标签的id属性
            String className = element.attributeValue("class"); // 获得标签的class属性
            BeanDefinition beanDefinition = new BeanDefinition(); // 封装成BeanDefinition
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);

            List<Element> propertyList = element.elements("property");
            MutablePropertyValues propertyValues = new MutablePropertyValues();
            for (Element element1 : propertyList) { // 一个element1代表一个<property>标签
                String name = element1.attributeValue("name"); // 获得name属性
                String ref = element1.attributeValue("ref");   // 获得ref属性
                String value = element1.attributeValue("value");// 获得value属性
                PropertyValue propertyValue = new PropertyValue(name,value,ref); // 封装为PropertyValue
                propertyValues.addPropertyValue(propertyValue);
            }

            beanDefinition.setPropertyValues(propertyValues);

            registry.registryBeanDefinition(id,beanDefinition);
        }
    }


通过该方法的最后一句代码,将生成的BeanDefinition保存在SimpleBeanDefinitionRegistry类中的Map里。

 4 IoC容器类 包括两个接口,一个抽象类,一个具体实现类,它们的继承与实现关系如下

 容器的关键在于是否是非延迟加载,抽象类中不仅实现了ApplicationContext接口(该接口继承了BeanFactory接口)。我们采用的非延迟加载的实现就在于在抽象类实现ApplicationContext的方法refresh()。抽象类定义了三个成员变量,修饰符protected(供子类实现)。1.Map<id字符串,Object实例对象>;2.reader具体实现类,加载配置文件,根据注册表中的BeanDefinition进行创建对象。3.配置文件名,是一个字符串。

下面我们着重看一下refresh()方法,这是写在抽象类中的

public abstract class AbstractApplicationContext implements ApplicationContext{
    protected Map<String,Object> map = new HashMap<>(); // bean储存容器,是单例
    protected BeanDefinitionReader beanDefinitionReader;// 解析XML
    protected String configLocation; // XML文件

    @Override
    public void refresh() throws Exception {
        beanDefinitionReader.loadBeanDefinitions(configLocation); // 其中创建了BeanDefinition,PropertyValue,放入registry中

        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();// 获得上面的registry
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            getBean(beanName);  // 该方法子类实现(模板方法)
        }
    }
}

 在具体实现类ClassPathXmlApplicationContext的构造方法中调用此方法,可以在new ClassPathXmlApplicationContext时执行refresh(),实现非延时加载。在构造方法中还给成员变量reader赋值为具体的reader实现类。

具体实现类最重要的还是通过反射以及依赖注入获得具体的bean实例,代码如下:

@Override
    public Object getBean(String name) throws Exception {
        Object obj = this.map.get(name);
        if(obj != null){
            return obj; // map单例中已经存在
        }

        // 获取BeanDefinition封装的bean标签,通过反射创建bean实例
        BeanDefinitionRegistry registry = this.beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();

        // 依赖注入
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues) {// 每一个propertyValue代表一个property标签
            String propertyName = propertyValue.getName();
            String value = propertyValue.getValue();
            String ref = propertyValue.getRef();
            
            // ref和value只能有一个
            if(ref != null && !"".equals(ref)){// 对象
                Object beanRef = getBean(ref); // ref属性 通过递归获得ref实例
                String methodName = getSetterMethod(propertyName);
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(methodName.equals(method.getName())){
                        method.invoke(beanObj,beanRef);  // setRef()
                    }
                }
            }

            if(value != null && !"".equals(value)){// 基本数据类型/字符串
                String methodName = getSetterMethod(propertyName);
                Method method = clazz.getMethod(methodName, String.class);
                method.invoke(beanObj,value);
            }
        }


        // 返回中放入map,下次不用经过此处
        this.map.put(name,beanObj);
        return beanObj;
    }

在写一个getBean()的重载方法,采用泛型

@Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
        Object beanObj = getBean(name);
        if(beanObj == null){
            return null;
        }

        T beanCast = clazz.cast(beanObj);
        return beanCast;
    }

5 总结

这只是spring框架实现的冰山一角,但可以帮助我们了解为什么写好配置文件之后变可以获得对象实例。各种类一层套一层,下图可以比较层次清晰地显示出类之间的关系。

简单流程:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");

 在new实例完成后➡构造方法中的refresh()实现非延迟加载➡调用reader具体实现类的loadBeanDefinitions()方法➡将BeanDefinition放入registry的Map中➡获得registry实例➡获得registry的map中的BeanDefinition➡根据封装的标签,反射创建bean对象➡将bean对象放入IoC的map中➡下次调用getBean()方法直接通过Object obj = this.map.get(name)获得

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值