IOC控制反转 + DI依赖注入

简介: 一种思想,两种实现方式IOC (Inversion of Control):控制反转,是一种概念和思想,指由Spring容器完成对象创建和依赖注入核心业务:(a)对象的创建 (b)依赖的注入2种实现方式基于xml实现IOC基于注解实现IOC基于xml的IOC在前3篇Spring博客中简单探讨过了,后面将探讨基于注解的IOC

Spring中的IOC

  • 一种思想,两种实现方式
  • IOC (Inversion of Control):控制反转,是一种概念和思想,指由Spring容器完成对象创建和依赖注入
  • 核心业务:(a)对象的创建 (b)依赖的注入
  • 2种实现方式
  • 基于xml实现IOC
  • 基于注解实现IOC
  • 基于xml的IOC在前3篇Spring博客中简单探讨过了,后面将探讨基于注解的IOC

基于注解的IOC

  • DI (Dependency Injection):基于注解的IOC被称为DI,即依赖注入, 是IOC思想的一种具体实现方式
  • 根据IOC的核心业务即:(a)对象创建,(b)依赖注入,对注解进行分类研究

注解类型

a. 创建对象的注解

  • 包含:创建任意对象的注解 + 创建三层架构各层对象的注解
  • @Conponent可以创建任意对象
  • @Controller:专门用来创建控制器对象(Servlet),这种对象可以用来接收用户的请求,可以返回处理结果给客户端
  • @Service:专门用来创建业务逻辑层对象,负责向下访问数据访问层,并将处理结果返回给界面层
  • @Repository:专门用来创建数据访问层对象,负责数据库中的CRUD操作

b. 依赖注入的注解

  • 包含:负责简单类型注入的注解 + 负责引用类型注入的注解

简单类型的注入

  • @Value:用来给简单类型(8 + 1)注入值

引用类型的注入

  • @Autowired:使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入同源类型可以是如下3种情况1.被注入的属性类型与待注入的数据类型是完全相同的类型2.被注入的属性(可以作为:父类)类型与待注入的数据(可以作为:子类)类型可以是父子类关系3.被注入的属性(可以作为:接口)类型与待注入的数据(可以作为:实现类)类型是可以是接口和实现类的关系
  • @Autowired + @Qualifier:使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入

注意

考虑到演示代码的复用性,减少代码冗余,并保证演示的清晰性,放在一起演示的代码:共用实体类 + 共用applicationContext.xml + 共用一个测试类。
不在一起演示的,另建一个新包并重新创建以上内容。
对实体类或配置文件的修改顺序,遵循博文的演示顺序。

  • @Conponent + @Value 放在一起演示
  • @Autowired:同源类型注入之完全相同类型 + 对应的(@Autowired + @Qualifier)名称注入 放在一起演示
  • @Autowired:同源类型注入之父子类型 + 对应的(@Autowired + @Qualifier)名称注入 放在一起演示
  • @Controller + @Service + @Repository 先不演示,在改造之前博客(Spring博客集中的Spring02)中的三层项目架构时再演示

--------------------------------------------------------------------------------------------------------------------------------------------------

@Conponent注解

实体类

  • Student实体类,并对实体类添加@Component注解
package com.example.s01;
import org.springframework.stereotype.Component;
@Component
public class Student {
    private String name;
    private int age;
    public Student() {
        System.out.println("Student无参构造方法被执行,实例对象被创建....");
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

applicationContext.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"
       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">
    <!-- 添加包扫描 -->
    <context:component-scan base-package="com.example.s01"/>
</beans>

测试1

  • 创建实体类对象的时机:和基于xml的IOC一样,当创建Spring容器时,创建实体类对象
  • 具体流程:创建Spring容器时,读取Spring核心配置文件:applicationContext.xml,进行包扫描,对于被扫描到的包,如果包中的实体类添加了@Component注解,则创建实体类对象
package com.example.test;
import com.example.s01.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestComponent {
    //测试Component注解
    @Test
    public void testComponent(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
        //获取容器中的bean对象
        Student student = (Student) ac.getBean("student");
        System.out.println(student);
    }
}

测试输出1

Student无参构造方法被执行,实例对象被创建....
Student{name='null', age=0}
Process finished with exit code 0

注意

实体类

  • 修改注解为@Component("stu")
@Component("stu")
public class Student {
  //...
}

测试2

  • 在获取Spring容器中的对象时根据指定的名称:"stu"来获取。注解未做特殊指定时,则遵循用类名的驼峰命名法来取
public class TestComponent {
    //测试Component注解
    @Test
    public void testComponent(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
        //获取容器中的bean对象
        Student student = (Student) ac.getBean("stu");
        System.out.println(student);
    }
}

测试输出2

  • 与测试输出1完全相同,不再赘述

@value注解

实体类

  • 为Student实体类的简单类型的属性添加@Value注解
@Component("stu")
public class Student {
    @Value("荷包蛋")
    private String name;
    @Value("20")
    private int age;
    //....
}

测试3

  • 和测试1完全相同,不再赘述

测试输出3

Student无参构造方法被执行,实例对象被创建....
Student{name='荷包蛋', age=20}
Process finished with exit code 0

--------------------------------------------------------------------------------------------------------------------------------------------------

@Autowired注解

同源类型注入3种情况之一:完全相同的类型的注入

实体类

  • 在新的包下构建的两个实体类:School实体类 + Student实体类
  • School实体类
package com.example.s02;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class School {
    @Value("nefu")
    private String name;
    @Value("哈尔滨")
    private String address;
    public School() {
        System.out.println("School无参构造方法执行,实例对象被创建....");
    }
    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
  • Student实体类新增对School实例对象的引用,其他内容和之前的Student类相同
package com.example.s02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
    //.....
    @Autowired
    private School school;
    //.....
}

applicationContext.xml

  • 添加包扫描,头文件不再赘述
<!-- 添加包扫描 -->
    <context:component-scan base-package="com.example.s02"/>

测试4

package com.example.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAutowired {
    //测试同源注入:完全相同的类型
    @Test
    public void testAutowired(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //从容器中获取Student实例对象
        System.out.println("学生对象: " + ac.getBean("student"));
    }
}

测试输出4

School无参构造方法执行,实例对象被创建....
Student无参构造方法被执行,实例对象被创建....
学生对象: Student{name='荷包蛋', age=20, school=School{name='nefu', address='哈尔滨'}}
Process finished with exit code 0

对应的名称注入

实体类

  • School实体类:将School的@Component注解改为@Component("theSchool")
@Component("theSchool")
public class School {
  //.....
}
  • Student实体类:新增@Qualifier注解,并必须在其后指定Bean工厂中已经注册的实体类对象的名称(类名的驼峰命名或自定义名称)
@Component
public class Student {
    //.....
    @Autowired
    @Qualifier("theSchool")
    private School school;
    //.....
}

测试5和测试输出5

  • 分别和测试4和测试输出4完全相同,不再赘述

注意

  • 只使用@Qualifier注解标签且后面跟的Bean工厂中注册的实体类对象的名称正确时,无法完成依赖名称注册,用名称进行注入时,这两个注解标签都要出现

实体类

  • Student实体类
@Component
public class Student {
    //.....
    @Qualifier("theSchool")
    private School school;
    //.....
}

测试6

  • 与测试4完全相同,不再赘述

测试输出6

  • 没有报错,但是根据名称进行依赖注入的操作并没有真正将引用类型的数据注入到Student实例中,引用类型school为null
School无参构造方法执行,实例对象被创建....
Student无参构造方法被执行,实例对象被创建....
学生对象: Student{name='荷包蛋', age=20, school=null}
Process finished with exit code 0

--------------------------------------------------------------------------------------------------------------------------------------------------

@Autowired注解

同源类型注入3种情况之二:父子类型的注入

实体类

  • 构建一个新的实体类包,含有3个实体类:SubSchool,School,Student
  • 新增实体类SubSchool,为School类的子类
package com.example.s03;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SubSchool extends School{
    @Value("nefu附小")
    private String name;
    @Value("香坊区")
    private String address;
    @Override
    public String toString() {
        return "SubSchool{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
    public SubSchool() {
        System.out.println("SubSchool无参构造方法被执行,实例对象被创建....");
    }
}
  • Student实体类内容不变
package com.example.s03;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
    //......
    @Autowired
    private School school;
    //......
}
  • School实体类内容不变
package com.example.s03;
import org.springframework.stereotype.Component;
@Component
public class SubSchool extends School{
    //......
}

applicationContext.xml

  • 添加包扫描
<!-- 添加包扫描 -->
    <context:component-scan base-package="com.example.s03"/>

测试7

package com.example.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAutowiredExtend {
    //测试同源类型注入:父子类型
    @Test
    public void testAutowiredExtend(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s03/applicationContext.xml");
        //从容器中获取Student实例
        System.out.printf("Student实例: " + ac.getBean("student"));
    }
}

测试输出7

  • 为什么"School无参构造方法执行,实例对象被创建...."被输出2次?
  • 原因:一次是构建School对象时,一次在构建SubSchool对象时(子类构造方法中调用父类无参构造方法)
School无参构造方法执行,实例对象被创建....
Student无参构造方法被执行,实例对象被创建....
School无参构造方法执行,实例对象被创建....
SubSchool无参构造方法被执行,实例对象被创建....
Student实例: Student{name='荷包蛋', age=20, school=School{name='nefu', address='哈尔滨'}}
Process finished with exit code 0

注意

  • 为什么SubSchool和School实体类对象都被注册了,在上述测试中,只是School的实体类对象被注入Student对象?
  • 原因:在同源类型的注入中,若进行父子类型的依赖注入,不是按照名称注入时,会按照注册的实体类对象的名称二次选择
  • 二次选择的原则:注册的实体类对象的名称和待注入的目标属性名称相同的,优先被选择为注入数据

实体类

  • School修改为
@Component("schoolFu")
public class School {
  //......
}
  • SubSchool修改为
@Component("school")
public class SubSchool extends School{
  //......
}

测试8

  • 测试7完全相同,不再赘述

测试输出8

  • 此时被注入到Student实例对象中的是SubSchool实例对象
School无参构造方法执行,实例对象被创建....
Student无参构造方法被执行,实例对象被创建....
School无参构造方法执行,实例对象被创建....
SubSchool无参构造方法被执行,实例对象被创建....
Student实例: Student{name='荷包蛋', age=20, school=SubSchool{name='nefu附小', address='香坊区'}}
Process finished with exit code 0

对应的名称注入

实体类

  • 为Student实体类新增注解@Qualifier("schoolFu")
@Component
public class Student {
    @Autowired
    @Qualifier("schoolFu")
    private School school;
    //......
}

测试9

  • 和测试7完全相同,不再赘述

测试输出9

  • 此时被注入到Student实例对象中的是School实例对象,因为@Qualifier("schoolFu")指定的注入数据和School实体类的注册类型相同,根据指定名称完成注入
School无参构造方法执行,实例对象被创建....
Student无参构造方法被执行,实例对象被创建....
School无参构造方法执行,实例对象被创建....
SubSchool无参构造方法被执行,实例对象被创建....
Student实例: Student{name='荷包蛋', age=20, school=School{name='nefu', address='哈尔滨'}}
Process finished with exit code 0
相关文章
|
XML Java 数据库
【Spring】SpringBoot 配置文件
【Spring】SpringBoot 配置文件
|
存储 安全 算法
[ web基础篇 ] session,cookie,token 那些事儿 ?
session ?cookie ?token ? 相信大家对这几个词并不陌生,不管是面试还是工作都会有涉及的,可想而知它的重要性。 网上关于 session、cookie、token 的文章有很多,每篇文章都有一些独特的见解。 在写文章之前,我看了很多篇 session、cookie、token 的文章,感觉很多都没有讲的很清楚,泛泛而谈。 在看了这么多的文章之后,我对这几次词又有了不一样的理解,在这里和大家分享一下。
573 0
[ web基础篇 ] session,cookie,token 那些事儿 ?
|
6月前
|
存储 自然语言处理 文件存储
|
10月前
|
消息中间件 缓存 监控
go高并发之路——消息中间件kafka
本文介绍了高并发业务中的流量高峰应对措施,重点讲解了Kafka消息中间件的使用,包括常用的Go语言库sarama及其版本问题,以及Kafka的版本选择建议。文中还详细解释了Kafka生产者的四种分区策略:轮询、随机、按Key和指定分区,并提供了相应的代码示例。
258 1
go高并发之路——消息中间件kafka
Sublime Text3安装插件报错:There are no packages available for installation
Sublime Text3安装插件报错:There are no packages available for installation
230 0
|
存储 人工智能 C语言
C语言程序设计核心详解 第六章 数组_一维数组_二维数组_字符数组详解
本章介绍了C语言中的数组概念及应用。数组是一种存储同一类型数据的线性结构,通过下标访问元素。一维数组定义需指定长度,如`int a[10]`,并遵循命名规则。数组元素初始化可使用 `{}`,多余初值补0,少则随机。二维数组扩展了维度,定义形式为`int a[3][4]`,按行优先顺序存储。字符数组用于存储字符串,初始化时需添加结束符`\0`。此外,介绍了字符串处理函数,如`strcat()`、`strcpy()`、`strcmp()` 和 `strlen()`,用于拼接、复制、比较和计算字符串长度。
435 4
|
存储 安全 NoSQL
Cookie、Session、Token 解析
Cookie、Session、Token 解析
536 1
|
Dubbo Java 应用服务中间件
从源码全面解析 dubbo 注解配置的来龙去脉
从源码全面解析 dubbo 注解配置的来龙去脉
|
机器学习/深度学习 算法 Go
【YOLO系列】YOLOv4论文超详细解读2(网络详解)
【YOLO系列】YOLOv4论文超详细解读2(网络详解)
1212 0
【YOLO系列】YOLOv4论文超详细解读2(网络详解)
|
数据采集 搜索推荐 中间件
【 ⑬】Scrapy库概述(简介、安装与基本使用)
【 ⑬】Scrapy库概述(简介、安装与基本使用)
384 0