Spring——IOC(控制反转)容器

362 阅读7分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

1、IoC理论推导

按我们原始的方式写一个Maven项目

dao层接口 UserDao

public interface UserDao {
    void getUSer();
}

dao层实现类 UserDaoImpl

public class UserDaoImpl implements UserDao{
    public void getUSer() {
        System.out.println("获取用户");
    }
}

service层接口 UserService

public interface UserService {
    void getUser();
}

service层实现类

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

测试一下

@Test
public void test(){
    UserService service = new UserServiceImpl();
    service.getUser();//获取用户
}

这是我们原来的方式,很简单,但在实际业务中,往往没有这么简单的情况

比如当客户需要时,我们需要增加一个UserDao接口的实现类USerDaoMysqlImpl

public class USerDaoMysqlImpl implements UserDao{
    public void getUSer() {
        System.out.println("mysql");
    }
}

如果要用新增的实现类,我们就必须去service层修改相应的代码,而测试代码则不需要修改

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoMySqlImpl();//修改这一行代码
    @Override
    public void getUser() {
        userDao.getUser();
    }
}

然后,我们又增加一个UserDao接口的实现类 UserDaoOracleImpl

public class UserDaoOracleImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("Oracle");
    }
}

要用新增的实现类,又得去service层修改相应的代码,而测试代码同样不需要修改

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoOracleImpl();//修改这一行代码
    @Override
    public void getUser() {
        userDao.getUser();
    }
}

假设新增的实现类需求非常大 , 上面这种方式就根本不适用了, 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了 。

把测试类看做客户端(或者servlet程序),如果像上面这样,每次客户端需要新的实现,都需要程序员去手动修改大量service层的代码,这肯定是极其难完成的,==最好的方式是我们可以在客户端 , 不去实现它 , 而是留下一个接口 , 利用set方法 , 让客户端自己去调用这个接口,从而达到完成新的实现。==

修改后的实现:

public class UserServiceImpl implements UserService {
    private UserDao userDao;
/*
修改前:private UserDao userDao = new UserDaoMySqlImpl();由程序控制,创建了一个对象
修改后:private UserDao userDao; 程序不用管对象从哪里来的,直接调用就行(接口的思想)
*/ 
    //利用set实现动态值的注入 
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void getUser() {
        userDao.getUser();
    }
}

修改后的测试:

@Test
public void test(){
  UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoSqlServerImpl());//我们要用那个实现类,就直接在测试端创建该实现类的对象就可以了,不用去修改业务层的代码了
        userService.getUser();
}

==以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 。这种改变是极其重要的。==

==这种思想的改变 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC(控制反转)的原型 !==

2、什么是IoC

什么是IoC(Inversion of Control): 控制反转:

  • 即控制权的转移,将我们创建对象的方式反转了,以前对象的创建是由我们开发人员自己维护,包括依赖关系也是自己注入。使用了spring之后,对象的创建以及依赖关系可以由spring完成创建以及注入,反转控制就是反转了对象的创建方式,从我们自己创建反转给了程序创建(spring))。
  • IoC用来减低计算机代码之间的耦合度。
  • IoC是Spring框架的核心内容
  • IoC是一种编程思想,由主动创建对象变为被动接收

3、IoC本质

  • 控制反转是一种通过描述(xml或注解)并通过第三方生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI Dependency Injection)

    **注入方式: 利用set方式注入 ** 注入类型: 1.值类型注入value 2.引用类型注入ref

  • 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法

在这里插入图片描述

4、用IoC容器实现理论推导

4.1、bean

在 Spring 中,构成应用程序主干并由 Spring IoC 容器 Management 的对象称为 bean。 Bean 是由 Spring IoC 容器实例化,组装和以其他方式 Management 的对象。否则,bean 仅仅是应用程序中许多对象之一。 Bean 及其之间的依赖关系反映在容器使用的配置元数据中。

4.2、配置元数据

Spring IoC 容器使用一种形式的配置元数据。配置元数据表示程序开发人员如何告诉 Spring 容器实例化,配置和组装应用程序中的对象。元数据的内容用来传达Spring IoC 容器的关键概念和功能。

传统上,配置元数据以简单直观的 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="..." class="...">  (1) (2)
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>
  • (1) id属性是标识单个 bean 定义的字符串,也就是对象名。
  • (2) class属性定义 bean 的类型并使用完全限定的类名。

基于 XML 的元数据不是配置元数据的唯一允许形式。还有注解配置方式java配置方式

注解配置方式

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

    <!--bean都放在com.cheng.pojo这个包下-->
    <context:component-scan base-package="com.cheng.pojo"/>
</beans>

因为Bean移到pojo了,所以在pojo给User类加上注解@Component,然后给它们的属性name命名。使用@Autowired注解注入

package com.cheng.pojo;

@Component("u")
public class User {
    private  String name;
    private int age;

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+name +"," +"age="+age);
    }
}

java配置方式

类上面加@Configuration,通过该注解来表明该类是一个Spring的配置,相当于一个xml文件 方法上加@Bean,通过该注解来表明是一个Bean对象,相当于xml中的<bean>

@Configuration
public class User {
     
    @Bean
    public void show(){
        System.out.println("name="+name +"," +"age="+age);
    }
}
    

4.3、用IoC容器实现理论推导

使用IoC容器实现后,代码的耦合度还可以降到更低

第一步:首先编写配置元数据文件,Ioc容器通过元数据文件来管理多个<bean></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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="mysqlImpl" class="com.cheng.dao.USerDaoMysqlImpl"/>
    <bean id="oracleImpl" class="com.cheng.dao.USerDaoOracleImpl"/>
    <bean id="sqlServerImpl" class="com.cheng.dao.UserDaoSqlServerImpl"/>
<!--
依赖注入:在UserServiceImpl对象里面注入mysqlImpl对象的依赖,实现对象调用的过程,这个过程由IoC容器控制
ref:引用Spring容器中创建好的对象
value:引用值
-->    
    <bean id="UserServiceImpl" class="com.cheng.service.UserServiceImpl">
        <property name="userDao" ref="mysqlImpl"></property>
    </bean>

</beans>

第二步:测试类

@Test
 public void test(){
     //获得Spring容器
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //从容器中获得对象
     UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
    userServiceImpl.getUser();
}

当我们需要修改代码的实现时,我们彻底不需要改程序了,只需要修改配置文件中的依赖注入即可(修改ref的值),这样代码的耦合度更低了。

5、IoC接口(BeanFactory)

IoC思想基于IoC容器完成,IoC容器底层就是对象工厂(BeanFactory

  • Spring提供两种实现IoC容器方式(两个接口)

    BeanFactory:IoC容器基本实现,是Spring内部的使用接口,不提供给开发人员进行使用

    ApplicationContext:``BeanFactory`接口的子接口,代表 Spring IoC 容器,并负责实例化,配置和组装 Bean。容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以 XML,Java 注解或 Java 代码表示。一般由开发人员进行使用。

  • 异同:

    BeanFactory:加载配置文件的时候不会创建对象,在获取对象(使用)才去创建对象

    ApplicationContext:加载配置文件的时候就会把配置文件对象进行创建

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。 ApplicationContextBeanFactory的完整超。

ApplicationContext接口的两个实现类:

  • 1、FileSystemXmlApplicationContext :配置文件对应的盘路径

    new FileSystemXmlApplicationContext ("E:\Spring\spring-study\spring-01-IOC01\src\main\resources")
    
  • 2、ClassPathXmlApplicationContext :配置文件src目录下的类路径

    new ClassPathXmlApplicationContext("beans.xml")