学习路线
1.Spring系统架构
-
Spring Framework
:Spring框架,是Spring中最早最核心
的技术,也是所有其他技术的基础。 -
SpringBoot
:Spring是来简化
开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发
。 SpringCloud
:这个是用来做分布式之微服务架构
的相关开发。
(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.Spring核心概念
Spring核心概念这部分内容中主要包含IOC/DI
、IOC容器
和Bean
,那么问题就来了,这些都是什么呢?
(1)业务层需要调用数据层的方法,就需要在业务层new数据层的对象
(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
(3)所以,现在代码在编写的过程中存在的问题是:耦合度偏高
业务层不想new对象,运行的时候*转换为由外部提供对象
这种实现思就是Spring的一个核心概念
3.IOC
1)理解IOC
(1)什么是控制反转呢?
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部
,此思想称为控制反转
。
- 业务层要用数据层的类对象,以前是自己
new
的 - 现在自己不new了,交给
别人[外部]
来创建对象 -
别人[外部]
就反转控制了数据层对象的创建权 - 这种思想就是
控制反转
,也就是利用IOC容器
实现数据的注入
(2)Spring和IOC之间的关系是什么呢?
-
Spring技术
对IOC思想
进行了实现
- Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的"外部"
- IOC思想中的
别人[外部]
指的就是Spring的IOC容器
(3)IOC容器的作用以及内部存放的是什么?
- IOC容器负责
对象的创建、初始化等一系列工作
,其中包含了数据层和业务层的类对象
-
被创建或被管理的对象
在IOC容器
中统称为Bean - IOC容器中放的就是一个个的
Bean对象
(4)当IOC容器中创建好service和dao对象后,程序能正确执行么?
-
不行
,因为service运行需要依赖dao对象 - IOC容器中虽然有service和dao对象
- 但是
service对象和dao对象没有任何关系
- 需要
把dao对象交给service
,也就是说要绑定service和dao对象之间的关系 - 在容器中建立对象与对象之间的绑定关系就要用到
DI
,也就是依赖注入
2)bean基础配置
(1)id与class
思考:
- class属性能不能写接口如
BookDao
的类全名呢? -
不行
,因为接口是没办法创建对象
的。
(2)bean的name属性
(3)bean作用范围scope配置
bean的创建单例
默认情况下,Spring创建的bean对象都是单例的
<?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.amy.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.amy.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
public class App2 {
public static void main(String[] args) {
//获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService1 = (BookService) ctx.getBean("bookService");
BookService bookService2 = (BookService) ctx.getBean("bookService");
System.out.println(bookService1);
System.out.println(bookService2);
}
}
com.amy.service.impl.BookServiceImpl@ff5b51f
com.amy.service.impl.BookServiceImpl@ff5b51f
bean的创建非单例
<?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">
<!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<bean id="bookService" name="service service4 bookEbi" class="com.amy.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
<bean id="bookDao" name="dao" class="com.iamy.dao.impl.BookDaoImpl" scope="prototype"/>
</beans>
public class App2 {
public static void main(String[] args) {
//获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService1 = (BookService) ctx.getBean("bookService");
BookService bookService2 = (BookService) ctx.getBean("bookService");
System.out.println(bookService1);
System.out.println(bookService2);
}
}
com.amy.dao.impl.BookDaoImpl@ff5b51f
com.amy.dao.impl.BookDaoImpl@25bbe1b6
使用bean的scope
属性可以控制bean的创建是否为单例:
-
singleton
默认为单例 -
prototype
为非单例
scope使用后续思考
介绍完scope
属性以后,我们来思考几个问题:
- 为什么bean默认为单例?
- bean为单例的意思是在
Spring的IOC容器中只会有该类的一个对象
- bean对象只有一个就
避免了对象的频繁创建与销毁
,达到了bean对象的复用,性能高
- bean在容器中是单例的,会不会产生线程安全问题?
- 如果对象是
有状态对象
,即该对象有成员变量可以用来存储数据
的, - 因为所有请求线程共用一个bean对象,所以会
存在线程安全
问题。 - 如果对象是
无状态对象
,即该对象没有成员变量没有进行数据存储
的, - 因方法中的
局部变量在方法调用完成后会被销毁
,所以不会存在线程安全
问题。
- 哪些bean对象适合交给容器进行管理?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些
bean对象不适合交给容器进行管理
?
-
封装实例的域对象
,因为会引发线程安全问题
,所以不适合。
3)bean实例化
- 实例化bean的三种方式,
构造方法
,静态工厂
和实例工厂
- bean本质上就是对象,对象在new的时候会使用构造方法完成,那创建bean也是使用构造方法完成的。
构造方法
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
<?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.amy.dao.impl.BookDaoImpl"/>
</beans>
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
book dao constructor is running ....
book dao save ...
将构造函数改成private测试
public class BookDaoImpl implements BookDao {
private BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
book dao constructor is running ....
book dao save ...
-
private和public的构造函数能都运行
的原因是Spring底层用的反射
构造函数中添加一个参数测试
public class BookDaoImpl implements BookDao {
private BookDaoImpl(int i) {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
- 程序会报错,说明
Spring底层使用的是类的无参构造方法
。
静态工厂
1.工厂方式创建bean
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
factory setup....
order dao save ...
2. 静态工厂实例化
在spring的配置文件application.properties中添加以下内容:
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
class
:工厂类的类全名
factory-mehod
:具体工厂类中创建对象的方法名
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
//静态工厂实例化
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
factory setup....
order dao save ...
实例工厂
1.实例工厂方式创建bean
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//创建实例工厂对象
public class OrderDaoFactory {
//并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法
public OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
//创建实例工厂对象
OrderDaoFactory orderDaoFactory= new OrderDaoFactory();
//通过实例工厂对象创建对象
OrderDao orderDao = orderDaoFactory.getOrderDao();
orderDao.save();
}
}
factory setup....
order dao save ...
2.实例工厂实例化
<!--方式三:使用实例工厂实例化bean-->
<bean id="orderDaoFactory" class="com.amy.factory.OrderDaoFactory"/>
<bean id="orderDao" factory-method="getOrderDao" factory-bean="orderDaoFactory" />
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//创建实例化工厂对象
public class OrderDaoFactory {
//去掉static,创建实例化工厂对象
public OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
factory setup....
order dao save ...
3.FactoryBean的使用
实例工厂实例化的方式
就已经介绍完了,配置的过程还是比较复杂
,所以Spring为了简化
这种配置方式就提供了一种叫FactoryBean
的方式来简化开发。
<!--方式四:使用FactoryBean实例化bean-->
<bean id="orderDao" class="com.amy.factory.UserDaoFactoryBean"/>
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class OrderDaoFactoryBean implements FactoryBean<OrderDao> {
//代替原始实例工厂中创建对象的方法
public OrderDao getObject() throws Exception {
System.out.println("factory setup....");
return new OrderDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {return OrderDao.class;
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
factory setup....
order dao save ...
这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
//创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class OrderDaoFactoryBean implements FactoryBean<OrderDao> {
//代替原始实例工厂中创建对象的方法
public OrderDao getObject() throws Exception {
System.out.println("factory setup....");
return new OrderDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {return OrderDao.class;
}
}
查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
方法一:getObject()
,被重写
后,在方法中进行对象的创建并返回
方法二:getObjectType()
,被重写后,主要返回的是被创建类的Class对象
方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例
,如何来验证呢?
思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。
4)bean的生命周期
具体的控制有两个阶段
:
-
bean创建之后
,想要添加内容,比如用来初始化需要用到资源 -
bean销毁之前
,想要添加内容,比如用来释放用到的资源
步
(1)bean的生命周期步骤
步骤1:添加初始化和销毁方法
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("destory...");
}
}
步骤2:配置生命周期
<!--init-method:设置bean初始化生命周期回调函数-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
步骤3:运行程序
public interface BookDao {
public void save();
}
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...");
}
}
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
<?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">
<!--init-method:设置bean初始化生命周期回调函数-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
</beans>
init...
book dao save ...
init方法执行了
,但是destroy方法却未执行,这是为什么呢?
- Spring的IOC容器是
运行在JVM
中 - 运行main方法后,
JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行
main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了
所以没有调用对应的destroy方法
(2)close关闭容器
- ApplicationContext中没有close方法
- 需要将ApplicationContext更换成ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
- 调用ctx的close()方法
ctx.close();
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
ctx.close();
}
}
init...
book dao save ...
destory...
(3)注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
- 调用ctx的
registerShutdownHook()
方法
ctx.registerShutdownHook();
注意:registerShutdownHook在ApplicationContext中也没有
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
ctx.registerShutdownHook();
}
}
init...
book dao save ...
destory...
两种方式介绍完后,close和registerShutdownHook选哪个?
相同点
:这两种都能用来关闭容器
不同点
:close()是在调用的时候关闭
,registerShutdownHook()是在JVM退出前调用关闭
。
(4)InitializingBean
, DisposableBean
实现接口
分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多也比较乱。
Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-method
和destroy-method
接下来在BookServiceImpl完成这两个接口的使用:
修改BookServiceImpl类,添加两个接口InitializingBean
, DisposableBean
并实现接口中的两个方法afterPropertiesSet
和destroy
public interface BookDao {
public void save();
}
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...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
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");
}
}
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
ctx.close();
}
}
<?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">
<!--init-method:设置bean初始化生命周期回调函数-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
(5)bean生命周期小结
(1)关于Spring中对bean生命周期控制提供了两种方式:
- 在配置文件中的bean标签中添加
init-method
和destroy-method
属性 - 类实现
InitializingBean
与DisposableBean
接口,这种方式了解下即可。
(2)对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
- 初始化容器
- 1.创建对象(内存分配)
- 2.执行构造方法
- 3.执行属性注入(set操作)
- 4.执行bean初始化方法
- 使用bean
- 1.执行业务操作
- 关闭/销毁容器
- 1.执行bean销毁方法
(3)关闭容器的两种方式:
ConfigurableApplicationContext
是ApplicationContext
的子类
- close()方法
- registerShutdownHook()方法
4.DI(Dependency Injection)依赖注入
1)理解依赖注入,IOC容器中bean之间关系
(1)什么是依赖注入呢?
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为
依赖注入
- 业务层要用数据层的类对象,以前是自己
new
的 - 现在自己不new了,靠
别人[外部其实指的就是IOC容器]
来给注入进来 - 这种思想就是依赖注入
(2)IOC容器中哪些bean之间要建立依赖关系呢?
- 根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建立依赖关系
Spring的IOC和DI的使用目的:充分解耦,具体实现靠:
- 使用
IOC容器管理bean
(IOC
) - 在
IOC容器内将有依赖关系的bean进行关系绑定
(DI
) - 最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系.
2)不使用IOC容器创建Bean
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring_01_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
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();
}
Spring的IOC入门案例已经完成,但是在
BookServiceImpl
的类中依然存在BookDaoImpl
对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入
。
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
public class App {
public static void main(String[] args) {
BookService bookService = new BookServiceImpl();
bookService.save();
}
}
book service save ...
book dao save ...
3) setter注入
需求:基于IOC入门案例,在BookServiceImpl类中删除new对象的方式,使用Spring的DI完成Dao层的注入
1.删除业务层中使用new的方式创建的dao对象
2.在业务层提供BookDao的setter方法
3.在配置文件中添加依赖注入的配置
4.运行程序调用方法
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;
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">
<!--1.导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->
<!--2.配置bean-->
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--7.配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
public class App2 {
public static void main(String[] args) {
//获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
book service save ...
book dao save ...
注意:配置中的两个bookDao的含义是不一样的
- name="bookDao"中
bookDao
的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()
方法进行对象注入 - ref="bookDao"中
bookDao
的作用是让Spring能在IOC容器中找到id为bookDao
的Bean对象给bookService
进行注入
2)构造器注入
(1)项目中添加BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ...");
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {//set方式注入
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
(2)resources下提供spring的配置文件
<?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">
<!-- 配置文件中进行配置set方式注入-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
(3)编写AppForDIConstructor运行类,加载Spring的IOC容器,并从中获取对应的bean对象
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
book service save ...
book dao save ...
(1)构造器注入引用数据类型
接下来,在上面这个环境中来完成构造器注入的学习:
需求:将BookServiceImpl类中的bookDao修改成使用构造器的方式注入。
1.将bookDao的setter方法删除掉
2.添加带有bookDao参数的构造方法
3.在applicationContext.xml中配置
步骤1:删除setter方法并提供构造方法
1.将bookDao的setter方法删除掉, 2.添加带有bookDao参数的构造方法
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService{
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
//this.bookDao = bookDao;
//}
//在BookServiceImpl类中将bookDao的setter方法删除掉,并添加带有bookDao参数的构造方法
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤2:配置文件中进行配置构造方式注入
在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 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>
标签中
name
属性对应的值为构造函数中方法形参的参数名
,必须要保持一致。ref
属性指向的是spring的IOC容器中其他bean对象
。
步骤3:运行程序
book service save ...
book dao save ...
(2)构造器注入多个引用数据类型
需求:在BookServiceImpl使用构造函数注入多个引用数据类型,比如userDao
1.声明userDao属性
2.生成一个带有bookDao和userDao参数的构造函数
3.在applicationContext.xml中配置注入
在BookServiceImpl声明userDao并提供多个参数的构造函数
步骤1:提供多个属性的构造函数
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ...");
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
public interface BookService {
public void save();
}
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();
}
}
步骤2:配置文件中配置多参数注入
在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 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>
的配置顺序可以任意
步骤3:运行程序
运行AppForDIConstructor类,查看结果,说明userDao已经成功注入。
book service save ...
book dao save ...
(3)构造器注入多个简单数据类型
需求:在BookDaoImpl中,使用构造函数注入databaseName和connectionNum两个参数。
参考引用数据类型的注入,我们可以推出具体的步骤为:
1.提供一个包含这两个参数的构造方法
2.在applicationContext.xml中进行注入配置
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
public interface BookService {
public void save();
}
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();
}
}
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
步骤1:添加多个简单属性并提供构造方法
修改BookDaoImpl类,添加构造方法
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);
}
}
步骤2:配置完成多个属性构造器注入
在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 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>
的配置顺序可以任意
步骤3:运行程序
运行AppForDIConstructor类,查看结果
book service save ...
book dao save ...mysql,666
(4)构造函数中方法的参数名发生变化
- 当构造函数中方法的参数名发生变化后,配置文件中的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注入
总结:Spring的依赖注入的实现方式:
- setter注入
- 简单数据类型
<bean ...>
<property name="" value=""/>
</bean>
- 引用数据类型
<bean ...>
<property name="" ref=""/>
</bean>
- 构造器注入
- 简单数据类型
<bean ...>
<constructor-arg name="" index="" type="" value=""/>
</bean>
- 引用数据类型
<bean ...>
<constructor-arg name="" index="" type="" ref=""/>
</bean>
- 依赖注入的方式选择上
- 建议使用setter注入
- 第三方技术根据情况选择
3) 自动配置
(1)什么是依赖自动装配?
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程
称为自动装配
(2)自动装配方式有哪些?
- 按类型(常用)
- 按名称
- 按构造方法
- 不启用自动装配
(3) 未自动装配代码
(1)项目中添加BookDao、BookDaoImpl、BookService和BookServiceImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
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();
}
}
(2)resources下提供spring的配置文件
<?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="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
(3)编写AppForAutoware运行类,加载Spring的IOC容器,并从中获取对应的bean对象
public class AppForAutoware {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
book service save ...
book dao save ...
(4)完成自动装配的配置
自动装配只需要修改applicationContext.xml配置文件即可:
(1)将<property>
标签删除
(2)在<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">
<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
</beans>
注意事项:
- 需要注入属性的类中对应属性的setter方法不能省略
- 被注入的对象必须要被Spring的IOC容器管理
- 按照类型在Spring的IOC容器中如果找到多个对象,会报
NoUniqueBeanDefinitionException
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为
:autowire=“byName”
<?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 class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName"/>
</beans>
注意事项:
- 按照名称注入中的名称指的是什么?
- bookDao是private修饰的,外部类无法直接方法
- 外部类只能
通过属性的set方法
进行访问 - 对外部类来说,setBookDao方法名,去掉
set后首字母小写是其属性名
- 为什么是去掉set首字母小写?
- 这个规则是
set方法生成的默认规则
,set方法的生成是把属性名首字母大写前面加set形成的方法名
- 所以按照名称注入,其实是和对应的set方法有关,但是如果按照标准起名称,属性名和set对应的名是一致的
- 如果按照名称去找对应的bean对象,找不到则注入Null
- 当某一个类型在IOC容器中有多个对象,按照名称注入只找其指定名称对应的bean对象,不会报错
两种方式介绍完后,以后用的更多的是按照类型注入。
最后对于依赖注入,需要注意一些其他的配置特征:
-
自动装配
用于引用类型依赖注入
,不能对简单类型进行操作
- 使用
按类型装配时(byType)
必须保障容器中相同类型的bean唯一
,推荐使用 - 使用
按名称装配时(byName)
必须保障容器中具有指定名称的bean
,因变量名与配置耦合,不推荐使用 -
自动装配优先级低于setter注入与构造器注入
,同时出现时自动装配配置失效
4)集合注入
常见的集合类型有哪些?
- 数组
- List
- Set
- Map
- Properties
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 setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.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);
}
}
public class AppForDICollection {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
<?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 name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!--map集合注入-->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
</beans>
book dao save ...
遍历数组:[100, 200, 300]
遍历List[itcast, itheima, boxuegu, chuanzhihui]
遍历Set[itcast, itheima, boxuegu]
遍历Map{country=china, province=henan, city=kaifeng}
遍历Properties{province=henan, city=kaifeng, country=china}
说明:
- property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签 - List的底层也是通过数组实现的,所以
<list>
和<array>
标签是可以混用 - 集合中要添加引用类型,只需要把
<value>
标签改成<ref>
标签,这种方式用的比较少