2.IoC控制反转

Posted 小马Mark

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.IoC控制反转相关的知识,希望对你有一定的参考价值。

概念

控制反转(Inversion of Control,缩写为IoC),是[面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**[依赖注入](Dependency Injection,简称DI**),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

技术描述

Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定

DI的两种实现方式

基于XML的DI

注入分类

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
根据注入方式的不同,常用的有两类:set 注入、构造注入。

set注入

set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。

容器创建对象是通过无参构造方法,没有无参构造方法,则会报错。

注意

  • set注入是spring通过类中的set()方法来给对象里的属性赋值的,所以类中必须有set方法(无论set方法的方法体中的代码是什么),否则无法使用。
  • 无论类中有没有指定的属性,但是只要有相关的set方法,程序也一样执行。

(简单类型:spring中规定java的基础类型(包括包装类)和String都是简单类型)

底层是先执行无参构造方法,然后再通过set()方法给对象赋值

类似于:

Student student = new Student();
student.setName("周晓雪");
student.setAge(18);
  1. 简单类型的set注入:

    语法:<property name="属性名称" value="想要给属性赋的值">

    <bean id="myStudent" class="com.maj.bean_di01.Student" >
        <!--
    		<property name="属性名称" value="想要给属性赋的值">
    	-->
        <property name="name" value="周晓雪"/> <!-- setName("周晓雪") -->
        <property name="age" value="18"/>  <!-- setAge(18) -->
    </bean>
    
  2. 引用类型的set注入:

    语法:<property name="属性名" ref="bean的id(对象的名称)" />

    <!-- 声明一个School对象 -->
    <bean id="mySchool" class="com.maj.bean_di02.School">
        <property name="name" value="青蛙大学" />
        <property name="address" value="你心目中的地方"/>
    </bean>
    
    <bean id="myStudent" class="com.maj.bean_di02.Student" >
        <property name="name" value="周晓雪"/>
        <property name="age" value="18"/>
    <!--
    	<property name="属性名" ref="bean的id(对象的名称)" />
    -->
        <property name="school" ref="mySchool"/> <!-- setSchool(mySchool) -->
    </bean>
    

构造注入

在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。

容器创建对象是用有参构造方法创建的,没有有参构造方法,则会报错。

语法:

<constructor-arg />标签(一个<constructor-arg />表示构造方法的一个参数)
该标签使用在<bean></bean>里面
属性有:
	1. name:指定参数名称。
	2. index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
	3. value:赋值,构造方法的形参类型是简单类型就用这个
	4. ref:赋值,是引用类型的使用这个

示例:

<bean id="mySchool" class="com.maj.bean_di03.School">
    <property name="name" value="青蛙大学" />
    <property name="address" value="你心目中的地方"/>
</bean>
<!-- 使用的name属性进行赋值 -->
<bean id="myStudent" class="com.maj.bean_di03.Student" >
    <constructor-arg name="name" value="佚名" />
    <constructor-arg name="age" value="3" />
    <constructor-arg name="school" ref="mySchool" />
</bean>
<!-- 使用index属性进行赋值 -->
<bean id="myStudent2" class="com.maj.bean_di03.Student">
    <constructor-arg index="0" value="傻叉" />
    <constructor-arg index="1" value="3" />
    <constructor-arg index="2" ref="mySchool" />
</bean>
<!-- 省略index进行赋值(必须按顺序赋值) -->
<bean id="myStudent3" class="com.maj.bean_di03.Student">
    <constructor-arg  value="牛逼" />
    <constructor-arg  value="66" />
    <constructor-arg  ref="mySchool" />
</bean>

引用类型属性自动注入

对于引用类型属性的注入,也可不在配置文件中显示的注入。

可以通过为<bean/>标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入
  • byType: 根据类型自动注入

byName方式自动注入

当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

示例:

// Student.java的一部分
public class Student 
    private String name;
    private int age;

    // 声明一个引用类型
    private School school;
    
    public void setSchool(School school) 
        this.school = school;
    
<!--配置文件的一部分-->

<!-- 这里的id需要跟调用者bean类的属性名相同 -->
<bean id="school" class="com.maj.bean_di04.School">
    <property name="name" value="野鸡大学" />
    <property name="address" value="山沟沟"/>
</bean>
                                                  <!-- 根据属性名称自动注入 -->
<bean id="myStudent" class="com.maj.bean_di04.Student" autowire="byName"> 
    <property name="name" value="何鼎东"/>
    <property name="age" value="3"/>
    <!--        <property name="school" ref="mySchool"/>  -->
</bean>

byType方式自动注入

使用 byType 方式自动注入,

要求:配置文件中 bean标签 的 class 属性指定的类,与java代码中调用者 bean 类的某引用类型属性类型是同源的。

同源就是一类的意思:(满足其中一个即可)

  1. java类中引用类型的数据类型和bean标签的class的值是一样的
  2. java类中引用类型的数据类型和bean标签的class的值是父子类关系的。(即前者为父,后者为子)
  3. java类中引用类型的数据类型和bean标签的class的值是接口与实现类的关系。(即前者是接口,后者是其实现类)

注意:这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

示例:(这里的示例满足同源2关系)

自己写了一个PrimarySchool类继承(extends)School类

<!--配置文件的一部分-->

                     <!-- 这里创建的对象是PrimarySchool对象 -->
<bean id="mySchool" class="com.maj.bean_di05.PrimarySchool">
    <property name="name" value="野鸡大学" />
    <property name="address" value="山沟沟"/>
</bean>
                                                  <!-- 根据属性类型自动注入 -->
<bean id="myStudent" class="com.maj.bean_di05.Student" autowire="byType"> 
    <property name="name" value="何鼎东"/>
    <property name="age" value="3"/>
    <!--        <property name="school" ref="mySchool"/>  -->
</bean>

多个Spring配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。

包含关系的配置文件:

  • 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 Java代码中只需要使用总配置文件对容器进行初始化即可。

在主配置文件中引入其他文件的语法:

<import resource="classpath:类路径"/>
<!--classpath是关键字-->

示例:

<!-- spring-total.xml -->

    <import resource="classpath:bean_di06/spring-student.xml"/>
    <import resource="classpath:bean_di06/spring-school.xml"/>
<!-- spring-school.xml -->
    <bean id="mySchool" class="com.maj.bean_di06.School">
        <property name="name" value="牛逼大学" />
        <property name="address" value="地球村"/>
    </bean>
<!-- spring-student.xml -->
    <bean id="myStudent" class="com.maj.bean_di06.Student">
        <property name="name" value="何鼎东"/>
        <property name="age" value="3"/>
        <property name="school" ref="mySchool"/>
    </bean>
    @Test
    public void text01()
        String config = "bean_di06/spring-total.xml";  // 这里只需要加载主配置文件即可
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Student s = (Student) ac.getBean("myStudent");
        System.out.println(s);
    

注意:

可使用通配符*。但,此时要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为spring-total.xml

基于注解的DI

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。

组件扫描器

需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。

扫描指定一个包的语法:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
          https://www.springframework.org/schema/context/spring-context.xsd">
    
    注意:这里xml文件必原先的多了几个
	<context:component-scan base-package="包的全限定名称" />
</beans>

指定多个包的三种方式:

  1. 使用多个context:component-scan标签来指定不同的包名

    <context:component-scan base-package="com.maj.bao01" />
    <context:component-scan base-package="com.maj.bao02" />
    
  2. 指定 base-package 的值使用分隔符

    分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。

    (1)<context:component-scan base-package="com.maj.bao01,com.maj.bao02" />
    (2)<context:component-scan base-package="com.maj.bao01;com.maj.bao02" />
    (3)<context:component-scan base-package="com.maj.bao01 com.maj.bao02" /> <!--不建议使用-->
    
  3. base-package 是指定到父包名

    base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。或者最顶级的父包(略,不建议使用,会导致速度变慢)

    <context:component-scan base-package="com.maj" />  <!--bao01和bao02都会去扫描-->
    

定义Bean的注解

@Component()

创建一个类的对象,需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。

语法:

import org.springframework.stereotype.Component;

/*
	@Component注解就类似于bean标签
	value属性就类似于bean的id
*/ 
@Component(value = "创建这个类的对象名")
public class Abc
    ...

--------------------------------------
@Component("创建这个类的对象名")
public class Abc
    ...


--------------------------------------
@Component      // 这里不指定对象名的话,就由spring默认指定为类名的首字母小写的单词
public class Abc   // 例如:这里默认创建的对象的名称为 abc
    ...

示例:

// 自己创建的一个Student类(这是一部分代码)
import org.springframework.stereotype.Component;

@Component(value = "myStudent")
public class Student 
    private String name;
    private Integer age;
    ......

另外三个注解

Spring 还提供了 3 个创建对象的注解:(用法与@Component一样)

  • @Repository 用于对 DAO 实现类进行注解(持久层)
  • @Service 用于对 Service 实现类进行注解(业务层)
  • @Controller 用于对 Controller 实现类进行注解(控制层)

这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。

@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。

简单类型属性的注入

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。

使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

语法:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "对象名")
public class 类名 
    @Value(value = "赋值")
    private 方法类型 成员名1;
    @Value("赋值")
    private 方法类型 成员名2;
    
    或者在setter方法上写注解
        
    @Value(value = "赋值")
    public void setName(String name) 
        this.name = name;
    

示例:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "myStudent")
public class Student 
    @Value(value = "段行")
    private String name;
    @Value("38")
    private Integer age;

引用类型属性的注入

@Autowired

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。

默认是(类似于bean的byType类型)

使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

补充:

  • @Autowired有一个属性required,是一个boolean类型,默认是true(@Autowired(required=true)

    当required=true时,如果引用类型赋值失败,则会报错,程序停止

    当required=true时,如果引用类型赋值失败,则程序继续执行,不会报错,但是赋值为null

示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "myStudent")
2.IoC控制反转

Spring的学习_____2.IoC 控制反转

Spring中的Ioc控制反转与DI注入

Spring 学习 2- IOC原理 控制反转/依赖注入

Spring5学习笔记 — “Spring IOC(控制反转)”

Spring框架思想