目录:
学习之前
1、Spring 要学习哪些技术:
- IOC
- AOP
- Spring 的事务处理
- 整合 Mybatis 框架
2、怎么学
- 学习 Spring 框架设计思想
- 学习基础操作,思考操作与思想建的联系
- 学习案例,熟练应用操作的同时,体会思想
3、初识 Spring
Spring 家族:
基础框架:Spring Framework ——是其他项目的根基
简化、加速开发:Spring Boot
分布式开发:Spring Cloud
Spring发展史:
学习路线:
4、Spring的核心Maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
参考:spring maven依赖 - 烟火里的尘埃 - 博客园 (cnblogs.com)
一、核心概念
代码书写现状:耦合度偏高
解决方案:使用对象时,在程序中不要主动使用 new 产生对象,而是转换为由外部提供对象。
1.1、IoC(Inversion of Control)控制反转
使用对象时,由程序主动 new 产生对象转换为由外部提供对象,此过程中对象的创建控制权也由程序转移到外部,这种思想称为控制反转。
实质上是对象的创建控制权发生了变化
Spring 提供了一个容器,称为 IOC 容器,用l来充当 IOC 思想中的“外部”
IOC 容器负责对象的创建、初始化等一系列工作,被创建或管理的对象在 IoC 容器中统称为 Bean
目标:
- 使用 IoC 容器管理 bean —— IOC
- 在 IoC 容器中将有依赖关系的 bean 进行关系绑定 —— DI
最终效果:使用对象是不仅可以从 IOC 容器中获取,并且获取到的 bean 中已经绑定了所有的依赖关系。
IOC入门思路分析:
-
管理什么?(Service 与 Dao)
-
如何将被管理的对象告知 IoC 容器?(配置 applicationContext.xml 文件)
-
被管理的对象交给 IoC 容器,使用时如何获取到 IoC 容器?(接口)
-
IoC 容器得到之后,如何从容器中获取 bean?(接口方法 getBean() )
-
使用 Spring 导入哪些坐标?(pom.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">
<!-- 1.导入spring坐标spring-context,对应版本是5.2.10.RELEASE-->
<!-- 2.配置bean -->
<!-- bean 标签表示配置 bean
id 属性表示给 bean 起名字,在同一个上下文中不能重复
class 属性表示给 bean 定义类型 -->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"/>
</beans>
1.2、DI(Dependency Inject)依赖注入
在容器中建立 bean 与 bean 之间的依赖关系的整个过程称为依赖注入
DI 入门案例思路分析:
- 基于 IoC 管理 bean
- Service 中使用 new 形式创建的 Dao对象是否保留(否)
- Service 中需要的 Dao 对象如何进入到 Service 中(提供方法)
- Service 与 Dao 间的关系如何描述(配置)
二、IOC
2.1、bean 的配置
2.1.1、bean 的基础配置(id与class)
<!-- bean 标签表示配置 bean
id 属性表示给 bean 起名字,在同一个上下文中不能重复
class 属性表示给 bean 定义类型,后面接的是全路径类名 -->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.wlplove.service.impl.BookServiceImpl">
<!-- 配置service与dao的关系 -->
<!-- property 标签表示配置当前 bean 的属性,需要其设置相应的set方法
name 属性表示配置哪一个具体的属性,是当前属性的名称
ref 属性表示参照哪一个 bean,是当前容器中存在的 bean -->
<property name="bookDao" ref="bookDao"/>
</bean>
类别 | 描述 |
---|---|
名称 | bean |
类型 | 属性 |
所属 | beans 标签 |
功能 | 定义 Spring 核心容器管理的对象 |
格式 | |
属性列表 | id:bean 的 id,使用容器可以通过 id 值获取对应的 bean,在一个 IoC 容器中 id 值唯一 class:bean 的类型,即配置的 bean 的全路径类名 |
范例 |
2.1.2、bean的别名配置(name)
<!-- name 属性表示别名 -->
<bean id="bookService" name="service service2 bookEbi" class="com.wlplove.service.impl.BookServiceImpl"/>
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean 标签 |
功能 | 定义 bean 的别名,可定义多个,使用逗号、分号、空格分开 |
范例 |
2.1.3、bean的作用范围(scope)
控制创建的 bean 实体的数量(是否单例)
<!-- "scope"是用来控制 bean 的作用域,默认是单例模式 singleton,也可以指定成 prototype(非单例) -->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl" scope="singleton"/>
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean 标签 |
功能 | 定义 bean 的作用范围,可选:singleton(单例,默认)、prototype(非单例) |
范例 |
- 为什么 bean 默认为单例?
- 适合交给容器管理的 bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 不适合交给容器管理的 bean
- 封装实体的域对象
2.2、实例化bean
bean 的实例化过程
主要解决两部分内容,分别是
- bean是如何创建的
- 实例化 bean 的三种方式,
构造方法
,静态工厂
和实例工厂
在讲解这三种创建方式之前,我们需要先确认一件事:
bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建bean也是使用构造方法完成的。
2.2.1、构造方法(常用)
提供可访问的构造方法:
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
配置:
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"/>
- 无参构造方法如果不存在,将抛出异常 BeanCreationException
2.2.2、静态工厂(了解)
//静态工厂创建对象
public class OrderDaoFactory {
// 工厂方法
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
配置:
<bean id="orderDao" class="com.wlplove.factory.OrderDaoFactory"
factory-method="getOrderDao"/>
2.2.3、实例工厂(了解)
FactoryBean(实用,较重要)
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
配置:
<bean id="userDao" class="com.wlplove.factory.UserDaoFactoryBean" />
2.3、bean的生命周期
- bean 生命周期:bean 从创建到销毁的整个过程
<!-- “init-method”方法指定 bean 初始化方法,“destory-method”方法指定 bean 销毁方法 -->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"></bean>
小结:
(1)关于Spring中对bean生命周期控制提供了两种方式:
- 在配置文件中的bean标签中添加
init-method
和destroy-method
属性 - 类实现
InitializingBean
与DisposableBean
接口,这种方式了解下即可。
(2)整个生命周期:
-
初始化容器
-
创建对象(内存分配)
-
执行构造方法
-
执行属性注入(set 注入)
-
执行 bean 初始化方法
-
-
使用 bean
- 执行业务操作
-
关闭/销毁容器
- 执行 bean 销毁方法
(3)关闭容器的两种方式:
- ConfigurableApplicationContext是ApplicationContext的子类
- close()方法
- registerShutdownHook()方法
三、DI
向一个类中传递数据的方式有几种:
- 普通方法(set 方法)
- 构造方法
依赖注入描述了在容器中简历 bean 与 bean 之间依赖关系的过程,如果 bean 运行需要的是数字或者字符串呢?
- 简单类型(基本数据类型与 String)
- 引用类型
依赖注入方式
- setter 注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
3.1、setter 注入
分为 简单类型 与 引用类型 的注入
-
类中需要提供可访问的 set 方法
public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } public void setDatebaseName(String datebaseName) { this.datebaseName = datebaseName; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setBookDao(BookDaoImpl bookDao) { this.bookDao = bookDao; }
-
xml文件配置:注入标签需要用 “
” <!-- 简单类型 (通过 value 属性注入普通类型值) --> <bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"> <property name="datebaseName" value="mysql"></property> <property name="connectionNum" value="100"></property> </bean> <!-- 引用类型 (通过 ref 属性注入引用类型值) --> <bean id="bookService" class="com.wlplove.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"></property> <property name="userDao" ref="userDao"></property> </bean>
3.2、构造方法注入
同样地,分为 简单类型 与 引用类型 的注入
-
类似于 setter 注入需要提供 set 方法,构造方法注入需要在类中提供构造方法:
public BookDaoImpl(int connectionNum, String datebaseName) { this.connectionNum = connectionNum; this.datebaseName = datebaseName; } public BookServiceImpl(BookDao bookDao, UserDaoImpl userDao) { this.bookDao = bookDao; this.userDao = userDao; }
-
xml 文件中的配置:注入标签需要用 “
” <!-- 普通类型 (通过 value 属性注入引用类型值,name属性的值是构造方法形参的值)--> <bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"> <constructor-arg name="connectionNum" value="666"></constructor-arg> <constructor-arg name="datebaseName" value="mysql"></constructor-arg> </bean> <bean id="userDao" class="com.wlplove.dao.impl.UserDaoImpl"></bean> <!-- 引用类型 (通过 ref 属性注入引用类型值,name 属性的值是构造方法形参的值)--> <bean id="bookService" class="com.wlplove.service.impl.BookServiceImpl"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
3.3、构造器注入的其他xml配置写法:
3.3.1、type 方式
配置中使用 constructor-arg 标签、type 属性,设置按构造方法的形参类型注入:
<!--type方式,解决 name 属性绑定了形参名称的问题-->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="666"></constructor-arg>
<constructor-arg type="java.lang.String" name="datebaseName" value="mysql"></constructor-arg>
</bean>
3.3.2、index 索引方式
配置中使用 constructor-arg 标签 index 属性设置按形参类型注入
<!-- index索引方式,解决参数类型重复问题,使用索引进行参数匹配对应 -->
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="6"></constructor-arg>
<constructor-arg index="1" name="datebaseName" value="mysql"></constructor-arg>
</bean>
3.4、依赖注入方式的选择
- 尽量选择 setter 注入:自己开发的模块推荐使用 setter 注入
- 强制依赖使用构造器进行注入,但是使用 setter 注入时不进行注入的话会导致 null 现象出现
- 可选依赖使用 setter 注入进行,灵活性强
- Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者也可同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
四、自动装配
IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配。
自动装配有这么几种方式:
- 按类型(常用)
- 按名称
- 按构造方法
自动装配时需要注意:
-
需要注入属性的类中对应属性的 setter 方法不能省略
-
被注入的对象必须要被 Spring 的 IoC 容器管理
自动装配只需要修改applicationContext.xml配置文件即可:
- 将
<property>
标签删除- 在
<bean>
标签中添加 autowire 属性
4.1、按类型 byType(推荐使用)
如果容器中只存在一个属性类型的 bean,则让属性自动装配。 如果存在多个,则会引发异常,这表明不能为该 bean 使用 byType 自动装配。 如果没有匹配的 bean,则不会发生任何事情(未设置属性)。
会自动在容器上下文中查找,和对象 set() 方法后面的值对应的 bean 的 id
需要保证所有 bean 的类型唯一,并且这个 bean 需要和自动注入的属性的 set()
方法的值一致。
按照类型自动装配时,如果在 Spring 的 IoC 容器中如果找到多个对象,会报 NoUniqueBeanDefinitionException
异常
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"/>
<!-- autowire="byType" 属性按类型自动装配,甚至都可以不指定 id 属性 -->
<bean id="bookService" class="com.wlplove.service.impl.BookServiceImpl" autowire="byType"/>
4.2、按名称 byName(不建议使用)
-
按照名称注入中的名称指的是什么?
- bookDao 是 private 修饰的,外部类无法直接访问,只能通过属性的 set 方法进行访问
- 对外部类来说,setBookDao 是方法名,去掉 set 后首字母小写是其属性名
- 为什么是去掉 set 首字母小写?
- 这个规则是 set 方法生成的默认规则,set方法的生成是把属性名首字母大写前面加set形成的方法名
- 所以按照名称注入,其实是和对应的 set 方法有关,但是如果按照标准起名称,属性名和 set 对应的名是一致的
-
如果按照名称去找对应的 bean 对象,找不到则注入 Null
-
当某一个类型在 IoC 容器中有多个对象,按照名称注入只找其指定名称对应的 bean 对象,不会报错
<bean id="bookDao" class="com.wlplove.dao.impl.BookDaoImpl"/>
<!-- autowire="byName" 按名称自动装配 -->
<bean id="bookService" class="com.wlplove.service.impl.BookServiceImpl" autowire="byName"/>
最后对于依赖注入,需要注意一些其他的配置特征:
- 自动装配只能用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
五、集合的注入
接下来的例子以 setter 注入为例进行说明,构造器注入也是类似的,将
5.1、注入数组类型数据
注入数组对象:
<!-- name 属性设置将数据注入哪一个数组 -->
<property name="test_array">
<!-- 此处 array 与 list 可以混用 -->
<array>
<value>123</value>
<value>456</value>
<value>789</value>
<!-- 引用类型可以这样写:-->
<!-- <ref bean="beanId"></ref>-->
</array>
</property>
5.2、注入 Set 类型数据
注入 Set 对象:
<property name="test_set">
<set>
<value>set_qwe1</value>
<value>set_asd2</value>
<value>set_zxc3</value>
<!-- 重了会自动过滤 -->
<value>set_zxc3</value>
</set>
</property>
5.3、注入 List 类型数据
注入 List 对象:
<property name="test_list">
<!-- 此处 array 与 list 可以混用 -->
<list>
<value>list_qwe1</value>
<value>list_asd2</value>
<value>list_zxc3</value>
</list>
</property>
5.4、注入 Map 类型数据
注入 Map 对象:
<property name="test_map">
<map>
<entry key="contry" value="China"></entry>
<entry key="province" value="Gansu"></entry>
<entry key="city" value="Lanzhou"></entry>
</map>
</property>
5.5、注入 property 类型数据
<property name="test_properties">
<props>
<prop key="contry">China</prop>
<prop key="province">Gansu</prop>
s<prop key="city">Lanzhou</prop>
</props>
</property>
说明:
- property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签 - List 的底层也是通过数组实现的,所以
<list>
和<array>
标签是可以混用 - 集合中要添加引用类型,只需要把
<value>
标签改成<ref>
标签,但这种方式用的比较少
六、加载properties文件
第一步:xml文件中添加 context 命名空间:
原文件:
<?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">
</beans>
通过复制修改过的新文件:
<?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
">
</beans>
第二步:在 xml 文件中添加,使用 context 空间加载指定 properties 文件
<context:property-placeholder location="jdbc.properties"/>
<!-- 可以加载多个文件 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
<!-- 或者用通配符加载所有文件 -->
<context:property-placeholder location="*.properties"/>
<!-- 用 system-properties-mode="NEVER" 禁用系统属性 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!-- 从类路径或 jar 包中搜索并加载所有 properties 文件 -->
<context:property-placeholder location="classpath*:*.properties"/>
最规范的写法可以这样:
<!-- 最规范的写法,标准格式 -->
<context:property-placeholder location="classpath:*.properties"/>
第三步:使用 ${} 读取加载的属性值
properties 的文件内容为:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
则这样写:
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
七、容器的核心操作
7.1、创建容器
-
方式一:通过类路径来加载配置文件的方式创建容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
-
方式二:通过文件路径来加载配置文件的方式创建容器
ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\applicationContext.xml");
-
以上两种方式也都支持加载多个配置文件:
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml"); ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\bean1.xml","E:\\bean2.xml");
7.2、获取 bean
-
方式一:使用 bean 名称获取
// 需要设置类型转换 BookDao bookDao = (BookDao) ctx.getBean("bookDao");
-
方式二:使用 bean 名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
-
方式三:使用 bean 类型获取
// 注意:参数是 类.class BookDao bookDao = ctx.getBean(BookDao.class);
7.3、容器类层次结构
7.4、BeanFactory
-
BeanFactory是延迟加载,只有在获取 bean 对象的时候才会去创建
-
ApplicationContext 是立即加载,容器加载的时候就会创建 bean 对象
-
ApplicationContext 要想成为延迟加载,只需要按照如下方式进行配置,设置 “ lazy-init="true" ”
<?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 id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" lazy-init="true"/> </beans>
7.5、核心容器内容总结
7.5.1、容器相关
- BeanFactory 是 IoC 容器的顶层接口,初始化 BeanFactory 对象时,加载的 bean 延迟加载
- ApplicationContext 接口是Spring容器的核心接口,初始化时 bean 立即加载
- ApplicationContext 接口提供基础的 bean 操作相关方法,通过其他接口扩展其功能
- ApplicationContext 接口常用初始化类
- ClassPathXmlApplicationContext(常用)
- FileSystemXmlApplicationContext
评论