为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Java


我们都知道,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文档的解析和对属性的填充。

感兴趣可以加Java架构师群获取Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限 都是大牛带飞 让你少走很多的弯路的 群..号是:855801563 对了 小白勿进 最好是有开发经验

注:加群要求

1、具有工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。

5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

©著作权归作者所有:来自51CTO博客作者java架构师1的原创作品,如需转载,请注明出处,否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消