一、Spring相关概念
1. Spring4 架构图

(1) 核心层
- Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
(2) AOP层
-
AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
-
Aspects:AOP是思想,Aspects是对AOP思想的具体实现
(3) 数据层
-
Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
-
Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
-
Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
(4) Web层
- 这一层的内容将在SpringMVC框架具体学习
(5) Test层
- Spring主要整合了Junit来完成单元测试和集成测试
2. IoC、IoC容器、Bean、DI
(1) 什么是控制反转?
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
(2) Spring和IoC之间的关系是什么呢?
-
Spring技术对IoC思想进行了实现
-
Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的"外部"
(3) IoC容器的作用以及内部存放的是什么?
-
IoC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象
-
被创建或被管理的对象在IoC容器中统称为Bean
-
IoC容器中放的就是一个个的Bean对象
(4) DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

二、入门案例
1. IoC入门案例
(1) 关键步骤
① 在配置文件中完成bean的配置
XML
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
</beans>
核心逻辑:
① 为什么 class 类型指定的是实现类,不是接口?
直接原因:Spring IOC 容器的核心是创建对象实例 ,如果你把 class 指定为接口(比如com.itheima.dao.BookDao),Spring 容器在尝试创建对象时,会和上面的 Java 代码一样失败 ------ 因为它无法实例化一个接口,最终会抛出类似Cannot instantiate interface com.itheima.dao.BookDao的异常。
② 从容器中获取对象进行方法调用
java
public class App {
public static void main(String[] args) {
//获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
2. DI入门实例
(1) 关键步骤
① 修改配置完成注入
XML
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
注意:配置中的两个bookDao的含义是不一样的
1). name="bookDao"中bookDao的作用是让Spring的IoC容器在获取到名称后,将首字母小写,前面加set找对应的setBookDao()方法进行对象注入
对应 BookServiceImpl 的代码:
java
public class BookServiceImpl implements BookService {
// 👇 这里的变量名是 bookDao,所以 name 必须写 "bookDao"
private BookDao bookDao;
// 👇 setter方法名是 setBookDao,Spring 会自动匹配 "bookDao"(去掉set,首字母小写)
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 如果把类里的变量名改成
dao,setter 改成setDao,那配置里的name必须改成"dao",否则 Spring 找不到要赋值的属性,会报错; - 比如:
private BookDao dao; + public void setDao(BookDao dao)→ 配置要写成<property name="dao" ref="bookDao"/>。
2). ref="bookDao"中bookDao的作用是让Spring能在IoC容器中找到id为bookDao的Bean对象给bookService进行注入

四、IoC相关内容
1. bean基础配置
(1) bean基础配置(id与class)

(2) bean的name属性

① 代码实现
步骤1:配置别名
打开spring的配置文件applicationContext.xml
XML
<!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>
步骤2:根据名称容器中获取bean对象
java
public class AppForName {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
BookService bookService = (BookService) ctx.getBean("service4");
bookService.save();
}
}
(3) bean作用范围scope配置

① 代码实现
1). 单例
XML
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="singleton"/>
2). 非单例
XML
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
2. bean实例化
(1) 构造方法实例化
① 编写运行程序
java
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
② BookDaoImpl
在BookDaoImpl类中添加一个无参构造函数,并打印一句话,方便观察结果。
java
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
无参构造方法是在执行 new ClassPathXmlApplicationContext("applicationContext.xml") 这行代码时被 Spring 调用的
第一步:执行new ClassPathXmlApplicationContext(...)时,Spring 会做这些事:
- 解析
applicationContext.xml,找到<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>这个配置; - 通过反射获取
BookDaoImpl的 Class 对象; - 调用
BookDaoImpl的无参构造方法创建实例(这就是 "构造方法实例化" 的核心步骤); - 把创建好的实例存入容器,等待后续获取。✅ 这一步,你写在
BookDaoImpl无参构造里的System.out.println("book dao constructor is running ....")就会执行 ------ 控制台打印这句话,就是无参构造被调用的铁证。
第二步:执行ctx.getBean("bookDao")
Spring 从自己的 "对象仓库" 里,找到id=bookDao的对象(就是刚才创建好的BookDaoImpl实例),把它返回给你。
第三步:执行bookDao.save()
调用这个对象的save()方法,控制台打印:book dao save ...。
(2) 静态工厂实例化(了解即可)
① 准备一个OrderDao和OrderDaoImpl类
java
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
② 创建一个工厂类OrderDaoFactory并提供一个静态方法
java
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
③ 编写AppForInstanceOrder运行类,在类中通过工厂获取对象
java
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
④ 在spring的配置文件中添加以下内容
XML
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
1). 关键部分解析:
**class:**工厂类的类全名
**factory-mehod:**具体工厂类中创建对象的方法名
2). 对应关系如下图:

⑤ 在AppForInstanceOrder运行类,使用从IoC容器中获取bean的方法进行运行测试
java
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
- Spring 加载
OrderDaoFactory类(无需创建工厂类实例,因为方法是静态的); - 调用
OrderDaoFactory.getOrderDao()静态方法; - 将方法返回的
OrderDaoImpl实例存入容器,标识为orderDao; - 后续
ctx.getBean("orderDao")获取的就是这个实例。
(3) 实例工厂与FactoryBean
① 准备一个UserDao和UserDaoImpl类
java
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
② 创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法,关键在于此处不是静态方法
java
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
③ 编写AppForInstanceUser运行类,在类中通过工厂获取对象
java
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();
}
④ 在spring的配置文件中添加以下内容
XML
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
实例化工厂运行的顺序是:
-
创建实例化工厂对象,对应的是第一行配置
-
调用对象中的方法来创建bean,对应的是第二行配置
-
factory-bean:工厂的实例对象
-
factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:
-

(4) FactoryBean的使用(重要)
① 创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
java
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
② 在Spring的配置文件中进行配置
XML
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
3. bean的生命周期
(1) 思路分析
-
bean创建之后,想要添加内容,比如用来初始化需要用到资源
-
bean销毁之前,想要添加内容,比如用来释放用到的资源
(2) 代码实现
① 添加初始化和销毁方法
java
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
② 配置生命周期
XML
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destory-method="destory"/>
Spring 创建bookDao这个 Bean 实例后,会自动调用 BookDaoImpl类中名为init的方法(初始化逻辑);当 Spring 容器关闭、销毁bookDao实例前,会自动调用 类中名为destroy的方法(销毁 / 清理逻辑)。
③ close关闭容器
java
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
五、DI相关内容
1. setter注入
(1) 注入简单数据类型
需求:给BookDaoImpl注入一些简单数据类型的数据
-
在BookDaoImpl类中声明对应的简单数据类型的属性
-
为这些属性提供对应的setter方法
-
在applicationContext.xml中配置
① 声明属性并提供setter方法
java
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
② 配置文件中进行注入配置
XML
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
-
对于引用数据类型 使用的是
<property name="" ref=""/> -
对于简单数据类型 使用的是
<property name="" value=""/>
2. 构造器注入引用数据类型
(1) 思路分析
-
将bookDao的setter方法删除掉
-
添加带有bookDao参数的构造方法
-
在applicationContext.xml中配置
(2) 代码实现
① 删除setter方法并提供构造方法
原来的BookServiceImpl
java
public class BookServiceImpl implements BookService {
private BookDao bookDao;
// 原来的 setter 方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
现在改成构造方法注入后:
java
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
**核心逻辑:**这个构造方法怪怪的,返回值是什么,方法名又是什么?
java
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
构造方法和普通方法的核心区别
| 特征 | 普通方法(比如 setter 方法) | 构造方法(你问的这段代码) |
|---|---|---|
| 方法名 | 任意(通常 set/get 开头) | 必须和类名完全一致 |
| 返回值类型 | 必须声明(void/int/String 等) | 不能声明任何返回值(连 void 都不能写) |
| 作用 | 执行特定功能(比如赋值) | 创建对象时初始化对象 |
| 调用方式 | 对象创建后手动调用(obj.setXxx ()) | 由 JVM 自动调用(new) |
逐行解释:
-
方法名
BookServiceImpl:必须和类名完全一致 (你的类名就是BookServiceImpl),这是构造方法的 "身份标识",少一个字母、大小写不一样都不行。 -
没有返回值类型 :你看不到
void、int等返回值声明,这是构造方法的强制规则 ------ 因为构造方法的 "返回值" 是创建好的当前类对象 ,这个返回值由 JVM 自动处理,不需要你手动写。比如你写new BookServiceImpl(bookDao)时,JVM 会执行这个构造方法,然后自动返回一个BookServiceImpl类型的对象,你不用管返回值的声明。
② 配置文件中进行配置构造方式注入
java
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
核心逻辑: 内层 <constructor-arg> 标签
这是构造方法注入的关键,专门用来 "给构造方法传参数":
name="bookDao":指定要传给构造方法的参数名 (对应你之前写的public BookServiceImpl(BookDao bookDao)里的bookDao参数);ref="bookDao":指定参数的值 ------ 这里的ref是 "引用" 的意思,告诉 Spring:"这个参数的值,不是新造的,而是去我的对象仓库里找那个名字叫bookDao的对象"。
Spring 启动后,会按顺序执行配置里的指令:
1. 执行 <bean id="bookDao" .../>:
- 调用
BookDaoImpl的无参构造(如果没写,编译器会默认生成),创建BookDaoImpl对象; - 把这个对象存到容器里,标记为 "id=bookDao"。
2. 执行 <bean id="bookService" .../>:
- 先看里面有
<constructor-arg>,知道要调用BookServiceImpl的带参构造方法; - 去容器里找到 "id=bookDao" 的那个对象(就是第一步造的);
- 调用
new BookServiceImpl(bookDao)创建BookServiceImpl对象; - 把这个对象存到容器里,标记为 "id=bookService"。
3. 构造器注入多个简单数据类型
(1) 思路分析
-
提供一个包含这两个参数的构造方法
-
在applicationContext.xml中进行注入配置
(2) 代码实现
① 提供多个属性的构造函数
java
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
② 在applicationContext.xml中配置注入
XML
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
4. 为什么构造器一定是强制依赖的,setter 不是(重点)
(1) setter 有参但不用传参:本质是 "我压根不调用这个方法"
setter 方法(比如 setBookDao(BookDao bookDao))的参数规则是:如果调用这个方法,就必须传 BookDao 参数;但我可以选择不调用这个方法。
java
public class BookServiceImpl implements BookService {
private BookDao bookDao; // 初始值为 null
// 有参的 setter 方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
bookDao.save(); // 没调用 setter 的话,这里会空指针
}
// 测试代码
public static void main(String[] args) {
// 1. 创建对象:调用无参构造,完全不用管 setter
BookServiceImpl service = new BookServiceImpl();
// 2. 可选操作:我可以调用 setter(传参),也可以不调用(不传参)
// service.setBookDao(new BookDaoImpl()); // 注释掉=不传参、不调用
// 3. 运行方法:没调 setter 的话,bookDao 是 null,运行时报错
service.save();
}
}
区分:
- setter 方法和构造方法是两码事:setter 是用来给成员变量赋值的普通方法(要声明返回值),构造方法是用来创建对象的特殊方法(不用声明返回值)
(2) 构造器有参就必须传参 ------ 因为 "创建对象必须调用构造器"
java
public class BookServiceImpl implements BookService {
private BookDao bookDao;
// 有参的构造器
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
// 测试代码
public static void main(String[] args) {
// 编译报错!因为创建对象必须调用构造器,而构造器要求传 BookDao 参数
BookServiceImpl service = new BookServiceImpl();
}
}
这里的关键是:创建对象的动作(new)和构造器的调用是绑定的 ------ 只要 new 对象,就必须调构造器,构造器要参数就必须传,躲不开;而 setter 是 new 完对象之后的 "额外动作",调不调全看你。