手写一个Spring(第三章 第四章 第五章)

第三章 对象实例化策略

在刚才的代码里已经实现了实例化对象容器管理,但还有一个关键的问题。

实例对象并非是全部都是无参构造函数,在面对有参的构造函数,刚才的代码就不行了。newInstance(); 实例化方式并没有考虑构造函数的入参,所以这个章节需要解决的就是这个问题。

参考 Spring Bean 容器源码的实现方式,在 BeanFactory 中添加 Object getBean(String name, Object... args) 接口,这样就可以在获取 Bean 时把构造函数的入参信息传递进去了。

另外一个核心的内容是使用什么方式来创建含有构造函数的 Bean 对象呢?这里有 两种方式可以选择,一个是基于 Java 本身自带的方法 DeclaredConstructor,另外一个是使用 Cglib 来动态创建 Bean 对象。 Cglib 是基于字节码框架 ASM 实现,所以你也可以直接通过 ASM 操作指令码来创建对象

 InstantiationStrategy :实例化策略接口,cglib、jdk通过实现这个接口来创造一个被设置了属性的实例对象

public interface InstantiationStrategy {
    /**
     * 实例化策略接口,cglib、jdk通过实现这个接口来创造一个被设置了属性的实例对象
     * @param beanDefinition
     * @param beanName
     * @param constructor
     * @param objects
     * @return
     */
    Object instantiate( BeanDefinition beanDefinition, String beanName, Constructor constructor,Object[] objects);
}

在实例化接口 instantiate 方法中添加必要的入参信息,包括:beanDefinition、 beanName、ctor、args

 其中 Constructor 你可能会有一点陌生,它是 java.lang.reflect 包下的 Constructor 类,里面包含了一些必要的类信息,有这个参数的目的就是为了拿到 符合入参信息相对应的构造函数。

 而 args 就是一个具体的入参信息了,最终实例化时候会用到。

接下来是两个实例化策略类:

CglibSubclassingInstantiationStrategy & SimpleInstantiationStrategy
public class SimpleInstantiationStrategy implements InstantiationStrategy{
    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor constructor, Object[] objects) {
        Class clazz = beanDefinition.getBeanClass();
        try {
            if (constructor != null){
                return clazz.getDeclaredConstructor(constructor.getParameterTypes()).newInstance(objects);
            }else {
                return clazz.getDeclaredConstructor().newInstance();
            }
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
            e.printStackTrace();
            throw new RuntimeException("实例化失败!");
        }
    }
}

 首先通过 beanDefinition 获取 Class 信息,这个 Class 信息是在 Bean 定义的时 候传递进去的。

 接下来判断 ctor 是否为空,如果为空则是无构造函数实例化,否则就是需要有构 造函数的实例化。

这里我们重点关注有构造函数的实例化,实例化方式为 clazz.getDeclaredConstructor(ctor.getParameterTypes()).ne wInstance(args);,把入参信息传递给 newInstance 进行实例化。

public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy{
    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor constructor, Object[] objects) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        });
        if (null == constructor) return enhancer.create();
        return enhancer.create(constructor.getParameterTypes(), objects);
    }
}

接着我们改造一下AbstractAutowireCapableBeanFactory里的代码,选择一个实例化策略,并写一个createBeanInstance来通过传入的参数找到一个合适的构造方法

首先在 AbstractAutowireCapableBeanFactory 抽象类中定义了一个创建对象的实 例化策略属性类 InstantiationStrategy instantiationStrategy, 这里我们选择了 Cglib 的实现类。

接下来抽取 createBeanInstance 方法,在这个方法中需要注意 Constructor 代表了你有多少个构造函数,通过 beanClass.getDeclaredConstructors() 方式可以 获取到你所有的构造函数,是一个集合。

接下来就需要循环比对出构造函数集合与入参信息 args 的匹配情况,这里我们 对比的方式比较简单,只是一个数量对比,而实际 Spring 源码中还需要比对入参 类型,否则相同数量不同入参类型的情况,就会抛异常了。

 /**
     * 使用CGlib的实例化方式
     */
    private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();


    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition,Object ...args) {
        Object bean = createBeanInstance(beanName,beanDefinition,args);
        addSingleton(beanName,bean);
        return bean;
    }

    protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object[] objects){
        Constructor constructorToUse = null;
        //获取容器内实例的信息
        Class beanClass = beanDefinition.getBeanClass();
        //获取容器内实例的全部构造方法
        Constructor[] declaredConstructors = beanClass.getDeclaredConstructors();
        //循环构造方法,找到一个参数个数和objects相同的构造方法
        for (Constructor ctor : declaredConstructors) {
            //这里只是简单比较一下参数个数,实际上要考虑参数类型等问题
            if (null != objects && ctor.getParameterTypes().length == objects.length) {
                //将该构造方法保存
                constructorToUse = ctor;
                break;
            }
        }
        //创建构造方法
        return instantiationStrategy.instantiate(beanDefinition,beanName,constructorToUse,objects);
    }

    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }

 至此,刚才的IOC容器便可以创建一个带有参数的实例。

Debug环节

@Test
    public void test(){
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 2. 注入 bean
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService", beanDefinition);
        // 3.获取 bean
        UserService userService = (UserService) beanFactory.getBean("userService", "销户");
        userService.queryUserInfo();
    }

1.获取实例

2. 将getBean方法抽出成doGetBean

3.通过createBeanInstance选择一个符合参数个数的构造方法

 4.

5.通过构造方法,选择实例化策略,创建实例

 6.将实例加入到单例容器内并返回 

本章节的主要以完善实例化操作,增加 InstantiationStrategy 实例化策略接口,并新增了两个实例化类。这部分类的名称与实现方式基本是 Spring 框架的一个缩小版大家在学习过程中也可以从 Spring 源码找到对应的代码。

从我们不断的完善增加需求可以看到的,当你的代码结构设计的较为合理的时候, 就可以非常容易且方便的进行扩展不同属性的类职责,而不会因为需求的增加导致 类结构混乱。所以在我们自己业务需求实现的过程中,也要尽可能的去考虑一个良好的扩展性以及拆分好类的职责。

 动手是学习起来最快的方式,不要让眼睛是感觉看会了,但上手操作就废了。也希 望有需要的读者可以亲手操作一下,把你的想法也融入到可落地实现的代码里,看 看想的和做的是否一致

第四章 注入属性和依赖对象

首先我们回顾下这几章节都完成了什么,包括:实现一个容器、定义和注册 Bean、实例化 Bean,按照是否包含构造函数实现不同的实例化策略,那么在创 建对象实例化这我们还缺少什么?其实还缺少一个关于类中是否有属性的问题, 如果有类中包含属性那么在实例化的时候就需要把属性信息填充上,这样才是 一个完整的对象创建。 对于属性的填充不只是 int、Long、String,还包括还没有实例化的对象属 性,都需要在 Bean 创建时进行填充操作。不过这里我们暂时不会考虑 Bean 的循环依赖,否则会把整个功能实现撑大,这样新人学习时就把握不住了,待 后续陆续先把核心功能实现后,再逐步完善

属性填充要在类实例化创建之后,也就是需要在 AbstractAutowireCapableBeanFactory 的 createBean 方法中添加 applyPropertyValues 操作。

由于我们需要在创建 Bean 时候填充属性操作,那么就需要在 bean 定义 BeanDefinition 类中,添加 PropertyValues (一个List集合)信息。

另外是填充属性信息还包括了 Bean 的对象类型,也就是需要再定义一个 BeanReference,里面其实就是一个简单的 Bean 名称,在具体的实例化操作时进 行递归创建和填充,与 Spring 源码实现一样。Spring 源码中 BeanReference 是一个接口 

在BeanDefinition中添加PropertyValues属性,用于保存填充属性的值

PropertyValue:

public class PropertyValue {
    private final String name;

    private final Object value;

    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public Object getValue() {
        return value;
    }
}

 PropertyValues:

public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<>();

    public void addPropertyValue(PropertyValue pv) {
        this.propertyValueList.add(pv);
    }

    public PropertyValue[] getPropertyValues() {
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName)) {
                return pv;
            }
        }
        return null;
    }

}

在这里对AbstractAutowireCapableBeanFactory做一些调整。

  1. 对createBean做调整,在创建完Bean后,为Bean中的属性进行填充。
  2. 首先获取BeanDefinition里的PropertyValues,对他进行循环。如果是普通属性(int,long等)则直接进行填充。如果是BeanReference属性的值,则会调用一个getBean方法,递归获取 Bean 实例,并将创建的实例注入到当前实例下。
  3. 这里没有解决循环依赖的问题,后续处理。
 @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition,Object ...args) {
        Object bean;
        try {
            bean = createBeanInstance(beanName,beanDefinition,args);
            //为Bean填充属性
            applyPropertyValues(beanName,bean,beanDefinition);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("创建bean时出现异常");
        }
        addSingleton(beanName,bean);
        return bean;
    }

    private void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        try {
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
                String name = propertyValue.getName();
                Object value = propertyValue.getValue();
                if (value instanceof BeanReference){
                    //依赖注入
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }
                //属性填充
                BeanUtil.setFieldValue(bean,name,value);
            }
        }catch (Exception e){
            throw new RuntimeException("Error setting property values :"+beanName);
        }
    }

 Debug环节:

 @Test
    public void test_BeanFactory() {
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2. UserDao 注册
        beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));

        // 3. UserService 设置属性[uId、userDao]
        PropertyValues propertyValues = new PropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("uId", "10001"));
        propertyValues.addPropertyValue(new PropertyValue("userDao",new BeanReference("userDao")));

        // 4. UserService 注入bean
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues);
        beanFactory.registerBeanDefinition("userService", beanDefinition);

        // 5. UserService 获取bean
        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo();
    }

1.进入填充属性

2.第一遍因为是填充普通属性,没有进入if

 3.第二遍填充的是一个BeanReference属性,所以会进入if内递归调用一次getBean获取实例

接下来就注入成功,代码结束。

1.在本章节中我们把 AbstractAutowireCapableBeanFactory 类中的创建对象功能又 做了扩充,依赖于是否有构造函数的实例化策略完成后,开始补充 Bean 属性信 息。当遇到 Bean 属性为 Bean 对象时,需要递归处理。最后在属性填充时需要 用到反射操作,也可以使用一些工具类处理。

2.每一个章节的功能点我们都在循序渐进的实现,这样可以让新人更好的接受关于 Spring 中的设计思路。尤其是在一些已经开发好的类上,怎么扩充新的功能时候 的设计更为重要。学习编程有的时候学习思路设计要比仅仅是做简单实现,更能提 升编程思维。

3.到这一章节关于 Bean 的创建操作就开发完成了,接下来需要整个框架的基础上完 成资源属性的加载,就是我们需要去动 Xml 配置了,让我们这小框架越来越像 Spring。另外在框架实现的过程中所有的类名都会参考 Spring 源码,以及相应的 设计实现步骤也是与 Spring 源码中对应,只不过会简化一些流程,但你可以拿相 同的类名,去搜到每一个功能在 Spring 源码中的实现。

第五章 资源加载器解析文件注册对象

在完成 Spring 的框架雏形后,现在我们可以通过单元测试进行手动操作 Bean 对象的定义、注册和属性填充,以及最终获取对象调用方法。但这里会有一个 问题,就是如果实际使用这个 Spring 框架,是不太可能让用户通过手动方式 创建的,而是最好能通过配置文件的方式简化创建过程。需要完成如下操作:

 1. 如图中我们需要把步骤:2、3、4 整合到 Spring 框架中,通过 Spring 配置文件的 方式将 Bean 对象实例化。

2.接下来我们就需要在现有的 Spring 框架中,添加能解决 Spring 配置的读取、解析、注册 Bean 的操作。

1. 资源加载器属于相对独立的部分,它位于 Spring 框架核心包下的 IO 实现内容, 主要用于处理 Class、本地和云环境中的文件信息。

2. 当资源可以加载后,接下来就是解析和注册 Bean 到 Spring 中的操作,这部分实现需要和 DefaultListableBeanFactory 核心类结合起来,因为你所有的解析后的注 册动作,都会把 Bean 定义信息放入到这个类中。

3. 那么在实现的时候就设计好接口的实现层级关系,包括我们需要定义出 Bean 定义 的读取接口 BeanDefinitionReader 以及做好对应的实现类,在实现类中完 成对 Bean 对象的解析和注册。 

 设计:

 1.本章节为了能把 Bean 的定义、注册和初始化交给 Spring.xml 配置化处理,那么 就需要实现两大块内容,分别是:资源加载器、xml 资源处理类,实现过程主要以 对接口 Resource、ResourceLoader 的实现,而另外 BeanDefinitionReader 接口则是对资源的具体使用,将配置信息注册到 Spring 容器中去。

2. 在 Resource 的资源加载器的实现中包括了,ClassPath、系统文件、云配置文件, 这三部分与 Spring 源码中的设计和实现保持一致,最终在 DefaultResourceLoader 中做具体的调用。

3.接口:BeanDefinitionReader、抽象类:AbstractBeanDefinitionReader、实现类: XmlBeanDefinitionReader,这三部分内容主要是合理清晰的处理了资源读取后的注 册 Bean 容器操作。接口管定义,抽象类处理非接口功能外的注册 Bean 组件填充,最终实现类即可只关心具体的业务实现

1. BeanFactory,已经存在的 Bean 工厂接口用于获取 Bean 对象,这次新增加了按 照类型获取 Bean 的方法: T getBean(String name, Class requiredType)

2. ListableBeanFactory,是一个扩展 Bean 工厂接口的接口,新增加了 getBeansOfType、getBeanDefinitionNames() 方法,在 Spring 源码 中还有其他扩展方法。

3. HierarchicalBeanFactory,在 Spring 源码中它提供了可以获取父类 BeanFactory 方法,属于是一种扩展工厂的层次子接口。Sub-interface implemented by bean factories that can be part of a hierarchy.

4. AutowireCapableBeanFactory,是一个自动化处理 Bean 工厂配置的接口,目前案例工程中还没有做相应的实现,后续逐步完善。

5. ConfigurableBeanFactory,可获取 BeanPostProcessor、BeanClassLoader 等的一个 配置化接口。

6. ConfigurableListableBeanFactory,提供分析和修改 Bean 以及预先实例化的操作接 口,不过目前只有一个 getBeanDefinition 方法。

 Resource接口定义了一个获取输入流的方法,之后的三种读取配置文件(classPath、File、net)的方法都会实现这个接口

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

 classPath主要是通过ClassLoader类来读取classPath路径下的配置文件

public class ClassPathResource implements Resource{

    private final String path;

    private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        this.path = path;
        this.classLoader = (classLoader != null) ? classLoader : ClassUtil.getClassLoader();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is = classLoader.getResourceAsStream(path);
        if (is == null){
            throw new FileNotFoundException(this.path + "cannot be opened it does not exist");
        }
        return is;
    }
}

FileSystem通过指定文件的方式获取流 ,比较常用

public class FileSystemResource implements Resource{

    private final File file;

    private final String path;

    public final String getPath() {
        return this.path;
    }

    public FileSystemResource(File file) {
        this.file = file;
        this.path = file.getPath();
    }

    public FileSystemResource(String path) {
        this.file = new File(path);
        this.path = path;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }
}

URL通过网络路径的方式读取,可以读取git仓库里的文件

public class UrlResource implements Resource{

    private final URL url;

    public UrlResource(URL url) {
        Assert.notNull(url,"URL must not be null!!");
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection con = this.url.openConnection();
        try {
            return con.getInputStream();
        }catch (IOException e){
            if (con instanceof HttpURLConnection){
                ((HttpURLConnection) con).disconnect();
            }
            throw e;
        }
    }
}

按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下 进行处理,外部用户只需要传递资源地址即可,简化使用。

public interface ResourceLoader {

    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String location);

}

1.在获取资源的实现中,主要是把三种不同类型的资源处理方式进行了包装,分为: 判断是否为 ClassPath、URL 以及文件。

2. 虽然 DefaultResourceLoader 类实现的过程简单,但这也是设计模式约定的具体结 果,像是这里不会让外部调用放知道过多的细节,而是仅关心具体调用结果即可。 

public class DefaultResourceLoader implements ResourceLoader{
    @Override
    public Resource getResource(String location) {
        Assert.notNull(location,"location must not be null!");
        if (location.startsWith(CLASSPATH_URL_PREFIX)){
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
        }else {
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            }catch (MalformedURLException e) {
                return new FileSystemResource(location);
            }
        }
    }
}

1.在获取资源的实现中,主要是把三种不同类型的资源处理方式进行了包装,分为: 判断是否为 ClassPath、URL 以及文件。

2.虽然 DefaultResourceLoader 类实现的过程简单,但这也是设计模式约定的具体结果,像是这里不会让外部调用放知道过多的细节,而是仅关心具体调用结果即可。

public interface BeanDefinitionReader {
    //提供给后面三个加载Bean的方法,用于加载和注册Bean
    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    void loadBeanDefinitions(Resource resource) throws BeansException;

    void loadBeanDefinitions(Resource ...resources) throws BeansException;

    void loadBeanDefinitions(String location) throws BeansException;

}

1.这是一个 Simple interface for bean definition readers. 其实里面无非定义了几个方 法,包括:getRegistry()、getResourceLoader(),以及三个加载 Bean 定义的方法。 

2.这里需要注意 getRegistry()、getResourceLoader(),都是用于提供给后面三个方法 的工具,加载和注册,这两个方法的实现会包装到抽象类中,以免污染具体的接口实现方法。

Bean定义抽象类实现

package cn.hwh.springframework.beans.factory.support;

import cn.hwh.springframework.beans.BeansException;
import cn.hwh.springframework.core.io.DefaultResourceLoader;
import cn.hwh.springframework.core.io.Resource;
import cn.hwh.springframework.core.io.ResourceLoader;

/**
 * @author wenhao hu
 * @title: AbstractBeanDefinitionReader
 * @projectName mini-spring
 * @description: TODO
 * @date 2022/1/2514:30
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry){
        this(registry,new DefaultResourceLoader());
    }

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry,ResourceLoader resourceLoader){
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

1.抽象类把 BeanDefinitionReader 接口的前两个方法全部实现完了,并提供了构造 函数,让外部的调用使用方,把 Bean 定义注入类,传递进来。

2. 这样在接口 BeanDefinitionReader 的具体实现类中,就可以把解析后的 XML 文 件中的 Bean 信息,注册到 Spring 容器去了。以前我们是通过单元测试使用,调用 BeanDefinitionRegistry 完成 Bean 的注册,现在可以放到 XMl 中操作了

解析 XML 处理 Bean 注册

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try (InputStream inputStream = resource.getInputStream()) {
            doLoadBeanDefinitions(inputStream);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            throw new BeansException("IOException parsing Xml document from" + resource + e);
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        Document doc = XmlUtil.readXML(inputStream);
        Element root = doc.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            //判断元素
            if (!(childNodes.item(i) instanceof Element)) continue;
            //判断对象
            if (!"bean".equals(childNodes.item(i).getNodeName())) continue;

            //解析标签
            Element bean = (Element) childNodes.item(i);
            String id = bean.getAttribute("id");
            String name = bean.getAttribute("name");
            String className = bean.getAttribute("class");
            //获取Class,方便获取类中的名称
            Class<?> clazz = Class.forName(className);

            //优先使用ID作为Bean的名称
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            //如果为空,则取Class的名称
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }
            //定义Bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            //读取属性并填充
            for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
                //判断元素
                if (!(childNodes.item(j) instanceof Element)) continue;
                //判断对象
                if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;

                //解析标签 property
                Element property = (Element) bean.getChildNodes().item(j);
                String attrName = property.getAttribute("name");
                String attrValue = property.getAttribute("value");
                String attrRef = property.getAttribute("ref");

                //获取属性值:引入对象、值对象
                Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
                //创建属性信息
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            //如果该Bean存在,表明Bean出现重复
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException(String.format("重复的Bean[%s]", beanName));
            }
            // 注册 BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }

    }

}

XmlBeanDefinitionReader 类最核心的内容就是对 XML 文件的解析,把我们本 来在代码中的操作放到了通过解析 XML 自动注册的方式。

 loadBeanDefinitions 方法,处理资源加载,这里新增加了一个内部方法: doLoadBeanDefinitions,它主要负责解析 xml

在 doLoadBeanDefinitions 方法中,主要是对 xml 的读取 XmlUtil.readXML(inputStream) 和元素 Element 解析。在解析的过程中 通过循环操作,以此获取 Bean 配置以及配置中的 id、name、class、value、ref 信息。

最终把读取出来的配置信息,创建成 BeanDefinition 以及 PropertyValue,最终把 完整的 Bean 定义内容注册到 Bean 容器: getRegistry().registerBeanDefinition(beanName, beanDefinition)

Debug环节:

项目结构:

 测试代码:

 @Test
    public void test_xml() {
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2. 读取配置文件&注册Bean
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("classpath:spring.xml");

        // 3. 获取Bean对象调用方法
        UserService userService = beanFactory.getBean("userService", UserService.class);
        String result = userService.queryUserInfo();
        System.out.println("测试结果:" + result);
    }

1.调取AbstractBeanDefinitionReader的构造方法

调取父类的构造方法,将BeanFactory注入

 2.AbstractBeanDefinitionReader创建默认的资源加载器

 3.读取配置文件

 4.获取刚才创建的默认资源加载器,调用方法来分析location并创建相应的资源加载实现。

 

 5.获取资源的文件流,调用BeanDefinitionReader接口定义的load方法对文件加载

 6.调用doLoadBeanDefinitions方法,开始分析文件

分析流程:

  1. 判断hi否是一个xml文件,并获取文件的节点list
  2. 遍历文件节点list,判断是否为一个节点且节点名称是否为bean
  3. 获取节点的基本信息并放到BeanDefinition中
  4. 获取节点的属性值,如果是普通属性则放入PropertyValue并加到BeanDefinition里的PropertyValues中。如果是ref属性则需要将这个ref放入一个BeanReference中
  5. 判断这个beanName在注册容器中是否被注册过,没注册过则进行注册。
  6. 循环到文件读取完毕

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值