Bean其实就是代指的那些被IOC容器管理的对象
@Component和@Bean的区别
- @Component作用于类,而@Bean作用于方法
- @Bean注解比@Component注解更灵活,可增加一些自定义的属性,而且很多时候我们只能使用@Bean,比如引用第三方库中的Jar包通常只能使用此方式来装载成Bean
- @Component是通过类路径扫描的方式,将所有有标识的类都自动装配到Spring容器中,需要配合@ComponentScan来使用,不然是无法扫描除自身包路径外的其他类的。而@Bean是标注一个方法,方法中通常会直接创建一个实例返回,注入到Spring 容器中。
@Bean使用示例(以下自定义注入是@Component无法实现的 )
@Configuration
public class AppConfig {
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
}
Bean作用域
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
- request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置bean作用域
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
Bean 是线程安全的吗
是否线程安全取决于Bean的作用域和状态
如果使用的是prototype作用域,每次获取bean都要创建一个新的bean实例,就不存在资源竞争问题。
而如果使用的默认singleton作用域,IoC容器中只有唯一的实例,可能会存在资源竞争问题。如果bean中成员对象是有状态的,可变的那就存在线程安全问题。
解决方式:
- Bean中尽量避免定义可变的成员变量
- 在类中定义一个ThreadLocal成员变量,将可变的变量保存到ThreadLocal中
Bean的创建过程
准备阶段
容器在启动阶段需要做很多预热的操作,为后面的Bean实例化到容器内做好充分的准备
1.配置元信息
通常Bean会有一些成员变量信息,配置元信息两种方式:
一种是xml的方式,如下所示:
<bean id="role" class="com.myBean.springxmlbean.entity.Role">
<!-- property元素是定义类的属性,name属性定义的是属性名称 value是值
相当于:
Role role=new Role();
role.setId(1);
role.setRoleName("高级工程师");
role.setNote("重要人员");-->
<property name="id" value="1"/>
<property name="roleName" value="高级工程师"/>
<property name="note" value="重要人员"/>
</bean>
另一种是Java配置的方式如下所示:
@Configutration
public class BeanTest {
private String name;
private Integer value;
@Bean
public BeanTest initBean() {
BeanTest t = new BeanTest();
t.setName("xx");
t.setValue(1);
}
}
2.BeanDefinition
spring内部是用什么样的形式来存储bean的配置信息的呢?答案就是使用BeanDefinition这个对象去存储属性,构造方法参数,bean名称以及bean作用域,延迟加载等bean的元信息。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
// 单例、原型标识符
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
// 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
// 设置、返回 Bean 的父类名称
void setParentName(@Nullable String parentName);
String getParentName();
// 设置、返回 Bean 的 className
void setBeanClassName(@Nullable String beanClassName);
String getBeanClassName();
// 设置、返回 Bean 的作用域
void setScope(@Nullable String scope);
String getScope();
// 设置、返回 Bean 是否懒加载
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
...
}
3.BeanDefinationReader
BeanDefinationReader是一个接口,其不同的实现类对不同形式的配置信息进行读取并封装为BeanDefination,下图为结构图:
如上图所示,XmlBeanDefinationReader可以读取xml文件类型的配置信息并存储为BeanDefination。其他Reader以此类推
4.BeanFactoryPostRegistry:BeanDefination的存储仓库
当BeanDefinationReader将配置文件读取并存储到BeanDefination中后,Spring需要通过bean的id寻找到对应的BeanDefination从而获取其配置信息。这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。
Spring通过BeanDefinationReader将配置元信息加载到内存生成相应的BeanDefination之后,就将其注册到BeanDefinationRegistry中,BeanDefinationRegistry就是一个存放BeanDefination的仓库,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。
5.BeanFactoryPostProcessor
BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的增强,修改与替换。
比如我们经常使用的占位符的方式来配置,例如JDBC的DataSource连接时可以这样配置:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="driverClassName"
value="${jdbc.driverClassName}">
</property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。
至此第一阶段,准备阶段全部完成了,第一阶段的成果就是注册到BeanDefinitionRegistry中的BeanDefinition
准备阶段总结
实例化阶段
Bean的实例化阶段是开发者可选择的,可以在容器启动则开始加载,也可能会使用懒加载的方式:
- 懒加载(isLazyInit) :直到我们伸手向Spring要依赖对象实例之前,Bean都是以BeanDefinationRegistry中的一个个的BeanDefination的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。
- 非懒加载:容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。
1.对象创建策略
对象的创建采用了策略模式,借助我们前面BeanDefinationRegistry中的BeanDefination,我们可以使用反射的方式创建对象,也可以使用CGlib字节码生成创建对象。
2.BeanWrapper——对象外衣
Spring中的Bean并不是直接new 实例化创建的,Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例进行一层封装,类似对象的一件外衣,这个外衣就是BeanWrapper
BeanWrapper实际上是对反射相关API包的一层简单封装,使得上层使用反射完成业务逻辑简化,要获取对象某个属性,调用某个对象方法的话不需要写反射方法,而只需要使用BeanWrapper即可
3.设置对象属性
上一步创建出来的对象还是个空白对象,需要为其设置属性以及依赖对象
- 基本类型:如果配置元信息中有配置,则直接使用配置元信息中的设置值赋值即可,基本类型的属性没有设置,那么得益于JVM的实例化过程,还是会分配一个默认初始化零值
- 引用类型:Spring会将所有已经创建好的对象放入一个Map结构中,这时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程
4.检查Aware相关接口
aware接口为Bean对象提供了解Spring容器本身的能力,aware系列接口增强了Spring bean的功能,但是也会造成对Spring框架的绑定,增大了与Spring框架的耦合度。(Aware是“意识到的,察觉到的”的意思,实现了Aware系列接口表明:可以意识到、可以察觉到)
一个Bean对象想要获得spring容器某个部分的引用作为自己成员变量进行使用,就需要实现上述某个接口,并声明相关的成员变量来接收,示例如下:
/**
* 实现了
* ApplicationContextAware
* BeanClassLoaderAware
* BeanFactoryAware
* BeanNameAware
* 接口
* @author dengp
*
*/
public class User implements ApplicationContextAware,BeanClassLoaderAware,BeanFactoryAware,BeanNameAware{
private int id;
private String name;
// 保存感知的信息
private String beanName;
// 保存感知的信息
private BeanFactory beanFactory;
// 保存感知的信息
private ApplicationContext ac;
// 保存感知的信息
private ClassLoader classLoader;
public BeanFactory getBeanFactory() {
return beanFactory;
}
public ApplicationContext getAc() {
return ac;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public User(){
System.out.println("User 被实例化");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBeanName() {
return beanName;
}
/**
* 自定义的初始化方法
*/
public void start(){
System.out.println("User 中自定义的初始化方法");
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", beanName=" + beanName + "]";
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println(">>> setBeanClassLoader");
this.classLoader = classLoader;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(">>> setApplicationContext");
this.ac = applicationContext;
}
@Override
public void setBeanName(String name) {
System.out.println(">>> setBeanName");
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(">>> setBeanFactory");
this.beanFactory = beanFactory;
}
}
5.BeanPostProcessor前置处理
目的:BeanPostProcessor前置处理就是在要生产的Bean实例放到容器之前,允许我们程序员对Bean实例进行一定程度的修改,替换等操作。
ApplicationContext对于Aware接口的检查与自动注入就是通过BeanPostProcessor实现的,在这一步Spring将检查Bean
Spring AOP就是在BeanPostProcessor前置这一步实现代理,产生一个增强过的代理对象,然后将源对象的方法调用转嫁到代理对象上。
例子,以下例子会在所有Bean实例化前做控制台输出:
mport org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行MyBeanPostProcessor的postProcessBeforeInitialization()方法...");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行MyBeanPostProcessor的postProcessAfterInitialization()方法...");
return bean;
}
6.自定义初始化逻辑
初始化有两种方式,实现InitializingBean接口或者配置init-method参数,如果两者同时存在InitiallizingBean接口的afterPropertiesSet()方法,凡是能继承该接口的类,
- 配置InitializingBean接口:InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性赋值后初始化的处理方法,它只有一个afterPropertiesSet方法
- 配置init-method参数:通过init-method参数指定某个方法为初始化方法,此方法会在Bean初始化时执行
<bean id="myBean" class="com.yuansu.MyBean" init-method="myInit"></bean>
7.BeanPostProcessor后置处理
与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。
8.自定义销毁过程
销毁有两种方式,实现DisposableBean接口或者配置init-method参数
- 实现DisposableBean接口:实现其destroy()方法。
- 配置destroy-method参数:指定destroy-method方法。
Bean的生命周期
生命周期流程图如下所示:
一旦将Bean交给Spring管理,就会使用BeanFactory和ApplicationContext来创建Bean的实例,然后进行属性初始化操作,其生命周期简单可以划分为(实例化->属性填充->初始化->销毁)四个过程,具体描述如下:
(1)实例化Bean
如果使用的是BeanFactory,那么当客户请求一个尚未初始化的对象时或者在初始化Bean期间需要注入另一个未初始化的依赖,容器就会调用createBean()利用反射原理去实例化Bean;
而ApplicationContext则是在容器启动结束后,一次性将所有Bean实例化。到BeanDefination处获取Bean的信息并实例化所有实例。
(2)设置Bean的属性值
Bean可以通过实现FactoryBean接口的getObject()方法生成一个自定义BeanDefination,会根据BeanDefination定义设置属性值,另外还有BeanWrapper提供的设置属性的接口完成依赖注入
(3)Bean实现Aware相关接口
Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean名字;
Bean实现了BeanClassloaderAware接口,调用setBeanClassLoader(),传入ClassLoader对象的实例
Bean实现了ApplicationContextAware接口调,用setApplicationContext(),传递的是Spring上下文
实现了相关的Aware接口都要在此处进行一个程序执行
(4)BeanPostProcessor的postProcessBeforeInitialization()方法
如果存在加载该bean相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
(5)如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法
(6)如果Bean配置文件 配置了init-method属性,执行其指定方法
(7) BeanPostProcessor的postProcessAfterInitialization()方法
如果存在加载该bean相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
至此整个Bean实例化已经完成,可以正常运行
(8)Bean正常运行,需要注入时从容器中获取
(9)销毁Bean
销毁Bean可以先后执行以下两步操作:
-
- 如果Bean实现了DisposableBean接口,先调用其destroy()方法
- 如果在Bean配置文件配置了destroy-method属性,Spring会自动调用其配置销毁方法
注意:
BeanPostProcessor跟InitializingBean作用类似,都是对Bean实例化前作一些特殊处理,但是存在一些区别:
BeanPostProcessor需要额外实现一个处理类,处理范围不限于某个Bean,没有特别指定的话所有Bean实例化都会执行处理类的方法,且可以实现多个处理类;
而InitializingBean只需要在Bean本类上实现即可,处理的范围仅限于当前Bean
BeanFactory和FactoryBean
BeanFactory是用于访问Spring容器的根接口,是从Spring容器中获取Bean对象的基础接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范。通过getBean方法获取IoC容器管理的Bean,ApplicationContext就是其实现类
FactoryBean是为IOC容器中的Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上,给Bean的实现加上了一个简单工厂模式和装饰模式。用户可以通过实现该接口,然后在getObject()方法中灵活配置,来定制实例化Bean的逻辑。 以下是它的源码 :
public interface FactoryBean<T> {
//返回的对象实例
T getObject() throws Exception;
//Bean的类型
Class<?> getObjectType();
//true是单例,false是非单例
boolean isSingleton();
}
其中最重要的是getObject方法,从BeanFactory及其实现类的getBean()方法中获取到的Bean对象,实际上是FactoryBean的getObject()方法创建并返回的Bean对象,而不是FactoryBean本身。
在bean生命周期中处于设置对象属性的期间执行,在AOP中广泛应用,其中一个很重要的类ProxyFactoryBean。
@Import和@Bean的区别
@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的元素相同的功能,注解定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
@Import注解非常的简单,只有一个属性value(),类型为类对象数组,如value={class.A,class.C}
@Bean和@Import注入的区别是@Bean 注入没有指定名称的话默认使用方法名,而@Import注入的Bean名称是该实例的全限定类名
如何解决循环依赖
原型(Prototype)模式的场景是不支持循环依赖的,通常走到AbstractBeanFactory类中下面的判断,抛出异常
if(isPrototypeCurrentlyInCreation) {
throw new BeanCurrentlyInCreationException(beanName);
}
原因很好理解,创建新的A时,发现要注入一个新的B,转头去创建一个新的B,由于循环依赖要注入一个A,所以创建一个新的A,这样会循环套娃,抛出OOM异常,所以Spring先给你抛出一个BeanCurrentlyInCreationException。
Spring解决循环依赖
Spring的DefaultSingletonBeanRegistry类中存在三个Map,相当于三级缓存:
- singletonObjects:它是单例池最常用的,用于保存已经经过完整生命周期的Bean(元信息配置,依赖注入,初始化完成),属于一级缓存
- earlySingletonObjects:映射的早期引用,也就是说在这个Map里的Bean是不完整的,只是一个简单引用,属于二级缓存
- singletonFactories:映射创建Bean的原始工厂,便于后续扩展,属于三级缓存
在实例化Bean的过程中,也就是AbstractFactory.getBean(),getBean有一个getSingleton()
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
//若一级缓存中没有,且当前bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
/**
* 二级缓存中没有,且允许循环依赖
*二级缓存作用:若涉及到三个及以上对象循环依赖,此时就可以直接从二级缓存中获取到值
*/
if (singletonObject == null && allowEarlyReference) {
//从三级缓存中获取,这里获取到的是一个对象工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 从对象工厂中获取到一个半成品bean
*/
singletonObject = singletonFactory.getObject();
//放入到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
单例模式实例bean流程:
- 比如有一个Class A在实例化时它会首先会去一级缓存就是singletonObjects中查找,找不到则进入创建方法BeanFactory.createBean()创建一个Bean,并在singletonFactories加入一个getEarlyBeanReference,表示Class A在创建中
- 当创建Bean A的时候发现注入了Class B,所以触发Class B的getBean方法,首先一样的会去一级缓存中去获取,当获取不到会跟步骤1类似进入创建方法,并在singletonFactories加入一个getEarlyBeanReference,表示ClassB在创建中
- 当ClassB填充属性的值pouplateBean时,发现依赖了ClassA,会再次触发实例ClassA的流程
- 此时已经陷入循环实例化了,首先还是会查找一级缓存,此时singletonObjects中一定获取不到,但是通过判断进入到二级,三级缓存中查找,发现singletonFactories这个Map中找到ClassA,取到后将其放入EarlySingletonObjects,并提供给ClassB填充属性pouplateBean时使用;(此时只是简单引用,不是完整的Bean,没有配置成员信息,依赖注入等等)
- ClassB接收到了ClassA的引用,填充属性值完成,此时可以开始创建一个完整的实例了;
- 由于ClassB已经完成实例化了,于是ClassA会注入ClassB实例,按照流程创建一个完整的实例
- ClassB引用了一个ClassA的地址,所以此时A创建好实例后,ClassB引用的A也因此完成
- 实例化完成之后删除早期引用map,并放入单例map中缓存singletonObjects。
无法解决循环依赖的情况
- 采用了构造器注入的方式
如果都使用了构造器注入的方式,A依赖B,B依赖A就陷入死循环,由于构造方式定死了无法生成半成品的实例,所以Spring无法解决循环依赖。如果其中有一个是字段注入,且先被Spring扫描到则可以解决
- 相互依赖的Bean都是原型Bean(prototype)
两个原型Bean每次创建都需要一个新的实例对象,实例化过程中需要彼此的新对象会陷入死循环
- 采用@DependOn注解指定依赖某些Bean
@DependOn主要用于指定当前Bean对象所依赖的其他Bean对象,Spring在创建当前Bean之前要优先创建该注解指定的对象
- 使用了@Async注解
在对象A(Spring 先扫描的对象)依赖了B,B中依赖了A,而且在A中使用了@Async注解去调用B,使用@Async时Bean初始化要经历一个后置处理器处理AsyncAnnotationBeanPostProcessor,该处理器会修改对象A的返回结果,生成一个新的代理对象,B注入的A跟这个代理不是同一个对象,所以Spring无法处理