目录
- Spring
- 简介
- 发展史
- Spring Framework系统架构
- spring 核心概念
- IOC、IOC容器、Bean、DI
- IOC快速入门
- DI快速入门
- IOC
- Bean基础配置
- id与class属性
- name属性
- scope属性
- Bean的实例化
- 构造方法
- 静态工厂
- 实例工厂
- FactoryBean的使用(工厂实例的简化)
- Bean的生命周期
- 添加初始化和销毁方法
- close关闭容器
- 注册钩子关闭容器
- 关闭方式的区别
- 通过接口来简化关闭容器
- DI
- setter注入
- 注入引用数据类型
- 注入简单数据类型
- 构造器注入
- 构造器注入多个数据类型
- 构造器注入多个简单数据类型
- 解决构造函数中参数名紧耦合
- 自动装配
- 什么是依赖自动装配
- 自动装配的方式有哪些?
- 按照类型完成自动装配的配置
- 按照名称完成自动注入装配的配置
- 注意事项(配置特征)
- 集合注入
- 加载 properties 文件
- 读取单个属性
- 核心容器
- 容器
- Bean 的三种获取方式
- 容器类层次结构
- BeanFactory的使用
- IOC/DI注解开发
- 注解开发定义bean
- 纯注解开发模式
- 注解开发bean作用范围与生命周期管理
- bean的作用范围
- bean的生命周期
- 注解开发依赖注入
- 注解实现按照类型注入
- 注解实现按照名称注入
- 简单数据类型注入
- 注解读取 properties 配置文件
- IOC/DI注解开发管理第三方bean
- 注解开发管理第三方bean
- 引入外部配置类
- 使用包扫描引入
- 使用@Import注解引入
- 注解开发实现为第三方bean注入资源
- 简单数据库类型
- 引用数据类型
- XML开发和注解的对比
Spring
官网:Spring | Home
简介
随着时代发展,软件规模与功能都呈几何式增长,开发难度也在不断递增,Spring可以简化开发
,降低企业级开发的复杂性,使开发变得更简单快捷
随着项目规模与功能的增长,遇到的问题就会增多,为了解决问题会引入更多的框架,Spring可以框架整合
,高效整合其他技术,提高企业级应用开发与运行效率
Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能
- Spring Framework: Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
- SpringBoot: Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
- SpringCloud: 这个是用来做分布式之微服务架构的相关开发。
发展史
- IBM(IT公司-国际商业机器公司)在1997年提出了EJB思想,早期的JAVAEE开发大都基于该思想。
- Rod Johnson(Java和J2EE开发领域的专家)在2002年出版的Expert One-on-One J2EE Design and Development ,书中有阐述在开发中使用EJB该如何做。
- Rod Johnson在2004年出版的Expert One-on-One J2EE Development without EJB ,书中提 出了比EJB思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是 Spring1.0。
- 随着时间推移,版本不断更新维护,目前最新的是Spring5
- Spring1.0是纯配置文件开发
- Spring2.0为了简化开发引入了注解开发,此时是配置文件加注解的开发方式
- Spring3.0已经可以进行纯注解开发,使开发效率大幅提升
- Spring4.0根据JDK的版本升级对个别API进行了调整
- Spring5.0已经全面支持JDK8
Spring Framework系统架构
Spring Framework 是 Spring 生态圈中最基础的项目,是其他项目的根基
Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整
Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的 是4的架构图
- 核心层
- Core Container:核心容器,这个模块是 Spring 最核心的模块,其他的都需要依赖该模块
- AOP层
- AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
- Aspect:AOP思想,Aspects 是对 AOP 思想的具体实现
- 数据层
- Data Access:数据访问,Spring 全家桶中有对数据访问的具体实现技术
- Data Integration:数据集成,Spring 支持整合其他数据层解决方案,比如MyBatis
- Transactions:事务,Spring 中事务管理是 Spring AOP 的一个具体实现
- Web层
- Spring MVC 框架
- Test层
- Spring 主要整合了 Junit 来完成单元测试和集成测试
spring 核心概念
IOC、IOC容器、Bean、DI
在这个代码中,由于业务层代码中需要数据层的对象,导致两层之间的耦合度很高,针对这个问题 Spring 提出了一个解决方案:使用对象时,在程序中不要主动使用 new 产生对象,转换为由外部提供对象
-
IOC(Inversion of Control)控制反转:控制反转的是对象的创建权
- 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建的控制权由程序转移到外部,此思想称为控制反转
- Spring技术对IOC思想进行了实现,提供了一个容器,称为
IOC容器
,用来充当IOC思想的 “ 外部 ” - IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为
Bean对象
-
DI(Dependency Injection)依赖注入:绑定对象与对象之间的依赖关系
但是现在又有一个问题,当 IOC 容器中创建好 service 和 dao 对象后,因为 service 运行需要依赖 dao 对象,但是 service 对象和 dao 对象没有任何关系,导致程序无法正确运行,而在容器中建立对象与对象之间的绑定关系就要用到 DI
- 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
介绍完 Spring 的 IOC 和 DI 的概念后,我们会发现这两个概念的最终目标就是:充分解耦
,具体实现靠:
- 使用 IOC 容器管理 bean(IOC)
- 在 IOC 容器内将有依赖关系的 bean 进行关系绑定(DI)
- 最终结果为:使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean 已经绑定了所有的依赖关系.
- IOC容器:Spring 创建了一个容器用来存放所创建的对象,这个容器就叫 IOC 容器
- Bean:容器中所存放的一个个对象就叫 Bean 或 Bean 对象
IOC快速入门
-
创建 Maven 项目
-
pom.xml引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
resources下创建 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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="项目下的Dao实现类路径"/> <bean id="bookService" class="项目下的Service实现类路径"/> </beans>
注意事项: bean 定义时 id 属性在同一个配置文件中不能重复
-
使用 Spring 提供的接口完成 IOC 容器的创建
-
从容器中获取对象进行方法调用
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");//这里的这个xml就是刚才创建的Spring的配置文件 BookDao bookDao = (BookDao) ctx.getBean("bookDao");//通过容器来获取Bean对象 bookDao.save(); } }
DI快速入门
实现依赖注入,必须要基于 IOC 管理 Bean
-
去除代码中的 new
-
为属性提供 setter 方法
public class BookServiceImpl implements BookService { //删除业务层中使用new的方式创建的dao对象 //private BookDao bookDao = new BookDaoImpl(); private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } //提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
在配置文件中添加依赖注入的配置
<?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"> <!--bean标签标示配置bean id属性表示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.XXX.service.impl.BookServiceImpl"> <!--配置server与dao的关系--> <!--property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性 ref属性表示参照哪一个bean --> <property name="bookDao" ref="bookDao"/> </bean> </beans>
注意: 配置中的两个 bookDao 的含义是不一样的
- name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前 面加set找对应的setBookDao()方法进行对象注入
- ref="bookDao"中 bookDao 的作用是让 Spring 能在IOC容器中找到id为bookDao的Bean对象给 bookService 进行注入
IOC
IOC 中对象的相关配置以及实例化方式和生命周期
Bean基础配置
id与class属性
注意: 因为 bookDao 和 bookService 一般在项目中是接口,接口不能实现,所以一般是使用它的实现类来进行对象的创建
name属性
XML中的配置写法
<!--name属性:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<!-- Ebi全称Enterprise Business Interface,翻译为企业业务接口 -->
<bean id="bookService" name="service service4 bookEbi"
class="BookServiceImpl的类路径">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" name="dao" class="BookDaoImpl的类路径"/>
注意:
- 通过 ref 属性来指定 bean ,被指定的 bean 必须在容器中存在,而且 ref 属性值可以是 bean 的 name 属性值
- 获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException
scope属性
注意:
- bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象,避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
- bean在容器中
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
- 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 表现层对象、业务层对象、数据层对象、工具对象适合交给容器进行管理,封装实例的域对象,因为会引发线程安全问题,所以不适合交给容器进行管理
Bean的实例化
bean本质上就是对象,对象在new的时候会使用构造方法完成,那创建bean也是使用构造方法完成的,基于此,Bean 有三种创建方式
构造方法
Spring底层使用的是类的无参构造方法,而且是通过反射来访问到类中的方法的
静态工厂
这种方式一般是用来兼容早期的一些老系统,需要在Spring的配置文件中加入以下内容:
<bean id="orderDao" class="OrderDaoFactory类路径名" factory-method="getOrderDao"/>
<!-- class:工厂类的类全名 -->
<!-- factory-mehod:具体工厂类中创建对象的方法名 -->
虽然在工厂类中也是直接new对象,但是在工厂的静态方法中,我们除了new 对象外还可以做一些必不可少的业务操作
实例工厂
在Spring的配置文件中加入以下内容:
<bean id="userFactory" class="UserDaoFactory的类路径"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
实例化工厂运行的顺序是:
- 创建实例化的工厂对象,对应的是第一行配置
- 调用对象中的方法来创建bean对象,对应的是第二行配置
- factory-bean:工厂对象
- factory-method:工厂对象中具体创建 bean 对象的方法名,对应关系如下:
FactoryBean的使用(工厂实例的简化)
这种方式在Spring去整合其他框架的时候会被用到
让工厂类(这里以UserDaoFactory为例)实现FactoryBean接口,重写接口的方法:
- 方法一: getObject(),被重写后,在方法中进行对象的创建并返回
- 方法二: getObjectType(),被重写后,主要返回的是被创建类的Class对象
- 方法三: 没有被重写,因为它已经给了默认值 true(单例,false为非单例),作用是设置对象是否为单例
在Spring的配置文件中进行配置
<bean id="userDao" class="UserDaoFactoryBean类路径"/>
Bean的生命周期
bean 的生命周期:bean 对象,从创建到消亡的完整过程
bean 生命周期控制的是 bean 对象从创建后到销毁前做一些事情
关于 Spring 中对 bean 生命周期控制提供了两种方式:
- 在配置文件中的 bean 标签中添加
init-method
属性和destory-method
属性 - 类实现
InitializingBean
接口和DisposableBean
接口
添加初始化和销毁方法
<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
init-method 属性是初始化方法,而 destroy-method 属性是销毁的方法
但是执行后只会执行初始化的 init 方法而未执行销毁方法destory,为什么?
原因是 Spring 的 IOC 容器是运行在 JVM 中,运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行,main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还未来得及销毁就已经结束了,所以没有调用对应的 destory 方法
close关闭容器
ApplicationContext 中没有 close 方法,所以需要将 ApplicationContext 更换为 ClassPathXmlApplicationContext(ClassPathXmlApplicationContext 是 ApplicationContext 的子类) 来调用 close 方法,这样就可以正常的执行容器的销毁了
注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器,调用 registerShutdownHook 方法
注意: RegisterShutdownHook 方法在 ApplicationContext 中也没有,所以依然要使用 ClassPathXmlApplicationContext
关闭方式的区别
close 方法和 RegisterShutdownHook 方法区别:
- 相同点:两种方法都能用来关闭容器
- 不同点:close 方法是在执行调用的时候关闭,registerShutdownHook 方法是在 JVM 退出前自动调用关闭
通过接口来简化关闭容器
在实现类中实现 InitializingBean 和 DisposableBean 两个接口之后重写 afterPropertiesSet 方法和 destory 方法
注意: InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为 属性设置之后,对于 servce 层中的实现类方法来说,Dao 层的实现类为它的一个属性,setxxxDao 方法是 Spring 的 IOC 容器为它注入属性的方法,而setxxxDao方法先执行,afterPropertiesSet 方法后执行
总结:
对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置
- 初始化容器
- 1.创建对象(内存分配)
- 2.执行构造方法
- 3.执行属性注入(set操作)
- 4.执行 bean 初始化方法
- 使用 bean
- 执行业务操作
- 关闭/销毁容器
- 执行 bean 销毁方法
DI
Spring 中提供了两种注入方式:
- setter 注入
- 简单类型(基础数据类型和字符串)
- 引用类型
- 构造器注入
- 简单类型(基础数据类型和字符串)
- 引用类型
setter注入
-
在 bean 中定义引用类型属性,并提供可访问的 set 方法
public class BookServiceImpl implements BookService { private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
配置中使用 property 标签 ref 属性注入引用类型对象
<bean id="bookService" class="BookServiceImpl类路径"> <property name="实现类中的bookDao的属性" ref="bean中的bookDao对象"/> </bean> <bean id="bookDao" class="BookDaoImpl类路径"/>
注入引用数据类型
-
在实现类中声明属性并提供 setter 方法
public class BookServiceImpl implements BookService{ private BookDao bookDao; private UserDao userDao;//声明属性 public void setUserDao(UserDao userDao) {//提供setter方法 this.userDao = userDao; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
配置文件中进行注入配置
<bean id="bookDao" class="BookDaoImpl类路径"/> <bean id="userDao" class="UserDaoImpl类路径"/> <bean id="bookService" class="BookServiceImpl类路径"> <property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/> <property name="实现类中的userDao属性" ref="bean中的userDao对象"/> </bean>
注入简单数据类型
-
在实现类中声明对应的简单数据类型的属性,并提供对应的 setter 方法
public class BookDaoImpl implements BookDao { private String databaseName;//声明属性 private int connectionNum;//声明属性 // 提供 setter 方法 public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } }
-
在配置问文件中进行注入配置
<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl"> <property name="实现类方法中的参数databaseName(形参)" value="形参中的数据值"/> <property name="实现类方法中的参数connectionNum(形参)" value="形参中的数据值"/> </bean> <bean id="userDao" class="com.XXX.dao.impl.UserDaoImpl"/> <bean id="bookService" class="BookServiceImpl类路径"> <property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/> <property name="实现类中的userDao属性" ref="bean中的userDao对象"/> </bean>
构造器注入
构造器注入也就是构造方法注入,无非是把实现类中的 setter 方法改为构造器方法
-
删除实现类中的 setter 方法并提供构造方法
public class BookServiceImpl implements BookService{ private BookDao bookDao; // 在构造方法中对属性进行设置 public BookServiceImpl(BookDao bookDao) { this.bookDao = bookDao; } }
-
在配置文件中配置构造方法注入
<bean id="bookDao" class="BookDaoImpl类路径"/> <bean id="bookService" class="BookServiceImpl类路径"> <constructor-arg name="实现类中构造方法的参数bookDao(形参)" ref="bean中的bookDao对象"/> <!-- 这里的constructor-arg标签就是通过构造方法注入的标签 --> </bean>
注意:
- name 属性对应的值为构造函数中方法形参的参数名,必须保持一致
- ref 属性指向的是 spring 的 IOC 容器中其他的 bean 对象
构造器注入多个数据类型
-
提供多个属性的构造函数
public class BookServiceImpl implements BookService{ private BookDao bookDao;// 声明引用类型属性 private UserDao userDao;// 声明引用类型属性 // 构造方法注入 public BookServiceImpl(BookDao bookDao,UserDao userDao) { this.bookDao = bookDao; this.userDao = userDao; } }
-
配置文件中配置
<bean id="bookDao" class="BookDaoImpl类路径"/> <bean id="userDao" class="UserDaoImpl类路径"/> <bean id="bookService" class="BookServiceImpl类路径"> <constructor-arg name="实现类中构造方法的参数bookDao" ref="bean中的bookDao对象"/> <constructor-arg name="实现类中构造方法的参数userDao" ref="bean中的userDao对象"/> </bean>
注意: constructor-arg 标签的顺序可以任意
构造器注入多个简单数据类型
-
添加多个简单属性并提供构造方法
public class BookDaoImpl implements BookDao { private String databaseName; //声明简单类型属性 private int connectionNum; //声明简单类型属性 // 通过构造器注入 public BookDaoImpl(String databaseName