Spring_模拟基于XML方式的SpringIOC(模拟属性注入, 构造方法注入, 自动注入)

1. 控制反转概念:

控制反转是一个比较抽象的概念,举例说明。在实际生活中,人们要用到一样东西时,人们的基本想法是找到东西,比如想喝杯橙汁,在没有饮品店的日子里,最直观的做法是,要买果汁机、橙子,准备开水。请注意这是你自己“主动”创造的过程,也就是一杯橙汁需要主动创造。然而到了今时今日,由于饮品店的盛行,已经没有必要自己去榨橙汁了。想喝橙汁的想法一出现,第一个想法是找到饮品店的联系方式,通过电话、微信等渠道描述你的需要、地址、联系方式等,下订单等待,过会就会有人送上橙汁了。请注意你并没有“主动”创造橙汁,也就是橙汁是由饮品店创造的,而不是你,但是也完全达到了你的要求。

控制反转是一种通过描述(在Java中可以是XML或者注解)并通过第三方去产生或获取特定对象的方式。

在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。正如上述的例子,果汁制造器依赖于饮品店和订单去制造果汁的,而饮品店是别人去创造的,我们只需要知道它能生产果汁就可以了,并不需要去理解如何创建果汁。

Spring IoC容器的设计主要是基于BeanFactoryApplicationContext两个接口,其中ApplicationContext是BeanFactory的子接口之一,换句话说BeanFactory是Spring IoC容器所定义的最底层接口,而ApplicationContext是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分的工作场景下,都会使用ApplicationContext作为Spring IoC容器

2. 模拟控制反转

思路:

  1. 对象信息是描述在XML文件中, 要实现控制反转, 首先要获取到对象信息; 因此可以使用XML解析工具对配置文件进行解析
  2. 模仿Spring中的配置信息: , 在每个bean标签中可以获取到对象名称和类路径, 有了类路径, 那么我们可以通过反射来实例化对象;
  3. 生成之后存储在哪里呢? 因为我们后面需要通过getBean()来获取到指定对象, 那么在实例化对象后, 需要将对象存储在容器中, 以便我们可以通过name获取到该对象; 因此我们可以使用map来存储已实例化的对象, name的值为key, 实例化对象为value;
  4. 但是在实例化某个对象时, 这个对象依赖于另外其他对象, 那么需要维护这些依赖关系, 也就是依赖注入; 常用的依赖注入: 属性注入, 构造方法注入, 自动注入这三种, 因此可以根据不同依赖的特点在解析实例化对象时采用不同注入策略;
  5. 属性注入: 通过进行配置, 通过被实例化对象的CLass对象获取到该类中的属性对象, 从map中通过userDao获取到已实例化完成的属性对象,然后注入;
    构造方法注入: 通过进行配置, 并且在需要维护依赖的类中声明带参的构造方法, 因此也会导致无参构造函数失效; 跟属性注入不同,属性注入可以通过默认构造函数直接进行对象实例化, 但是由于已经声明了带参够赞方法, 那么无参构造方法无法调用, 因此我们需要先根据声明的参数对象userDao创建带参构造方法, 在利用该构造方法对对象进行实例化
    自动注入: 实现自动注入, 首先需要加入自动注入标识; 在解析根标签时会对beans的属性进行解析, 判断是否声明了自动注入, 由于前两种输入方法优先级高于自动注入; 因此, 我们可以在两种注入方式实现完成后, 如果此时该对象存在属性依赖, 但是在前两种,注入方式中并没有解决依赖关系, 那么可以通过自动注入来维护依赖; 自动注入又分为两种: 一种是根据类型注入, 另一种是根据名称进行注入; 根据类型注入, 我们在获取到属性对象时, 可以获取到该对象的类型, 然后循环map容器, 逐个判断是否有类型相同的对象; 注意: 此处的类型相同是对象实现接口的类型; 在判断时要进行技术 因为可能存在两个实现类实现了同一个接口, 因此如果匹配到两个或以上的则需要抛出异常,这里可以自定义异常; 根据名称注入, 通过名称注入, 我们只需要获取到对象的属性名称或者setter方法的名称, 然后循环map容器, 判断是否有相同的名称, 如果有,则进行注入;
    注意, 这里是不存在出现两个相同名称的属性对象的; 在注册到map容器中, 如果存在相同的名称,则会进行覆盖;

注意: 在模拟过程中, 只考虑标准情况; 在Spring实例化对象维护依赖时, 如果依赖的对象还没有注册到容器中, 那么Spring会先去注册依赖的对象, 并加入缓存, 在按顺序注册对象时也会首先检查缓存中是否已经有该对象, 在spring中对循环依赖注入的解决方法比较复杂, 在实例化对象时, 在未完成实例化时就会提前将对象暴露在缓存中; 这里稍微提一下, 感兴趣的可以去阅读源码


[项目源码已托管到github上,需要的自行下载] -->下载源码


2.1 创建dao层接口

package com.lic.dao;
public interface UserDao {
    public void query();
}

2.2 创建dao层实现类

package com.lic.dao;
public class UserDaoImpl implements UserDao{
    @Override
    public void query() {
        System.out.println("模拟查询");
    }
}

2.3 创建service层接口

package com.lic.service;

public interface UserService {
    public void query();
}

2.4 创建service层实现类

package com.lic.service;
import com.lic.dao.UserDao;
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    private UserDao userDao1;
 
    //构造方法
    /*public UserServiceImpl(UserDao userDao,UserDao userDao1) {
        this.userDao = userDao;
        this.userDao1 = userDao1;
    }*/
    
    public void query() {
        userDao.query();
        userDao1.query();
    }
}

2.5 创建配置文件




    
    

    
        
       

        
       
    

2.6 创建工厂类

package com.lic.spring;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

public class BeanFactory {
    // ioc容器, 用来存储注册的bean对象
    Map map =new HashMap<>();
    // 构造方法
    public BeanFactory(String path){
        parseXml(path);
    }

    //解析配置文件并注册相关对象
    private void parseXml(String path){
        File file = new File(this.getClass().getResource("/").getPath()+"//"+path);
        //需要导入dom4j jar包
        SAXReader reader = new SAXReader();
        try{
            //使用dom4j解析配置文件
            Document document = reader.read(file);
            //获取根节点
            Element elementRoot = document.getRootElement();

            //自动注入标签,默认为false
            boolean autowireFlag = false;
            String autowire = elementRoot.attribute("default-autowire").getValue();
            //如果设置了自动注入,则在后面将会视情况进行注入(属性注入, 构造方法注入优先级高于自动注入)
            if (autowire.trim().length() > 1) {
                autowireFlag = true;
            }

            //获取根节点的子节点
            for (Iterator beanFir = elementRoot.elementIterator(); beanFir.hasNext();) {
                /**
                 * setup1、实例化对象, 获取实例化对象的类信息
                 */
                Element elementChild = beanFir.next();
                //获取当前bean对象name属性值
                String attrName = elementChild.attribute("name").getValue();
                //获取当前bean对象的class属性值
                String attrClass = elementChild.attribute("class").getValue();
                Class clazz = Class.forName(attrClass);

                /**
                 * setup2、维护依赖关系
                 * 看这个对象有没有依赖(判断是否有property子标签。或者判断类是否有属性)
                 * 如果有则注入
                 */
                Object object = null;
                //存储构造方法的参数,实例化对象时需要参数
                List conObj = new ArrayList();
                //存储构造方法参数的类型,以便可以找到指定构造方法
                List conArgs = new ArrayList();
                //当构造方法所有参数准备完毕后,在for循环外对对象进行实例化
                boolean haveConstructor = false;
                for (Iterator propertyFir = elementChild.elementIterator(); propertyFir.hasNext(); ) {
                    /*
                     *    通过属性名称进行注入
                     */
                    //获取属性键值
                    Element propertyEle = propertyFir.next();
                    if (propertyEle.getName().equals("property")) {
                        System.out.println(attrName + "正在通过属性名称注入...");
                        //利用默认构造函数实例化对象
                        if (object == null)
                            object = clazz.newInstance();
                        //获取name属性值
                        String proAttrName = propertyEle.attribute("name").getValue();
                        //获取ref属性值
                        String proAttrRef = propertyEle.attribute("ref").getValue();
                        //从容器中获取到已注册的属性对象
                        Object objectRefObj = map.get(proAttrRef);
                        //获取对象依赖的属性
                        Field property = clazz.getDeclaredField(proAttrName);
                        property.setAccessible(true);
                        property.set(object, objectRefObj);

                        /*
                         *    通过构造函数进行注入
                         */
                    } else if (propertyEle.getName().equals("constructor-arg") && object == null) {
                        System.out.println(attrName + "正在通过构造方法注入...");
                        haveConstructor = true;
                        //不能使用默认构造函数实例化对象
                        //获取ref属性值
                        String conArgRef = propertyEle.attribute("ref").getValue();
                        //从容器中获取到已注册的属性对象
                        Object conArgRefObj = map.get(conArgRef);
                        conObj.add(conArgRefObj);
                        conArgs.add(conArgRefObj.getClass().getInterfaces()[0]);
                    } else if(object == null){
                        throw new CannotParseExecption("这啥标签啊,解析失败...");
                    }
                }
                //通过自定义构造函数初始化对象
                if (haveConstructor) {
                    Class[] args = new Class[conArgs.size()];
                    for (int i = 0; i < conArgs.size(); i++) {
                        args[i] = conArgs.get(i);
                    }
                    Constructor constructor = clazz.getConstructor(args);
                    object = constructor.newInstance(conObj.toArray());
                }

                //如果声明了自动注入,并且在bean中没有使用属性注入或构造函数注入,则使用自动注入
                if (autowireFlag && object == null) {
                    /*
                     *   根据类型自动注入注入
                     */
                    if ("byType".equalsIgnoreCase(autowire)) {
                        //获取当前类中定义的所有属性
                        Field[] fields = clazz.getDeclaredFields();
                        for (Field field : fields) {
                            //获取属性类型的名称: com.lic.UserDao
                            String fieldClassName = field.getType().getName();
                            //复合条件的bean对象的个数
                            int countClass = 0;
                            //存储符合条件的bean对象
                            Object tempObject = null;
                            //根据属性的class逐个判断与map中已注册的类实现的接口是否相同,如果相同且只有一个,则注入
                            for (String key : map.keySet()) {
                                //获取map中已注册的类实现的接口
                                String tempClassName = map.get(key).getClass().getInterfaces()[0].getName();
                                //进行比较
                                if (fieldClassName.equals(tempClassName)) {
                                    countClass++;
                                    tempObject = map.get(key);
                                }
                            }
                            if (countClass > 1) {
                                //如果匹配到两个相同的,则抛出异常
                                throw new TooManyExpectClassrExecption("期望找到一个对象,但是找到了两个!");
                            } else {
                                System.out.println(attrName + "正在根据类型自动注入...");
                                //使用默认构造函数创建当前对象
                                if (object == null)
                                    object = clazz.newInstance();
                                //设置属性值
                                field.setAccessible(true);
                                field.set(object, tempObject);
                            }
                        }

                        /*
                         *   根据属性名称自动注入
                         */
                    } else if ("byName".equalsIgnoreCase(autowire)) {
                        Field[] fields = clazz.getDeclaredFields();
                        for (Field field : fields) {
                            String fieldName = field.getName();
                            int countClass = 0;
                            for (String key : map.keySet()) {
                                if (fieldName.equalsIgnoreCase(key)) {
                                    //在map中key是唯一的,所以不可能出现第二个相同的name
                                    System.out.println(attrName + "正在根据名称自动注入...");
                                    if (object == null)
                                        object = clazz.newInstance();
                                    field.setAccessible(true);
                                    field.set(object, map.get(key));
                                    break;
                                }
                            }
                        }
                    } else {
                        throw new CannotParseExecption("这啥属性啊,解析失败...");
                    }
                }

                //如果该bean对象没有子标签,则调用默认构造方法
                if(object == null){
                    object=clazz.newInstance();
                }
                //将创建好的对象放入容器中
                map.put(attrName,object);

            }
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    public Object getBean(String beanName){
        return map.get(beanName);
    }
}

 
  

2.7 自定义异常

package com.lic.spring;
public class CannotParseExecption extends RuntimeException{
    public CannotParseExecption(String msg) {
        super(msg);
    }
}
package com.lic.spring;
public class TooManyExpectClassrExecption  extends RuntimeException{
    public TooManyExpectClassrExecption(String msg) {
        super(msg);
    }
}

2.8 创建测试方法

package com.lic.test;
import com.lic.service.UserService;
import com.lic.spring.BeanFactory;

public class SpringTest {
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactory("spring.xml");
        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.query();
    }
}

分析:
通过dom4j对配置文件进行解析, 将解析后的对象信息, 利用反射生成对象注册到map容器中, 并维护依赖, 对依赖的维护主要有三种:属性注入, 构造方法注入, 自动注入; 在生命自动注入后, 如果在自动注入之前使用了其他两种类型注入, 则自动注入不再使用;

下一篇:
Spring源码_模拟基于注解方式的SpringIOC(模拟ByType自动注入,ByName自动注入)

你可能感兴趣的:(Spring4.x源码解析)