Spring Core-IOC

1. Spring Bean是什么?

我们常说IOC容器,容器是指用以容纳物料并以壳体为主的基本装置。在Spring中,IOC容器同样也是用来容纳“东西”的。但是这个东西在Spring中变成了bean对象。

在 Spring 中,构成应用程序主干的对象和由 Spring IoC 容器管理的对象称为 Bean。Bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则,Bean 只是应用程序中的许多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

2. Spring IoC

2.1 Spring IoC简介

IoC(Inversion of Control)就是常说的控制反转。依赖注入 (DI) 是 IoC 的一种特殊形式,对象仅通过构造函数参数、工厂方法的参数或在对象实例上设置的属性来定义其依赖关系(即它们使用的其他对象)。由工厂方法构造或返回。然后,IoC 容器在创建 bean 时注入这些依赖项。这个过程从根本上来说是 bean 本身的逆过程(因此得名“控制反转”),通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置。

org.springframework.beans和包org.springframework.context是 Spring Framework 的 IoC 容器的基础。该 BeanFactory 接口提供了能够管理任何类型对象的高级配置机制。 ApplicationContextBeanFactory接口的子接口。它在BeanFactory接口功能基础上添加了更多企业特定的功能。如aop特性、事件发布功能等等,因此我们使用最多的也是ApplicationContext

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

BeanFactory提供了配置框架和基本功能,ApplicationContext在此基础上添加了更多企业特定的功能。

2.2 控制反转

org.springframework.context.ApplicationContext 代表 Spring IoC 容器,负责实例化、配置和组装Bean。容器通过读取配置元数据来获取关于要实例化、配置和组装哪些对象的指令。

配置元数据以 XML、Java annotations或 Java Code表示。组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

讲解控制反转之前,我们先来思考一下:

  • 谁控制谁,控制什么

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

image-20230228152049160
  • 为何是反转,哪些方面反转了?

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

image-20230228152120929

主动创建:

img

IOC容器注入:

img

传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活

3. DI—Dependency Injection

即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。能够使得相互协作 软件保持松散耦合。

控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式

  • 谁依赖谁?

在spring项目中,将对象理解为Bean,也可以叫bean对象,这个bean和容器之间有个依赖关系,bean对象的创建是依赖容器的

image-20230228152658783
  • 谁注入谁?

通过容器注入了bean对象,而且这个过程是自动化的,也就是说容器会自动找到和bean对象匹配的类型实例注入到对象中

image-20230228152243110

4. IoC配置元数据(Configuration metadata)

容器通过读取配置元数据获取关于实例化、配置和组装什么对象的指令。配置元数据用 XML、 Java 注释或 Java 代码表示。目前的主流方式是 注解 + Java 配置

元数据(metadata)描述的是“自身”的一些信息,包括

ClassMetadata 描述的是class类的基本信息

AnnotationMetadata描述的是注解信息

MethodMetadata描述的是方法的信息

4.1 XML方式

顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。

  • 优点: 可以使用于任何场景,结构清晰,通俗易懂,所以xml方式是最全面的配置元数据的方式
  • 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="userService" class="com.itszt.ioc.service.UserServiceImpl">
<property name="userDao" ref ="userDao"></property>
<property name="userDao" ref ="userDao1"></property>
</bean>

<bean id="userDao" class="com.itszt.ioc.dao.UserDaoImpl"></bean>


<import resource="services.xml"/>
<!--使用一个或多个出现的 < import/> 元素从另一个文件加载 bean 定义前导斜杠被忽略,但是考虑到这些路径
相对的,最好不要使用斜杠-->

<!--使用Bean节点来创建对象id属性标识着对象,不能重复name属性代表着要创建对象的类全名-->
</beans>

4.2Java 配置

从 Spring 3.0开始,Spring JavaConfig 项目提供的许多特性成为核心 Spring 框架的一部分。因此,您可以使用 Java 而不是 XML 文件在应用程序类之外定义 bean。要使用这些新特性,请参见@Configuration、@bean@import 和@DependsOn 注释

  1. 创建一个配置类, 添加@Configuration注解声明为配置类
  2. 创建方法,方法上加上@bean,该方法用于创建实例并返回,该实例创建后会交给spring管理

@Configuration注解声明为配置类@Bean来修饰方法,该方法返回一个对象,容器中bean的ID默认为方法名(方法名建议与实例名相同(首字母小写)),Spring内部会将该对象加入到Spring容器中。

  • 优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活
  • 缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class Configuration {

   @Bean("userDao")
   public UserDao userDao() {
       UserDao userDao = new UserDao();
       System.out.println("我是在configuration中的"+userDao);
       return userDao;
   }

//可以不写,默认为方法名
@Bean
public UserServiceImpl userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao());
return userService;
}

@Bean
public Bird bird(){
Bird bird = new Bird();
bird.setBirdAge(18);
bird.setBirDid("1");
bird.setBirdName("小小鸟");
return bird;
}

}

4.3 注解方式

Spring 2.5引入了对基于注释的配置元数据的支持:Annotation-based configuration,使得我们可以通过简单的注解方式即可使用spring容器中的bean

  • 优点:开发便捷,通俗易懂,方便维护。
  • 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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
http://www.springframework.org/schema/context/spring-context.xsd">

<!--IoC的注解扫描-->
<context:component-scan base-package="com.xxx.ioc.service, com.xxx.ioc.dao"></context:component-scan>

</beans>

自定义扫描类以@CompoentScan修饰来扫描IOC容器的Bean对象

1
2
3
4
5
6
//表明该类是配置类
@Configuration
@ComponentScan("bb")
public class AnnotationScan {

}

就可以在代码中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service("userService")
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
public void gogo(String data1,int data2) {
System.out.println("UserServiceImpl.gogo");
userDao.sayHello(data1,data2);
}

}

通过注解把对象添加到IOC容器中,对象名默认首字母小写。注解方式注入在 XML 注入之前执行,因此对于通过两种方法连接的属性,后者的配置将覆盖前者。

5.依赖注入

上述我们简单的讲述了如何向spring容器中注入bean对象。一个bean对象可以有自己的属性(基本数据类型或者引用数据类型),因此需要通过“依赖注入”将所需要的属性注入到目标对象中去。

举个例子:现在我们像IOC容器里注入一个自定义的bean,且我们为其注入值或者依赖。

Dog:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Dog {
private String dogname;
private int dogage;

public String getDogname() {
return dogname;
}

public void setDogname(String dogname) {
this.dogname = dogname;
}

public int getDogage() {
return dogage;
}

public void setDogage(int dogage) {
this.dogage = dogage;
}

@Override
public String toString() {
return "Dog{" +
"dogname='" + dogname + '\'' +
", dogage=" + dogage +
'}';
}
}

5.1 setter注入(Setter Injection)

setter注入也叫setter装配,本质是在xml里配置,然后通过setter方法给对象注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MyBean1 {

private String data1;
private int data2;
private String[] data3;
private List<String> data4;
private Map<String,Integer> data5;
private List<Dog> data6;
private Dog data7;

public void setData6(List<Dog> data6) {
this.data6 = data6;
}

public void setData5(Map<String, Integer> data5) {
this.data5 = data5;
}

public void setData4(List<String> data4) {
this.data4 = data4;
}

public void setData3(String[] data3) {
this.data3 = data3;
}

public void setData1(String data1) {
this.data1 = data1;
}

public void setData2(int data2) {
this.data2 = data2;
}


public void setData7(Dog data7) {
this.data7 = data7;
}

@Override
public String toString() {
return "MyBean1{" +
"data1='" + data1 + '\'' +
", data2=" + data2 +
", data3=" + Arrays.toString(data3) +
", data4=" + data4 +
", data5=" + data5 +
", data6=" + data6 +
", data7=" + data7 +
'}';
}
}
  • 在XML配置方式中,property都是setter方式注入,比如下面的xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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">

<!--setter装配-->
<bean id="mybean1" class="com.itszt.ioc.setter.MyBean1">
<property name="data1" value="呵呵哒"></property>
<property name="data2" value="123456"></property>

<property name="data3">
<!--String[]-->
<array>
<value>hehe1</value>
<value>hehe2</value>
<value>hehe3</value>
<value>hehe4</value>
</array>
</property>

<property name="data4">
<!--List<String>-->
<list>
<value>haha1</value>
<value>haha2</value>
<value>haha3</value>
<value>haha4</value>
</list>
</property>

<property name="data5">
<!--Map<String, Integer>-->
<map>
<entry key="xx1" value="123456"></entry>
<entry key="xx2" value="222333"></entry>
</map>
</property>

<property name="data6">
<!--List<Dog>-->
<list>
<ref bean="dog1"></ref>
<ref bean="dog2"></ref>
</list>
</property>

<!--Dog-->
<property name="data7" ref ="dog1"></property>

</bean>

<bean id="dog1" class="com.itszt.ioc.setter.Dog">
<property name="dogname" value="dahuang1"></property>
<property name="dogage" value="55"></property>
</bean>

<bean id="dog2" class="com.itszt.ioc.setter.Dog">
<!--<property name=" 另一个接口/类 " ref="xx(另一个类的id) "></property>-->
<property name="dogname" value="dahuang222"></property>
<property name="dogage" value="888"></property>
</bean>

</beans>

测试:

1
2
3
4
5
6
7
8
9
public class TestSetter {
@Test
public void test1(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/xxx/ioc/spring-config-setter.xml");

MyBean1 mybean1 = applicationContext.getBean("mybean1", MyBean1.class);
System.out.println(mybean1.toString());
}
}

输出:

1
2
17:31:33,050  INFO XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [com/xxx/ioc/spring-config-setter.xml]
MyBean1{data1='呵呵哒', data2=123456, data3=[hehe1, hehe2, hehe3, hehe4], data4=[haha1, haha2, haha3, haha4], data5={xx1=123456, xx2=222333}, data6=[Dog{dogname='dahuang1', dogage=55}, Dog{dogname='dahuang222', dogage=888}], data7=Dog{dogname='dahuang1', dogage=55}}

5.2 构造器注入(Constructor Injection)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyBean2 {

private String data1;
private int data2;
private String[] data3;
private List<String> data4;
private Map<String,Integer> data5;
private List<Dog> data6;

public MyBean2(String data1, int data2, String[] data3, List<String> data4, Map<String, Integer> data5, List<Dog> data6) {
this.data1 = data1;
this.data2 = data2;
this.data3 = data3;
this.data4 = data4;
this.data5 = data5;
this.data6 = data6;
}

@Override
public String toString() {
return "MyBean2{" +
"data1='" + data1 + '\'' +
", data2=" + data2 +
", data3=" + Arrays.toString(data3) +
", data4=" + data4 +
", data5=" + data5 +
", data6=" + data6 +
'}';
}
}
  • 在XML配置方式中<constructor-arg>是通过构造函数参数注入,比如下面的xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?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">

<!--构造器装配1-->
<bean id="mybean2" class="demo.xx.MyBean2">
<constructor-arg index="0" value="dahuang2"></constructor-arg>
<constructor-arg index="1" value="123456"></constructor-arg>
<constructor-arg index="2">
<array>
<value>hehe1</value>
<value>hehe2</value>
<value>hehe3</value>
<value>hehe4</value>
</array>
</constructor-arg>

<constructor-arg index="3">
<list>
<value>haha1</value>
<value>haha2</value>
<value>haha3</value>
<value>haha4</value>
</list>
</constructor-arg>

<constructor-arg index="4">
<map>
<entry key="xx1" value="123456">
</entry>

<entry key="xx2" value="222333">
</entry>
</map>

</constructor-arg>

<constructor-arg index="5">
<list>
<ref bean="dog1"></ref>
<ref bean="dog2"></ref>
</list>
</constructor-arg>

</bean>
<!--构造器装配1-->


<!--构造器装配2-->
<bean id="mybean22" class="demo.xx.MyBean2">
<constructor-arg name="data1" value="dahuang2"></constructor-arg>
<constructor-arg name="data2" value="123456"></constructor-arg>
<constructor-arg name="data3">
<array>
<value>hehe1</value>
<value>hehe2</value>
<value>hehe3</value>
<value>hehe4</value>
</array>
</constructor-arg>

<constructor-arg name="data4">
<list>
<value>haha1</value>
<value>haha2</value>
<value>haha3</value>
<value>haha4</value>
</list>
</constructor-arg>

<constructor-arg name="data5">
<map>
<entry key="xx1" value="123456">
</entry>

<entry key="xx2" value="222333">
</entry>
</map>

</constructor-arg>
<constructor-arg name="data6">
<list>
<ref bean="dog1"></ref>
<ref bean="dog2"></ref>
</list>
</constructor-arg>

</bean>
<!--构造器装配2-->
</beans>

构造器赋初始值的时候,index=角标(指定索引还可以解决构造函数具有两个相同类型的参数时的不确定性。注意,索引是基于0的。)或者name="名称"这两种方式都可以赋初始值,属性为String和int在里面不用写" "

我们在上面通过index=角标name="名称"分别额注入了不同的bean。

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test1(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/xxx/ioc/spring-config-constructor.xml");
MyBean2 mybean2 = applicationContext.getBean("mybean2", MyBean2.class);
System.out.println(mybean2.toString());
}

@Test
public void test2(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/xxx/ioc/spring-config-constructor.xml");
MyBean2 mybean22 = applicationContext.getBean("mybean22", MyBean2.class);
System.out.println(mybean22.toString());
}

输出:

1
2
3
4
mybean2:MyBean2{data1='dahuang2', data2=123456, data3=[hehe1, hehe2, hehe3, hehe4], data4=[haha1, haha2, haha3, haha4], data5={xx1=123456, xx2=222333}, data6=[Dog{dogname='dahuang1', dogage=55}, Dog{dogname='dahuang222', dogage=888}]}


mybean22:MyBean2{data1='dahuang2', data2=123456, data3=[hehe1, hehe2, hehe3, hehe4], data4=[haha1, haha2, haha3, haha4], data5={xx1=123456, xx2=222333}, data6=[Dog{dogname='dahuang1', dogage=55}, Dog{dogname='dahuang222', dogage=888}]}

5.3 注解注入

也叫属性注入以@Autowired(自动注入)注解注入为例,修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。

  • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
  • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
  • byType:查找所有的set方法,将符合符合参数类型的bean注入。

解释一下参数类型:java思想是一切皆对象,因此可以通过接口或者抽象类,父类等方式去标识一类。byType就是限定于同一种"参数类型"下。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13

@Service
public class UserServiceImpl {

@Autowired
private UserDaoImpl userDao;

public List<User> findUserList() {
return userDao.findUserList();
}

}

为什么推荐构造器注入方式?

先来看看Spring在文档里怎么说:

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.”

简单的翻译一下:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。

依赖不可变:其实说的就是final关键字。

依赖不为空(省去了我们对其检查):当要实例化UserServiceImpl的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。

完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。

在另一篇文章,详细的介绍了为什么推荐构造器注入方式?

Spring Core-IOC

1.Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)

2.Spring 框架托管创建的Bean放在哪里呢? 这便是IoC Container;

3.Spring 框架为了更好让用户配置Bean,必然会引入不同方式来配置Bean? 这便是xml配置,Java配置,注解配置等支持

4.Spring 框架既然接管了Bean的生成,必然需要管理整个Bean的生命周期等;

5.应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ; 所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式

6.在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier... 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)

6.Read more

:lollipop::https://pdai.tech/md/spring/spring-x-framework-ioc.html


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


Spring Core-IOC
https://nanchengjiumeng123.top/2022/05/15/framework/spring/Spring IoC/2022-05-15_Spring Core-IOC/
作者
Yang Xin
发布于
2022年5月15日
许可协议