【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)


注:学习Spring的初期可能无法理解控制反转、依赖注入、注解等概念,不要气馁,坚持学习。学完注解开发基础后,再回头尝试去理解这些感念,会发现容易许多。(个人的一些感想)



文章目录






一、基础概念

1、Spring框架概述

  • 2003年兴起的轻量级Java开发框架,为解决企业应用开发的复杂性而创建。
  • Spring的核心是 控制反转(IoC)面向切面编程(AOP)
  • ​Spring官网​
  • 可以将Spring容器看成一个超级大工厂,负责创建、管理所有Java对象,这些Java对象被称为Bean。Spring通过“依赖注入(DI)”的方式管理Bean之间的依赖关系,使用“控制反转(IoC)”实现对象之间的“解耦合”。

2、Spring的优点

  • 1)轻量级:Spring框架使用的jar均较小,通常早1M以下,Spring核心功能所需的jar共4M左右。Spring框架运行占用资源少,运行效率高,且不依赖其它jar包。
  • 2)针对接口编程,解耦合:Spring提供了控制反转(IoC),由容器管理对象及对象之间的依赖关系。
  • 3)AOP编程的支持:即将公共的、通用的重复的代码单独开发,在需要的时候反织回去,底层的原理是动态代理。
  • 4)方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,且Spring可降低各种框架的使用难度,Spring提供了对各种优秀框架(Struts2、Hibernate、MyBatis等)的直接支持。

3、Spring体系架构

  • Spring由20多个模块组成,可分为:数据访问/集成、Web、AOP、JVM代理、消息发送、核心容器、测试。
    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_IoC控制反转

4、IOC的概念(重点)

  • IoC是一个概念,又是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现独享的创建、属性的赋值、依赖的管理。
  • IoC的实现,当今流行的方式为依赖注入

5、依赖注入(Dependency Injection,DI)

  • 依赖:A类中含有B类的实例,在A中调用B的方法完成某项功能,即称A类对B类有依赖。
  • DI, 程序代码不用做定位查询,这些工作由容器自行完成。DI是指程序运行过程中,若需要调用另一个对象协助,无序代码中创建被调用者,而是依赖外部容器(Spring),有外部容器创建后传递给程序。
  • Spring框架使用依赖注入(DI)实现控制反转(IoC)。

二、控制反转(Inversion of Control,IoC)


以下通过案例阐述IoC与DI

案例基于Maven实现,有关Maven的使用不再阐述。


1、一个简单的Spring程序

1.1 目录结构

【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_IoC控制反转_02

1.2 引入Maven依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.Etui</groupId>
<artifactId>spring_01_demo</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!-- 引入Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>

</dependencies>

<build>
<resources>

<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>

<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>

</build>
</project>
1.3 自定义实体类Student
package com.Etui.entity01;

public class Student {
private String name;
private Integer age;

public void setName(String name) {
this.name = name;
}

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

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public Student() {
System.out.println("Student对象被创建……………………");
}
}
1.4 Spring的配置文件applicationContext.xml
  • Spring 的配置文件位于rsrc/main/resources目录下,文件名任意。
  • <bean />:用于定义一个实例对象,一个实例对象对应一个bean元素。
  • id:bean实例的唯一标识,相当于实例对象的名称。
  • class:指定该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">

<!--
创建学生对象
等同于 Student stu = new Student();
id就是对象名称
name是对象的类型,底层通过反射构建对象

启动容器即创建对象
-->
<bean id="stu" class="com.Etui.entity01.Student">
<!-- setter注入方式 -->
<property name="name" value="神里绫华"></property>
<property name="age" value="20"></property>
</bean>

</beans>
1.5 测试类testSpring.java
package com.Etui.test01;

import com.Etui.entity01.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testSpring {

// 传统常见Student对象的方法
@Test
public void testStudent() {
Student student = new Student();
System.out.println(student);
}

// 通过Spring创建对象的方法
@Test
public void testStudentSpring() {
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
Student student = (Student) ac.getBean("stu");
System.out.println(student);
}
}
1.6 运行结果

【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_注解开发基础_03

2、Spring的容器接口和实现类

2.1 ApplicationContext 接口(容器)
  • ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色,它的实现类有两个:

    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_Java_04

  • Spring 配置文件在项目的类路径下,则使用ClassPathXmlApplicationContext 实现类进行加载。
2.2 ApplicationContext容器中对象的装配时机
  • ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。Spring初始化对象时要使用无参的构造方法,切记保证类中有无参构造方法。
2.3 Spring创建java对象结构图

【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_Spring框架_05

3、注入分类(重点)


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


3.1 set注入
  • 通过setter方法传入调用者的实例。这种注入方式简单直观,在spring的依赖注入中大量使用。
3.1.1 简单类型
  • School类
package com.Etui.entity02;

public class School {
private String name;
private String address;

public School() {
System.out.println("School类被创建………………");
}

@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}

public void setName(String name) {
this.name = name;
}

public void setAddress(String address) {
this.address = address;
}
}
  • spring配置文件
<!-- 创建School对象 -->
<bean id="school" class="com.Etui.entity02.School">
<property name="name" value="合肥学院"></property>
<property name="address" value="合肥市蜀山区"></property>
</bean>
  • 测试类

    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_Spring框架_06

3.1.2 引用类型
  • 当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。如下,Student类中含有Scholar类。
package com.Etui.entity02;

public class Student {
private String name;
private Integer age;
private School school;

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}

public void setName(String name) {
this.name = name;
}

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

public void setSchool(School school) {
this.school = school;
}

public Student() {
}
}
  • 对其他对象的引用,使用bean标签的ref属性:
<!-- 创建Student对象 -->
<bean id="student" class="com.Etui.entity02.Student">
<property name="name" value="凝光"></property>
<property name="age" value="21"></property>
<property name="school" ref="school"></property>
</bean>
  • 测试类

    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_注解开发基础_07

3.2 构造方法的注入
  • 构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器依赖关系。在实体类中必须提供相应参数的构造方法。
  • 构造注入通过<constructor-arg / >标签实现,该标签属性有:
  • name:指定参数名称。
  • index:参数对应着构造器的第几个参数,从零开始。可不写,但若参数类型相同,或参数之间有包含关系,则需要保证赋值顺序与构造器中的参数顺序一致。
  • 实体类如下:
package com.Etui.entity02;
public class School {
private String name;
private String address;
}
public class Student {
private String name;
private Integer age;
}
public class Student {
private String name;
private Integer age;
private School school;
}
  • 构造方法注入共有以下三种注入方式:
<!-- 创建学校的对象,使用构造方法参数名称注入值 -->
<bean id="school" class="com.Etui.entity03.School">
<constructor-arg name="name" value="合肥学院"></constructor-arg>
<constructor-arg name="address" value="合肥市蜀山区"></constructor-arg>
</bean>

<!-- 创建学生对象,使用构造方法的参数的下标注入值 -->
<bean id="student" class="com.Etui.entity03.Student">
<constructor-arg index="0" value="钟离"></constructor-arg>
<constructor-arg index="1" value="23"></constructor-arg>
<constructor-arg index="2" ref="school"></constructor-arg>
</bean>

<!-- 创建学生对象,使用默认的构造方法的参数顺序 -->
<bean id="studentSequence" class="com.Etui.entity03.Student">
<constructor-arg value="甘雨"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
<constructor-arg ref="school"></constructor-arg>
</bean>
3.3 引用类型属性自动注入
3.3.1 byName方式自动注入
  • 当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
3.3.2 byType 方式自动注入
  • 使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

4、基于注解的DI

  • DI 使用注解,不再需要在Spring配置文件中声明bean实例。Spring中使用注解,需要在原有的基础之上配置组件扫描器,用于指定在基本包中扫描注解。如下:
    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_IoC控制反转_08

4.1 指定多个包的三种方式

  • 1)使用多个 context:component-scan 指定多个不同的包路径
<context:component-scan base-package="com.Etui.entity"></context:component-scan>
<context:component-scan base-package="com.Etui.service"></context:component-scan>
  • 2)指定 base-package的值使用分隔符
<!-- 分隔符可使用逗号、分号或空格 -->
<context:component-scan base-package='com.Etui.entity, com.Etui.service'></context:component-scan>
  • 3)base-package指定到父包名
<context:component-scan base-package="com.Etui"></context:component-scan>

4.2 常用注解

4.2.1 创建对象的注解
  • @Component:创建所有对象,都可以使用此注解,除了控制器,业务逻辑层、数据访问层的对象。
  • @Controller:创建控制器层的对象,此对象可以接收用户请求,返回处理结果。
  • @Service:创建业务逻辑层的对象,此对象可实施事务控制,向上给控制器返回数据,向下调用数据访问层。
  • @Repository:创建数据访问层的对象,对数据库中的数据进行增删改查。
4.2.2 给对象赋值的注解
  • @Value:给简单类型赋值。
  • @Autowired:给引用类型按类型注入。
  • Qualifier:给引用类型按名称注入。
4.2.3 应用案例
  • 目录结构:

    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_Java_09

  • 实体类如下:
// School类
@Component("schoolParent")
public class School {
@Value("合肥大学")
private String name;
@Value("合肥市蜀山区")
private String address;

public School() {
System.out.println("School类的构造函数………………");
}

@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}


// Student类
@Component
public class Student {

@Value("砂糖")
private String name;
@Value("18")
private Integer age;

// 引用类型按类型注入
// @Autowired
// private School school;

// 引用类型按名称注入
@Autowired
@Qualifier("school")
private School school;

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}

public Student() {
}
}


// Student类的子类
@Component("school")
public class SubSchool extends School{
@Value("合肥168中学")
private String name;
@Value("合肥市经开区")
private String address;

@Override
public String toString() {
return "SubSchool{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}

public SubSchool() {
System.out.println("SubSchool的构造方法…………");
}
}
  • Spring配置文件(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.Etui.entity03"></context:component-scan>

</beans>
  • 测试代码

    【Spring】Spring控制反转(Inversion of Control,IoC)(笔记一)_Spring框架_10

Over!