Spring之IoC理论

概述

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

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

Spring之IoC理论

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

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

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 类的继承关系图。

Spring之IoC理论

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

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

Spring之IoC理论

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

Spring之IoC理论

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

Spring之IoC理论

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

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

Spring之IoC理论

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

Spring之IoC理论

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

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

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

原文 

https://juejin.im/post/5e132520e51d4541162c9b67

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring之IoC理论

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址