Spring学习总结-01
1. Spring优势
-
Spring技术是javaEE开发必备技能,企业开发技术选型命中率>=90%
-
Spring框架的两大优势在于:简化开发 和 框架整合
-
简化开发:降低企业级开发的复杂性。Spring的简化开发是基于以下两大核心技术实现的
Spring框架中两大核心技术:AOP 和 IOC
- AOP:面向切面编程
- IOC:控制反转
-
框架整合:整合其他技术,提高企业级应用的开发和运行效率。Spring可以整合市面上几乎所有主流框架
Spring可以整合的框架:
- MyBatis
- Mybatis-Plus
- Hibernate
- …
-
2. Spring相关概念
2.1 Spring家族
-
Spring并不是单一的技术,而是一个大家族,一大套技术集。官网:https://spring.io
- Spring可以用于开发web,微服务以及分布式系统等等
- Spring已形成了完整的生态圈,我们完全可以使用Spring技术完成整个项目的构建、设计和开发
- Spring有若干个项目,可以根据需要进行选择,下图是所有项目组合
-
目前学习了Spring Framework、SrpingBoot、SpringCloud
- Spring Framework:Spring框架,是Spring其他所有技术的基础,一般简称Spring
- SrpingBoot:Spring用来简化开发,SpringBoot在此基础上实现快速开发
- SpringCloud:微服务架构,用来做分布式之微服务架构的相关开发
2.2 Spring系统架构
-
Spring Framework是Spring生态圈的基础项目,是其他项目的根基
-
Spring Framework发展经历多次版本更新,以下是4版本的架构图
-
核心层:
-
Core Container:核心容器
-
Spring框架最核心的部分,其他模块依赖它运行
-
顾名思义,容器它是用来装东西的,装的是java对象
因此可以说Spring是一个用来管对象的技术
-
-
AOP层:
-
AOP:面向切面编程。
与面向对象编程一样是一种编程思想,换句话说它教你程序应该怎么做,是一个设计型的概念
-
依赖于核心容器,目的是在不改变原有代码的基础下对其实现功能增强
-
Aspects:AOP思想的实现。
以后实现AOP开发时,不仅要导AOP的坐标还要导Aspects的坐标
-
-
数据层:
-
Data Access:数据访问
Spring内部提供了自主访问数据层的技术
-
Data Integration:数据集成
Spring支持整合其他技术实现对数据层的访问,比如Mybatis
-
Transactions:事务
Spring在事务这个方面做了大的突破,提供了一种开发起来效率非常高的事务控制方案
-
-
Web层
-
Test层:
- Spring整合了Junit来完成单元测试和集成测试
2.3 Spring核心概念
2.3.1 目前项目中的问题以及解决思想:
-
代码书写现状:
-
业务层需要调用数据层的方法,就需要在业务层创建数据层的对象
new BookDaoImpl()
-
如果数据层的实现类发生变化,那么业务层也需要变化
new BookDaoImpl() ——>new BookDaoImpl2()
-
因此项目需要重新编译、打包、部署
-
所以,当前代码编写过程存在问题:耦合度过高
-
-
解决思想:
- 针对如上问题,我们把new对象的那行代码去掉,这样就能降低依赖,但显然程序将无法运行,因为bookDao没有赋值为null,强行运行会报空指针异常
-
解决方法:
-
目前问题在于业务层不想创建对象,而运行时又需要这个对象,该如何解决?
-
根据此问题,Spring提出了一个解决方案:
-
使用对象时,不再由程序主动new产生对象,而是由外部提供对象
这种实现思想是Spring的一个核心概念,称为IOC(控制反转)
-
-
2.3.2 IOC、IOC容器、Bean、DI
-
IOC:控制反转
使用对象时,不再由程序主动的创建对象,而是由外部提供对象。此过程中对象的创建权由程序转移到外部,这就是控制反转(IOC)的思想
-
IOC容器:Spring对IOC思想进行了实现,提供了一个容器
Spring提供了一个容器,称为IOC容器,充当控制反转思想中的‘外部’
-
Bean
IOC容器负责对象的创建、初始化等一系列工作,这些被创建并管理的对象在IOC容器中称为Bean
-
DI:依赖注入
当IOC容器创建好service和dao对象后,程序还不能正常运行,因为两个对象虽然存在但没有任何关系。需要将dao对象交给service,也就是说要绑定service和dao对象之间的关系
因此,在容器中建立Bean和Bean之间的依赖关系的过程,称为DI(依赖注入)
2.3.3 目标与最终效果
-
目标:充分解耦
- 使用IOC容器管理Bean
- 在IOC容器中将有依赖关系的Bean进行关系绑定(DI)
-
最终效果:使用对象时不仅可以直接从IOC容器中获取,并且获取到的Bean已经绑定了所以依赖关系
3. 入门案例
3.1 IOC入门案例
3.1.1 入门案例思路分析
- Spring是使用容器来管理bean对象的,那么管什么?
- 主要管理项目中所使用到的类对象,比如(Service和Dao)
- 如何将被管理的对象告知IOC容器?
- 使用配置文件
- 被管理的对象交给IOC容器,要想从容器中获取对象,就先得思考如何获取到IOC容器?
- Spring框架提供相应的接口
- IOC容器得到后,如何从容器中获取bean?
- 调用Spring框架提供对应接口中的方法
- 使用Spring导入哪些坐标?
- 用别人的东西,就需要在pom.xml添加对应的依赖
3.1.2 入门案例代码实现
-
创建maven工程
-
在pom.xml文件中添加Spring依赖坐标
-
添加案例所需的类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
-
创建Spring配置文件
-
在配置文件中配置bean
- 使用bean标签配置bean
id:给bean起名字
class: bean类型 -
创建app类,在主函数里获取IOC容器
通过创建ClassPathXmlApplicationContext()对象获取容器
参数为配置文件名 -
获取IOC容器中的bean,即获取对象
通过容器的getBean()方法获取对象
参数为bean标签的id -
运行
-
获取IOC容器的service bean,并执行
以上就是IOC入门案例的代码实现,显然BookServiceImpl类里存在BookDaoImpl对象的new操作,因此需要实现DI:依赖注入
3.2 DI入门案例
3.2.1 入门案例思路分析
- 要想实现依赖注入,必须要基于IOC管理Bean
- DI的入门案例要依赖于前面IOC的入门案例
- Service中使用new形式创建的Dao对象是否保留?
- 需要删除掉,最终要使用IOC容器中的bean对象
- Service中需要的Dao对象如何进入到Service中?
- 在Service中提供方法,让Spring的IOC容器可以通过该方法传入bean对象
- Service与Dao间的关系如何描述?
- 使用配置文件
3.2.2 入门案例代码实现
-
删除service层使用new创建的dao对象
-
在service层提供dao的setter方法
- 可以使用alt+insert快捷键生成
- 该setter方法是给容器调用的
-
在配置文件完成注入
- 因为要在service里注入dao对象,因此应该在service的bean标签中完成注入
- 在bean标签中使用property标签实现注入
- property标签中name为service实现类中定义的属性名,ref为需要注入的bean的id
-
运行
以上结果可以看出DI注入成功,不需要在service层创建dao对象也可以使用dao对象并使用其方法
4. IOC相关内容
4.1 bean基础配置
4.1.1 bean的基础配置
4.1.2 bean的别名配置
4.1.3 bean的作用范围配置
- 为什么bean默认为单例?
bean为单例的意思是在Spring的IOC容器中只会有该类的一个对象
bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
- bean在容器中是单例的,会不会产生线程安全问题?
如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,
因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题
- 哪些bean对象适合交给容器进行管理?
表现层对象
业务层对象
数据层对象
工具对象
- 哪些bean对象不适合交给容器进行管理?
封装实例的域对象,因为会引发线程安全问题,所以不适合
4.2 bean的实例化(bean如何被Spring创建出来的)
4.2.1 通过构造方法
- 在BookServiceImpl类中加入无参构造函数,输出一句话
- 运行主函数
通过输出结果可以看出,Spring创建bean默认是通过构造函数,并且是无参构造函数创建。如果将构造函数换位私有(private),仍然会输出这句话,Spring通过构造函数创建bean时使用暴力反射
4.2.2 通过静态工厂
-
创建静态工厂类,实现静态方法获取dao对象
-
在配置文件中进行配置
class:写的是静态工厂全类名。如果只写class,Spring创建的是静态工厂对象
factory-method:写的是静态工厂创建dao对象的静态方法 -
运行
4.2.3 通过实例工厂
-
创建实例工厂类,实现创建dao对象的方法
-
在配置文件中进行配置
- 需要先创建实例工厂的对象,因此先配置实例工厂bean
- 需要通过实例工厂的get方法创建dao对象,因此配置创建dao对象的bean
factory-bean:实例工厂bean的id
factory-method:实例工厂中创建dao对象的方法
-
运行
4.2.4 通过FactoryBean
-
该方式是基于实例工厂实现的,需要掌握
-
创建工厂类
- 实现FactoryBean接口,并指定要创建的对象类型
- 实现接口中的三个方法,isSingleton()可以不写,默认单例
getObject()返回创建的对象
getObjectType()返回所创建类的class对象
4.3 bean的生命周期
为BookDao添加生命周期的控制方法,具体的控制有两个阶段:
- bean创建之后,想要添加内容,比如用来初始化需要用到资源
- bean销毁之前,想要添加内容,比如用来释放用到的资源
4.3.1 方式一:通过配置文件
-
在BookDao实现类中添加init方法和destory方法
-
修改配置文件,在bean标签内添加init-method和destory-method
-
运行结果
4.3.2 方式二:实现接口
- 在BookDaoImpl类中实现InitializingBean和DisposableBean接口
- 实现接口中的destroy和afterPropertiesSet方法
- 运行结果
从结果中可以看出,init方法执行了,但是destroy方法却未执行,这是为什么呢?
- Spring的IOC容器是运行在JVM中
- 运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行
- main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了
- 所以没有调用对应的destroy方法
- 以下两种方式关闭容器,让bean销毁并执行方法
4.3.3 close关闭容器
- ApplicationContext中没有close方法,需要将ApplicationContext更换成ClassPathXmlApplicationContext
- 调用容器对象的close()方法
- 该方法相对暴力
4.3.4 注册钩子关闭容器
- 在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
- 调用容器对象的registerShutdownHook()方法
注意:registerShutdownHook在ApplicationContext中也没有
相同点:这两种都能用来关闭容器
不同点:close()是在调用的时候关闭,registerShutdownHook()是在JVM退出前调用关闭
4.3.5 小结
- 关于Spring中对bean生命周期控制提供了两种方式:
- 在配置文件中的bean标签中添加init-method和destroy-method属性
- 类实现InitializingBean与DisposableBean接口,这种方式了解下即可。
- 对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
- 初始化容器
- 1.创建对象(内存分配)
- 2.执行构造方法
- 3.执行属性注入(set操作)
- 4.执行bean初始化方法
- 使用bean
- 1.执行业务操作
- 关闭/销毁容器
- 1.执行bean销毁方法
- 初始化容器
- 关闭容器的两种方式:
- ConfigurableApplicationContext是ApplicationContext的子类
- close()方法
- registerShutdownHook()方法
- ConfigurableApplicationContext是ApplicationContext的子类
5. DI相关内容
两种注入方式
- setter注入
- 注入简单数据类型
- 注入引用数据类型
- 构造器注入
- 注入简单数据类型
- 注入引用数据类型
5.1 setter注入
5.1.1 注入引用数据类型
- 上文DI案例中便是setter注入引用类型的案例
- 回顾:
-
在BookServiceImpl中声明BookDao属性,并提供其set方法
-
在配置文件bean标签内添加property标签:
-
property标签中name为定义的属性名,ref为该引用类型bean的id
-
-
5.1.2 注入简单数据类型
-
在BookDaoImpl类中声明对应的简单数据类型的属性,并提供其set方法
-
在配置文件bean标签内添加property标签:
- value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换
- value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换
5.2 构造器注入
5.2.1 注入引用数据类型
- 在BookServiceImpl中声明BookDao属性,并提供有参构造方法,参数为BookDao类型
- 在配置文件bean标签内添加constructor-arg标签:
- constructor-arg标签中name为构造方法的参数名,ref为该引用类型bean的id
- constructor-arg标签中name为构造方法的参数名,ref为该引用类型bean的id
5.2.2 注入简单数据类型
- 在BookDaoImpl类中声明对应的简单数据类型的属性,并提供有参构造方法
- 在配置文件bean标签内添加constructor-arg标签
构造器注入方式耦合度高,因为构造方法的参数名需要与配置文件中constructor-arg标签中的name一致
在解决这个问题之前,需要提前说明的是,这个参数名发生变化的情况并不多,所以上面的还是比较主流的配置方式,下面介绍的,以了解为主。
- 方式一:将name属性替换为type属性,即参数类型
弊端:如果参数存在两个或多个一样的类型这种方式就不太好实现 - 方式二:将name属性替换为index属性,即参数索引
弊端:如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题
5.3 选择
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 强制依赖指对象在创建的过程中必须要注入指定的参数
- 可选依赖使用setter注入进行,灵活性强
- 可选依赖指对象在创建过程中注入的参数可有可无
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
5.4 自动装配
IOC容器根据bean所依赖的资源在容器自动查找并注入到bean中的过程称为自动装配
方式有:
- 按类型
- 按名称
- 按构造方法
- 不启动自动装配
5.4.1 按类型自动装配
-
给需要注入的类提供setter方法
-
修改配置文件
- 删除setter注入的property标签
- 在bean中加入autowire属性,并选择byType值
注:
- 使用按类型自动装配时,被注入的引用类型的bean必须唯一,即只能有一个该引用类型的bean
5.4.2 按名称自动装配
-
给需要注入的类提供setter方法
-
修改配置文件
- 删除setter注入的property标签
- 在bean中加入autowire属性,并选择byName值
注:
- 使用按名称自动装配时,被注入的引用类型的bean的id必须和所定义的属性名一致
5.4.3 自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
5.5 集合注入
- 准备工作
5.5.1 注入数组类型数据
在bean标签中使用property标签
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
5.5.2 注入List类型数据
在bean标签中使用property标签
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
5.5.3 注入Set类型数据
在bean标签中使用property标签
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
5.5.4 注入Map类型数据
在bean标签中使用property标签
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
5.5.5 注入Properties类型数据
在bean标签中使用property标签
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>