二、Spring 控制反转(IOC)学习总结




一、IoC思想


       IoC(Inversion of Control,控制倒转)。Spring最核心的部分。所谓IoC,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
       该思想提出的背景:在Spring没出现之前,编写的功能代码之间存在着大量关联,导致代码耦合度很高,往往修改一处代码,就需要修改很多处互相关联的代码。
       用业务的角度来看,如果用户需求产生变化,会直接影响原本的代码程序,这时候只能不断的去修改原本的代码,如果代码量十分庞大,那么改动成本也是十分昂贵的。




二、IoC 理论推导


       以往的业务实现我们通常都是使用的三层架构,“用户层、业务层、dao层”。逻辑通常为用户层调用业务层,业务层调用Dao层获取数据返还给用户层,用户层与Dao层不产生直接关联,都是通过业务层去获取Dao层的数据。
  • Dao层代码

    • UserDao 接口

      public interface UserDao {
        void getUser();
      }
      
    • UserDaoImpl 实现类

      public class UserDaoImpl implements UserDao{
      
          @Override
          public void getUser() {
              System.out.println("获取默认用户信息");
          }
      }
      
  • service层(业务层)代码

    • UserService 接口

      public interface UserService {
          void getUser();
      }
      
      
    • UserServiceImpl 实现类

      public class UserServiceImpl implements UserService{
      
          private UserDao userDao = new UserDaoImpl();
          @Override
          public void getUser() {
              userDao.getUser();
          }
      }
      
  • 用户层代码

    public class MyTest {
    
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            userService.getUser();
        }
    }
    
  • 运行结果:

    在这里插入图片描述

       从结果可以看出,用户层获取到了Dao层的数据,没有问题。

       现在的代码业务中,只要一个需求,就是从Dao层中获取默认用户数据。如果用户的需求增加了,需要获取其他用户数据,就需要在Dao层中重新创建新的实现类,并且在业务层的实现类中重新 new 一个Dao层中新创建的实现类的对象,用户层业务不变。
  • Dao层

    • 新增 MySqlUserDaoImpl(新的数据来源)

      
      public class MySqlUserDaoImpl implements UserDao{
          @Override
          public void getUser() {
              System.out.println("通过MySQL获取用户数据");
          }
      }
      
  • service层(业务层)代码

    • 修改 UserServiceImpl 实现类
      public class UserServiceImpl implements UserService{
      
          // private UserDao userDao = new UserDaoImpl();
          private UserDao userDao = new MySqlUserDaoImpl();
          @Override
          public void getUser() {
              userDao.getUser();
          }
      }
      
  • 用户层代码不变
    在这里插入图片描述

       用户层成功获取到了新的数据来源。

       但是每次新增加数据来源都需要去将 service 层的实现类中 new 新的对象,不但程序耦合性极高,如果程序代码量十分庞大的话,还需要耗费大量的时间去修改。

       为了解决这个问题,Java革命性的一步出现了!!Dao层写法不变,在业务层的实现类中新增 set() ,专门用来接收对象。在从此我们便不在需要一个一个的去 new 新的对象了,而是将创建对象的主动权交给用户层手中,让用户自己去选择。
  • service层(业务层)代码

    • UserService 接口中新增 set() 方法用来接收用户创建的对象

      public interface UserService {
          void getUser();
          void setUser(UserDao userDao);
      }
      
      
    • 修改 UserServiceImpl 实现类

      public class UserServiceImpl implements UserService{
      
          // private UserDao userDao = new UserDaoImpl();
          // 将UserDao 设置成变量,而不是创建指定的对象
          private UserDao userDao;
          @Override
          public void getUser() {
              userDao.getUser();
          }
          
          @Override
          public void setUser(UserDao userDao) {
              this.userDao = userDao;
          }
      }
      
  • 用户层代码

    public class MyTest {
    
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            //用户需要创建那个对象就创建那个对象,将创建权交给用户
            userService.setUser(new UserDaoImpl());
            userService.getUser();
        }
    }
    
  • 运行结果
    在这里插入图片描述

  • 以往的业务逻辑是程序是主动创建对象!控制权在程序猿手上!

  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!用户掌握主动权,由用户选择所要调用的业务!

        这种思想,从本质上解决了问题,我们不在需要在程序中管理此类对象的创建了,将对象的创建权交给用户。这样做不但可以大大降低程序代码的耦合,还可以使我们更加专注将经理放在业务实现上。这就是 Spring 框架 IoC思想的原型。



三、IoC 的本质


        控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。也有人认为DI只是IoC的另一种说法,但是其实DI是IoC的一种实现方式。

        没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。使用IoC控制反转之后,程序会将对象的创建转移给第三方,程序本身不创建对象,而变成了被动的接收对象。个人认为所谓控制反转就是:获得依赖对象的方式反转了。

        控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其的实现方法是依赖注入(Dependency Injection,DI),而依赖注入就是就是通过 set方法来进行注入的。

        一句话总结IOC:Bean(对象)的创建、管理以及装配全部交给Spring 来负责!

在这里插入图片描述

  • 由上述图片可以看出,早些时期,对象的创建都是通过程序来控制,程序互相之间依赖性过大,导致程序耦合。为了解决这类问题,IoC容器诞生了,通过IoC容器当中间件的角色,来调用其他程序,以此来解放程序,大大降低了程序耦合度。




四、IoC 实现的方式以及IoC在Spring框架中的作用


  • 实现IoC的方式

            IoC的实现方式又很多,可以通过XML配置,可以使用注解实现,新版的Spring框架也可以无需配置实现IoC。

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

  • IoC在Spring框架中的作用

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


4.1 Spring 通过 XML 配置 Bean 的方式


       Spring 可以通过配置 xml 文件来 进行 Bean 的管理 和装配,在 xml 文件中使用标签来管理 Bean 之间的依赖关系
       在Spring中,XML配置文件的根元素是< beans >,< beans >中可以包含多个< bean >子元素,每一个< bean >子元素定义了一个Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。< bean >子元素中包含多个属性和子元素,常用的属性和子元素如下所示:
  • beans 标签:在Spring中,XML配置文件的根元素是< beans >,< beans >中可以包含多个< bean >子元素。

  • bean 标签:每一个 bean 标签都定义一个 Bean, 负责管理 Bean 之间的依赖关系,以及将 Bean 装配到 Spring 容器中。

    • bean 标签的属性:

      • id: 是 Bean 的唯一标识,IoC 容器中 bean 的 id 标签不能重复,否则报错。相当于 class 属性所指定的类的对象别名。spring 对 Bean 的配置和管理都是通过 id 来进行的。

      • class: 定义 Bean 实现类的全路径(包名+类名)。只有子类Bean不用定义该属性。



    • property 标签: bean 标签的子标签,用于调用 Bean 实例中的 set()方法完成属性赋值,从而完成依赖注入。
      • property 标签的属性:
        • name: 用于指定属性的名称,与 Bean 实现类中的set方法所对应的属性名称需要保持一致。(即:指定创建实现类中的某一个属性的 Bean)
        • value:用于给指定属性赋予一个具体的值,类型为基本类型、字符串类型,值为标签内的文本内容,可以使用null值将属性的值设置为null。
        • ref: 用于给指定属性赋值,类型为引用对象类型,值为其属性的值。


4.2 编写第一个 Spring 程序,给 Bean 注入基本类型或者字符串数据 (通过XML方式配置Bean)


  • Hello Spring

    • 创建 maven 项目,在项目中创建实体类
       public class Hello {
       
       	    private String str;
       	
       	    public String getStr() {
       	        return str;
       	    }
       	
       	    public void setStr(String str) {
       	        this.str = str;
       	    }
       	
       	    @Override
       	    public String toString() {
       	        return "Hello{" +
       	                "str='" + str + '\'' +
       	                '}';
       	    }
       }
    
    • 配置 xml 配置文件用来管理 Bean,可以抽象的理解为,该文件就是 Spring 的容器(beans.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
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--使用Spring来创建对象,在Spring中这些对象被称为Bean-->
        <bean id="hello" class="com.sys.pojo.Hello">
        	<!--value:给指定属性赋值,只能为基本类型和字符串类型-->
            <property name="str" value="SpringIoC"/>
        </bean>
    
    </beans>
    
    • 创建测试类
    public class MyTest {
        public static void main(String[] args) {
            //获取 Spring 的上下文对象,ClassPathXmlApplicationContext 通过配置文件来获取 Bean
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            //将对象交给Spring来处理,我们要使用对象直接去Spring容器中取出对象即可
            Hello hello = (Hello) context.getBean("hello");//获取指定的对象
            System.out.println(hello);
        }
    }
    
    • 运行结果
      在这里插入图片描述
       个人学习总结理解:程序运行时,会将 beans.xml 中所管理的 Bean存储到 Spring 容器当中。当执行 main 方法时,所创建的 ClassPathXmlApplicationContext 对象的构造方法会通过传入的参数匹配 Spring 容器中 beans.xml 所管理的全部的 Bean。 然后通过调用该对象父类的 getBean(),来获取容器中指定的 Bean。在通过 bean 标签的 property 子标签对该 Bean进行赋值,然后通过 Bean 实现类中的 set() 进行赋值,在通过实现类中的toString() 将 Bean 中的值进行返回打印到控制台上。


4.3 给 Bean 注入 Spring 容器中已经创建好的对象(引用类型数据)


       将 IoC 理论推导的代码进行改造
  • Dao层代码

    • UserDao 接口:新增 OracleUserDaoImpl 用户实现类

      public interface UserDao {
        void getUser();
      }
      
    • UserDaoImpl 实现类

      public class UserDaoImpl implements UserDao{
      
          @Override
          public void getUser() {
              System.out.println("获取默认用户信息");
          }
      }
      
    • MysqlUserDaoImpl 实现类

      public class MysqlUserDaoImpl implements UserDao{
      
          @Override
          public void getUser() {
              System.out.println("获取MySQL用户数据");
          }
      }
      
    • OracleUserDaoImpl 实现类

      public class OracleUserDaoImpl implements UserDao{
      
          @Override
          public void getUser() {
              System.out.println("获取Oracle用户数据");
          }
      }
      
  • service层(业务层)代码

    • UserService 接口

      public interface UserService {
      	    void getUser();
      	
      	    void setUser(UserDao userDao);
      }
      
    • UserServiceImpl 实现类

      public class UserServiceImpl implements UserService {
      	
      	    private UserDao user;
      	
      	    public void getUser() {
      	        user.getUser();
      	    }
      	
      	    @Override
      	    public void setUser(UserDao user) {
      	        this.user = user;
      	    }
      }
      
  • 在 resources 资源文件夹下新增 Beans.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
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="userDao" class="com.sys.dao.UserDaoImpl"/>
        <bean id="mysqlUserDao" class="com.sys.dao.MysqlUserDaoImpl"/>
        <bean id="oracleUserDao" class="com.sys.dao.OracleUserDaoImpl"/>
    
        <bean id="userService" class="com.sys.service.UserServiceImpl">
            <!--ref:将 Spring 容器中已经创建好的对象注入到该 Bean-->
            <property name="user" ref="userDao"/>
        </bean>
    
        <bean id="userService2" class="com.spring.service.UserServiceImpl">
            <property name="user" ref="mysqlUserDao"/>
        </bean>
    
        <bean id="userService3" class="com.spring.service.UserServiceImpl">
            <property name="user" ref="oracleUserDao"/>
        </bean>
    
    </beans>
    
  • 用户层代码: 不在使用 new UserServiceImpl 来实现对象的传入,而是通过调用容器中不同的 Bean 来实现不同的依赖关系

    public class MyTest {
        public static void main(String[] args) {
    
            ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
            UserServiceImpl service = (UserServiceImpl) context.getBean("userService");
            service.getUser();
            
            UserServiceImpl service2 = (UserServiceImpl) context.getBean("userService2");
            service2.getUser();
            
            UserServiceImpl service3 = (UserServiceImpl) context.getBean("userService3");
            service3.getUser();
        }
    }
    
  • 运行结果:

    在这里插入图片描述



4.4 IoC 创建对象的方式(依赖注入,通过构造函数注入)


  • 创建一个子模块

    • 创建 User 实体类,并给无参构造方法一个输出

      public class User {
      
          private String name;
      
          public User(){
              System.out.println("学Java喽");
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void show(){
              System.out.println("name:" + name);
          }
      }
      
    • 创建 Beans.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
              https://www.springframework.org/schema/beans/spring-beans.xsd">
      
      
          <bean id="user" class="com.sys.dto.User">
              <property name="name" value="姚青"/>
          </bean>
      
      </beans>
      
    • 创建 MyTest

      public class MyTest {
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
              User user = (User) context.getBean("user");
              user.show();
          }
      }
      
    • 运行结果
      在这里插入图片描述

    • 将 User 实体中的无参构造修改成有参构造

      	public class User {
      		
      		    private String name;
      		
      		    public User(String name){
      		        this.name = name;
      		    }
      		
      		    public String getName() {
      		        return name;
      		    }
      		
      		    public void setName(String name) {
      		        this.name = name;
      		    }
      		
      		    public void show(){
      		        System.out.println("name:" + name);
      		    }
      		}
      
    • 修改后运行会发生报错,并且 Bean.xml中也会标红
      在这里插入图片描述
      在这里插入图片描述

       通过运行结果可知, IoC 容器在创建对象时默认使用了无参构造方法。如果在默认情况下使用有参构造会报错。

       但是 IoC 容器不可能只要默认创建对象的方式,在 < bean > 标签中,有个 < constructor-arg > 标签就行专门用来解决这个问题的。




4.4.1 < constructor-arg > 标签,通过该标签创建对象的三种方式


       指定创建类对象时使用哪个构造函数,每一对或者每一个constructor-arg子标签配置一个参数列表中的参数值;如果不配置子标签,则默认使用无参构造方法实例化对象(即该标签通过指定参数列表,对应地来指定多个构造方法中一个具体的构造方法)。
  • constructor-arg 标签属性:

    • name属性:通过Bean中的参数名找到参数列表中对应参数

    • index属性:通过参数在参数列表中的索引找到参数列表中对应参数,index从0开始:

    • type属性:通过参数的数据类型找到参数列表中对应参数

    • value属性:对参数列表参数进行赋值,只能是基本数据类型和String类型的数据

    • ref属性:如果参数值为非基本数据类型,则可通过ref为参数注入值,其值为另一个bean标签id或name属性的属性值




  • 通过 constructor-arg 标签创建对象的三种方式

    • 通过下标创建

       <bean id="user" class="com.sys.dto.User">
       		<!--<property name="name" value="姚青"/>-->
              <constructor-arg index="0" value="在慵懒的生活中学习Java"/>
       </bean>
      
      • 运行结果
        在这里插入图片描述
    • 通过类型创建,不建议使用(通常实体中不会只存在一种类型的数据,局限性太高)

      <bean id="user" class="com.sys.dto.User">
          <!--<property name="name" value="姚青"/>-->
          <!--<constructor-arg index="0" value="在慵懒的生活中学习Java"/>-->
          <constructor-arg type="java.lang.String" value="qwert"/>
      </bean>
      
      • 运行结果
        在这里插入图片描述
    • 通过参数名创建,不建议使用

          <bean id="user" class="com.sys.dto.User">
              <!--<property name="name" value="姚青"/>-->
              <!--<constructor-arg index="0" value="在慵懒的生活中学习Java"/>-->
              <!--<constructor-arg type="java.lang.String" value="qwert"/>-->
              <constructor-arg name="name" value="姚青"/>
          </bean>
      
      • 运行结果
        在这里插入图片描述
  • 注:constructor-arg 配置参数的数量是随着构造函数而变化的

    • 实体构造函数参数增加
      public class User {
          private String str;
      
          public String getAge() {
              return age;
          }
      
          public void setAge(String age) {
              this.age = age;
          }
      
          private String age;
      
          private String name;
      
          public String getStr() {
              return str;
          }
      
          public void setStr(String str) {
              this.str = str;
          }
      
          public User(String name,String str,String age){
              this.name = name;
              this.str = str;
              this.age = age;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void show(){
              System.out.println("name:" + name);
              System.out.println("age:" + age);
              System.out.println("str:" + str);
          }
      }
      
    • Bean.xml
      <bean id="user" class="com.sys.dto.User">
          <constructor-arg index="1" value="在慵懒的生活中学习Java"/>
          <constructor-arg name="name" value="姚青"/>
          <constructor-arg type="java.lang.String" value="24"/>
      </bean>
      
    • 运行结果
      在这里插入图片描述




五、 Spring 容器配置


5.1 给 Bean 起别名

	<bean id="user" class="com.sys.dto.User">
	      <property name="name" value="姚青"/>
	</bean>
	
    <!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
    <alias name="user" alias="userNew"/>

5.2 配置总的容器文件(import)

       import 标签的作用是将其他的配置文件引入到当前文件中。在团队开发时,通常每个人都需要创建一个自己开发使用的 Bean 容器,不同的类注册到不同的容器中。这样做可能会存在一些弊端,所有在团队开发时,一般都是创建一个总的 Bean 容器(applicationContext.xml),通过 import 标签将全部的 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">

    <import resource="Beans.xml"/>
    <import resource="Beans2.xml"/>
    <import resource="Beans3.xml"/>

</beans>




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值