美文网首页
Spring之IoC理论

Spring之IoC理论

作者: 虾米咬小米 | 来源:发表于2020-07-23 11:40 被阅读0次

    概述

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


    为什么需要 IoC

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

    1、先写一个 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 + '\'' +                '}';    }}
    
    

    2、写一个 UserDao 接口

    public interface UserDao {    public void getUser();}
    

    3、再去写 Dao 的实现类

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

    4、写 UserService 的接口

    public interface UserService {    public void getUser();}
    

    5、最后写 UserService 的实现类

    public class UserServiceImpl implements UserService {    private UserDao userDao = new UserDaoImpl();    public void getUser() {        userDao.getUser();    }}
    

    6、测试一下

    public class UserGetTest {    @Test    public void getUser(){        UserService userService = new UserServiceImpl();        userService.getUser();    }}
    

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

    再增加 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点是依赖倒置原则的概念,也是核心。主要是说模块之间不要依赖具体实现,依赖接口或抽象。

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

    image.png

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

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

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

    image.png

    采用 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>复制代码
    

    测试代码如下:

    @Testpublic void MyBean(){    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");    serviceImpl.getUser();}
    

    之后我们不需要再去程序中改动了,要实现不同的操作,只需要在 XML 配置文件中进行修改。所谓的 IoC 就是对象由 Spring 来创建、管理和装配。


    IoC涉及到的组件

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

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

    image.png

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

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

    image.png

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

    image.png

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

    image.png

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

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

    image.png

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

    image.png

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

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

    上述提到的六个重要知识点是 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"/>
    

    参考:

    Spring之IoC理论

    相关文章

      网友评论

          本文标题:Spring之IoC理论

          本文链接:https://www.haomeiwen.com/subject/myvclktx.html