导航:
【黑马Java笔记+踩坑汇总】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城
目录
1,Spring背景
1.1 Spring地位
1.2 Spring优势和学习内容
2,Spring相关概念
2.1 初识Spring
2.1.1 Spring家族
2.1.2 Spring发展史
2.2 Spring系统架构
2.2.1 系统架构图
2.2.2 学习内容
2.3 Spring核心概念
2.3.1 不使用Spring项目中的问题
2.3.2 IOC、IOC容器、Bean、DI
2.3.3 核心概念小结
3,入门案例(配置开发版)
3.1 IOC入门案例
3.1.1 入门案例思路分析
3.1.2 入门案例代码实现
3.2 DI入门案例,通过配置除掉service里的new
3.2.1 入门案例思路分析
3.2.2 入门案例代码实现
4,IOC相关内容
4.1 bean基础配置
4.10 概述
4.1.1 bean基础配置(id与class)
4.1.2 bean的name属性
4.1.3 bean作用范围scope配置,单例和非单例
4.2 bean的三种创建方式
4.2.1 环境准备
4.2.2 bean的创建方法:1:无参构造方法实例化(常用)
4.2.3 Spring的报错信息从下往上看
4.2.4 bean的创建方式2:静态工厂实例化(了解)
4.2.5 bean的创建方式3:实例工厂(了解)与FactoryBean(常用)
4.2.6 bean实例化小结
4.3 bean的生命周期
4.3.1 环境准备
4.3.2 生命周期控制
4.3.3 关闭IOC容器方法:close关闭容器、注册钩子关闭容器
4.3.4 InitializingBean, DisposableBean控制生命周期
4.3.5 bean生命周期小结
5,DI相关内容
5.1 setter注入
5.1.1 环境准备
5.1.2 注入多个引用数据类型(property标签的name和ref)
5.1.3 注入简单数据类型(property标签的name和value)
5.2 构造器注入
5.2.1 环境准备
5.2.2 构造器注入引用数据类型(constructor-arg标签的name和ref)
5.2.3 构造器注入多个引用数据类型
5.2.4 构造器注入多个简单数据类型(constructor-arg标签的name和value)
5.2.5 依赖注入方式选择
5.3 自动配置依赖注入
5.3.1 什么是依赖自动装配?
5.3.2 自动装配方式有哪些?
5.3.3 准备下案例环境
5.3.4 完成自动装配的配置
5.4 数组、集合注入
5.4.1 环境准备
5.4.2 注入数组类型数据
5.4.3 注入List类型数据
5.4.4 注入Set类型数据
5.4.5 注入Map类型数据
5.4.6 注入Properties类型数据
从使用和占有率看
Spring在市场的占有率与使用率高,未使用Spring的项目一般都是些比较老的项目,大多都处于维护阶段。
Spring在企业的技术选型命中率高,Spring是微服务架构基础,是传统开发主流技术。所以说,Spring技术是JavaEE开发必备技能
Spring框架主要的优势是在简化开发
和框架整合
上:
简化开发: Spring两大核心技术IOC、AOP。
框架整合: Spring可以整合市面上几乎所有主流框架,比如:
学习内容:
官网:https://spring.io
Projects
中查看其包含的所有技术。Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。
Spring已形成了完整的生态圈。我们可以使用Spring技术完成整个项目的构建、设计与开发。
Spring有若干个项目。可以根据需要自行选择,把这些个项目组合起来,起了一个名称叫全家桶,如下图所示
说明:
图中的图标都代表什么含义,可以进入https://spring.io/projects
网站进行对比查看。
这些技术并不是所有的都需要学习,额外需要重点关注Spring Framework
、SpringBoot
和SpringCloud
:
除了上面的这三个技术外,还有很多其他的技术,也比较流行,如SpringData,SpringSecurity等,这些都可以被应用在我们的项目中。
我们今天所学习的Spring其实指的是Spring Framework。
接下来我们介绍下Spring Framework这个技术是如何来的呢?
Spring发展史
Expert One-on-One J2EE Design and Development
,书中有阐述在开发中使用EJB该如何做。Expert One-on-One J2EE Development without EJB
,书中提出了比EJB思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是Spring1.0。本节介绍了Spring家族与Spring的发展史,需要大家重点掌握的是:
spring框架主要指的是Spring Framework。
Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基。Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整。
Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的是Spring4的架构图
系统架构讲究上层依赖下层,所以越下层越核心,最下层的是核心容器。
(1)核心层
(2)AOP层
(3)数据层
(4)Web层
(5)Test层
主要包含四部分内容:
在Spring核心概念这部分内容中主要包含IOC/DI
、IOC容器
和Bean
,那么问题就来了,这些都是什么呢?
回顾:
在上一节整合Javaweb开发商品管理系统写业务层的时候,方案是写BrandService接口,然后BrandServiceImp1实现BrandService接口,在Servlet创建service对象时候是
BrandService service=new BrandServiceImp1;
JavaWeb基础10——VUE&Element&整合Javaweb的商品管理系统_vincewm的博客-CSDN博客
当时优化时说使用接口实现类方法比直接业务类更好,能降低耦合度。在更新业务层实现方法时候,Servlet里就只需要修改new后的实现类名,甚至在使用Spring后可以不改Servlet代码。
问题: 耦合度偏高
(1)业务层需要调用数据层的方法,就需要在业务层new数据层的对象
(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
(3)所以,现在代码在编写的过程中存在的问题是:耦合度偏高
解决方案:不要主动使用new产生对象,转换为由外部提供对象。即Ioc控制反转思想。
把框中的内容给去掉,就可以降低依赖了,同时为了使程序可以运行,Spring就提出了一个解决方案:
1.IOC(Inversion of Control)控制反转
inversion译为反转,相反。
(1)控制反转:
new
的别人[外部]
来创建对象别人[外部]
就反转控制了数据层对象的创建权(2)Spring和IOC之间的关系是什么呢?
别人[外部]
(3)IOC容器的作用以及内部存放的是什么?
作用:
内部存放:
(4)当IOC容器中创建好service和dao对象后,还需要DI依赖注入
Dao(Data Access Object):数据访问对象
像这种在容器中建立对象与对象之间的绑定关系就要用到DI依赖注入。
2.DI(Dependency Injection)依赖注入
(1)依赖注入
new
的,现在靠IOC容器
注入进来(2)IOC容器中哪些bean之间要建立依赖关系?
Spring的IOC和DI这两个概念的最终目标就是:充分解耦,具体实现方式:
这节比较重要,重点要理解什么是IOC/DI思想
、什么是IOC容器
和什么是Bean
:
(1)什么IOC/DI思想?
(2)什么是IOC容器?
Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器
(3)什么是Bean?
容器中所存放的一个个对象就叫Bean或Bean对象
了解即可,以后更多用的是注解开发。
介绍完Spring的核心概念后,接下来我们得思考一个问题就是,Spring到底是如何来实现IOC和DI的,那接下来就通过一些简单的入门案例,来演示下具体实现过程:
IOC创建bean有四种方式:无参构造方法实例化(常用)、静态工厂实例化(了解)、实例工厂(了解)、FactoryBean(常用)。
这里入门案例使用的是无参构造方法实例化(常用)。
(1)Spring是使用容器来管理bean对象的,bean对象有哪些?
(2)如何将被管理的对象告知IOC容器?
(3)如何获取到IOC容器?
// ClassPathXmlApplicationContext了解即可,有new后面会被优化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
(4)如何从容器中获取bean?
(5)使用Spring需要导入哪些坐标?
BookDao bookDao = (BookDao) applicationContext.getBean("bookDao");
bookDao.save();
org.springframework
spring-context
5.3.22
需求分析:
将BookServiceImpl和BookDaoImpl交给Spring管理,并从容器中获取对应的bean对象进行方法调用。
步骤:
1.创建Maven的java项目
2.pom.xml添加Spring的依赖jar包
3.创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
4.resources下添加spring配置文件,并完成bean的配置
5.使用Spring提供的接口完成IOC容器的创建
6.从容器中获取对象进行方法调用
步骤1:创建Maven项目
步骤2:添加Spring的依赖jar包
pom.xml导入Spring和junit依赖
org.springframework
spring-context
5.2.10.RELEASE
junit
junit
4.12
test
步骤3:创建dao和service
创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
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();
}
}
步骤4:添加spring配置文件
resources下添加spring配置文件applicationContext.xml。
applicationContext译为配置文件、应用上下文,是约定俗成的Spring配置文件命名规范。
创建完上面有提示让配置应用程序上下文,点击确定即可:
步骤5:在配置文件中完成bean的配置
bean标签:配置bean
id属性:给bean起名字,唯一标识
class属性:给bean定义类型
注意事项:bean定义时id是bean的未知标识,属性在同一个上下文中(配置文件)不能重复
步骤6:获取IOC容器
使用Spring提供的ApplicationContext 接口和ClassPathXmlApplicationContext实现类完成IOC容器的创建,创建App类,编写main方法
ClassPathXmlApplicationContext实现类了解即可,用到new耦合度高,以后会被优化。
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
步骤7:从容器中获取bean并调用bean的方法
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是前面定义的bean的id,这里是根据id获取ioc容器里的bean
bookService.save();
}
}
步骤8:运行程序
测试结果为:
Spring的IOC入门案例已经完成。但是在BookServiceImpl
的类中依然存在BookDaoImpl
对象的new操作,它们之间的耦合度还是比较高,这就需要用到下面的DI:依赖注入
。
Spring提供了两种注入方式,分别是:
这里入门案例使用的是引用类型setter注入。
(1)要想实现依赖注入,必须要基于IOC管理Bean
(2)Service中使用new形式创建的Dao对象是否保留?
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
(3)Service中需要的Dao对象如何进入到Service中?
(4)Service与Dao间的关系如何描述?
需求:
基于IOC入门案例,在BookServiceImpl类中删除new对象的方式,使用Spring的DI完成Dao层的注入。
步骤:
1.删除业务层中使用new的方式创建的dao对象
2.在业务层提供BookDao的setter方法
3.在配置文件中添加依赖注入的配置
4.运行程序调用方法
步骤1: 去除业务实现类中的new,仅留下定义数据访问对象dao引用
在业务层BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤2:为业务实现类属性提供setter方法
在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
步骤3:修改配置完成property属性注入
是service里调用dao,所以要在配置文件service的bean中配置service和dao的关系
注意:property标签中的两个bookDao的含义是不一样的
name="bookDao"中
bookDao
:表示配置哪一个具体的属性。作用:让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的
setBookDao()
方法进行对象注入ref="bookDao"中
bookDao
:表示参照哪一个id的bean。作用:让Spring能在IOC容器中找到id为
bookDao
的Bean对象给bookService
进行注入综上所述,对应关系如下:
步骤4:运行程序
运行,测试结果为:
IOC容器主要是创建和管理类对象bean的,这一章节主要讲bean的配置、实例化、生命周期。
关于bean的基础配置中,需要大家掌握以下属性:
对于bean的基础配置,在前面的案例中已经使用过:
其中,bean标签的功能、使用方式以及id和class属性的作用,我们通过一张图来描述下
需要重点掌握的是:bean标签的id和class属性的使用。
思考:
- class属性能不能写接口如
BookDao
的类全名呢?答案肯定是不行,因为接口是没办法创建对象的。
- 前面提过为bean设置id时,id必须唯一,但是如果由于命名习惯而产生了分歧后,该如何解决?
通过bean的name属性起别名。
首先来看下别名的配置说明:
步骤0:重新搭建和前面一样的的案例:
步骤1:配置别名
打开spring的配置文件applicationContext.xml
说明:service起名bookEbi。Ebi全称Enterprise Business Interface,翻译为企业业务接口
步骤2:根据name在容器中获取bean对象
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:运行程序
测试结果为:
注意事项:
singleton译为单例,单独的人、物。
prototype译为非单例,原型、雏形
默认情况下,Spring创建的bean对象都是单例的。
单例避免了对象的频繁创建与销毁,达到了bean对象的复用。
验证IOC容器中对象是否为单例
单例singleton: 下面两个引用指向同一个实例化的bean对象
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao"); BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
验证思路
同一个bean获取两次,将对象打印到控制台,看打印出的地址值是否一致。
具体实现
创建一个AppForScope的类,在其main方法中来验证
public class AppForScope { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao1 = (BookDao) ctx.getBean("bookDao"); BookDao bookDao2 = (BookDao) ctx.getBean("bookDao"); System.out.println(bookDao1); System.out.println(bookDao2); } }
结论:默认情况下,Spring创建的bean对象都是单例的
获取到结论后,问题就来了,那如果我想创建出来非单例的bean对象,该如何实现呢?
配置bean为非单例
将scope设置为prototype
运行AppForScope,打印看结果
结论,使用bean的scope
属性可以控制bean的创建是否为单例:
singleton
默认为单例prototype
为非单例思考
- 为什么bean默认为单例?
- bean为单例的意思是在Spring的IOC容器中只会有该类的一个对象
- bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
- bean在容器中是单例的,会不会产生线程安全问题?
- 如果对象是有状态对象(该对象有成员变量可以用来存储数据的)
- 因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
- 如果对象是无状态对象(该对象没有成员变量没有进行数据存储的,不建议存在IOC容器中)
- 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 哪些bean对象适合交给容器进行管理?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些bean对象不适合交给容器进行管理?
- 封装实例的域对象,因为会引发线程安全问题,所以不适合。
- JSP四大域对象
1.page域对象(只在当前页面中有效)
2.request域对象(只在一次请求中有效,服务端跳转有效,客户端跳转无效)
3.session域对象(在一次会话中有效,服务端客户端跳转都有效)
4.application域对象(在整个应用程序中都有效)
域对象:可以在不同的Servlet中进行数据传递的对象就称为域对象。
域对象必须有的方法:
setAttribute(name,value);存储数据的方法 getAttribute(name);根据name获取对应数据值 removeAttribute(name);删除数据
思考:
容器是如何来创建对象的呢?
bean本质上就是对象,对象在new的时候会使用构造方法完成,那创建bean也是使用构造方法完成的。
在这块内容中主要解决两部分内容,分别是
构造方法
,静态工厂
和实例工厂
重新准备个开发环境,
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
Spring底层是通过反射的无参构造方法创建类的对象bean。
步骤1:准备需要被创建的类
准备一个BookDao和BookDaoImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
步骤2:将类配置到Spring容器
步骤3:编写运行程序
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
步骤4:DAO类中提供无参构造函数测试,证明IOC容器创建对象通过无参构造方法
在BookDaoImpl类中添加一个无参构造函数,并打印一句话,方便观察结果。
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
运行程序,如果控制台有打印构造函数中的输出,说明Spring容器在创建对象的时候也走的是构造函数
步骤5:将构造函数改成私有,证明IOC容器通过反射调用无参构造方法
public class BookDaoImpl implements BookDao {
private BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见Spring底层用的是反射
步骤6:构造函数中添加一个参数,证明IOC通过无参数的构造方法创建bean
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底层使用的是类的无参构造方法。
()
引起
,即出现错误的原因没有这样的方法异常
():哪个类的哪个方法没有被找到导致的异常,
()指定是类的无参构造方法如果最后一行错误获取不到错误信息,接下来查看第二层:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
()
引发
bean实例化异常
看到这其实错误已经比较明显,给大家个练习,把倒数第三层的错误分析下吧:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bookDao' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
()。
至此,关于Spring的构造方法实例化就已经学习完了,因为每一个类默认都会提供一个无参构造函数,所以其实真正在使用这种方式的时候,我们什么也不需要做。这也是我们以后比较常用的一种方式。
静态工厂一般在service层里使用,用于获取dao对象。
了解为主,这种方式一般是用来兼容早期的一些老系统,所以了解为主。
回顾,工厂方式创建对象
以前最常接触到的工厂类是封装SqlSessionFactory工具类,提高代码复用性、通过静态代码块避免工厂类资源浪费。
public class SqlSessionFactoryUtils { private static SqlSessionFactory sqlSessionFactory; static { //静态代码块会随着类的加载而自动执行,且只执行一次 try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; } }
SqlSessionFactory sqlSessionFactory=SqlSessionFactoryUtils.getSqlSessionFactory();
工厂方式创建bean
(1)准备一个OrderDao和OrderDaoImpl类
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
(2)创建一个工厂类OrderDaoFactory并提供一个静态方法创建对象
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
(3)编写AppForInstanceOrder运行类,在类中通过工厂获取对象
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
(4)运行后,可以查看到结果
Spring静态工厂实例化
具体实现步骤为:
(1)在spring的配置文件application.properties中配置工厂类的bean
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名
对应关系如下图:
(2)在AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//直接获取dao对象。获取方式是调用配置的factory里的静态方法getOrderDao
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
(3)运行后,可以查看到结果
出现问题:工厂类中也是直接new对象的,和自己直接new的区别
主要的原因是:
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}
}
这种方式一般是用来兼容早期的一些老系统,所以了解为主。
环境准备
(1)准备一个UserDao和UserDaoImpl类
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
(2)创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
(3)编写AppForInstanceUser运行类,在类中通过工厂获取对象
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();
}
(4)运行后,可以查看到结果
对于上面这种实例工厂的方式如何交给Spring管理呢?
实例工厂实例化方法A
了解即可,更多用下面方法B,FactoryBean
(1)在spring的配置文件中添加以下内容:
实例化工厂运行的顺序是:
factory-mehod:具体工厂类中创建对象的方法名
(2)在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
(3)运行后,可以查看到结果
实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean
的方式来简化开发。
实例工厂实例化方法B:FactoryBean的使用
(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法。这两个方法会在创建ApplicationContext对象时候就先后调用。
public class UserDaoFactoryBean implements FactoryBean {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
//返回dao实现类的实例
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class> getObjectType() {
//返回dao接口的字节码
return UserDao.class;
}
}
(2)在Spring的配置文件中进行配置,注意class里UserDaoFactoryBean
(3)AppForInstanceUser运行类不用做任何修改,直接运行
注意:
在IOC容器创建时会直接创建其内所有bean对象,像下面代码,即使没有调用任何方法,运行后会输出getObject和getObjectType
public class BookDaoFactoryBean implements FactoryBean
{ @Override public BookDao getObject() throws Exception { System.out.println("getObject"); return new BookDaoImpl(); } @Override public Class> getObjectType() { System.out.println("getObjectType"); return BookDao.class; } } ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
FactoryBean接口的三个方法
查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
T getObject() throws Exception;
Class> getObjectType();
default boolean isSingleton() {
return true;
}
方法一:getObject(),被重写后,在方法中进行对象的创建并返回
方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象
方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?
思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) ctx.getBean("userDao");
UserDao userDao2 = (UserDao) ctx.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
}
}
打印结果,如下:
通过验证,会发现默认是单例。
FactoryBean设置单例、非单例:
只需要将isSingleton()方法进行重写,修改返回为false,即可
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class> getObjectType() {
return UserDao.class;
}
public boolean isSingleton() {
return false;
}
}
重新运行AppForInstanceUser,查看结果
从结果中可以看出现在已经是非单例了,但是一般情况下我们都会采用单例,也就是采用默认即可。所以isSingleton()方法一般不需要进行重写。
通过这一节的学习,需要掌握:
(1)bean是如何创建的呢?
构造方法
(2)Spring的IOC实例化对象的三种方式分别是:
这些方式中,重点掌握构造方法
和FactoryBean
即可。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。
思考:
为了方便大家后期代码的阅读,我们重新搭建下环境:
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加BookDao、BookDaoImpl、BookService和BookServiceImpl类
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 setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
(2)resources下提供spring的配置文件
(3)编写AppForLifeCycle运行类,加载Spring的IOC容器,并从中获取对应的bean对象
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
接下来,在上面这个环境中来为BookDao添加生命周期的控制方法,具体的控制有两个阶段:
步骤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:配置生命周期
在配置文件添加配置,如下:
步骤3:运行程序
运行AppForLifeCycle打印结果为:
从结果中可以看出,init方法执行了,但是destroy方法却未执行,这是为什么呢?
解决办法1:使用ClassPathXmlApplicationContext的close方法。
解决方法2:注册钩子关闭容器
另外还可以用Spring的两个接口InitializingBean
, DisposableBean
实现初始化和销毁,但想要销毁方法执行出来还得用上面两种解决办法。
了解即可,主要用4.3.4中的
InitializingBean
,DisposableBean
控制生命周期
close关闭容器
ApplicationContext中没有close方法
需要将ApplicationContext更换成ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
调用ctx的close()方法
ctx.close();
运行程序,就能执行destroy方法的内容
销毁方法二:注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
调用ClassPathXmlApplicationContext的registerShutdownHook()方法
ctx.registerShutdownHook();
注意:registerShutdownHook在ApplicationContext中也没有
运行后,查询打印结果
两种方式介绍完后,close和registerShutdownHook选哪个?
相同点:这两种都能用来关闭容器
不同点:
close()是在调用的时候强制关闭,如果后面还有IOC的相关代码会报错。
registerShutdownHook()是在JVM退出前调用关闭。
InitializingBean
, DisposableBean
控制生命周期Disposable译为用完即丢弃的,一次性的
Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-method
和destroy-method,坏处是
想要销毁方法执行出来还得用上面两种解决办法。
代码示例:
修改service实现类BookServiceImpl,添加两个接口InitializingBean
, DisposableBean
并实现接口中的两个方法afterPropertiesSet
和destroy
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 BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDao save...");
}
private void init() {
System.out.println("book dao init");
}
private void destroy() {
System.out.println("book dao destroy");
}
}
public class Demo {
public static void main(String[] args) {
// ClassPathXmlApplicationContext了解即可,有new后面会被优化
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("创建IOC容器后...");
BookDao bookDao=(BookDao)applicationContext.getBean("bookDao");
bookDao.save();
applicationContext.close();
}
}
重新运行AppForLifeCycle类,
思考:
dao先初始化、最后销毁的原因:
配置的初始化和销毁方法,要更严格,实现接口的方法都是创建和销毁中间完成的。
控制台出现service的原因:
创建IOC容器时会创建所有的bean对象,并加载这些对象的init和destroy方法。
就像前面4.2.5 FactoryBean那里,创建IOC容器时候会调用getObject和getObjectType方法。
小细节
对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后
。
对于BookServiceImpl来说,bookDao是它的一个属性,setBookDao方法是Spring的IOC容器为其注入属性的方法
思考:afterPropertiesSet和setBookDao谁先执行?
(1)关于Spring中对bean生命周期控制提供了两种方式:
init-method
和destroy-method
属性InitializingBean
与DisposableBean
接口,这种方式了解下即可。(2)对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
(3)关闭容器的两种方式:
前面我们已经完成了bean相关操作的讲解,接下来就进入第二个大的模块
DI依赖注入
,首先来介绍下Spring中有哪些注入方式?我们先来思考
- 向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
- 依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型(前面3.2快速入门方案,service里注入dao的引用)
- 简单类型(基本数据类型与String)
Spring提供了两种注入方式:
对于setter方式注入引用类型的方式之前已经学习过,快速回顾下:
在bean中定义引用类型属性,并提供可访问的set方法
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
为了更好的学习下面内容,我们依旧准备一个新环境:
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
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) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
(2)resources下提供spring的配置文件
(3)编写AppForDISet运行类,加载Spring的IOC容器,并从中获取对应的bean对象
public class AppForDISet {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
接下来,在上面这个环境中来完成setter注入的学习:
因为有ref,所以叫引用数据类型,引用值是被注入类里定义的私有注入类的对象。
需求:在bookServiceImpl对象中注入userDao
1.在BookServiceImpl中声明userDao属性
2.为userDao属性提供setter方法
3.在配置文件中使用property标签注入
步骤1:声明属性并提供setter方法
在BookServiceImpl中声明userDao属性,并提供setter方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}
步骤2:配置文件中进行注入配置
在applicationContext.xml配置文件中使用property标签注入
步骤3:运行程序
运行AppForDISet类,查看结果,说明userDao已经成功注入。
需求:
给BookDaoImpl注入一些简单数据类型的数据
参考引用数据类型的注入,我们可以推出具体的步骤为:
1.在BookDaoImpl类中声明对应的简单数据类型的属性
2.为这些属性提供对应的setter方法
3.在applicationContext.xml中配置
思考:
问题:引用类型使用的是
,简单数据类型还是使用ref么?
答案:不是ref,二手property标签的name和value。
问题:ref是指向Spring的IOC容器中的另一个bean对象的,对于简单数据类型,没有对应的bean对象,该如何配置?
答案:
步骤1:声明属性并提供setter方法
在BookDaoImpl类中声明对应的简单数据类型的属性,并提供对应的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);
}
}
步骤2:配置文件中进行注入配置
在applicationContext.xml配置文件中使用property标签注入
property标签里,name填的是bean类里定义的私有变量名,value填的是注入的值。
注意:
value后面跟的是简单数据类型,注意数据类型格式。
对于参数类型,Spring在注入的时候会自动转换,但是不能写成
这样的话,spring在将
abc
转换成int类型的时候就会报错。
步骤3:运行程序
运行AppForDISet类,查看结果,说明userDao已经成功注入。
注意:两个property注入标签的顺序可以任意。
对于setter注入方式的基本使用就已经介绍完了,
构造器注入也就是构造方法注入,学习之前,还是先准备下环境,和前面的都一致:
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(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) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
(2)resources下提供spring的配置文件
(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();
}
}
接下来,在上面这个环境中来完成构造器注入的学习:
需求:将BookServiceImpl类中的bookDao修改成使用构造器的方式注入。
1.将bookDao的setter方法删除掉
2.添加带有bookDao参数的构造方法
3.在applicationContext.xml中配置
步骤1:删除setter方法并提供构造方法
在BookServiceImpl类中将bookDao的setter方法删除掉,并添加带有bookDao参数的构造方法
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();
}
}
步骤2:配置文件中进行配置构造方式注入
在applicationContext.xml中配置,bean便签中的constructor-arg便签
说明:
arg是argument的缩写,译为参数、争论、理由。constructor-arg译为构造参数。
标签constructor-arg的属性
name属性:对应的值为构造函数中方法形参的参数名,不是私有成员变量名,必须要保持一致。
ref属性:指向的是spring的IOC容器中其他bean对象的id。
步骤3:运行程序
运行AppForDIConstructor类,查看结果,说明bookDao已经成功注入。
需求:在BookServiceImpl使用构造函数注入多个引用数据类型,比如userDao
1.声明userDao属性
2.生成一个带有bookDao和userDao参数的构造函数
3.在applicationContext.xml中配置注入
步骤1:提供多个属性的构造函数
在BookServiceImpl声明userDao并提供多个参数的构造函数
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中配置注入
说明:这两个
的配置顺序可以任意
步骤3:运行程序
运行AppForDIConstructor类,查看结果,说明userDao已经成功注入。
需求:在BookDaoImpl中,使用构造函数注入databaseName和connectionNum两个参数。
参考引用数据类型的注入,我们可以推出具体的步骤为:
1.提供一个包含这两个参数的构造方法
2.在applicationContext.xml中进行注入配置
步骤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中进行注入配置,bean标签内constructor-arg标签的属性name和value
说明:这两个
的配置顺序可以任意
步骤3:运行程序
运行AppForDIConstructor类,查看结果
存在问题:name属性的紧耦合
解决方案(有缺点,了解即可):
了解即可。参数名发生变化的情况并不多,并且解决方案有缺点类型限制或顺序限制。
方式一:删除name属性,添加type属性,按照类型注入
方式二:删除type属性,添加index属性,按照索引下标注入,下标从0开始
总结:建议用setter注入。
这节中主要讲解的是Spring的依赖注入的实现方式:
setter注入
简单数据类型
引用数据类型
构造器注入
简单数据类型
引用数据类型
依赖注入的方式选择上
前面花了大量的时间把Spring的注入去学习了下,很麻烦,可以使用自动配置优化。
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(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的配置文件
(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();
}
}
自动装配只需要修改applicationContext.xml配置文件即可:
(1)将dao的id删除,service的bean里
标签删除
(2)在service的
标签中添加autowire属性,autowire就译为自动装配
(3)要确保自动装配的类里的有setter方法。
按照类型注入的配置
注意事项:
- 需要注入属性的类中对应属性的setter方法不能省略
- 被注入的对象必须要被Spring的IOC容器管理
- 按类型注入必须保障容器中相同类型的bean唯一,否则会报
NoUniqueBeanDefinitionException
容器中相同类型的bean不唯一的情况:
按照名称注入:
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:
注意事项:
两种方式介绍完后,以后用的更多的是按照类型注入。
自动配置依赖注入的特点:
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合中既可以装简单数据类型也可以装引用数据类型。先来回顾下,常见的集合类型有哪些?
针对不同的集合类型,该如何实现注入呢?
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加添加BookDao、BookDaoImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private int[] array;
private List list;
private Set set;
private Map 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....方法省略,自己使用工具生成
}
(2)resources下提供spring的配置文件,applicationContext.xml
(3)编写AppForDICollection运行类,加载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();
}
}
接下来,在上面这个环境中来完成集合注入
的学习:
下面的所以配置方式,都是在bookDao的bean标签中使用进行注入
100
200
300
itcast
itheima
boxuegu
chuanzhihui
itcast
itheima
boxuegu
boxuegu
注意map最内标签不是value,是entry键值对。
china
henan
kaifeng
配置完成后,运行下看结果:
说明: