Spring Ioc 源码分析之Bean的加载和构造

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Ioc 源码分析之Bean的加载和构造相关的知识,希望对你有一定的参考价值。

我们都知道,Spring Ioc和Aop是Spring的核心的功能,因此花一点时间去研究还是很有意义的,如果仅仅是知其所以然,也就体会不到大师设计Spring的精华,还记得那句话,Spring为JavaEE开发带来了春天。
IOC就是Inversion of control 也就是控制反转的意思,另一种称呼叫做依赖注入,这个可能更直观一点,拿个例子来说吧:

@Component
public class UserService {
    @Autowired
    private UserMapper mapper;
}

比如在UserService可能要调用一个Mapper,这个Mapper去做DAO的操作,在这里我们直接通过@Autowired注解去注入这个Mapper,这个就叫做依赖注入,你想要什么就注入什么,不过前提它是一个Bean。至于是怎么注入的,那是Spring容器做的事情,也是我们今天去探索的。
在进行分析之前,我先声明一下,下面的这些代码并不是从spring 源码中直接拿过来,而是通过一步步简化,抽取spring源码的精华,如果直接贴源码,我觉得可能很多人都会被吓跑,而且还不一定能够学到真正的东西。
Spring要去管理Bean首先要把Bean放到容器里,那么Spring是如何获得Bean的呢?

首先,Spring有一个数据结构,BeanDefinition,这里存放的是Bean的内容和元数据,保存在BeanFactory当中,包装Bean的实体:

public class BeanDefinition {
    //真正的Bean实例
    private Object bean;
    //Bean的类型信息
    private Class beanClass;
    //Bean类型信息的名字
    private String beanClassName;
    //用于bean的属性注入 因为Bean可能有很多熟属性
    //所以这里用列表来进行管理
    private PropertyValues propertyValues = new PropertyValues();
}

PerpertyValues存放Bean的所有属性

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

PropertyValue存放的是每个属性,可以看到两个字段,name和valu。name存放的就是属性名称,value是object类型,可以是任何类型

public class PropertyValue {
    private final String name;
    private final Object value;
}

定义好这些数据结构了,把Bean装进容器的过程,其实就是其BeanDefinition构造的过程,那么怎么把一些类装入的Spring容器呢?

Spring有个接口就是获取某个资源的输入流,获取这个输入流后就可以进一步处理了:

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

UrlResource 是对Resource功能的进一步扩展,通过拿到一个URL获取输入流。

public class UrlResource implements Resource {
    private final URL url;
    public UrlResource(URL url) {
        this.url = url;
    }
    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

ResourceLoader是资源加载的主要方法,通过location定位Resource,
然后通过上面的UrlResource获取输入流:

public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}

大家可能会对上面的这段代码产生疑问:

    URL resource = this.getClass().getClassLoader().getResource(location);

为什么通过得到一个类的类类型,然后得到对应的类加载器,然后调用类加载器的Reource怎么就得到了URL这种类型呢?
我们来看一下类加载器的这个方法:

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

从这个方法中我们可以看出,类加载器去加载资源的时候,先会去让父类加载器去加载,如果父类加载器没有的话,会让根加载器去加载,如果这两个都没有加载成功,那就自己尝试去加载,这个一方面为了java程序的安全性,不可能你用户自己随便写一个加载器,就用你用户的。

接下来我们看一下重要角色,这个是加载BeanDefinition用的。

public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}

这个接口是用来从配置中读取BeanDefinition:
其中registry key是bean的id,value存放资源中所有的BeanDefinition

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

最后我们来看一个通过读取Xml文件的BeanDefinitionReader:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    /**
     * 构造函数 传入我们之前分析过的ResourceLoader 这个通过
     * location 可以 加载到Resource
     */
    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 这个方法其实是BeanDefinitionReader这个接口中的方法
     * 作用就是通过location来构造BeanDefinition
     */
    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        //把location传给ResourceLoader拿到Resource,然后获取输入流
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        //接下来进行输入流的处理
        doLoadBeanDefinitions(inputStream);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
        //因为xml是文档对象,所以下面进行一些处理文档工具的构造
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        //把输入流解析成一个文档,java可以处理的文档
        Document doc = docBuilder.parse(inputStream);
        // 处理这个文档对象 也就是解析bean
        registerBeanDefinitions(doc);
        inputStream.close();
    }

    public void registerBeanDefinitions(Document doc) {
        //得到文档的根节点,知道根节点后获取子节点就是通过层级关系处理就行了
        Element root = doc.getDocumentElement();
        //解析根节点 xml的根节点
        parseBeanDefinitions(root);
    }

    protected void parseBeanDefinitions(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //element有属性的包装
            if (node instanceof Element) {
                Element ele = (Element) node;
                processBeanDefinition(ele);
            }
        }
    }

    protected void processBeanDefinition(Element ele) {
        /**
         *  <bean id="object***" class="com.***.***"/>
         */
        //获取element的id
        String name = ele.getAttribute("id");
        //获取element的class
        String className = ele.getAttribute("class");
        BeanDefinition beanDefinition = new BeanDefinition();
        //处理这个bean的属性
        processProperty(ele, beanDefinition);
        //设置BeanDefinition的类名称
        beanDefinition.setBeanClassName(className);
        //registry是一个map,存放所有的beanDefinition
        getRegistry().put(name, beanDefinition);
    }

    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        /**
         *类似这种:
         <bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
         <property name="userDao" ref="userDaoImpl"> </property>
         </bean>
         */
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                //获得属性的名称
                String name = propertyEle.getAttribute("name");
                //获取属性的值
                String value = propertyEle.getAttribute("value");
                if (value != null && value.length() > 0) {
                    //设置这个bean对应definition里的属性值
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                } else {
                    //value是Reference的话  就会进入到这里处理
                    String ref = propertyEle.getAttribute("ref");
                    if (ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException("Configuration problem: <property> element for property ‘"
                                + name + "‘ must specify a ref or value");
                    }
                    //构造一个BeanReference 然后把这个引用方到属性list里
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }
}

上面用到了BeanReference(如下),其实这个和PropertyValue类似,用不同的类型是为了更好的区分:

public class BeanReference {
     private String name;
     private Object bean;
}

好了,到现在我们已经分析完了Spring是如何找到Bean并加载进入Spring容器的,这里面最主要的数据结构就是BeanDefinition,ReourceLoader来完成资源的定位,读入,然后获取输入流,进一步的处理,这个过程中有对xml文档的解析和对属性的填充。

以上是关于Spring Ioc 源码分析之Bean的加载和构造的主要内容,如果未能解决你的问题,请参考以下文章

Spring5源码分析(021)——IoC篇之bean加载

Spring源码分析之ioc容器是如何创建和获取单实例bean的

Spring5源码分析(006)——IoC篇之核心类DefaultListableBeanFactory和XmlBeanDefinitionReader

Spring IOC 容器源码分析 - getBean调用方法解析 -- Bean的初始化之BeanPostProcessor

Spring IoC源码解析之getBean

spring源码之IOC的核心部分