1.spring framework4的架构图
(1)核心层
-
Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
(2)AOP层
-
AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强==
-
Aspects:AOP是思想,Aspects是对AOP思想的具体实现
(3)数据层
-
Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
-
Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
-
Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
(4)Web层
-
这一层的内容将在SpringMVC框架具体学习
(5)Test层
-
Spring主要整合了Junit来完成单元测试和集成测试
2.IOC、Bean、DI
在学习spring之前我们要想获得某个对象中的属性、方法都需要通过创建对象的形式来获取,但是大量的创建对象就会对jvm堆内存以及运行效率造成一定影响,内存资源浪费,下面就是目前项目中遇到的问题
(1)业务层需要调用数据层的方法,就需要在业务层new数据层的对象
(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
(3)所以,现在代码在编写的过程中存在的问题是:==耦合度偏高==
针对这个问题,该如何解决呢?
如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?
答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?
针对这个问题,Spring就提出了一个解决方案:
-
使用对象时,在程序中不要主动使用new产生对象,转换为由==外部==提供对象
这种实现思就是Spring的一个核心概念
2.1.IOC、Bean、DI简述
-
==IOC(Inversion of Control)控制反转==
(1)什么是控制反转呢?
-
使用对象时,由主动new产生对象转换为由==外部==提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
-
业务层要用数据层的类对象,以前是自己
new
的 -
现在自己不new了,交给
别人[外部]
来创建对象 -
别人[外部]
就反转控制了数据层对象的创建权 -
这种思想就是控制反转
-
别人[外部]指定是什么呢?继续往下学
-
(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:
-
==DI(Dependency Injection)依赖注入==
(1)什么是依赖注入呢?
-
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
-
业务层要用数据层的类对象,以前是自己
new
的 -
现在自己不new了,靠
别人[外部其实指的就是IOC容器]
来给注入进来 -
这种思想就是依赖注入
-
(2)IOC容器中哪些bean之间要建立依赖关系呢?
-
这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建立依赖关系
介绍完Spring的IOC和DI的概念后,我们会发现这两个概念的最终目标就是:==充分解耦==,具体实现靠:
-
使用IOC容器管理bean(IOC)
-
在IOC容器内将有依赖关系的bean进行关系绑定(DI)
-
最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系.
2.2DI相关内容
注入方式: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>
集合:
注入数组类型数据
<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>
自动配置:
注意事项:
-
需要注入属性的类中对应属性的setter方法不能省略
-
被注入的对象必须要被Spring的IOC容器管理
-
按照类型在Spring的IOC容器中如果找到多个对象,会报
NoUniqueBeanDefinitionException
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:
<?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>
-
自动装配用于引用类型依赖注入,不能对简单类型进行操作
-
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
-
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
-
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
2.3 小结
这节比较重要,重点要理解什么是IOC/DI思想
、什么是IOC容器
和什么是Bean
:
(1)什么IOC/DI思想?
-
IOC:控制反转,控制反转的是对象的创建权
-
DI:依赖注入,绑定对象与对象之间的依赖关系
(2)什么是IOC容器?
Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器
(3)什么是Bean?
容器中所存放的一个个对象就叫Bean或Bean对象
3.bean的作用域
创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个
配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中
的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在
Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表
五种作用域中,request、session 和 global session 三种作用域仅在基于web的应用中使用(不必关
心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。
3.1singleton——唯一 bean 实例
当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所
有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。 singleton 是单例
类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他
都存在了,每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要
在XML中将 bean 定义成 singleton ,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
也可以通过 @Scope 注解(它可以显示指定bean的作用范围。)的方式
@Service
@Scope("singleton")
public class ServiceImpl{
}
3.2prototype——每次请求都会创建一个新的 bean 实例
当一个bean的作用域为 prototype,表示一个 bean 定义对应多个对象实例。prototype 作用域的
bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的
getBean() 方法)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候
并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是
同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使
用 singleton 作用域。** 在 XML 中将 bean 定义成 prototype ,可以这样配置:
3.3request——每一次HTTP请求都会产生一个新的bean,该bean 仅在当前HTTP request内有效
request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP
request内有效,当请求结束后,该对象的生命周期即告结束。 在 XML 中将 bean 定义成 prototype
,可以这样配置:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
3.4session——每一次HTTP请求都会产生一个新的 bean,该bean 仅在当前 HTTP session 内有效
session只适用于Web程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,
同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样,可以根据需要放心的更改所创建
实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定
于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内
的bean也会被废弃掉。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
3.5globalSession
global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才
有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的
portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围
内。
<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
简单总结:
(1)Singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
(2)Prototype:为每一个bean请求创建一个实例。
(3)Request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)Session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
(5)GlobalSession:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
4.bean的生命周期
Bean的生命周期流程如上图所示,流程大致如下:
1.首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化,
2.按照Bean定义信息配置信息,注入所有的属性,
3.如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,
此时该Bean就获得了自己在配置文件中的id,
4.如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的
BeanFactory,这样该Bean就获得了自己所在的BeanFactory,5.如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入
该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext,
6.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方
法,
7.如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法,
8.如果Bean配置了init-method方法,则会执行init-method配置的方法,
9.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方
法,
10.经过流程9之后,就可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓
存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,期生命周
期就交给调用方管理了,不再是Spring容器进行管理了
11.容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,
12.如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的
生命周期结束
Spring 中 Bean 的生命周期较复杂,可以表示为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。
Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
5.BeanFactory与FactoryBean的区别
BeanFactory是Spring容器的顶级接⼝,给具体的IOC容器的实现提供了规范。
FactoryBean也是接⼝,为IOC容器中Bean的实现提供了更加灵活的⽅式,FactoryBean在IOC容器的基
础上给Bean的实现加上了⼀个简单⼯⼚模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者
模式,Decoration) 我们可以在getObject()⽅法中灵活配置。其实在Spring源码中有很多FactoryBean
的实现类.
区别:BeanFactory是个Factory,也就是IOC容器或对象⼯⼚,FactoryBean是个Bean。在Spring中,
所有的Bean都是由BeanFactory(也就是IOC容器)来进⾏管理的。
但对FactoryBean⽽⾔,这个Bean不是简单的Bean,⽽是⼀个能⽣产或者修饰对象⽣成的⼯⼚Bean,
它的实现与设计模式中的⼯⼚模式和修饰器模式类似
5.1BeanFactory
BeanFactory,以Factory结尾,表示它是⼀个⼯⼚类(接⼝), 它负责⽣产和管理bean的⼀个⼯⼚。在
Spring中,BeanFactory是IOC容器的核⼼接⼝,它的职责包括:**实例化、定位、配置应⽤程序中的
对象及建⽴这些对象间的依赖。**
BeanFactory只是个接⼝,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如
DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是
常⽤的⼀个,该实现将以XML⽅式描述组成应⽤的对象及对象间的依赖关系。XmlBeanFactory类将持
有此XML配置元数据,并⽤它来构建⼀个完全可配置的系统或应⽤。
都是附加了某种功能的实现。它为其他具体的IOC容器提供了最基本的规范,例如
DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext 等具体的容器都是实现了
BeanFactory,再在其基础之上附加了其他的功能。
BeanFactory和ApplicationContext就是spring框架的两个IOC容器,现在⼀般使⽤
ApplicationnContext,其不但包含了BeanFactory的作⽤,同时还进⾏更多的扩展。
BeanFacotry是spring中⽐较原始的Factory。如XMLBeanFactory就是⼀种典型的BeanFactory。
原始的BeanFactory⽆法⽀持spring的许多插件,如AOP功能、Web应⽤等。ApplicationContext接⼝,
它由BeanFactory接⼝派⽣⽽来 ApplicationContext包含BeanFactory的所有功能,通常建议⽐BeanFactory优先
ApplicationContext以⼀种更向⾯向框架的⽅式⼯作以及对上下⽂进⾏分层和实现继承,
ApplicationContext包还提供了以下的功能:
1MessageSource, 提供国际化的消息访问
2资源访问,如URL和⽂件
3事件传播
4载⼊多个(有继承关系)上下⽂ ,使得每⼀个上下⽂都专注于⼀个特定的层次,⽐如应⽤的web
层;
BeanFactory提供的⽅法及其简单,仅提供了六种⽅法供客户调⽤:
1.boolean containsBean(String beanName) 判断⼯⼚中是否包含给定名称的bean定义,若有
则返回true
2.Object getBean(String) 返回给定名称注册的bean实例。根据bean的配置情况,如果是
singleton模式将返回⼀个共享实例,否则将返回⼀个新建的实例,如果没有找到指定bean,该⽅法
可能会抛出异常
3.Object getBean(String, Class) 返回以给定名称注册的bean实例,并转换为给定class类型
4.Class getType(String name) 返回给定名称的bean的Class,如果没有找到指定的bean实例,则排
除NoSuchBeanDefinitionException异常
5.boolean isSingleton(String) 判断给定名称的bean定义是否为单例模式
6.String[] getAliases(String name) 返回给定bean名称的所有别名
5.2FactoryBean
⼀般情况下,Spring通过反射机制利⽤ <bean> 的class属性指定实现类实例化Bean,在某些情况下,
实例化Bean过程⽐较复杂,如果按照传统的⽅式,则需要在 <bean> 中提供⼤量的配置信息。配置⽅
式的灵活性是受限的,这时采⽤编码的⽅式可能会得到⼀个简单的⽅案。
Spring为此提供了⼀个org.springframework.bean.factory.FactoryBean的⼯⼚类接⼝,⽤户可以通过
实现该接⼝定制实例化Bean的逻辑。FactoryBean接⼝对于Spring框架来说占⽤重要的地位,Spring⾃
身就提供了70多个FactoryBean的实现。它们隐藏了实例化⼀些复杂Bean的细节,给上层应⽤带来了
便利。从Spring3.0开始,FactoryBean开始⽀持泛型,即接⼝声明改为 FactoryBean<T> 的形式
以Bean结尾,表示它是⼀个Bean,不同于普通Bean的是:它是实现了 FactoryBean<T> 接⼝的
Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,⽽
不是FactoryBean本身,如果要获取FactoryBean对象,请在id前⾯加⼀个&符号来获取。
例如⾃⼰实现⼀个FactoryBean,功能:⽤来代理⼀个对象,对该对象的所有⽅法做⼀个拦截,在调⽤
前后都输出⼀⾏LOG,模仿ProxyFactoryBean的功能。
FactoryBean是⼀个接⼝,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String
BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,⽽是这个实现类中的getObject()⽅
法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上
&。
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
在该接⼝中还定义了以下3个⽅法:
TgetObject():返回由FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放
到Spring容器中单实例缓存池中;
booleanisSingleton():返回由FactoryBean创建的Bean实例的作⽤域是singleton还是
prototype;
ClassgetObjectType():返回FactoryBean创建的Bean类型。
当配置⽂件中 <bean> 的class属性配置的实现类是FactoryBean时,通过getBean()⽅法返回的不是
FactoryBean本身,⽽是FactoryBean#getObject()⽅法所返回的对象,相当于
FactoryBean#getObject()代理了getBean()⽅法。
总结
BeanFactory是个Factory,也就是IOC容器或对象⼯⼚,FactoryBean是个Bean。在Spring中,所有的
Bean都是由BeanFactory(也就是IOC容器)来进⾏管理的。但对FactoryBean⽽⾔,这个Bean不是简单
的Bean,⽽是⼀个能⽣产或者修饰对象⽣成的⼯⼚Bean,它的实现与设计模式中的⼯⼚模式和修饰器模
式类似
6.bean的实例化
6.1 构造方法实例化
在上述的环境下,我们来研究下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容器
<?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"/>
</beans>
步骤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:类中提供构造函数测试
在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:将构造函数改成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 ...");
}
}
运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见Spring底层用的是反射
步骤6:构造函数中添加一个参数测试
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底层使用的是类的无参构造方法。
6.1.1分析Spring的错误信息
接下来,我们主要研究下Spring的报错信息来学一学如阅读。
-
错误信息从下往上依次查看,因为上面的错误大都是对下面错误的一个包装,最核心错误是在最下面
-
Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
<init>
()-
Caused by 翻译为
引起
,即出现错误的原因 -
java.lang.NoSuchMethodException:抛出的异常为
没有这样的方法异常
-
com.itheima.dao.impl.BookDaoImpl.
<init>
():哪个类的哪个方法没有被找到导致的异常,<init>
()指定是类的构造方法,即该类的无参构造方法
-
如果最后一行错误获取不到错误信息,接下来查看第二层:
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.<init>
()
-
nested:嵌套的意思,后面的异常内容和最底层的异常是一致的
-
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;
-
Caused by:
引发
-
BeanInstantiationException:翻译为
bean实例化异常
-
No default constructor found:没有一个默认的构造函数被发现
-
看到这其实错误已经比较明显,给大家个练习,把倒数第三层的错误分析下吧:
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.<init>
()。
至此,关于Spring的构造方法实例化就已经学习完了,因为每一个类默认都会提供一个无参构造函数,所以其实真正在使用这种方式的时候,我们什么也不需要做。这也是我们以后比较常用的一种方式。
6.2静态工厂实例化
接下来研究Spring中的第二种bean的创建方式静态工厂实例化
:
6.2.1工厂方式创建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)运行后,可以查看到结果
(2)在AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
(3)运行后,可以查看到结果
看到这,可能有人会问了,你这种方式在工厂类中不也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?
主要的原因是:
-
在工厂的静态方法中,我们除了new对象还可以做其他的一些业务操作,这些操作必不可少,如:
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}
}
之前new对象的方式就无法添加其他的业务内容,重新运行,查看结果:
介绍完静态工厂实例化后,这种方式一般是用来兼容早期的一些老系统,所以==了解为主==。
6.3实例工厂与FactoryBean
接下来继续来研究Spring的第三种bean的创建方式实例工厂实例化
:
6.3.1 环境准备
(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管理呢?
6.3.2 实例工厂实例化
具体实现步骤为:
(1)在spring的配置文件中添加以下内容:
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
实例化工厂运行的顺序是:
-
创建实例化工厂对象,对应的是第一行配置
-
调用对象中的方法来创建bean,对应的是第二行配置
-
factory-bean:工厂的实例对象
-
factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:
-
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
的方式来简化开发。
6.3.3 FactoryBean的使用
具体的使用步骤为:
(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
(2)在Spring的配置文件中进行配置
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
(3)AppForInstanceUser运行类不用做任何修改,直接运行
这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
查看源码会发现,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);
}
}
打印结果,如下:
通过验证,会发现默认是单例,那如果想改成单例具体如何实现?
只需要将isSingleton()方法进行重写,修改返回为false,即可
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
public boolean isSingleton() {
return false;
}
}
重新运行AppForInstanceUser,查看结果
从结果中可以看出现在已经是非单例了,但是一般情况下我们都会采用单例,也就是采用默认即可。所以isSingleton()方法一般不需要进行重写。
6.3 bean实例化小结
(1)bean是如何创建的呢?
构造方法
(2)Spring的IOC实例化对象的三种方式分别是:
-
构造方法(常用)
-
静态工厂(了解)
-
实例工厂(了解)
-
FactoryBean(实用)
-
7aop
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
-
OOP(Object Oriented Programming)面向对象编程
-
作用:在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式。
前面咱们有技术就可以实现这样的功能即代理模式
。
Spring的循环依赖指的是在Bean的创建过程中,存在A依赖于B,而B又依赖于A的情况,形成了一个循环引用的关系。这种循环依赖会导致Spring无法完成Bean的初始化,从而引发异常。
Spring提供了三种解决循环依赖的方式:
-
构造函数注入:通过构造函数注入依赖可以解决循环依赖。当A依赖于B,而B依赖于A时,可以将B的依赖通过构造函数的方式注入到A中,同时将A的依赖通过构造函数的参数传递给B。这样,在创建A时,会创建B并将其注入到A中,然后再创建B时,会使用A的实例,从而打破了循环依赖。
-
Setter方法注入:如果使用通过Setter方法注入依赖的方式,Spring无法在创建Bean时解决循环依赖。但是,可以使用@Lazy注解延迟加载Bean,将Bean的创建和初始化过程推迟到真正使用时,借此解决循环依赖的问题。
-
使用代理:Spring启用了循环依赖检测机制,在发现循环依赖的情况时,会通过提前暴露半成品Bean的方式,将尚未初始化完全的Bean提前暴露给其他Bean使用。使用代理的方式进行循环依赖的解决,将A和B均使用代理对象,代理对象拦截对循环引用Bean的访问,确保Bean在完全初始化之前返回的是代理对象。
需要注意的是,在循环依赖的场景下,过于复杂或不正确的依赖关系会导致Spring无法解决循环依赖,最终引发异常。因此,在设计和使用Bean时,应尽量避免出现循环依赖的情况,并且合理设计依赖关系,以保证Bean的正确初始化和使用。
为什么使用三级缓存解决spring的循环依赖,而不是二级缓存
在Spring中,三级缓存是指在Bean的创建过程中,使用三个缓存来保存Bean实例,包括singletonObjects、earlySingletonObjects和singletonFactories。当需要创建一个Bean时,Spring首先在singletonObjects缓存中查找是否已经存在该Bean的实例,如果存在则直接返回。如果不存在则尝试从earlySingletonObjects缓存中获取半成品Bean(尚未完全初始化的Bean),如果获取成功则将其返回,否则尝试从singletonFactories缓存中获取创建Bean的工厂方法。
当使用三级缓存时,可以在循环依赖的场景下提前暴露半成品Bean,在半成品Bean中可以引用已经创建好的其他Bean实例,并在初始化完全后将其作为已创建的Bean存放在singletonObjects缓存中。
相较而言,二级缓存是指在创建Bean的过程中,使用两个缓存singletonObjects和singletonFactories来保存Bean实例。在循环依赖的场景下,二级缓存无法直接解决循环依赖,因为在二级缓存中无法获取到半成品Bean。
分析解决循环依赖 三级缓存singletonFactories
首先,singletonFactories存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory放入singletonFactoies中。【ObjectFactory是一个函数式接口,所以支持Lambda表达式:()->getEarlyReference(beanName, mbd, bean)】
ObjectFactory(上文的Lambda表达式),中间有getEarlyBeanReference方法【获取代理对象】,注意此时我们存入singletonFactories时不会执行Lambda表达式,也就是不会执行getEarlyBeanReference方法。
getEarlyBeanReference():①得到cachekey(beanName) ②将beanName和bean(原始对象)存入earlyProxyReferences ③调用wraplfNecessary进行AOP,得到代理对象
1、从singletonFactories根据beanName得到ObjectFactory,然后执行其getEarlyBeanReference方法,此时会得到A经过AOP后的代理对象,然后将其放入earlySingletonObjects中,此时并没有将代理对象放入singletonObjects中2、earlySingletonObjects作用:此时,我们只得到了A的代理对象,这个对象还不完整,因为还未进行属性注入,所以此时只能将A的代理对象放入earlySingletonObjects(singletonObjects单例池中放入的是全部生命周期后的bean),这样就能保证其他依赖了A对象的类获取到的就是同一个代理对象了。3、在B创建完之后,A继续进行生命周期,在A完成属性注入后,会按照本来的逻辑进行AOP,而此时A的原始对象已经经历过了AOP,所以对于A本身而言就不会再去进行AOP了。
如何判断是否已经进行AOP?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断档期按beanName是否存在earlyProxyReferences,如果存在则表示已经提前进行过AOP,无需再次进行。
4、对于A而言,进行AOP判断以及BeanPostProcessor(AOP实现)执行后,需要将A对应的对象放入singletonObjects中,但是,此时应该是从earlySingletonObjects中得到A的代理对象,然后放入singletonObjects(单例池)中
至此,整个循环依赖解决完毕。
总结1.singletonObjects:缓存某个经历了完整生命周期的bean2.earlySingletonObjects:缓存提前拿到原始对象并进行了AOP之后的代理对象;未进行属性注入的以及后续的3.BeanPostProcessor【AOP】等生命周期的bean4.singletonFactories:缓存的是一个ObjectFactory,用来生成进行了AOP之后的代理对象(在每个Bean的生成过程中都会提前暴露一个工厂),这个工厂可能用得到,也可能用不到5.earlyProxyReferences:记录对象是否进行了AOP