浅识SpringIOC

浅识 Spring IOC

小小了解一下 Spring 家族

官网地址:spring.io/

项目列表:spring.io/projects

Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。

Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首 次在 Apache 2.0 许可下发布。

Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。

Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应 用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践

通过上面的粗略介绍 ,我们可以简单的了解一下Spring家族的厉害之处下面就是我们今天学习的重点

Spring IOC( Inversion of Control )反转控制

First : 理解什么是IOC思想

图示理解:

Spring IOC.png文字解释:

  • ①获取资源的传统方式

    传统的方式是组件主动的从容器中获取所需要的资源,在这样的 模式下开发人员往往需要知道在具体容器中特定资源的获取方式

  • ②反转控制方式获取资源

    反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主 动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源 的方式即可,极大的降低了学习成本,提高了开发的效率。

  • ③DI:Dependency Injection,翻译过来就是依赖注入

    DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器 的资源注入。相对于IOC而言,这种表述更直接。 所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

Second : 在Spring中的IOC是怎么体现出来的

image.pngSpring中的IOC有两种实现方式 :

1. BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用

2. ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。(原因显而易见, 我们无法控制内部)

ApplicationContext的主要实现类

  • ClassPathXmlApplicationContext :
    • 见名知意 它就是通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
  • FileSystemXmlApplicationContext :
    • 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容 器对象
  • ConfigurableApplicationContext :
    • ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、 关闭和刷新上下文的能力。
  • WebApplicationContext :
    • 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对 象,并将对象引入存入 ServletContext 域中。

Third : 基于XML 管理IOC容器

首先是创建所有的项目都绕不过去的坑—-引入依赖
1
2
3
4
5
6
7
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
复制代码
创建配置文件

image.png

1. 操作顺序及其思路

创建自定义组件类的时候一定要创建有参和无参构造器两个, 良好的代码习惯是避免犯错的前提 Spring底层默认是通过反射来调用自定义组件类中的无参构造器来创建组件对象的image.png

2. 获取Bean的方式

  • ①方式一:根据id获取
  • ②方式二:根据类型获取
  • ③方式三:根据id和类型
1
2
3
4
5
6
7
8
9
10
ApplicationContext ioc = new ClassPathXmlApplicationContext("Spring配置文件.xml");
<!--根据id获取
由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象
-->
HappyComponent hc = ioc.getBean("bean的id属性");
<!--根据类型获取-->
HappyComponent hc = ioc.getBean(自定义组件类.class);
<!--根据id和类型-->
HappyComponent hc = ioc.getBean("bean的id属性",自定义组件类.class);

获取bean时注意点

首先 : 在根据类型获取对象时 创建的Bean 对象只能有一个。因为默认情况下我们bean中的scope属性的值为”singleton” 如果想要创建多个对象 ,那么就将scope的属性设置为”prototype”

其次 :如果组件的类型是接口类型 ,那么就必须设置接口的实现类的Bean是唯一的 ,否则报错

3. DI 依赖注入

这里用的是Student组件类 属性: id , name , age , sex

① setXXX()方法 注入

1
2
3
4
5
6
7
8
9
10
11
<bean id="studentOne" class="spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
-->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
复制代码

② 构造器 注入

1
2
3
4
5
6
7
<bean id="studentTwo" class="spring.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
复制代码

③ 特殊类型(引用类型)的 注入

首先创建被引用的类的Bean

比如下面 :先创建Clazz类的Bean ,然后该Bean的id (clazzOne) 作为需要引用的属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="clazzOne" class="spring.bean.Clazz">
<property name="clazzId" value="1111"></property>
<property name="clazzName" value="一班"></property>
</bean>

<bean id="studentFour" class="spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
</bean>
复制代码

特殊类型的注入第二种方法就是创建内部bean(不常用 , 做了解即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="studentFour" class="spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<property name="clazz">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="clazzInner" class="spring.bean.Clazz">
<property name="clazzId" value="2222"></property>
<property name="clazzName" value="远大前程班"></property>
</bean>
</property>
</bean>
复制代码

特殊类型的注入第三种方法: 级联属性赋值 (一定先引用某个bean为属性赋值,才可以使用级联方式更新属性)

1
2
3
4
5
6
7
8
9
10
11
<bean id="studentFour" class="spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="三班"></property>
</bean>
复制代码

④ 特殊类型(数组类型) 注入

在前面的基础上 新增属性 String [ ] hobbies;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="studentFour" class="spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>

<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>打麻将</value>
</array>
</property>
</bean>
复制代码

⑤ 特殊类型(List集合类型) 注入

在Clazz 类中新增 List < Student > students;

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="clazzTwo" class="spring.bean.Clazz">
<property name="clazzId" value="4444"></property>
<property name="clazzName" value="Javaee0222"></property>
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>
复制代码

⑥ 特殊类型(Map集合类型) 注入

新创建教师类Teacher 在学生类中添加Map< String , Teacher >

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
<bean id="studentFour" class="spring.bean.Student">
<property name="id" value="1004"></property>
<property name="name" value="赵六"></property>
<property name="age" value="26"></property>
<property name="sex" value="女"></property>
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<property name="teacherMap">
<map>
<entry>
<key><value>10010</value>
<!--这里我们同样的创建Teacher类的Bean-->
</key>
<ref bean="teacherOne"></ref>
</key>
</entry>
<entry>
<key>
<value>10086</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</map>
</property>
</bean>
复制代码

Fourth : Bean的生命周期

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围

image.png

具体的生命周期过程

  1. bean对象创建(调用无参构造器)
  2. 给bean对象设置属性
  3. bean对象初始化之前操作(由bean的后置处理器负责)
  4. bean对象初始化(需在配置bean时指定初始化方法)
  5. bean对象初始化之后操作(由bean的后置处理器负责)
  6. bean对象就绪可以使用
  7. bean对象销毁(需在配置bean时指定销毁方法)
  8. IOC容器关闭

Fifth : FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个 FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是 getObject()方法的返回值。通过这种机制 **(反射机制)**,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

image.png

1
2
<bean id="user" class="bean.UserFactoryBean"></bean>
复制代码

Sixth : 基于注解管理Bean★★★★★

首先了解一下注解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行

常用的注解 :

@Component:将类标识为普通组件 @Controller:将类标识为控制层组 @Service:将类标识为业务层组件 @Repository:将类标识为持久层组件

对于上述的除了Component注解之外的其他三个注解 Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这 三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

扫描组件的几种情况

  • include-filter : 包含扫描 , 只扫描谁
  • exclude-filter : 排除扫描 , 不扫描谁
  • 如果没有那么就默认全部扫描
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
<?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">
<!--扫描组件 : 让spring知道 ,什么加了注解
通过包来进行识别 ,直接扫描spring ,然后她下面的所有的包 以及类都会被扫描
-->
<context:component-scan base-package="com_Ray.spring" >
<!-- 在实际的项目开发中, 我们需要进行选择性的扫描 ,所以这时我们就需要进行排除
include-filter : 包含扫描 , 只扫描谁
[需要在context:component-scan标签中添加属性 :use-default-filters="false" ]
exclude-filter : 排除扫描 , 不扫描谁
type : 排除的类型(根据什么进行排除/或者只包括什么类型):
annotation(注解的类型) aspectj(了解即可)
assignable(类的类型) custom(了解即可) regex(了解即可)
expression : 放置全类名(或者注解全名)
-->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<context:include-filter type="assignable" expression="com_Ray.spring.controller.UserController"/>
</context:component-scan>
<bean id="userServiceInpl" class="com_Ray.spring.service.impl.UserServiceImpl" scope="prototype">

</bean>
<bean id="userServiceImpl" class="com_Ray.spring.service.impl.UserServiceImpl">
</bean>
</beans>
复制代码

基于注解自动装配之Autowried注解

首先 ,自动装配是: 通过我们指定的策略 ,为我们当前ioc容器所管理的bean中的成员变量进行赋值的操作

img

@Autowried注解标识的位置:

  1. 标识在成员变量上
  2. set方法上
  3. 为当前变量赋值的有参构造器上

@Autowried注解的原理

场景参考:img

原理 :

  1. byType 根据类型来找到一个类型匹配的bean ,来为当前的属性自动进行赋值

自动装配: 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的 类类型或接口类型属性 赋值

img

  1. byName 把需要赋值的属性的名字来作为bean的id在ioc容器中去匹配到某一个bean来为当前的属性赋值

img

实现流程:

img

image.png

Finally : 写在最后

如果各位觉得有用,请点赞支持一下

如果我写的哪里有不足, 以及各位觉得哪里不明白的请评论区指出

诚挚欢迎各位交流学习,共同进步