1. Bean的作用域
1.1 单例与多例
我们通过getBean()
方法,通过beanId去获取这个bean的实例,然后输出其hashCode()
,然后我们发现其hashCode()
是一致的。
Spring默认情况下是如何管理这个bean的?(singleton)
默认情况下是单例,在Spring上下文初始化的时候进行实例化。每一次调用
getBean()
方法都将会返回那个单例的对象。我们想要得到一个多例的,就需要添加标签
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl" autowire="byType" scope="prototype"/>
在这种情况下,spring初始化的时候不会初始化这些bean,而是在调用
getBean()
的时候实例化该bean
1.2 关于bean的其他作用域
八种
在spring-webmvc
中,还可以设置bean
的作用域于session
、request
中
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl" autowire="byType" scope="session"/>
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl" autowire="byType" scope="request"/>
- request:一次请求一个bean
- session:一次请求一个bean
- global session:prolet应用中专用的,如果在
Servlet
的web应用中使用global session
的话,和session一样,servlet
是在tomcat中运行的,而portlet
是在portlet
容器中的 - application:一个应用定义一个bean
webscoket
:一个websocket生命周期对应一个bean- 自定义scope
1.3 自定义Scope
public class SimpleThreadScope implements Scope {}
public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered {}
在配置文件中注册该范围
<bean id="StudentServiceBean" class="com.orm.service.impl.StudentServiceImpl" autowire="byType" scope="myThread"/>
<!--引入外部配置文件-->
<!--使用此标签即可引入资源,location默认从类的根路径下开始加载资源-->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myThread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
2. Gof之工厂模式
设计模式:一种可以被重复利用的解决方案。
该书中描述了23种设计模式。我们平常所说的设计模式就是指这23种设计模式。
不过除了GoF23种设计模式之外,还有其它的设计模式,比如:JavaEE的设计模式(DAO模式、MVC模式等)。
GoF23种设计模式可分为三大类:
- 创建型(5个):解决对象创建问题。
单例模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
- 结构型(7个):一些类或对象组合在一起的经典结构。
代理模式
- 装饰模式
- 适配器模式
- 组合模式
- 享元模式
- 外观模式
- 桥接模式
- 行为型(11个):解决类或对象之间的交互问题。
策略模式
- 模板方法模式
- 责任链模式
- 观察者模式
- 迭代子模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式。这里为什么学习工厂模式呢?这是因为Spring框架底层使用了大量的工厂模式。
3. Bean的实例化方式
Spring为Bean
提供了多种实例化方式,通常包括四种方式
- 通过构造方法实例化
- 通过简单工厂模式实例化
- 通过
factory-bean
实例化 - 通过
Factorybean
接口实例化
3.1 通过构造方法实例化
在这种情况下,会调用bean
的无参构造方法
public class StudentServiceImpl implements StudentService{
public StudentServiceImpl(){System.out.println("执行了构造方法"); }
}
3.2 通过简单工厂模式实例化
- 定义一个bean
public class StudentServiceImpl implements StudentService{
public StudentServiceImpl(){System.out.println("执行了构造方法"); }
}
- 编写简单工厂模式当中的工厂类
public class StudentServiceFactory {
/**
* 通过工厂模式进行实例化
* @return
*/
public static StudentService getStudentService(){
return new StudentServiceImpl();
}
}
- 编写bean的标签配置
<!--通过简单工厂模式,你需要在Spring配置文件中告诉Spring框架,调用哪个类的哪个方法获取bean-->
<bean id="StudentServiceBean" class="com.orm.factory.StudentServiceFactory" factory-method="getStudentService"/>
3.3 通过factory-bean通过工厂方法模式进行实例化
本质区别
静态工厂方法中造对象的方法是静态的,而且所有的产品对应一个工厂
工厂方法模式是一个产品对应一个工厂,方法是实例方法
<!--提供的实例化方法,通过factory-bean属性,factory-method来共同完成-->
<!--告诉哪个对象的哪个方法?调用哪个对象的哪个方法来获取bean-->
<bean id="StudentServiceFactory" class="com.orm.factory.StudentServiceFactory"/>
<bean id = "StudentServiceBean" factory-bean="StudentServiceFactory" factory-method="getStudentService"/>
public class StudentServiceFactory {
public StudentService getStudentService(){
//最终创建的还是我们自己负责进行new的
return new StudentServiceImpl();
}
}
3.4 通过factory-bean实例化bean
在以上的三种方式中,factory-bean
是我们自定义的,factory-method
也是我们自己定义的
在Spring中,当你编写的类实现了FactoryBean
接口之后,factory-bean
不需要指定了,factory-method
也不需要指定了
factory-bean
会自动指向实现FactoryBean
接口的类,然后factory-method
还会自动指向getObject()
方法
public class StudentServiceFactory implements FactoryBean<StudentService> {
@Override
public StudentService getObject() throws Exception {
return new StudentServiceImpl();
}
@Override
public Class<?> getObjectType() {
return StudentService.class;
}
@Override
public boolean isSingleton() {
//默认是单例的
return FactoryBean.super.isSingleton();
}
}
它本身也是一个Bean
只不过这个Bean比较特殊,叫做工厂Bean
,通过工厂Bean
可以获取一个普通的Bean
<!--Spring提供的实例化方式,第四种:通过FactoryBean接口来实现的-->
<!-- 由于你实现了接口规范,所以就不需要你自己指定你的方法了-->
<bean id="StudentService" class="com.orm.factory.StudentServiceFactory"/>
- 通过这个
FactoryBean
这个工厂Bean
主要是想对普通Bean
进行加工处理。
3.5 BeanFactory和FactoryBean的区别
这个问题首先可以从命名的后缀来看出其区别
BeanFactory
的后缀是Factory
,也就是说它是一个工厂对象,用来创建对象的,那么它是创建什么类型的对象呢?根据其名字可以看出,它创建出来的对象类型就是Bean
对象- 它在Spring是
IoC
容器的顶级对象,在IoC
容器中,Bean
工厂负责创建交给Spring
管理的所有Bean
对象
关于FactoryBean
FactoryBean
的后缀是Bean
,也就说它本质上是一个Bean
对象,在Spring
中,它也是通过BeanFactory
创建出来的,只不过它与普通的bean
在功能分工上有所不同,它能够支持Spring
对Bean管理
进行更加细粒度的划分,可以辅助Spring
实例化其他对象。
3.6 FactoryBean注入Date对象
public class DateFactory implements FactoryBean<Date> {
private String birth;
public DateFactory(String birth) {
this.birth = birth;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date parse = sdf.parse(birth);
return parse;
}
@Override
public Class<?> getObjectType() {
return Date.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="date" class="com.orm.factory.DateFactory" c:birth="1980-10-11"/>
<bean id="peopleBean" class="com.orm.domain.People">
<property name="birth" ref="date"/>
</bean>
4. Bean的生命周期
4.1 什么是Bean的生命周期
Spring本质上就是一个管理Bean
对象的工厂,它负责了对象的创建,对象的销毁等
所谓的生命周期就是对象从被创建开始到最终被销毁的整个过程
什么时候创建Bean
对象?
创建Bean
对象前后会调用什么方法?
Bean
对象会什么时候销毁
Bean
对象的销毁前后会调用什么方法?
4.2 为什么需要知道Bean
的生命周期
其实生命周期的本质:在哪个时间节点上调用了哪个类的哪个方法
需要充分地了解在这个生命线上,都有哪些特殊的时间节点
只有知道了特殊的时间节点在哪,才能知道代码应该写在哪
我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上,当生命线走到这里的时候,自然就会被调用
4.3 Bean的五步生命周期
Bean
生命周期管理可以粗略地划分为5步:
- 实例化Bean
- Bean属性赋值
- 初始化Bean
- 使用Bean
- 销毁Bean
public class Dog {
private String name;
public Dog(){
System.out.println("1.实例化Bean");
}
public Dog(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性进行赋值");
}
public void initBean(){
System.out.println("3.初始化Bean");
}
public void destroyBean(){
System.out.println("5.销毁了Bean");
}
}
<!--研究Bean的生命周期-->
<bean id="dogBean" class="com.orm.domain.Dog" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="大黄"/>
</bean>
4.4 Bean的生命周期7步
生命周期的七步分别是在第三步的之前和之后(也就是初始化阶段)
以上的五步中,第三步初始化Bean
,如果你想要在初始化前后初始化后添加代码,那么应该加入Bean后处理器
编写一个类实现BeforePostProcessor
- 实例化Bean
- Bean属性赋值
- 执行Bean后处理器的before方法
- 初始化Bean
- 执行Bean后处理器的after方法
- 使用Bean
- 销毁Bean
public class MyProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的before方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的after方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
然后你光写了还不行,你必须把你的这个对象交给Spring进行管理,就像之前的自定义Scope
一样。
<!--配置Bean后处理器将作用于你的xml文件中获得的bean-->
<bean class="com.orm.common.MyProcessor"/>
4.5 Bean生命周期之10步
- 实例化Bean
- Bean属性赋值
- 检查Bean是否实现了
Aware
等接口,并设置相关依赖 - 执行Bean后处理器的before方法
- 检查Bean是否实现了
InitializingBean
接口,并调用接口方法 - 初始化Bean
- 执行Bean后处理器的after方法
- 使用Bean
- 检查Bean是否实现了
DisposableBean
接口,并调用接口方法 - 销毁Bean
点位1:在
Bean后处理器
的before之前点位1主要检查
Bean
是否实现了Aware
的相关接口,如果有的话则会传递相关参数,方便开发点位2:在
Bean后处理器
的before之后点位2主要检查是否实现了
InitializingBean
接口,如果实现了这个接口,就会调用接口内的方法点位3:销毁
Bean
之前点位3主要检查是否实现了
DisposableBean
接口。它们都是根据你是否实现了这些接口,如果实现了这些接口,则Spring容器会调用这些接口中的方法
1.实例化Bean
2.Bean属性进行赋值
3.Bean的名字是dogBean
4.执行Bean后处理器的before方法
5.在属性赋值之后,在初始化之前
6.初始化Bean
7.执行Bean后处理器的after方法
8.使用bean
9.在彻底销毁之前
10.销毁了Bean
@Override
public void destroy() throws Exception {//DisposableBean
System.out.println("在彻底销毁之前");
}
@Override
public void afterPropertiesSet() throws Exception {//InitializingBean
System.out.println("在属性赋值之后,在初始化之前");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("Bean的可加载器是"+classLoader.getClass().getName());
}
@Override
public void setBeanName(String s) {//如果实现了接口,然后调用这些接口中的方法,然后更加方便的使用
System.out.println("Bean的名字是"+s);
}
4.6 Bean的作用域不同,管理方式不同
Spring 会根据Bean
的作用域来选择管理方式
- 对于singleton作用域的
Bean
,Spring能够精确地知道该Bean
何时被创建,何时初始化完成,以及何时被销毁 - 而对于
prototype
作用域的Bean
,Spring
只负责创建,当容器创建了Bean
的实例之后,Bean
的实例就交给了客户端进行管理,Spring
容器将不再跟踪其生命周期。
也就是说到了使用Bean
这一步之后,Spring将不再进行管理。
4.7 自己new出来的对象如何让Spring管理?
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring.xml");
People people = new People();
people.setAge(11);
people.setBirth(new Date());
people.setName("张三");
DefaultListableBeanFactory listableBeanFactory = new DefaultListableBeanFactory();
listableBeanFactory.registerSingleton("peopleBean",people);
People peopleBean = listableBeanFactory.getBean("peopleBean", People.class);
logger.info("{}",peopleBean);
5. Bean的循环依赖问题
5.1 什么是循环依赖的问题
A对象中有B属性,而B对象中有A属性,这就是循环依赖问题。
比如说:
public class Husband {
private String name;
private Wife wife;
}
public class Wife {
private String name;
private Husband husband;
}
在这个实体中,如果我们想用容器来管理这两个Bean
,就会产生一个问题,也就是A
在注入属性的时候必须先有B
,B
在注入属性的必须先有A
,如果两者就这样推算下去,就会出现两者无法初始化的情况。
5.2 singleton下的set依赖注入产生的循环依赖问题
<!--研究循环依赖问题-->
<bean id="husbandBean" class="com.orm.domain.Husband">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.orm.domain.Wife">
<property name="name" value="李四"/>
<property name="husband" ref="husbandBean"/>
</bean>
我们在配置文件中配置Wife
和Husband
两个bean,可见其依赖是循环的。
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
logger.info("{}",wifeBean);
2022-11-24 08:54:15 701 [main] INFO SpringTest - Wife{name='李四', husband=张三}
而在程序的最终执行后,我们发现Spring是能够解决singleton+set注入的循环依赖问题的
这是因为在singleton
的模式下,这个Bean
在Spring容器中是单例的。
5.2 一个singleton和一个prototype下的set注入产生的循环依赖
<!--研究循环依赖问题-->
<bean id="husbandBean" class="com.orm.domain.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.orm.domain.Wife" scope="singleton">
<property name="name" value="李四"/>
<property name="husband" ref="husbandBean"/>
</bean>
可以看到此时程序正常执行:
2022-11-24 09:01:14 981 [main] INFO SpringTest - Wife{name='李四', husband=张三}
5.3 全为prototype下产生的set注入产生的循环依赖
<!--研究循环依赖问题-->
<bean id="husbandBean" class="com.orm.domain.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.orm.domain.Wife" scope="prototype">
<property name="name" value="李四"/>
<property name="husband" ref="husbandBean"/>
</bean>
可见此时的运行是会报错的。
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'husbandBean' defined in class path resource [Spring.xml]:
Cannot resolve reference to bean 'wifeBean' while setting bean property 'wife';
Error creating bean with name 'wifeBean': Requested bean is currently in creation:
Is there an unresolvable circular reference?
翻译为:创建名为husband
的bean的时候出错了,请求的bean
当前正在创建中,是否存在无法解析的循环依赖?
这是因为我们之前学到过,在prototype
的scope下,我们每一次去getBean()
的时候,每一次注入的wifeBean
都必须是新new出来的Bean
5.4 singleton下的构造注入产生的循环依赖
<!--研究循环依赖问题-->
<bean id="husbandBean" class="com.orm.domain.Husband" scope="singleton" c:name="张三" c:wife-ref="wifeBean"/>
<bean id="wifeBean" class="com.orm.domain.Wife" scope="singleton" c:name="李四" c:husband-ref="husbandBean"/>
我们猜测这时候也会产生循环依赖的问题报错如下
Caused by:
Error creating bean with name 'hBean':
Requested bean is currently in creation: Is there an unresolvable circular reference?
其主要原因是因为通过构造方法
注入导致的,因为构造方法注入会导致实例化对象的过程和对象赋值的过程无法分开,也就导致硬性规定:A要创建出来,必须要有B的对象,B要创建出来,必须要有A的对象,那么这样的实例化过程永远无法完成,这个是本质的问题。
5.5 Spring是如何解决循环依赖的?
同样的是循环依赖,为什么Spring
能够解决set+singleton
下的循环依赖?
根本的原因在于,这种方式可以做到将实例化Bean
和给Bean
进行赋值这两个动作分开
第一个阶段:在Spring容器加载的时候,实例化Bean
,只要其中任意一个Bean
调用无参的构造方法之后实例化出来之后,马上进行曝光
第二个阶段:Bean在曝光之后才进行属性的赋值。
其核心解决方案是实例化对象和属性注入的分离。
我们知道,Bean
对象如果互相依赖,那么在其对象内部变量存储的是另外一个Bean
的引用地址,一旦对象被创建,那么其引用地址直到被GC都不会变化,所以的话我可以先调用无参构造方法,我先把这个对象new出来,然后曝光给外界。最后调用setter
方法来完成这个过程
而至于你说为什么使用prototype
的setter
会导致无法实例化对象?这是因为prototype
模式下,你每次getBean()
都会创建新的对象,然后在注入的时候,如果你提前曝光,这时候你创建出来的新对象有很多很多个,然后这时候就不知道你到底要注入哪一个了。
这就是程序的歧义问题。如果你觉得你生产出来的
Wife
和Husband
是成对出现的,利用上述的提前曝光机制确实是可以解决问题的,但是依赖注入的这个事情,程序并不知道你是要注入以前就已经存在过的对象,还是注入新创建的对象。它只能够依照你的配置进行对象的创建,也就是说prototype
的情况下,你new一个husband
,然后就会走一个Bean
的流程,它觉得你没有外部依赖,因此会走整个流程,在创建完对象之后直接进行属性的赋值,就是在这一步,产生了死递归。而singleton也可以是没有觉得你有外部依赖的,不过它在实例化Bean的过程中,它是先将所有的实例化的Bean曝光,在这个时间点,Spring会把这个Bean放到缓存之中。最终才会调用
setter
方法。
源码追踪
Bean的创建过程
// Eagerly cache singletons to be able to resolve circular references
// 提前缓存单例,以便能够解析循环引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充bean,给你的bean赋值
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
public class DefaultSingletonBeanRegistry extends
SimpleAliasRegistry implements SingletonBeanRegistry {
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
//三个缓存
//一级缓存:这三个缓存都是Map集合,Map集合的key都是bean的id
//它存储的是完整的单例Bean对象,这个集合中的单例Bean都经过了属性赋值
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//二级缓存:早期Bean单例对象,这个缓存中的单例bean属性是没有缓存的
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//三级缓存:单例工厂对象,存储了大量的工厂对象,每一个单例Bean对象都会对应一个单例工厂对象
//这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象
//比如说 gunBean gunFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {//同步方法
if (!this.singletonObjects.containsKey(beanName)) {//一次缓存
//先存到三级缓存中,把创建对象的工厂进行缓存
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
- 先从
singletonObject
完整的单例对象中取对象,如果拿不到,才去二级缓存取 - 如果二级还没有,那么就去造这个对象的工厂去拿这个对象。
从上面可以看出,实际上曝光的不是半成品对象,而是曝光的是这个对象的工厂