深入理解spring(spring源码(IOC))(七)

回顾,总结!

1、上一篇讲道JDK的动态代理。我们已经手动实现了一个山寨版本的动态代理。
2、思考一个问题?动态代理给我们的都是代理类。如果我们不想要代理类呢?怎么办?

1、spring 源码 之IOC(手动装配,自动装配)!

1、今天主要跟大家分享一下Spring 的IOC。

老规矩,先上代码!引入问题。
如下图所示,UserServiceImpl中要调用 UserDao的query()方法?我们该怎么做?

在这里插入图片描述

方法很多!

1、我们可以用代理。
2、可以用装饰者模式,把userDao对象传进去,
3、可以直接new UserDao()对象出来。。

先看看Spring怎么做的(Spring 手动装配)
1、先写一个我们自己的spring.xml 下面的xml 用过spring的人都很熟悉,这里就不做多的讲解了。
<?xml version="1.0" encoding="UTF-8"?>
<!--
    1、哪些类需要我来关联
    2、怎么告诉我这些类(写bean)
    3、怎么维护依赖关系(settr、constr)
    4、怎么体现setter或者constr
 -->
<beans>
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl">
        <!--set方法 方式 注入-->
        <property name="dao" ref="dao"></property>
    </bean>
</beans>
2、有了XML 我们只需要dom4j来解析XML 然后反射就能拿到bean对象完成set方法装配?

1、这里讲一下如下代码片段 山寨版本BeanFactory 的思路。
2、先传入需要解析的XML,实例化dom4j对象。
3、获取到XML的跟节点 拿到子节点,获取ID 与class就能拿到XML中我们定义的内容
4、判断如果 xml中存在字标签,且子标签的名称为property
5、从map中拿出 ref的引用对象,反射拿到名称为 name的属性,调用set方法,set对象到当前实例

public class BeanFactory {
    /**
     * 构造方法,传入要解析的xml
     */
    public BeanFactory(String xml) throws UnsupportedEncodingException {
        parseXml(xml);
    }
    //定义 模拟spring 容器
    Map<String,Object> map = new HashMap<String,Object>();
    /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //获取到当前类根目录下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //创建Dom4j读取spring.xml内容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //获取到跟节点
            Element elementRoot = document.getRootElement();
            //遍历节点
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id属性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class属性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 维护依赖关系
                 * 看这个对象有没有依赖(判断是否有property。或者判断类是否有属性)
                 * 如果有则注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通过value得到对象(map)
                    // 得到name的值,然后根据值获取一个Filed的对象
                    //通过field的set方法set那个对象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property")){
                        //由于是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }
                }
                if(object==null){//没有子标签
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }

    /**
     * 获取Bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return map.get(beanName);
    }
3、测试一把?
    public static void main(String[] args) throws UnsupportedEncodingException {
        UserService service = (UserService) new BeanFactory("spring.xml").getBean("service");
        service.find();
    }
    ------------------控制台输出-------------------------------
    {dao=com.myspring.dao.impl.UserDaoImpl1@3b9a45b3, service=com.myspring.service.UserServiceImpl@7699a589}
	service
	dao1

以上就完成 我们手动装配的 set方法装配!

4、那么问题来了,spring的构造方法装配。又是怎么实现的呢?

1、改造一下 刚刚的spring.xml文件。我们用构造方式装配。其实这些标签我们可以随便写。都懂吧?

<beans>
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl">
        <!--构造方法方式 注入-->
        <constructor-arg name="dao" ref="dao"></constructor-arg>
    </bean>
</beans>

2、改造一下,我们的实现类。加上一个构造方法。(既然是构造方法装配,肯定得有构造方法是吧?)

public class UserServiceImpl implements UserService {
    UserDao dao;
    public void setDao(UserDao userDao) {
        this.dao = userDao;
    }
    public UserServiceImpl(UserDao dao) {
        this.dao = dao;
    }
    @Override
    public void find() {
        System.out.println("service");
        dao.query();
    }
}

3、改造一下 我们山寨版的BeanFactory 添加构造方法装配的逻辑。
1.因为我们这个是 借鉴spring源码 山寨版实现的方式。。所以就没判断多实现的一种情况。。大家可以完善
1、具体思路,如果不为property 证明有构造方式 装配。
2、然后拿到 ref标签,通过ref名称 找到接口的第一个实现,(可能会多实现大家自己完善)
3、通过反射 实现构造方法模式的初始化。

public class BeanFactory {
    /**
     * 构造方法,传入要解析的xml
     */
    public BeanFactory(String xml) throws UnsupportedEncodingException {
        parseXml(xml);
    }
    //定义 模拟spring 容器
    Map<String,Object> map = new HashMap<String,Object>();
    /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //获取到当前类根目录下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //创建Dom4j读取spring.xml内容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //获取到跟节点
            Element elementRoot = document.getRootElement();
            //遍历节点
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id属性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class属性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 维护依赖关系
                 * 看这个对象有没有依赖(判断是否有property。或者判断类是否有属性)
                 * 如果有则注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通过value得到对象(map)
                    // 得到name的值,然后根据值获取一个Filed的对象
                    //通过field的set方法set那个对象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property")){
                        //由于是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        if (null == field)throw new RuntimeException("没有找到"+nameVlaue+"属性");
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }else {
                        //证明构造方法装配
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        Class injectObjectClazz = injetObject.getClass();
                        Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]);
                        object = constructor.newInstance(injetObject);
                    }
                }
                if(object==null){//没有子标签
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }

    /**
     * 获取Bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return map.get(beanName);
    }
5、测试构造方法装配?

同样得方式,这里就不贴 代码出来了。。大家可以自己去试一下。

6、spring 除了手动装配。还有一个自动装配。

1、接下来 演示一下自动装配的实现方式。

第一步 改造spring.xml

1、加上一个标签 default=“byType” 我们这次主要演示,byType的方式 实现自动装配。

<beans default="byType">
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl"></bean>
</beans>
第二步,改造业务代码类。

1、删掉了,构造方法,与set方法。这里没有加注解。我们一步一步来。。后面会引入注解。byName方式注入。

public class UserServiceImpl implements UserService {
    UserDao dao;
    @Override
    public void find() {
        System.out.println("service");
        dao.query();
    }
}
第三步,改造BeanFactory类。

1、如下,是我们第三次改造后 完整的 解析XML方法
2、具体思路如下。先判断是否包含有 自动注入的标签。如果有则 flag=true;
3、如果是自动注入,先拿到当前装配类的所有属性。
4、如果map中的class 与当前装配对象属性的CLass相等。则装配该属性到当前对象。

  /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //获取到当前类根目录下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //创建Dom4j读取spring.xml内容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //获取到跟节点
            Element elementRoot = document.getRootElement();
            //判断是否自动装配
            Attribute attribute = elementRoot.attribute("default");
            boolean flag=false;
            if (attribute!=null){
                flag=true;
            }
            //遍历节点
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                /**
                 * setup1、实例化对象
                 */
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id属性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class属性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 维护依赖关系
                 * 看这个对象有没有依赖(判断是否有property。或者判断类是否有属性)
                 * 如果有则注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通过value得到对象(map)
                    // 得到name的值,然后根据值获取一个Filed的对象
                    //通过field的set方法set那个对象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property") && elementSecondChil.getName().equals("property")){
                        //由于是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        if (null == field)throw new RuntimeException("没有找到"+nameVlaue+"属性");
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }else {
                        //证明构造方法装配
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        Class injectObjectClazz = injetObject.getClass();
                        Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]);
                        object = constructor.newInstance(injetObject);
                    }
                }
                //spring 自动装配逻辑
                if(object==null) {
                    if (flag) {
                        if (attribute.getValue().equals("byType")) {
                            //判断是否有依赖
                            Field fields[] = clazz.getDeclaredFields();
                            for (Field field : fields) {
                                //得到属性的类型,比如String aa那么这里的field.getType()=String.class
                                Class injectObjectClazz = field.getType();
                                /**
                                 * 由于是bytype 所以需要遍历map当中的所有对象
                                 * 判断对象的类型是不是和这个injectObjectClazz相同
                                 */
                                int count = 0;
                                Object injectObject = null;
                                for (String key : map.keySet()) {
                                    Class temp = map.get(key).getClass().getInterfaces()[0];
                                    if (temp.getName().equals(injectObjectClazz.getName())) {
                                        injectObject = map.get(key);
                                        //记录找到一个,因为可能找到多个count
                                        count++;
                                    }
                                }
                                if (count > 1) {
                                    throw new RuntimeException("需要一个对象,但是找到了两个对象");
                                } else {
                                    object = clazz.newInstance();
                                    field.setAccessible(true);
                                    field.set(object, injectObject);
                                }
                            }
                        }
                    }
                }
                if(object==null){//没有子标签
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }
7、测试自动装配

1、正向流程,就跟前面的测试一模一样。。大家可以自己去试。
2、这里讲一下反向流程
3、如图所示,如果我们userdao是有多个实现类。则按类型注入,因为都实现了 UserDao.就不知道找哪一个了。。
在这里插入图片描述

总结!

1、如上类容 我们先实现了。山寨版本的 set方法手动注入,
2、接着实现了。构造方法的方式注入。
3、然后实现了 自动装配。。
那么我们自己写的山寨版本的 BeanFactory 跟spring的BeanFactory有什么区别呢?
其实基本上spring 大致的思路 都是我们写的差不多。。不过spring的代码会复杂很多。。
后面我们会慢慢来研究spring的源码。。包括注解方式注入。后面都会一一研究。。

有兴趣的 朋友可以点一个关注哦。

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