Spring之IoC理论

概述为何须要 IoCIoC本质实战分析编写代码思考IoC涉及到的组件IoC建立对象无参构造器有参构造器Spring中XML配置参考文献php

概述

上一篇spring概述咱们搭建完基于 Spring 框架的环境, 这篇咱们开始真正的阅读 Spring 的源码,分析 Spring 的源码以前咱们先来简单回顾下 Spring 核心功能的简单使用。java


为何须要 IoC

假若有这么一个业务场景:dao 层从不一样的地方获取用户数据,service 层用来调用获取用户的方法,如何控制从想要的地方获取用户数据?mysql

一、先写一个 User 类程序员

public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码

二、写一个 UserDao 接口web

public interface UserDao {
    public void getUser();
}
复制代码

三、再去写 Dao 的实现类spring

public class UserDaoImpl implements UserDao {
    public void getUser() {
        User user = new User("hresh");
        System.out.println("从bean中获取到的用户数据为"+user);
    }
}
复制代码

四、写 UserService 的接口sql

public interface UserService {
    public void getUser();
}
复制代码

五、最后写 UserService 的实现类数据库

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    public void getUser() {
        userDao.getUser();
    }
}
复制代码

六、测试一下编程

public class UserGetTest {

    @Test
    public void getUser(){
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}
复制代码

这样就实现了一种读取用户信息的方式,接下来咱们再增长一种从 Mysql 数据库中读取用户信息的方法。bash

再增长 UserDao 的实现类

public class UserDaoMysqlImpl implements UserDao {
    public void getUser() {
        User user = new User("acorn");
        System.out.println("从MySQL数据库中获取到的用户数据为"+user);
    }
}
复制代码

紧接着咱们要去使用 MySql 的话 , 咱们就须要去 service 实现类里面修改对应的实现。

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoMySqlImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
复制代码

一样若是咱们须要从 Oracle 数据库中读取数据,还须要构建一个 UserDao 的实现类,而后修改 UserServiceImpl 类。 假设咱们的这种需求很是大 , 这种方式就根本不适用了,每次变更 , 都须要修改大量代码 . 这种设计的耦合性过高了, 牵一发而动全身 。

那咱们如何去解决?

咱们能够在调用 UserDao 实现类的地方,不去实例化该对象,而是留出一个接口 ,利用 set 方法,代码以下:

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 利用set实现
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
复制代码

如今在测试类里,进行测试:

public class UserGetTest {

    @Test
    public void getUser(){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoImpl());
        userService.getUser();

        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    }
}
复制代码

执行结果为:

从bean中获取到的用户数据为User{name='hresh'}
从MySQL数据库中获取到的用户数据为User{name='acorn'}
复制代码

虽然只是 UserServiceImpl 类中的代码作了修改,看起来变更不大,甚至你可能会说测试类中还变复杂了。可是仔细想一下,以前全部的 Dao 实现类都是在 UserServiceImpl 中控制建立,而如今由更接近用户的测试类中控制建立对象,把主动权交给了调用者,程序不用去管怎么建立,怎么实现了,它只负责提供一个接口便可。

这种思想 ,从本质上解决了问题 , 咱们程序员再也不去管理对象的建立了,更多的去关注业务的实现 ,耦合性大大下降 。这也就是 IoC 的原型 !


IoC本质

IoC( Inverse of Control:控制反转 )是一种设计思想,就是将本来在程序中手动建立对象的控制权,交由 Spring 框架来管理。IoC 在其余语言中也有应用,并不是 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体,IoC 容器实际上就是个 Map(key,value),Map 中存放的是各类对象。

要了解控制反转,有必要先了解软件设计的一个重要思想:依赖倒置原则( Dependency Inversion Principle )。

  • 高层模块不该该依赖于底层模块,二者应该依赖于其抽象。
  • 抽象不该该依赖具体实现,具体实现应该依赖抽象。

上面2点是依赖倒置原则的概念,也是核心。主要是说模块之间不要依赖具体实现,依赖接口或抽象。

其实依赖倒置原则的核心思想是面向接口编程。

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样能够很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂同样,当咱们须要建立一个对象的时候,只须要配置好配置文件/注解便可,彻底不用考虑对象是如何被建立出来的。在实际项目中一个 Service 类可能有几百甚至上千个类做为它的底层,假如咱们须要实例化这个 Service,你可能要每次都要搞清楚这个 Service 全部底层类的构造函数,这可能会把人逼疯。若是利用 IoC 的话,你只须要配置好,而后在须要的地方引用就好了,这大大增长了项目的可维护性且下降了开发难度。

IoC 在 Spring 中有多种实现方式,可使用 XML 配置,也可使用注解,新版本的 Spring 也能够零配置实现 IoC。

Spring 容器在初始化时先读取配置文件,根据配置文件或元数据建立与组织对象存入容器中,程序使用时再从 IoC 容器中取出须要的对象。

采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式能够把二者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。


实战分析

编写代码

定义一个 bean 类:

public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码

源码很简单,bean 没有特别之处,Spring 的目的就是让咱们的 bean 成为一个纯粹的 POJO,这就是 Spring 追求的,接下来就是在配置文件中定义这个 bean,配置文件以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="user" class="com.msdn.bean.User">
        <property name="name" value="hresh" />
    </bean>

</beans>
复制代码

在上面的配置中咱们能够看到bean的声明方式,在spring中的bean定义有N种属性,可是咱们只要像上面这样简单的声明就可使用了。
具体测试代码以下:

public class MyBeanTest {

    @Test
    public void MyBean()
{
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");

        //getBean : 参数即为spring配置文件中bean的id .
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码

执行结果为:

User{name='hresh'}
复制代码

思考

  • User 对象是谁建立的?【user 对象是由 Spring 建立的】

  • User 对象的属性是怎么设置的?【user 对象的属性是由 Spring 容器设置的】

    这个过程就叫作控制反转:

  • 控制:谁来控制对象的建立,传统应用程序的对象是由程序自己控制建立的,使用 Spring 后,对象是由 Spring 来建立的。

  • 反转:程序自己不建立对象,而变成被动地接收对象。

    依赖注入:利用 set 方法来进行注入的。

    IOC是一种编程思想,由主动的编程变成被动的接收

    关于 ClassPathXmlApplicationContext 的学习后续会单独介绍,有兴趣的朋友能够去看一下。

按照上述的方式咱们对以前提到的业务场景进行修改。首先新增 一个 Spring 配置文件 application_context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="mysqlImpl" class="com.msdn.dao.UserDaoMysqlImpl" />
    <bean id="oracleImpl" class="com.msdn.dao.UserDaoOracleImpl" />

    <bean id="serviceImpl" class="com.msdn.service.UserServiceImpl">
         <!--注意: 这里的name并非属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另一个bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="oracleImpl" />
    </bean>

</beans>
复制代码

测试代码以下:

@Test
public void MyBean(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");
    serviceImpl.getUser();
}
复制代码

以后咱们不须要再去程序中改动了,要实现不一样的操做,只须要在 XML 配置文件中进行修改。所谓的 IoC 就是对象由 Spring 来建立、管理和装配。


IoC涉及到的组件

在上文测试代码中咱们用到的是 ApplicationContext,具体实现是 ClassPathXmlApplicationContext。因此接下来咱们简单分析一下在此过程当中涉及到的组件。

首先是 ClassPathXmlApplicationContext 类的继承关系图。

基本上包含了 IOC 体系中大部分的核心类和接口。 下面咱们就针对这个图进行简单的拆分和补充说明。

Resource 主要负责对资源的抽象,它的每个实现类都表明了一种资源的访问策略,如 ClasspathResource 、 URLResource ,FileSystemResource 等。

有了资源,就须要有资源加载模块,Spring 利用 ResourceLoader 来进行统一资源加载,关系图以下:

资源加载完毕以后就须要 BeanFactory 来进行加载解析,它是一个 bean 容器,其中 BeanDefinition 是它的基本结构,它内部维护着一 个 BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的建立和管理。

BeanFacoty 有三个直接子类 ListableBeanFactoryHierarchicalBeanFactoryAutowireCapableBeanFactoryDefaultListableBeanFactory 为最终默认实现,它实现了全部接口。

BeanDefinition 用来描述 Spring 中的 Bean 对象。

BeanDefinitionReader 的做用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。

ApplicationContext 是个 Spring 容器,也叫作应用上下文。它继承 BeanFactory,同时也是 BeanFactory 的扩展升级版。因为 ApplicationContext 的结构就决定了它与 BeanFactory 的不一样,其主要区别有:

  1. 继承 MessageSource ,提供国际化的标准访问策略;
  2. 继承 ApplicationEventPublisher,提供强大的事件机制;
  3. 扩展 ResourceLoader,能够用来加载多个 Resource,能够灵活访问不一样的资源;
  4. 对 Web 应用的支持。

上述提到的六个重要知识点是 Spring IoC 中最核心的部分,后续的学习也是针对这些内容进行详细解读。

IoC建立对象

无参构造器

当对象由无参构造器建立时,属性是由该类的 set 方法写入的。

User 类

public class User {
    private String name;

    public User() {
        System.out.println("user无参构造方法");
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

}
复制代码

application_context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="user" class="com.msdn.bean.User">
        <property name="name" value="hresh" />
    </bean>

</beans>
复制代码

测试代码:

public class MyBeanTest {

    @Test
    public void MyBean(){
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");

        //在执行getBean的时候, user已经建立好了,属性是经过set方法写入的
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码

执行结果为:

user无参构造方法
User{name='hresh'}
复制代码

若是将 User 类中的 set 方法注释掉,再次调用测试代码,会报错,说明对象是由无参构造器建立成功后,会调用 set 方法完成实例的初始化。

有参构造器

User 类

public class User {
    private String name;

    public User() {
        System.out.println("user无参构造方法");
    }

    public User(String name) {
        this.name = name;
        System.out.println("user有参构造方法");
    }

    public String getName() {
        return name;
    }

//    public void setName(String name) {
//        this.name = name;
//    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

}
复制代码

application_context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="user" class="com.msdn.bean.User">
        <constructor-arg name="name" value="hresh" />
    </bean>

</beans>
复制代码

测试代码:

public class MyBeanTest {

    @Test
    public void MyBean(){
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");

        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码

执行结果为:

user有参构造方法
User{name='hresh'}
复制代码

结论:Spring 容器根据 XML 文件中的配置,调用 bean 类的有参构造器来建立对象。


Spring中XML配置

别名

alias 设置别名 , 为bean设置别名 , 能够设置多个别名 。

<!--设置别名:在获取Bean的时候可使用别名获取-->
<alias name="userT" alias="userNew"/>
复制代码

Bean的配置

<!--bean就是java对象,由Spring建立和管理-->

<!--
    id 是bean的标识符,要惟一,若是没有配置id,name就是默认标识符
    若是配置id,又配置了name,那么name是别名
    name能够设置多个别名,能够用逗号,分号,空格隔开
    若是不配置id和name,能够根据applicationContext.getBean(.class)获取对象;

    class是bean的全限定名=包名+类名
-->

<bean id="hello" name="hello2 h2,h3;h4" class="com.msdn.bean.Hello">
    <property name="name" value="Spring"/>
</bean>
复制代码

import

团队的合做经过import来实现 ,当有多个关于 bean 定义的文件,最后能够集中在一个文件中。

<import resource="{path}/beans.xml"/>
复制代码

参考文献

https://blog.kuangstudy.com/index.php/archives/518/

相关文章
相关标签/搜索