从零开始学Spring-详解Bean


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的作用域于sessionrequest

<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在功能分工上有所不同,它能够支持SpringBean管理进行更加细粒度的划分,可以辅助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>

我们在配置文件中配置WifeHusband两个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方法来完成这个过程

而至于你说为什么使用prototypesetter会导致无法实例化对象?这是因为prototype模式下,你每次getBean()都会创建新的对象,然后在注入的时候,如果你提前曝光,这时候你创建出来的新对象有很多很多个,然后这时候就不知道你到底要注入哪一个了。

这就是程序的歧义问题。如果你觉得你生产出来的WifeHusband是成对出现的,利用上述的提前曝光机制确实是可以解决问题的,但是依赖注入的这个事情,程序并不知道你是要注入以前就已经存在过的对象,还是注入新创建的对象。它只能够依照你的配置进行对象的创建,也就是说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完整的单例对象中取对象,如果拿不到,才去二级缓存取
  • 如果二级还没有,那么就去造这个对象的工厂去拿这个对象。

从上面可以看出,实际上曝光的不是半成品对象,而是曝光的是这个对象的工厂


文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录