Spring - 1( 相关了解 + IOC 容器 + DI 依赖注入 + )
Spring - 1
了解
- spring官网
- Spring 家族三巨头:
- Spring Framework:框架,spring 所有技术都是依赖此框架
- Spring Boot:在简化开发的基础上加快速度,主提速
- Spring Cloud:分布式开发相关技术
Spring Framework 系统架构
- Spring Framework 是 Spring 家族中其他框架的底层基础
- Spring4.0 根据 JDK 的版本升级对个别 API 进行了调整
- Spring5.0 已经全面支持 JDK8
系统架构图
-
核心层
- Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
-
AOP 层
-
AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
-
Aspects:AOP是思想,Aspects 是对 AOP 思想的具体实现
-
-
数据层
-
Data Access:数据访问,Spring 全家桶中有对数据访问的具体实现技术
-
Data Integration:数据集成,Spring 支持整合其他的数据层解决方案,比如 Mybatis
-
Transactions:事务,Spring 中事务管理是 Spring AOP 的一个具体实现,也是后期学习的重点内容
-
-
Web层
- 这一层的内容将在 SpringMVC 框架具体学习
-
Test层
- Spring 主要整合了 Junit 来完成单元测试和集成测试
一、核心容器
相关概念
- IOC、DI 思想
- IOC 容器
- Bean
存在问题
- 一开始都是
xxxServiceImpl implements xxxService { 接口=new Impl实现类 }
、xxxDaoImpl implements xxxDao
- 想更换使用的 Dao 层方法,就要重新 new ( 重新编译、测试、部署、发布 ...... ),这样耦合度偏高
- 想降低耦合度去掉等于号 ( = ) 后面的内容即可,但会空指针异常
解决引出 IOC
-
使用对象时,在程序中不主动使用 new 产生对象,转换为由外部提供对象 ———— 即:IOC ( Inversion of Control ) 控制反转
-
IOC 控制反转:
- 使用对象时,由主动 new 产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
-
Spring 和 IOC 之间的关系
- Spring 技术对 IOC 思想进行了实现
- Spring 提供了一个容器,称为 IOC 容器 ( 架构图中的核心容器 ),用来充当 IOC 思想中的 " 外部 "
- IOC 思想中的外部 ( 别人 ) 指的就是 Spring 的 IOC 容器
-
IOC 容器的作用以及内部存放是
- IOC 容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象
- 被创建或被管理的对象在 IOC 容器中统称为 Bean
- IOC 容器中放的就是一个个的 Bean 对象
仍存在问题并引出 DI
- 但 IOC 容器中创建好 service 和 dao 对象后,service 仍不能运行,因为要依赖对应的 dao 对象
- 所有就需要把 dao 对象交给 service,也就是说要绑定 service 和 dao 对象之间的关系
- 这个时候就引出了 ———— DI ( Dependency Injection ) 依赖注入
- DI ( Dependency Injection ) 依赖注入
- 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
- 业务层要用数据层的类对象,以前是自己 new 的
- 现在自己不 new 了,靠 别人 ( 外部其实指的就是 IOC 容器 ) 来注入进来
- 这种思想就是依赖注入
- 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
完成目标:充分解耦
- 使用 IOC 容器管理 bean ( IOC )
- 在 IOC 容器内将有依赖关系的 bean 进行关系绑定 ( DI )
最终结果
- 使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean 已经绑定了所有的依赖关系
IOC 入门案例
分析
-
Spring 是使用容器来管理 bean 对象的
- 主要管理项目中所使用到的类对象,比如 Service 和 Dao
-
如何将被管理的对象告知 IOC 容器
- 使用配置文件的方式
-
被管理的对象交给 IOC 容器,那如何获取到 IOC 容器
- Spring 框架提供相应的接口
-
IOC 容器得到后,如何从容器中获取 bean
- 调用 Spring 框架提供对应接口中的方法
-
使用 Spring 导入哪些坐标
- 用别人的东西,就需要在 pom.xml 添加对应的依赖
实现
-
创建 Maven 的 java 项目
-
pom.xml 添加 Spring 的依赖 jar 包
-
要先加入依赖才能创建 Spring 的配置文件
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.7.RELEASE</version> <!-- 版本自选 --> </dependency>
-
-
创建 BookService,BookServiceImpl,BookDao 和 BookDaoImpl 四个类 ( 此时 dao 和 service 还暂时是 new 的老方法 )
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { 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(); public void save() { System.out.println("book service save ..."); bookDao.save(); } }
-
resources 下添加 spring 配置文件,并完成 bean 的配置
-
自定义命名:applicationContext.xml
<bean id="bookDao" class="com.qut.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.qut.service.impl.BookServiceImpl"/>
-
注意:bean 定义时 id 属性在同一个上下文 ( 配置文件 ) 中不能重复
-
-
使用 Spring 提供的接口完成 IOC 容器的创建,新建 App 类的 main 方法中:
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
-
从容器中获取对象进行方法调用
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(); } }
-
运行结果:
book service save ... book dao save ...
-
DI 入门案例
分析
-
要想实现依赖注入,必须要基于 IOC 管理 Bean
- DI 的入门案例要依赖于前面 IOC 的入门案例
-
Service 中使用 new 形式创建的 Dao 对象
- 需要删除掉 new,最终要使用 IOC 容器中的 bean 对象
-
Service 中需要的 Dao 对象如何进入到 Service中
- 在 Service 中提供方法,让 Spring 的 IOC 容器可以通过该方法传入 bean 对象
-
Service 与 Dao 间的关系的描述
- 使用配置文件
实现
-
删除业务层中使用 new 的方式创建的 dao 对象
-
ServiceImpl 中只写前部分:
private BookDao bookDao;
-
-
在业务层提供 BookDao 的 setter 方法
-
ServiceImpl 中:
private BookDao bookDao; // ...... //提供上面bookDao的set获取方法(就像属性的setter方法一样的获取方式) //set + 下面xml中name所指bookDao的首字母大写 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void yyy() { //...... bookDao.yyy(); //...... }
-
-
在配置文件中添加依赖注入的配置
-
applicationContext.xml 中:
<bean id="bookDao" class="com.qut.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.qut.service.impl.BookServiceImpl"> <!--配置server与dao的关系--> <!--property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性 ref属性表示参照哪一个bean --> <property name="bookDao" ref="bookDao"/> </bean>
- name="bookDao":中的 bookDao 是让 Spring 的 IOC 容器在获取到名称后,将首字母大写后前面加 set 找对应的 setBookDao() 方法进行对象注入 ( 即第二步
public void setBookDao
中后面的 BookDao ) - ref="bookDao":中 bookDao 是让 Spring 能在 IOC 容器中找到 id 为 bookDao 的 Bean 对象给 bookService 进行注入
- ctrl 左键点击可见
- name="bookDao":中的 bookDao 是让 Spring 的 IOC 容器在获取到名称后,将首字母大写后前面加 set 找对应的 setBookDao() 方法进行对象注入 ( 即第二步
-
-
运行程序调用方法
-
运行结果同上
book service save ... book dao save ...
-
IOC 相关内容
bean 配置
id、class 基础配置
- id:bean 的 id,使用容器可以通过 id 值获取对应的 bean,在一个容器中 id 值唯一
- class:bean 的类型,即配置的 bean 的全路径类名
- 注意:class 属性不能写接口,如
BookDao
的类全名,因为接口是没办法创建对象的
- 注意:class 属性不能写接口,如
name 别名配置
- 多个人的 id 起名习惯不同,所以就出现了别名,即一个 bean 有多个名字
- 配置文件 xml 中的 bean 标签除了 id 外还可以取别名:
- 标签加一个 name 属性指定别名
- 别名可以有多个,用逗号、分号或空格隔开
- 不仅在 main 方法中调用可以别名,service 的 bean 标签的 ref 指向也可以是别名 ( 建议还是用 id )
- 获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException
scope 作用范围
-
bean 标签的 scope 属性有两个值:singleton、prototype
- 默认是单例
singleton
prototype
是非单例
- 默认是单例
-
更改 main 方法中的调用方法为
-
( 打印对象地址 )
System.out.println(bookDao1); System.out.println(bookDao2);
-
单例的话两个打印的地址相同,非单例的话两个地址不同
-
思考
- 为什么 bean 默认为单例
- bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象
- bean 对象只有一个就避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
- bean 在容器中是单例的,会不会产生线程安全问题
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
- 因为所有请求线程共用一个 bean 对象,所以会存在线程安全问题。
- 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,
- 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
- 哪些 bean 对象适合交给容器进行管理 ( 造一次就行,可以反复使用 )
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些 bean 对象不适合交给容器进行管理
- 封装实例的域对象 ( 有状态的 )
- 因为会引发线程安全问题,所以不适合。
- 封装实例的域对象 ( 有状态的 )
bean 实例化
构造方法实例化
- bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建 bean 也是使用构造方法完成的
- 就是前面讲的解耦的 IOC + DI 的方法
- 私有的构造方法也可以创建出对象 ( 体现了 Spring 底层用的是反射 )
- 调用的是无参的构造方法,有参会报错
静态工厂实例化 ( 了解 )
-
bean:
<bean id="orderDao" class="com.qut.factory.OrderDaoFactory" factory-method="getOrderDao"/>
- class:工厂类的类全名
- factory-mehod:具体工厂类中创建对象的方法名 ( 静态的 )
-
在工厂的静态方法中,除了 new 对象还可以做其他的一些业务操作,而这些操作又必不可少
-
如:
public class OrderDaoFactory { public static OrderDao getOrderDao(){ System.out.println("factory setup....");//一些必要的业务操作 return new OrderDaoImpl(); } }
-
之前 new 对象的方式就无法添加其他的业务内容
-
-
这种方式一般是用来兼容早期的一些老系统,了解为主
实例工厂实例化 - 旧 ( 了解 )
-
实例工厂与 FactoryBean
-
Bean:
<bean id="userFactory" class="com.qut.factory.UserDaoFactory"/> <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
- factory-bean 就是指这个工厂的实例在哪 —— 上面的工厂 bean 的 id
- factory-method 指这个工厂用哪个方法 new 的对象
-
main 中和构造方法实例化的相同
-
配置的过程还是比较复杂,了解即可
- 工厂 bean 纯是为了配合使用
- 所以 Spring 为了简化这种配置方式就提供了一种叫
FactoryBean
的方式来简化开发
实例化工厂实例化 - 简化改良
-
创建一个 UserDaoFactoryBean 的类,实现 FactoryBean 接口,重写接口的方法
-
其中的 FactoryBean 的泛型就是你想造什么对象就写什么对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替原始实例工厂中创建对象的方法(即方法名指定了统一叫getObject) public UserDao getObject() throws Exception { return new UserDaoImpl(); } //返回所创建类的Class对象 public Class<?> getObjectType() { return UserDao.class; } }
-
-
在 Spring 的配置文件中进行配置
<bean id="userDao" class="com.qut.factory.UserDaoFactoryBean"/>
-
AppForInstanceUser运行类不用做任何修改,直接运行 ( 正常运行不报错 )
-
这种方式在 Spring 去整合其他框架的时候会被用到,所以这种方式需要理解掌握
方法拓展
- 查看源码会发现,FactoryBean 接口其实会有三个方法,分别是:
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
-
方法一:getObject(),被重写后,在方法中进行对象的创建并返回
-
方法二:getObjectType(),被重写后,主要返回的是被创建类的 Class 对象
-
方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认 true 单例
- 想要改为非单例只需要将 isSingleton() 方法进行重写,修改 true 返回为 false 即可
- 一般都是单例,所以一般都不写此方法
bean 的生命周期
相关了解
- 什么是生命周期
- 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期
- bean 生命周期是什么
- bean 对象从创建到销毁的整体过程
- bean 生命周期控制是什么
- 在 bean 创建以后,到销毁之前做一些事情
- 即下述的两种 init 与 destroy 等
实例操作
-
在起初 IOC + DI 环境中来为 BookDao 添加生命周期的控制方法,具体的控制有两个阶段:
- bean 创建之后,想要添加内容,比如用来初始化需要用到资源
- bean 销毁之前,想要添加内容,比如用来释放用到的资源
-
添加初始化和销毁方法
-
针对这两个阶段,我们在 BooDaoImpl 类中分别添加两个方法,方法名任意
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("destroy..."); } }
-
-
配置生命周期
-
在配置文件添加配置,如下:
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
-
-
运行结果
init... book dao save ...
-
init 方法执行了,但是 destroy 方法未执行
- 因为 Spring 的 IOC 容器是运行在 JVM 中
- 运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行
- main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还没有来得及销毁就已经结束了,所以没有调用对应的 destroy 方法
- 因为 Spring 的 IOC 容器是运行在 JVM 中
销毁操作方法一:close()
-
使用 close() 在退出虚拟机之前关闭此容器
-
但 ApplicationContext 接口中不具有 close() 方法,但是 ClassPathXmlApplicationContext 具有,所以想要关闭就将语句改为
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 然后就可以调用
ctx.close();
- 此方法必须放在 main 最后的地方
- 然后就可以调用
-
运行结果:
init... book dao save ... destroy...
销毁操作方法二:注册钩子关闭容器
-
在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器
-
调用 ctx 的 registerShutdownHook() 方法,仍是
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- registerShutdownHook 在 ApplicationContext 中也没有
- 此关闭钩子在任何时间都可以,不一定要放在 main 最后的位置
-
运行结果相同
比较
- 上两种方法其实都不怎么会用到
- 相同点:这两种都能用来关闭容器
- 不同点:close() 是在调用的时候关闭,registerShutdownHook() 是在 JVM 退出前调用关闭
Spring 提供接口控制生命周期
-
Spring 提供了两个接口来完成生命周期的控制,好处是可以不用在 xml 配置里的 bean 标签写
init-method
和destroy-method
-
直接在 BookServiceImpl 完成这两个接口的使用
-
修改 BookServiceImpl 类,添加两个接口
InitializingBean
,DisposableBean
并实现接口中的两个方法afterPropertiesSet
和destroy
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; public void setBookDao(BookDao bookDao) { System.out.println("属性设置..."); this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } public void destroy() throws Exception { System.out.println("service destroy"); } public void afterPropertiesSet() throws Exception { System.out.println("service init"); } // afterPropertiesSet:属性设置之后 }
-
运行结果
init... 属性设置... service init book dao save ... service destroy destroy...
-
方法了解即可
生命周期小结
- 对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置如下:
- 初始化容器
- 创建对象 ( 内存分配 )
- 执行构造方法
- 执行属性注入 ( set 操作,即上述的属性设置部分 )
- 执行 bean 初始化方法
- 使用 bean
- 执行业务操作
- 关闭 / 销毁容器
- 执行 bean 销毁方法
DI 依赖注入相关内容
分析
- 向一个类中传递数据的方式有几种
- 普通方法 ( set 方法 )
- 构造方法
- 依赖注入描述了在容器中建立 bean 与 bean 之间的依赖关系的过程,如果 bean 运行需要的是数字或字符串呢
- 引用类型
- 简单类型 ( 基本数据类型与 String )
-
基于上面这些知识点,Spring 提供了两种注入方式:
-
setter 注入
- 简单类型
- 引用类型 ( 先前已讲的 xxServiceImpl 内 setxxDao 方法,bean 里用 ref 引用 )
-
构造器注入
- 简单类型
- 引用类型
-
setter 注入
引用类型
-
本文前面 DI 入门案例中的实现所讲:
-
在 BookServiceImpl 中声明 userDao 属性
-
为 userDao 属性提供 setter 方法 ( service 方法里直接
dao属性.方法
使用即可 ) -
在配置文件中使用 property 标签注入
-
-
在一个 xxServiceImpl 中可以有多个 dao、多个对应 dao 的 setter 方法
- 对应的,xml 里就得有多个 bean,service 的
<property ..../>
也得写多个
- 对应的,xml 里就得有多个 bean,service 的
简单数据类型
-
类似上述引用类型方式:
-
在 BookDaoImpl 类中声明对应的简单数据类型的属性
-
为这些属性提供对应的 setter 方法
-
在 applicationContext.xml 中配置
-
-
声明属性并提供 setter 方法
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); } }
-
配置文件中进行注入配置 ( 仍是 property 标签,但 dao 里的属性有所改变 )
<?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.qut.dao.impl.BookDaoImpl"> <property name="databaseName" value="mysql"/> <property name="connectionNum" value="10"/> </bean> <bean id="userDao" class="com.qut.dao.impl.UserDaoImpl"/> <bean id="bookService" class="com.qut.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"/> <property name="userDao" ref="userDao"/> </bean> </beans>
- value:后面跟的是简单数据类型,对于参数类型,Spring 在注入的时候会自动转换,但是不能写成
<property name="connectionNum" value="abc"/>
- 这样的话,spring 在将 abc 转换成 int 类型的时候就会报错
- connectionNum 在 service 声明为了 int 类型
- value:后面跟的是简单数据类型,对于参数类型,Spring 在注入的时候会自动转换,但是不能写成
-
运行结果
book dao save ...mysql,10
构造器注入
多个引用数据类型
-
提供多个属性的构造函数
public class BookServiceImpl implements BookService{ private BookDao bookDao; private UserDao userDao; public BookServiceImpl(BookDao bookDao,UserDao userDao) { this.bookDao = bookDao; this.userDao = userDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); userDao.save(); // 都调用 } }
- 原为
setBookDao()
方法的方式
- 原为
-
配置文件中配置多参数注入
<?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"/> <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>
- 上述两个
<contructor-arg>
的配置顺序可以任意 - 此处的 name 是指形参的名称,即
public BookServiceImpl(BookDao bookDao)
这个形参列表里最后面的bookDao
,而不是初始化属性的命名
- 上述两个
-
运行程序
book service save ... book dao save ... user dao save ...
简单数据类型
标准书写 — 耦合高
-
添加多个简单属性并提供构造方法
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); } }
-
配置完成多个属性构造器注入
<?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"> <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>
- 同上
<contructor-arg>
的配置顺序可以任意 - 引用类型是 ref,基本数据类型是 value
- 同上
-
运行程序
book service save ... book dao save ... mysql,666 user dao save ...
-
但是这种方法在 BookDaoImpl 构造器中形参列表中的形参若是做了更改,在 xml 配置文件中
constructor-arg
的name
属性值也要更改,配置文件和代码部分的耦合度较高,
特殊书写 — 了解为主
-
解决方法一:删除name属性,添加type属性,按照类型注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg type="int" value="10"/> <constructor-arg type="java.lang.String" value="mysql"/> </bean>
- 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了
-
解决方法二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从 0 开始
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg index="1" value="100"/> <constructor-arg index="0" value="mysql"/> </bean>
- 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题
上述两种依赖注入方式选择
- 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现
- 强制依赖指对象在创建的过程中必须要注入指定的参数,因为不存在的话连对象都造不出来,即强制型
- 可选依赖使用 setter 注入进行,灵活性强
- 可选依赖指对象在创建过程中注入的参数可有可无
- Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 框架的编写要求严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
- 可以,但一般不这样写
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
- 自己管理的代码部分不一定是自己写的
- 自己开发的模块推荐使用 setter 注入
自动装配
- IOC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
- 自动装配方式
- 按类型 ( 常用 )
- 按名称
- 按构造方法
- 不启用自动装配
实例演示
-
BookServiceImpl 的
setBookDao()
方法做保留public class BookServiceImpl implements BookService{ private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } }
-
仅更改 xml 配置文件:
- 将
<property>
标签删除 - 在
<bean>
标签中添加 autowire 属性
<?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"> <!-- 按类型的话这里可以不写id(但是按名称的话必须要写) --> <bean class="com.itheima.dao.impl.BookDaoImpl"/> <!--autowire属性:开启自动装配,通常使用按类型装配--> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/> </beans>
- 因为 BookServiceImpl 中要 BookDao 类型的属性,所以 xml 中要有满足此 BookDao 接口的 bean ( 因为是按类型装配 )
- 但若是有两个不同 id 但都是 BookDao 接口就会报错 ( 不唯一类型会报错 )
- 但只有一个接口却没 id 也可以,不会报错
- 但不用
byType
而用byName
就可解决,即让 bean 标签的 id 与 BookServiceImpl 的 setBookDao 方法的去掉 set 首字母小写的 bookDao 对应即可
- 将
特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时 ( byType ) 必须保障容器中相同类型的 bean 唯一,推荐使用
- 使用按名称装配时 ( byName ) 必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
集合注入
-
常见的集合类型
- 数组
- List
- Set
- Map
- Properties
-
项目中添加添加 BookDao、BookDaoImpl 类
public interface BookDao { public void save(); } // —————————————————————————————————— public class BookDaoImpl implements BookDao { private int[] array; private List<String> list; private Set<String> set; private Map<String,String> map; private Properties properties; public void save() { System.out.println("book dao save ..."); System.out.println("遍历数组:" + Arrays.toString(array)); System.out.println("遍历List" + list); System.out.println("遍历Set" + set); System.out.println("遍历Map" + map); System.out.println("遍历Properties" + properties); } //setter....方法省略不展示 }
-
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 id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!-- property注入所需类型数据 --> </bean> </beans>
-
运行类,main 里加载 Spring 的 IOC 容器并获取 bean 对象
public class AppForDICollection { public static void main( String[] args ) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }
注入数组类型数据
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
注入List类型数据
<property name="list">
<list>
<value>cast</value>
<value>xxx</value>
<value>asc</value>
<value>wsad</value>
</list>
</property>
注入Set类型数据
<property name="set">
<set>
<value>cast</value>
<value>xxx</value>
<value>asc</value>
<value>asc</value>
</set>
</property>
- 重复的 asc 会只留一个
注入Map类型数据
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="shandong"/>
<entry key="city" value="zaozhuang"/>
</map>
</property>
注入Properties类型数据
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">shandong</prop>
<prop key="city">zaozhuang</prop>
</props>
</property>
说明
- property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签 - List 的底层也是通过数组实现的,所以
<list>
和<array>
标签是可以混用 - 集合中要添加引用类型,只需要把
<value>
标签改成<ref>
标签,这种方式用的比较少
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通