Spring-基础概念
# 学习目标
- 能够说出Spring的体系结构
- 能够编写IOC入门案例
- 能够编写DI入门案例
- 能够配置
setter
方式注入属性值- 能够配置构造方法注入属性值
- 能够理解自动装配
# Spring 简介
# Spring课程介绍
# 第一个问题:为什么学习Spring框架
- Spring技术框架是JavaEE开发必备的技能,企业开发时技术选型命中率**>=90%**
- 为什么:
- 简化开发:降低企业级开发的复杂度
- 框架整合:快捷、高效整合其他技术,提高企业级应用的开发和运行效率
# 第二个问题:学习Spring的什么内容?
学习Spring我们从两个维度进行:
- 简化开发
- IOC(控制反转)
- AOP(面向切面编程):事务处理应用
- 框架整合
- MyBatis
- MyBatis-plus
- Struts
- Struts2
- Hibernate
- FastJson
- Redis
- ...
# 第三个问题:怎么学习Spring?
- 学习Spring框架的核心概念和设计思想
- 学习Spring的基础操作,总结操作与思想间的练习
- 结合案例,熟练进行Spring各种操作的同时,体会Spring的设计思想
# 初识Spring
# Spring家族
下面我们首先来简单认识一下Spring,Spring发展到现在已经形成了一个基于Spring开发的生态圈,它提供了若干个项目,每个项目用于完成特定的功能。
Spring官网:https://spring.io
The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise
applications - on any kind of deployment platform.
A key element of Spring is infrastructural support at the application level: Spring focuses on the "plumbing"
of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to
specific deployment environments.
Core technologies: dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL, AOP.
2
3
4
5
6
7
8
上面是Spring官网的一段简介,翻译过来就是:
Spring框架为基于Java的企业应用程序提供了一个全面的编程和配置模型--并且适用于任何类型的部署平台。
Spring的一个重要作用就是为企业级应用开发提供底层的基础结构支持:Spring专注于企业级应用程序的“架构”,以便开发团队专注于业务逻辑的开发,而不用但因应用与特定的部署环境产生联系。
核心技术:依赖注入(重点)、事件、资源、国际化、校验、数据绑定、类型转换、SpEL表达式、面向切面编程(重点)。
2
3
4
5
# Spring发展历史
拓展
EJB(Enterprise Java Beans)是基于分布式事务处理的企业级应用程序的组件。SUN公司发布的文档中对EJB的定义是:EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构。
最初由于IBM和SUN等EJB提倡者力推其前景,起初一些大公司纷纷采用EJB部署他们的系统,然而随后各种问题边接踵而至,对EJB的恶评短时间内激增。
对于初学者,EJB的API显得太过困难;对于许多程序员来说,书写哪些必须抛出特定异常的接口并将bean类作为抽象类实现的做法既不直观也不正常。
当然,EJB所被赋予的使命,如对象关系映射和事务管理确实有其天然的复杂性,单其API之复杂还是零开发人员望而却步,一些人开始怀疑EJB除引入了复杂的实现手段以外似乎并未带来什么实际好处。
# Spring体系结构
Spring Framework是Spring生态中最基础的项目,是其他项目的根基。
# Spring Framework 系统架构图
下面我们就来了解一下Spring生态中我们常用的技术:
# Spring Framework 课程学习路线
Spring生态中有很多种技术,我们并不能掌握所有内容,因此我们要有针对性的选取几种重点常用技术进行学习,如下图:
# Spring核心概念
# JavaWeb阶段代码存在的问题
开始正式本章节前,我们先来回顾一下JavaWeb课程时写的部分代码,看下相关代码存在什么样的问题,以及如何解决这些问题。
- JavaWeb阶段代码问题:耦合度高,需要进行解耦。
- 解决方案:使用对象时,在程序中不要主动
new
对象,转换位由外部提供对象。
如何理解解耦?
解耦通俗地说就是原来两个部分互相影响,现在希望让他们独立发展,核心思想还是最小职责原则,每个部分都只做一件事情。与此同时,只要一个地方负责了多项事情,就存在解耦的可能。
在系统每个层次都可以体现解耦的思想,比如在架构层面把存储和业务逻辑解耦,把动态页面和静态页面解耦,在模块层面把业务模块和统计模块解耦,在代码层面把多个功能模块解耦等等。
这有点类似外科手术,主刀医生需要用到的各种医疗器械,并不需要自己去找、去拿,只要发出指令,护士就会提供给他相应的工具。
如何实现?
加入有BrandServlet和BrandServiceImpl两个对象,BrandServlet依赖于BrandServiceImpl,那么BrandServlet在初始化或者运行 到某一点的时候,自己必须主动去创建BrandServiceImpl对象或者使用已经在内存中创建好的BrandServiceImpl对象。那么无论是自己创建 还是使用已有对象,控制权都在自己手上,而我们要做的就是让BrandServlet和BrandServiceImpl失去直接联系,当BrandServlet对象运行 到需要BrandServiceImpl对象的时候,由外部创建一个BrandServiceImpl对象主动注入到BrandServlet对象需要的地方。通过上面的流程, BrandServlet对象获取BrandServiceImpl对象的过程,就由主动行为变为了被动行为。
# 核心概念
通过对上面两个问题的解答,我们已经初步了解到Spring的一个核心概念,就是IOC(Inversion of Control)控制反转,下面我们就来详细学习一下这一Spring核心概念。
IOC(Inversion of Control)控制反转:
使用对象时,由主动new
创建对象转换为由外部提供对象,此过程中对象控制权由程序转移到外部,这一思想被称为控制反转,通俗的讲就是将new
对象的权利交给Spring,我们从Spring中获取对象使用。
通过对IOC思想的学习,相信大家对其有了初步理解,咱们再来看下Spring对IOC思想是如何落地实现的:
- Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的外部
- IOC容器负责对象的创建、初始化等一系列工作,被创建和管理的对象在容器中统称为Bean。
有了IOC容器也即有了IOC思想中的外部,那么如何通过IOC容器将对象提供给程序使用呢?这就涉及到另外一个概念DI(Dependency Injection)依赖注入。
DI(Dependency Injection)依赖注入:
在容器中建立Bean
与Bean
之间的依赖关系的整个过程,称之为依赖注入。
掌握IOC和DI我们就可以解决JavaWeb阶段代码存在的问题,实现解耦的目标:
- 使用IOC容器管理
Bean
(IOC) - 在IOC容器内将有依赖关系的
Bean
进行关系绑定(DI)
最后,我们即可实现使用对象时不仅可以直接从IOC容器中获取,而且获取到的Bean
已经绑定了所有的依赖关系。
# IOC和DI入门案例(重点)
# IOC入门案例
# IOC入门案例思路
- IOC容器管理什么?(Service和Dao)
- 如何将被管理的对象告知IOC容器?(XML配置文件)
- IOC容器管理
Bean
,那么如何获取IOC容器?(接口) - 获取到IOC容器后,如何从容器中获取
Bean
?(接口方法) - 使用Spring需要导入哪些坐标,如何导入?(pom.xml)
# IOC入门实现步骤
- 导入Spring依赖坐标
- 定义Spring管理的类(接口)
- 创建Spring配置文件,配置对应类作为Spring管理的
Bean
对象 - 初始化IOC容器(Spring核心容器/Spring容器),通过容器获取
Bean
对象
# IOC入门案例环境准备
- 创建空目录,用以存放项目代码,注意路径不要包括中文和特殊字符
- 使用Idea创建空项目
- 配置Maven
- 创建Spring快速入门
spring-demo
模块
执行完上述步骤后,Idea会默认初始化一部分模块目录结构,需要稍等几分钟,待初始化完成后,目录结构如下:
在JavaWeb课程阶段,我们讲过,通过使用Maven骨架创建的Web项目需要手动补全相关目录结构,补全后的目录结构如下:
- 维护pom.xml文件,修改后的pom.xml文件内容如下
<?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-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
编辑好pom.xml文件后,按如下操作刷新下Maven模块:
- 在pom.xml文件中引入Spring坐标
<?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-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
引入Spring坐标后,需要等一下,Maven会从远程仓库下载Spring的依赖Jar包,下载完成后,pom.xml文件中不会出现红色报错,如果出现报错需要检查Maven配置,网络是否连通,Jar包下载是否出错等。
如果Jar包下载出错可以找到配置的Maven本地仓库,将对应的依赖的文件目录删除,然后安装步骤5重新刷新下Maven模块即可:
初始化包结构
- 创建
com.itheima.dao
和com.itheima.dao.impl
包
- 重复上面步骤,创建
com.itheima.service
和com.itheima.service.impl
包
- 创建
定义Spring管理的类(接口)
- 在
com.itheima.dao
包下定义BrandDao
接口
package com.itheima.dao; /** * Dao层接口 */ public interface BrandDao { void save(); }
1
2
3
4
5
6
7
8
9
10- 在
com.itheima.dao.impl
包下定义BrandDaoImpl
实现类
package com.itheima.dao.impl; import com.itheima.dao.BrandDao; /** * BrandDao实现类 */ public class BrandDaoImpl implements BrandDao { @Override public void save() { System.out.println("Brand Dao Save ..."); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- 在
com.itheima.service
包下定义BrandService
接口
package com.itheima.service; /** * Brand业务层接口 */ public interface BrandService { void save(); }
1
2
3
4
5
6
7
8
9
10- 在
com.itheima.service.impl
包下定义BrandServiceImpl
实现类
package com.itheima.service.impl; import com.itheima.service.BrandService; /** * Brand业务层实现类 */ public class BrandServiceImpl implements BrandService { private BrandDao brandDao = new BrandDaoImpl(); @Override public void save() { System.out.println("Brand Service Save ..."); brandDao.save(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- 在
创建Spring配置文件,配置对应类作为Spring管理的
Bean
对象- 在main/resources目录下创建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"> </beans>
1
2
3
4
5
6注意
如果找不到图中的Spring Config选项,也可以直接创建一个XML文件,将上面的内容复制到创建的文件当中。
- 编辑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="brandService" class="com.itheima.service.impl.BrandServiceImpl"></bean> </beans>
1
2
3
4
5
6
7
8
9
10
11注意
bean
定义时id
属性在同一个上下文中(IOC容器中)不允许重复。初始化IOC容器(Spring核心容器/Spring容器),通过容器获取
Bean
对象- 在
com.itheima
包下创建测试类App.java
package com.itheima; import com.itheima.service.BrandService; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Spring IOC 快速入门 */ public class App { public static void main(String[] args) { // 1. 创建IOC容器对象,加载Spring核心配置文件 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 从IOC容器中获取Bean对象(BrandService对象) BrandService brandService = (BrandService) ctx.getBean("brandService"); // 3. 调用Bean对象(BrandService对象)的方法 brandService.save(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- 运行结果如下:
- 在
# DI入门案例
上面我们完成了Spring IOC的快速入门,咱们来分析一下相关代码,通过applicationContext.xml的配置我们可以知晓当前IOC容器中
只管理了一个brandService
Bean
对象,在BrandServiceImpl
中我们使用BrandDaoImpl
时依旧通过new BrandServiceImpl()
来实现的,
那么这块代码依旧存在强耦合问题,下面我们就来看下如何通过DI解决这个问题。
# DI入门案例思路分析
- 基于IOC容器管理
Bean
Service
中使用new
形式创建BrandDaoImpl
对象的代码是否保留?(否)- 那么,
Service
中需要用到的Dao
对象如何设置到Service
中?(提供方法) Service
与Dao
间的依赖关系如何进行描述?(Spring核心配置文件)
# DI入门案例实现步骤
- 删除使用
new
创建BrandDaoImpl
对象的代码 - 在
BrandServiceImpl
中提供依赖对象brandDao
的setter
方法 - 在Spring核心配置文件applicationContext.xml中配置
Service
与Dao
之间的依赖关系
# DI入门案例实现代码
- 在
BrandServiceImpl
中删除new BrandDaoImpl()
代码
package com.itheima.service.impl;
import com.itheima.dao.BrandDao;
import com.itheima.service.BrandService;
/**
* Brand业务层实现类
*/
public class BrandServiceImpl implements BrandService {
// 1. 第一步:删除 new BrandDaoImpl();代码
private BrandDao brandDao;
@Override
public void save() {
System.out.println("Brand Service Save ...");
brandDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 在
BrandServiceImpl
中提供brandDao
的setter
方法
package com.itheima.service.impl;
import com.itheima.dao.BrandDao;
import com.itheima.service.BrandService;
/**
* Brand业务层实现类
*/
public class BrandServiceImpl implements BrandService {
// 1. 第一步:删除 new BrandDaoImpl();代码
private BrandDao brandDao;
@Override
public void save() {
System.out.println("Brand Service Save ...");
brandDao.save();
}
// 2. 第二步:提供brandDao对应的setter方法
public void setBrandDao(BrandDao brandDao) {
this.brandDao = brandDao;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 在Spring核心配置文件applicationContext.xml文件中配置
Service
和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标签:配置bean
id属性:给bean起名字
class属性:给bean定义类型
-->
<bean id="brandDao" class="com.itheima.dao.impl.BrandDaoImpl"></bean>
<bean id="brandService" class="com.itheima.service.impl.BrandServiceImpl">
<!--
配置Service与Dao的关系
property标签:表示配置当前bean的属性
name属性:表示配置哪一个具体的属性名
ref属性:表示引用哪一个bean
-->
<property name="brandDao" ref="brandDao" />
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# DI入门案例运行结果及图解
图解分析:
BrandServiceImpl
需要使用BrandDaoImpl
对象,但又没有主动new
创建BrandServiceImpl
中有一个brandDao
属性- Spring IOC容器中管理了两个
Bean
,一个是brandService
,另一个是brandDao
- 通过Spring IOC容器获取
brandService
Bean
对象时,通过Spring的核心配置文件发现Service
和Dao
之间存在依赖关系, 因此Spring IOC容器通过DI将自己管理的brandDao
Bean
对象通过BrandServiceImpl
中的setBrandDao
setter
方法 设置到brandService
Bean
对象中。
# Bean的基础配置
上面通过IOC入门案例和DI入门案例简单了解了Spring核心配置文件applicationContext.xml相关的配置,下面我们就来详细学习下在Spring中Bean
的基础配置。
# Bean基础配置
- 配置说明
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans 标签 |
功能 | 定义Spring核心容器管理的对象 |
格式 | <beans> <bean /> <bean></bean> </beans> |
属性列表 | id : bean 的id ,使用容器可以通过id 获取对应的bean ,在一个容器中id 值唯一;class : bean 的类型,即配置的bean 的全类名。 |
范例 | <bean id="brandDao" class="com.itheima.dao.impl.BrandDaoImpl" /> <bean id="brandService" class="com.itheima.service.impl.BrandServiceImpl"></bean> |
- 代码演示
参考IOC入门案例中applicationContext.xml的配置
# Bean别名配置
- 配置说明
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean 标签 |
功能 | 定义bean 的别名,别名可定义多个,使用逗号, ,分号; ,空格( )分割 |
范例 | <bean id="brandDao" name="dao brandDaoImpl" class="com.itheima.dao.impl.BrandDaoImpl" /> <bean name="service,brandServiceImpl" class="com.itheima.service.impl.BrandServiceImpl" /> |
- 代码说明
<?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定义类型
name属性:给bean起别名,别名可以起多个,多个别名之间用逗号、分号或者空格进行分割
-->
<bean id="brandDao" name="dao brandDaoImpl" class="com.itheima.dao.impl.BrandDaoImpl"></bean>
<bean name="service,brandServiceImpl" class="com.itheima.service.impl.BrandServiceImpl">
<!--
配置Service与Dao的关系
property标签:表示配置当前bean的属性
name属性:表示配置哪一个具体的属性名
ref属性:表示引用哪一个bean
-->
<property name="brandDao" ref="brandDao" />
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itheima;
import com.itheima.service.BrandService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Spring IOC 快速入门
*/
public class App {
public static void main(String[] args) {
// 1. 创建IOC容器对象,加载Spring核心配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从IOC容器中获取Bean对象(BrandService对象)
BrandService brandService = (BrandService) ctx.getBean("service");
// 3. 调用Bean对象(BrandService对象)的方法
brandService.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 运行结果及代码图解
# Bean作用范围配置
- 配置说明
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean 标签 |
功能 | 定义bean 的作用范围,可选配置如下:1. singleton : 单例(默认) 2. prototype : 非单例 |
范例 | <bean id="brandDao" class="com.itheima.dao.impl.BrandDaoImpl" scope="prototype" /> |
扩展
scope
的取值不仅仅只有singleton
和prototype
两项,还有request
、session
、application
、websocket
,表示创建出的对象放置在Web容器(Tomcat)对应的位置。比如:request
表示保存到request
域对象中。
- 代码演示
<?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定义类型
name属性:给bean起别名,别名可以起多个,多个别名之间用逗号、分号或者空格进行分割
scope属性:表示bean对象的作用范围
-->
<bean id="brandDao" name="dao brandDaoImpl" class="com.itheima.dao.impl.BrandDaoImpl" scope="prototype"></bean>
<bean name="service,brandServiceImpl" class="com.itheima.service.impl.BrandServiceImpl">
<!--
配置Service与Dao的关系
property标签:表示配置当前bean的属性
name属性:表示配置哪一个具体的属性名
ref属性:表示引用哪一个bean
-->
<property name="brandDao" ref="brandDao" />
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima;
import com.itheima.dao.BrandDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForScope {
public static void main(String[] args) {
// 1. 创建IOC容器对象,加载Spring核心配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从IOC容器中获取Bean对象(BrandDao对象)
BrandDao brandDao1 = (BrandDao) ctx.getBean("brandDao");
BrandDao brandDao2 = (BrandDao) ctx.getBean("brandDao");
// 3. 输出brandDao1,brandDao2内存地址,判断是否为一个对象
System.out.println(brandDao1);
System.out.println(brandDao2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 运行结果
说明
在企业实际开发工作中,绝大部分的Bean
都是单例的,因此绝大部分Bean
是不需要配置scope
属性的,使用默认(单例)即可。
# Bean的实例化
# Bean是如何创建的(理解)
在Spring当中,万事万物都是Bean
对象,每一个对象都可以封装成BeanDefinition
,然后去生成Bean
对象。
- 第一步,Spring首先需要找到哪些
Bean
需要实例化,有两种方式,第一种是我们入门当中使用的XML方式,将需要实例化的Bean
在applicationContext.xml中使用bean
标签进行配置,通过解析XML可以找到所有需要实例化的Bean
,第二种为我们后面要学习的 注解方式,通过扫描所有添加了Spring注解的Bean
,然后把找到的Bean
封装成一个BeanDefinition
放入List
集合中; - 第二步,循环遍历
List
集合,获取BeanDefinition
中的全类名,通过反射进行实例化、属性注入等操作,如果还有初始化的动作, 可以在属性注入后进行,比如:通过在applicationContext.xml中指定init-method
方法,也可以让Bean
实现InitializingBean
这个接口,重写afterPropertiesSet()
方法,那么Bean
对象初始化时即可自动调用afterPropertiesSet()
方法,如果Bean
还需要 被代理,则可以通过后置通知,去生成代理的Bean
对象,如果Bean
实现了接口就是用JDK动态代理,如果没有实现接口,就是用CGLIB; - 第三步,完成后将
Bean
方法到Spring IOC容器中,我们即可通过Spring IOC容器获取Bean
对象; - 第四步,上面介绍了
Bean
对象在Spring IOC容器中创建和初始化的过程,下面我们来看下Bean
对象的销毁过程,当Spring IOC容器关闭时, 会触发Bean
对象的销毁,Bean
对象的销毁过程我们也有两种方式进行控制,一种是在applicationContext.xml文件中 通过destroy-method
指定Bean
销毁时调用的方法,但仅使用于单例对象,另一种是实现DisposableBean
接口,重写其destroy()
方法。
上面整个过程实际上就是Bean
对象的生命周期,也即是Bean
从创建到销毁的整个过程,下面我们先来演示下Bean
对象的实例化,后面我们再
对Bean
的生命周期进行详细的演示。
# 实例化Bean的三种方式
# 方式一:构造方法(重点)
BrandDaoImpl
实现类:在类中添加无参构造方法,并输出提示内容
package com.itheima.dao.impl;
import com.itheima.dao.BrandDao;
/**
* BrandDao实现类
*/
public class BrandDaoImpl implements BrandDao {
@Override
public void save() {
System.out.println("Brand Dao Save ...");
}
/**
* 方式一:通过构造方法实例化Bean
*/
public BrandDaoImpl() {
System.out.println("BrandDaoImpl 被创建了 ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 修改applicationContext.xml文件内容
<bean id="brandDao" name="dao brandDaoImpl" class="com.itheima.dao.impl.BrandDaoImpl" scope="prototype"></bean>
修改为:
<bean id="brandDao" class="com.itheima.dao.impl.BrandDaoImpl"></bean>
- 创建
AppForInstance
测试类
package com.itheima;
import com.itheima.dao.BrandDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForInstance {
public static void main(String[] args) {
// 1. 创建IOC容器对象,加载Spring核心配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从IOC容器中获取Bean对象(BrandDao对象)
BrandDao brandDao = (BrandDao) ctx.getBean("brandDao");
// 3. 调用Bean对象(BrandDao对象)的方法
brandDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
注意
使用构造方法实例化Bean
,如果类中无参构造方法不存在,将抛出异常BeanCreationException
# 方式二:静态工厂模式
- 在
com.itheima.dao
包下定义OrderDao
接口
package com.itheima.dao;
public interface OrderDao {
void save();
}
2
3
4
5
- 在
com.itheima.dao.impl
包下定义OrderDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.OrderDao;
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println("Order Dao Save ...");
}
}
2
3
4
5
6
7
8
9
10
- 创建
com.itheima.factory
包,并在该包下定义OrderDaoFactory
类
package com.itheima.factory;
import com.itheima.dao.OrderDao;
import com.itheima.dao.impl.OrderDaoImpl;
public class OrderDaoFactory {
public static OrderDao getOrderDao() {
System.out.println("Order Dao Factory Setup ...");
return new OrderDaoImpl();
}
}
2
3
4
5
6
7
8
9
10
11
- 修改applicationContext.xml配置文件,配置静态工厂实例化
OrderDaoImpl
的bean
标签
<!--
方式二:使用静态工厂实例化OrderDaoImpl
-->
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao" />
2
3
4
- 在
com.itheima
包下创建AppForOrder
测试类
package com.itheima;
import com.itheima.dao.OrderDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForOrder {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
运行结果:
# 方式三:实例工厂方式
- 在
com.itheima.dao
包下定义UserDao
接口
package com.itheima.dao;
public interface UserDao {
void save();
}
2
3
4
5
- 在
com.itheima.dao.impl
包下定义UserDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("User Dao Save ...");
}
}
2
3
4
5
6
7
8
9
10
- 在
com.itheima.factory
包下创建UserDaoFactory
工厂类
package com.itheima.factory;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
public class UserDaoFactory {
public UserDao getUserDao() {
System.out.println("方式三: UserDaoFactory 实例工厂初始化Bean");
return new UserDaoImpl();
}
}
2
3
4
5
6
7
8
9
10
11
- 修改applicatoinContext.xml配置文件,添加一个
bean
标签
<!--
方式三:使用实例工厂实例化Bean
-->
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory" />
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory" />
2
3
4
5
- 在
com.itheima
包下创建AppForUser
测试类
package com.itheima;
import com.itheima.dao.UserDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForUser {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
运行结果:
# 方式四:实现FactoryBean<T>
接口(了解即可)
- 在
com.itheima.dao
包下创建GoodsDao
接口
package com.itheima.dao;
public interface GoodsDao {
void save();
}
2
3
4
5
- 在
com.itheima.dao.impl
包下创建GoodsDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.GoodsDao;
public class GoodsDaoImpl implements GoodsDao {
@Override
public void save() {
System.out.println("Goods Dao Save ...");
}
}
2
3
4
5
6
7
8
9
10
- 在
com.itheima.factory
包下创建GoodsDaoFactoryBean
类,并实现FactoryBean<GoodsDao>
接口,重写getObject()
和getObjectType()
两个方法
package com.itheima.factory;
import com.itheima.dao.GoodsDao;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.GoodsDaoImpl;
import org.springframework.beans.factory.FactoryBean;
public class GoodsDaoFactoryBean implements FactoryBean<GoodsDao> {
@Override
public GoodsDao getObject() throws Exception {
System.out.println("GoodsDaoImpl 被创建了 ...");
return new GoodsDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 修改applicationContext.xml配置文件,添加
bean
标签
<!--
方式四:通过实现FactoryBean<T>接口实例化Bean
-->
<bean id="goodsDao" class="com.itheima.factory.GoodsDaoFactoryBean" />
2
3
4
- 在
com.itheima
包下创建AppForGoods
测试类
package com.itheima;
import com.itheima.dao.GoodsDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForGoods {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
GoodsDao goodsDao = (GoodsDao) ctx.getBean("goodsDao");
goodsDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
运行结果:
# Bean的生命周期
在Bean
的实例化章节我们已经讲解过Bean
生命周期相关的步骤,下面我们就来详细学习下如何在Bean
的生命周期当中嵌入我们的代码。
# 生命周期相关概念
首先,我们先了解一下相关概念。
- 生命周期:从创建到消亡的完整过程
Bean
生命周期:Bean
从创建到销毁的整体过程Bean
生命周期控制:在Bean
创建后到销毁前做一些事情
# 生命周期控制代码演示
在Spring中对Bean
生命周期进行控制,有两种方式:
- 通过配置applicationContext.xml配置文件
- 通过实现接口,初始化实现
InitializingBean
接口,重写afterPropertiesSet()
方法;销毁实现DisposableBean
接口,重写destroy()
方法
- 在
com.itheima.dao
包下创建BookDao
接口
package com.itheima.dao;
public interface BookDao {
void save();
}
2
3
4
5
- 在
com.itheima.dao.impl
包下创建BookDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save ...");
}
public void init() {
System.out.println("BookDaoImpl init 方法执行了 ...");
}
public void destroy() {
System.out.println("BookDaoImpl destroy 方法执行了 ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 在
com.itheima.service
包下创建BookService
接口
package com.itheima.service;
public interface BookService {
void save();
}
2
3
4
5
- 在
com.itheima.service.impl
包下创建BookServiceImpl
实现类,该类除实现BookService
接口外,还需实现InitializingBean
和DisposableBean
接口
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
@Override
public void save() {
System.out.println("BookServiceImpl save 方法执行了 ...");
bookDao.save();
}
@Override
public void destroy() throws Exception {
System.out.println("BookServiceImpl destroy 方法执行了 ...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BookServiceImpl afterPropertiesSet 方法执行了 ...");
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- 修改applicationContext.xml配置文件,添加两个
bean
标签
<!--
Bean生命周期控制的两种方式
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy" />
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao" />
</bean>
2
3
4
5
6
7
- 在
com.itheima
包下创建AppForBook
测试类
package com.itheima;
import com.itheima.service.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForBook {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
ctx.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
运行结果:
# Bean销毁时机
- Spring IOC容器关闭前触发
Bean
的销毁 - 关闭Spring IOC容器的方式有:
- 手动关闭:
ConfigurableApplicationContext
中的close()
方法 - 注册关闭钩子:作用是在JVM退出前先关闭容器再退出虚拟机,通过调用
ConfigurableApplicationContext
中的registerShutdownHook()
方法实现
- 手动关闭:
所以上面的AppForBook
代码也可以改成下面的形式
package com.itheima;
import com.itheima.service.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForBook {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
// 1. 手动关闭
// ctx.close();
// 2. 注册关闭钩子
ctx.registerShutdownHook();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 依赖注入
上面Spring核心概念章节,我们简单介绍了依赖注入的概念:在容器中建立Bean
与Bean
之间依赖关系的过程。下面
我们来通过几个问题来详细了解一下依赖注入的概念及作用。
- 第一个问题:什么是依赖?
每个软件,都是由很多组件构成的,这里的组件是指广义的组件--组成软件的部件,它可能是函数、类、包、也可能是微服务。 软件的架构,就是组件和组件之间的关系,而这些组件之间的关系,就是广义的依赖关系。
- 第二个问题:依赖有多重要?
软件的维护工作,本质上都是有变化引起的,只要软件还活着,我们就无法对抗变化,只能顺应它,而组件之间的依赖关系决定了变化的传导范围。
一般来说,当被依赖的组件变化时,其依赖者也会随之变化,但是软件开发最怕的就是牵一发而动全身。所幸,并不是每次变化都必然会传导给它的 依赖者们。
对于组件具体实现细节的修改,只要没有改变其外部契约(可简单理解为接口),其依赖者就不需要修改。那么对于更大规模的修改, 比如各式各样的收费系统中要更换计费策略,我们是不是就无法控制变化的传播了?也不见得,只要我们的设计能让依赖者和被依赖者两者之间的接口 保持一致,就可以把变化控制在尽可能小的范围内。
- 第三个问题:什么是依赖注入?
Java开发中,开发者在某个类中如果需要使用其他类的方法,通常是new
一个类实例对象再调用其方法,这种开发存在的问题是new
的类实例对象不好统一管理,Spring因此就提出了依赖注入的思想,即被依赖的类不再由开发者实例化,而是通过Spring容器new
指定实例并将实例对象注入到需要该对象的类中。依赖注入的另外一种说法是控制反转。
- 第四个问题:为什么要用依赖注入来管理依赖?
让我们来再次回顾下JavaWeb阶段综合案例中的代码:
通过分析上面的代码我们可以知道:BrandServlet
依赖BrandServiceImpl
,如果现在要替换BrandServiceImpl
中分页功能的实现,
但是又不想修改BrandServiceImpl
类,那该如何做呢?我们应该是再创建一个类,比如BrandServiceImpl2
,
该类实现BrandService
接口,然后修改BrandServlet
中的new BrandServiceImpl()
为new BrandServiceImpl2()
,相信这样的思路大家
都可以想到。但是,这样业务层的修改就传递给了Web层,而在软件架构设计当中,我们要尽可能的避免这样的传递,而使用依赖注入就可以外部配置
来减少这样的传递。
# 依赖注入方式
依赖注入从技术实现上来说,有两种方式:
setter
方法注入,可以注入如下两种类型- 简单类型
- 引用类型(常用,重点掌握)
- 构造方法注入,也有两种类型
- 简单类型
- 引用类型
# setter
方法注入代码演示
- 引用类型
参见DI入门案例实现代码。
简单类型,按照如下步骤修改DI入门案例实现代码:
- 修改
com.itheima.service.impl
包下的BrandServiceImpl
类,添加一简单类型引用
package com.itheima.service.impl; import com.itheima.dao.BrandDao; import com.itheima.service.BrandService; /** * Brand业务层实现类 */ public class BrandServiceImpl implements BrandService { // 1. 第一步:删除 new BrandDaoImpl();代码 private BrandDao brandDao; // setter方法注入简单类型 private int number; @Override public void save() { System.out.println("Brand Service Impl Save ..."); System.out.println("Brand Service Impl number: " + this.number); brandDao.save(); } // 2. 第二步:提供brandDao对应的setter方法 public void setBrandDao(BrandDao brandDao) { this.brandDao = brandDao; } // setter方法注入简单类型 public void setNumber(int number) { this.number = number; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33- 修改applicationContext.xml文件中
brandService
的bean
定义
<bean id="brandService" class="com.itheima.service.impl.BrandServiceImpl"> <!-- 配置Service与Dao的关系 property标签:表示配置当前bean的属性 name属性:表示配置哪一个具体的属性名 ref属性:表示引用哪一个bean --> <property name="brandDao" ref="brandDao" /> <property name="number" value="12345789" /> </bean>
1
2
3
4
5
6
7
8
9
10- 运行
App
类
package com.itheima; import com.itheima.service.BrandService; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Spring IOC 快速入门 */ public class App { public static void main(String[] args) { // 1. 创建IOC容器对象,加载Spring核心配置文件 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 从IOC容器中获取Bean对象(BrandService对象) BrandService brandService = (BrandService) ctx.getBean("brandService"); // 3. 调用Bean对象(BrandService对象)的方法 brandService.save(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- 运行结果
- 修改
# 构造方法注入代码演示
我们依旧采用修改DI快速入门代码来实现构造方法注入:
- 修改
com.itheima.service.impl
包下的BrandServiceImpl
类,注释setter
方法,添加两个参数的构造方法,主要改动了这两个地方
package com.itheima.service.impl;
import com.itheima.dao.BrandDao;
import com.itheima.service.BrandService;
/**
* Brand业务层实现类
*/
public class BrandServiceImpl implements BrandService {
// 构造方法注入引用类型
private BrandDao brandDao;
// 构造方法注入简单类型
private int number;
public BrandServiceImpl(BrandDao brandDao, int number) {
this.brandDao = brandDao;
this.number = number;
}
@Override
public void save() {
System.out.println("Brand Service Impl Save ...");
System.out.println("Brand Service Impl number: " + this.number);
brandDao.save();
}
// 2. 第二步:提供brandDao对应的setter方法
// public void setBrandDao(BrandDao brandDao) {
// this.brandDao = brandDao;
// }
// setter方法注入简单类型
// public void setNumber(int number) {
// this.number = number;
// }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 修改applicationContext.xml文件中
brandService
的bean
定义内容
<bean id="brandService" class="com.itheima.service.impl.BrandServiceImpl">
<!-- 构造方法注入引用类型 -->
<constructor-arg name="brandDao" ref="brandDao" />
<!-- 构造方法注入简单类型 -->
<constructor-arg name="number" value="987654321" />
</bean>
2
3
4
5
6
- 运行
App
类,输出如下结果,说明注入成功
比较
setter
注入和构造注入,这两种注入方式没有绝对的好坏,只是使用的场景不一样。
相比而言setter
注入有以下优点:
- 与传统的JavaBean写法类似,开发者更容易理解、接受,通过
setter
方法设定依赖关系显得更直观、自然; - 对于复杂的依赖关系,如果采用构造方法注入,会导致构造方法过于臃肿、难于阅读,Spring在创建
Bean
实例时,需要同时实例化其依赖的全部实例,会导致性能下降; - 在某些属性是否必须注入可以选择的情况下,多参数的够着方法注入更加笨重。
某些情况下,构造方法注入也有一定的优势:
- 构造注入可以在构造方法中决定依赖关系的注入顺序,优先依赖的优先注入,例如,组件中其他依赖关系的注入,常常需要依赖于
Datasource
的注入,就可采用构造方法注入; - 对于依赖关系无需变化的
Bean
,构造注入更加有用,所有依赖关系全部都在构造方法中设定,无需担心后续代码对依赖关系的破坏; - 依赖关系只能在构造方法中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚原则。
建议:采用setter
注入为主,构造方法注入为辅的策略。对于依赖关系无需变化的尽量使用构造方法注入,而其他依赖关系的注入,考虑setter
方法注入。
# 依赖自动装配(了解)
自动装配是Spring满足bean
依赖的一种方式。在使用Spring配置bean
时,我们都要给配置的bean
的属性设置一个值,如果不手动设置则是空,
而自动装配的好处就在于,我们不用手动去设置这个值,Spring会在上下文中自动寻找并装配合适的值。
Spring IOC容器根据bean
所依赖的资源在容器中自动查找并注入到bean
中的过程称为自动装配。
自动装配有如下几种方式:
- 按类型(常用)
- 按名称
- 按构造方法
依赖自动装配可以在applicationContext.xml文件中使用bean
标签的autowire
属性设置自动装配的类型。
<!--
自动装配,通过autowire设置
byType: 按类型
byName: 按名称
constructor: 按构造方法
no: 不启用自动装配
-->
<bean id="person" class="com.itheima.pojo.Person" autowire="byName" />
2
3
4
5
6
7
8
# 手动装配演示
- 创建
com.itheima.pojo
包,并在该包下创建三个类Dog
、Cat
、Person
Dog.java:
package com.itheima.pojo;
public class Dog {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Cat.java:
package com.itheima.pojo;
public class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Person.java:
package com.itheima.pojo;
/**
* 自动装配
*/
public class Person {
private Dog dog;
private Cat cat;
public void sout() {
System.out.println("我有一个宠物狗, 名字叫:" + this.dog.getName());
System.out.println("我有一个宠物猫,名字叫:" + this.cat.getName());
}
public void setDog(Dog dog) {
this.dog = dog;
}
public void setCat(Cat cat) {
this.cat = cat;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在applicationContext.xml文件中添加配置
<bean id="dog" class="com.itheima.pojo.Dog">
<property name="name" value="小黑" />
</bean>
<bean id="cat" class="com.itheima.pojo.Cat">
<property name="name" value="小白" />
</bean>
<!--手动装配-->
<bean id="person" class="com.itheima.pojo.Person">
<property name="dog" ref="dog" />
<property name="cat" ref="cat" />
</bean>
2
3
4
5
6
7
8
9
10
11
- 在
com.itheima
包下创建AppForPerson
测试类
package com.itheima;
import com.itheima.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForPerson {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = ctx.getBean("person", Person.class);
person.sout();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 运行测试类,查看结果
# byName
自动装配演示
- 修改applicationContext.xml文件中
person
的bean
标签定义
<bean id="dog" class="com.itheima.pojo.Dog">
<property name="name" value="小黑" />
</bean>
<bean id="cat" class="com.itheima.pojo.Cat">
<property name="name" value="小白" />
</bean>
<!--
自动装配,通过autowire设置
byType: 按类型
byName: 按名称
constructor: 按构造方法
no: 不启用自动装配
-->
<bean id="person" class="com.itheima.pojo.Person" autowire="byName" />
2
3
4
5
6
7
8
9
10
11
12
13
14
- 运行
AppForPerson
测试类,查看运行结果
注意事项
byName
自动装配通过匹配bean
的id
是否根setter
方法对应,对应则自动装配:
如,applicationContext.xml中Dog
类bean
的id
为dog
,则在Person
类中必须有一个setDog(Dog dog)
方法,否则就会报错。
我们将Person
类中的setDog
方法改为setDog1
来看下效果
# byType
自动装配演示
byType
通过匹配bean
中所需要的依赖类型在容器上下文中自动寻找装配,上面通过byName
方式由于setDog1
和bean
的id
不匹配导致自动装配失败了,
那么用byType
可以装配成功吗?我们来验证一下,稍微改动一下applicationContext.xml文件,将person
的自动装配方式修改为byType
:
<bean id="dog" class="com.itheima.pojo.Dog">
<property name="name" value="小黑" />
</bean>
<bean id="cat" class="com.itheima.pojo.Cat">
<property name="name" value="小白" />
</bean>
<!--
自动装配,通过autowire设置
byType: 按类型
byName: 按名称
constructor: 按构造方法
no: 不启用自动装配
-->
<bean id="person" class="com.itheima.pojo.Person" autowire="byType"
2
3
4
5
6
7
8
9
10
11
12
13
14
再次运行AppForPerson
,输出结果如下:
问题:byType
方式的自动装配和bean
标签的id
属性没有关系吗?是的,还真就没有关系,甚至在配置cat
和dog
这两个bean
时可以不写id
都不会报错。
但是byType
的自动装配存在一个严重的问题,因为不是通过唯一的id
来匹配,而是通过类型来匹配,所以容器中不能存在多个相同类型的bean
。
小结
byName
和byType
也不能说孰强孰弱,它们各有优劣:
byName
需要保证bean
的id
唯一,且这个bean
需要自动注入的属性的setter
方法要与bean
的id
保持一致byType
需要保证bean
的class
唯一,且这个bean
需要自动注入的属性和类型要保持一致
模式 | 说明 |
---|---|
no | 默认,不启用自动装配,Bean 的引用必须用ref 属性定义,对于大型软件的部署,建议不要修改默认设置,明确指定协作者可以提供更好的控制和清晰度 |
byName | 按属性名自动装配,Spring查找需要自动装配的与属性同名的bean |
byType | 乳沟容器中恰好存在一个该属性类型的bean ,则该属性可自动装配,但如果存在多个会引发致命错误 |
constructor | 类似与byType ,但适用与构造函数参数,如果容器中不存在构造函数参数类型的一个bean ,会引发致命错误 |
# 集合注入
上面我们重点介绍了引用类型、简单类型的注入,实际上还有一种比较特殊的引用类型,就是集合,下面我们通过演示代码来简单了解一下它们:
- 修改
com.itheima.pojo
包下的Person
类
package com.itheima.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* 自动装配
*/
public class Person {
private Dog dog;
private Cat cat;
private String[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void sout() {
System.out.println("我有一个宠物狗, 名字叫:" + this.dog.getName());
System.out.println("我有一个宠物猫,名字叫:" + this.cat.getName());
System.out.println("注入数组数据");
for (String s : this.array) {
System.out.println(s);
}
System.out.println("注入List数据");
for (String s : this.list) {
System.out.println(s);
}
System.out.println("注入Set数据");
for (String s : this.set) {
System.out.println(s);
}
System.out.println("注入Map数据");
Set<String> keys = this.map.keySet();
for (String key : keys) {
System.out.println(key + " : " + this.map.get(key));
}
System.out.println("注入Properties数据");
Set<Object> objects = this.properties.keySet();
for (Object o : objects) {
Object value = this.properties.get(o);
System.out.println(o + " : " + value);
}
}
public void setDog1(Dog dog) {
this.dog = dog;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public void setArray(String[] 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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- 修改applicationContext.xml文件中的配置
<bean id="person" class="com.itheima.pojo.Person" autowire="byType">
<!--注入数组数据-->
<property name="array">
<array>
<value>item1</value>
<value>item2</value>
<value>item3</value>
</array>
</property>
<!--注入List集合数据-->
<property name="list">
<list>
<value>element1</value>
<value>element2</value>
<value>element3</value>
<value>element4</value>
</list>
</property>
<!--注入Set集合数据-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
</set>
</property>
<!--注入Map集合数据-->
<property name="map">
<map>
<entry key="country" value="china" />
<entry key="province" value="henan" />
<entry key="city" value="luoyang" />
</map>
</property>
<!-- 注入Properties数据 -->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="city">北京</prop>
</props>
</property>
</bean>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- 运行
AppForPerson
测试类,查看结果