Spring的核心IOC容器的实现

Spring的核心IOC容器的实现

        1.1  IOC容器和依赖反转模式

        依赖反转:依赖的对象获得了反转,还有叫依赖注入的,我们都知道很多应用都是由多个类通过彼此的合作来实现业务逻辑的,这使每个对象都需要与其合作对象的引用,如果获取过程靠自身实现,会导致代码高度耦合并且难以测试

        首先我简单说一下java对象是怎么样的一个实例化过程:

          (1)检查类是否加载,如果没有加载就加载这个类,并且加载所有父类,简单说就是创建完类会生成.java文件,然后通过IDEA或者其他编译器javac命令生成.class文件

            (2)虚拟机会通过main方法入口,寻找到new关键字在内存堆中分配对象空间,递归分配所有父类和子类属性空间,先父类在子类,属性默认自动初始化,自动初始化为“0”值

             (3)进行属性的赋值,递归调用父类构造器(默认调用父类无参构造器)在调用本类构造器

        举个例子:

        下面有一个描述账户的类

  public class Account {
            private Integer id;
            private String name;//用户名称
            private Double money;//账户金额
            //getter setter toString
        }

        创建AccountDao接口及其实现类,模拟新增账户方法

public interface AccountDao {
    /**
     * 保存账户信息
     */
    void save(Account account);
}
public class AccountDaoImpl implements AccountDao {

    public void save(Account account) {
        System.out.println("模拟保存账户:保存成功啦!!!");
    }
}

       创建AccountService接口及其实现类,模拟新增账户方法

    

public interface AccountService {
    /**
     * 保存账户信息
     */
    void save(Account account);
}

    

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDaoImpl();//此处有耦合

    public void save(Account account) {
        accountDao.save(account);
    }
}

 

       我们使用面向对象的系统可以对某一类的事物进行增删改查,但是会有一个问题,如果合作的对象的引用或依赖关系的管理由具体的对象来完成,会导致代码的高度耦合和可测试性降低,要是把这种对于对象的封装和数据的处理,对象的依赖关系交给框架或IOC容器来完成,即可以解耦也可提高代码的可测试性

        那是Spring容器的是怎么解耦的呢?

       我们就以XML方式为例

        首先创建beans.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!--
   配置需要注入自定义IOC容器的对象
   id 唯一标识
   class 类全限定名
-->
<beans>
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
</beans>

创建Bean工厂类:初始化Bean工厂时创建所有的对象,装载到工厂

public class BeanFactory {
    //IOC容器的本质其实就是一个Map集合;key-value,key是唯一标识,value是创建好的对象
    private static Map<String,Object> map = new HashMap<String, Object>();
    /**
     * 目标:自定义IOC容器,实现三层架构解耦。
     * 解耦三层架构:将对象的创建,交给Bean工厂,
     * 需要对象的时候直接从工厂中取,而不是自己new。
     * 1.创建Beans.xml配置文件
     * 2.类加载器读取Beans.xml配置文件输入流
     * 3.创建xml文件解析器SAXReader,解析XML文件
     * 4.用XPath取出配置文件中的所有bean标签,返回元素的集合对象
     * 5.循环遍历标签list集合,取出bean标签的id和class
     * 6.射创建对象,装进自定义ioc容器
     * 7.Bean工厂对外提供方法,获取自定义IOC容器中的对象
     */
    static {
        //2.类加载器读取Beans.xml配置文件输入流
        InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        try {
            //3.创建xml文件解析器SAXReader,解析XML文件
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            //4.用XPath取出配置文件中的所有bean标签,返回元素的集合对象
            List<Element> elementNodes = document.selectNodes("//bean");
            //5.循环遍历标签list集合,取出bean标签的id和class
            for (Element element : elementNodes) {
                //6.射创建对象,装进自定义ioc容器
                //<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
                String idValue = element.attributeValue("id");
                String classValue = element.attributeValue("class");
                Class<?> clazz = Class.forName(classValue);
                Object obj = clazz.newInstance();
                //装进自定义ioc容器
                map.put(idValue,obj);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /**
     * 7.Bean工厂对外提供方法,获取自定义IOC容器中的对象
     * 根据bean对象唯一标识获取工厂中创建好对象
     */
    public static Object getBean(String id){
       return map.get(id);
    }
}

        在AccountService实现类中,通过工厂获取对象

public class AccountServiceImpl implements AccountService {
    //耦合
    //private AccountDao accountDao = new AccountDaoImpl();
    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

    public void save(Account account) {
        accountDao.save(account);
    }
}

       编写测试类

public class TestCustomIoc {
    @Test
    public void test(){
        //AccountService accountService = new AccountServiceImpl();
        AccountService accountService = (AccountService) BeanFactory.getBean("accountService");
        accountService.save(new Account());
    }
}

二.IoC中两个主要的容器系列

    

    1.实现了BeanFactory接口的简单容器系统,实现了容器的最基本功能

    2.ApplicationContext应用上下文,作为容器的高级形态存在.

    

从上面接口设计图中可以看到两条主要的设计主线:

    第一条从BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是主要的BeanFactory设计路径.通过这些接口设计的叠加,定义了BeanFactory就是简单IoC容器的基本功能.

    第二条是以ApplicationContext应用上下文接口为核心的接口设计.对于AppilcationContext除了细化了许多BeanFactory的接口能外之外,还通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,添加了许多对高级容器的特性的支持.

  •    容器中起作用的主要数据类型是BeanDefinition,它是对依赖反转模式中管理的对象依赖关系的数据抽象,依赖反转功能都是围绕着对这个BeanDefinition的处理完成的.

   三:BeanFactory的应用场景

1.BeanFactory提供的是最基本的IoC容器的功能,关于这些功能定义,我们可以在接口BeanFatory中看到。

2.BeanFactory接口定义了IoC容器最基本的容器的形式,并且提供了IoC容器所应该遵守的最基本的服务契约,同时,这也是我们使用IoC容器所应遵守的最底层最基本编程规范,这些接口定义勾画出了IoC的基本轮廓。

3.很显然,在Spring的代码实现中,BeanFactory只是一个接口类,并没有给出容器的具体实现,而我们在图中看到的各种具体类,比如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等都可以看成是容器附加了某种功能的具体实现,也就是容器体系中的具体容器产品

1.用户使用容器时,可以使用转义符"&"来得到FactoryBean本身,用来区分通过容器来获取FactoryBean产生的对象获取FactoryBean本身

2.举例来说,如果myJndiObject是一个FactoryBean,那么使用&myJndiObject得到的是FactroyBean,而不是myJndiObject这个FactoryBean产生出来的对象

3.关于具体的FactoryBean的设计和实现模式

4.注意,理解上面这段话需要很好地区分FactoryBean和BeanFactory这两个在Spring中使用频率很高的类,它们在拼写上非常相似。

5.一个是Factory,也就是IoC容器或对象工厂一个是Bean

6.在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)来进行管理的。

7.但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器类似。

            

四.BeanFactory容器的设计原理

以XmlBeanFactory为例,最底层实现,与应用上下文相比,显著的特点是:它能提供最基本的IoC容器功能.

1.继承DefaultListableBeanFactory拥有默认完成的IoC容器功能外,还可以读取Xml文件定义BeanDefinition.

2.XmlBeanFactory源码实现读取Xml文件功能

@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {
 
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
 
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
 
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
 
/**
*    总结分析具体的实现步骤:
*        1.创建IoC配置文件的抽象资源resource,资源包含了BeanDefinition.
*        2.创建BeanFactory,这里使用DefaultListableBeanFactory.
*        3.创建BeanDefinition读取器,这里使用XmlBeanDefinitionReader,通过回调配置给BeanFactory.
*        4.从定义好的资源位置读入配置信息,由XmlBeanFactory完成具体的解析过程.最终建立起IoC容器.
*
*
编程式实现
ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
}

 

五ApplicationContext的应用场景

appicationContext是一个高级形态的IoC容器,在BeanFactory的基础上添加了额外的功能,从而具备了新特性:

六.ApplicationContext容器的设计原理

如:以FileSystemXmlApplicationContext来说明ApplicationContext的设计原理.

ApplicationContext应用上下文的主要功能已经在FileSystemXmlApplicationContext的基类AbstractXmlApplicationContext中实现,它只需要实现和它自身设计相关的两个功能即可.