bean的作用域_Spring学习(一)bean的初始化过程

一、Spring注入示例

    这里以注解的形式来进行注入示例的演示。

(1)通过@ComponentScan扫描com.ywl.leetcode下面所有的类。

@ComponentScan("com.ywl.leetcode")public class AppConfig {
}

(2)申明一个类并加上@Component注解,无参构造函数中打印日志记录。

@Componentpublic class TestService {public TestService() {
System.out.println("testService创建结束"); }
}

(3)启动spring容器,观察运行结果。

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); TestService testService = context.getBean(TestService.class);}

(4)运行结果

be5734288c558801c10de08bf4e56809.png

    运行完毕,TestService这个bean创建完毕。

二、疑问点:TestService这个bean是在spring容器启动时被创建出来的还是在context.getBean时候被创建出来的?

    验证方式:注释getBean代码。仍打印创建结束日志,说明是在spring容易启动时被创建出来的。

    代码如下:

    public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);// TestService testService = context.getBean(TestService.class);

}

//运行结果

testService创建结束

三、阅读spring源码来验证bean初始化的过程

(1)普通类的java初始化过程。

    java文件经过虚拟机编译,编译成.class字节码文件,当运行main方法时,会启动JVM虚拟机,通过虚拟机从磁盘上将.class字节码文件存储到方法区或者元数据空间,当对象被new出来时,会在堆上分配一块内存用来存储这个对象。

(2)bean的初始化过程。

    开始的初始化过程与普通类的初始化过程一样,会由jvm分配一块内存空间给这个对象。当spring容器开始加载时,首先会解析AppConfig.class。

02bae495cdb661acfc4fc30ef3ddc3a1.png

发现AppConfig中指定了扫描路径,需要找到扫描路径中需要被spring容器加载的类,即加了@Component、@Service、@Controller等注解的类,对这些类进行解析。

    解析后,这些类会被构建成一个spring中的BeanDefinition类,来存储类中的所有基本信息,比如beanClassName(类的type)、parentName(类的父类名字)、scope(类的作用域)、lazyInt(是否懒加载)等。

BeanDefinition详细存储信息可以查看org.springframework.beans.factory.config.BeanDefinition。

    构建成BeanDefinition后会把他放到一个map中,key为beanName,value为BeanDefinition。

    最后对这些类进行遍历,会在spring加载时对单例类并且不是懒加载的类进行bean的初始化,初始化完毕后,会放入到一个单例池的map中,即singletonMap。

    而对于那些不是单例或者是懒加载的类,就会在调用getBean方法时被初始化出来。

·  查看TestService的beanDefinition

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); BeanDefinition beanDefinition = context.getBeanDefinition("testService"); System.out.println(beanDefinition);}

beanDefinition里面存储的信息:

// Generic bean: class [com.ywl.leetcode.spring.ready.TestService];// scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0;// autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null;// initMethodName=null; destroyMethodName=null;

·  java对象和spring bean的区别

    上述初始化过程中可以看到java对象和bean其实有很大的区别。总而言之spring bean是一个java对象,而java对象不一定是一个bean。

    因为我读完spring源码后,个人认为bean与java对象最大的区别在于,java对象就是一个普通的对象,而bean是一个具有spring生命周期的一个对象。

·  调用AnnotationConfigApplicationContext

public AnnotationConfigApplicationContext(Class>... annotatedClasses) {this();   register(annotatedClasses);   refresh();}

    开启断点进行调试,发现refresh方法执行完毕,发现构造函数中的日志被打印,说明初始化的核心方法在refresh中。

·  refresh()

synchronized (this.startupShutdownMonitor) {

      //...
//这里主要是扫描指定路径下加了主键的类,并放入到beanDefinitionMap中 invokeBeanFactoryPostProcessors(beanFactory); //...

    在执行invokeBeanFactoryPostProcessors之前的beanDefinitionMap没有我们需要的类。

51679b8b4c38c23b24b5d148af9f4354.png

    执行完后,出现了testService。

0e5a13c838d9da8baac6eff1b5597682.png

    debug执行完finishBeanFactoryInitialization后发现,打印了构造函数中的日志。说明bean初始化的核心方法在该方法中。

   // ...
   finishBeanFactoryInitialization(beanFactory);   // ...  

·  finishBeanFactoryInitialization()

    核心代码在beanFactory.preInstantiateSingletons中,该方法的作用为加载非懒加载的单例bean。

//...

beanFactory.preInstantiateSingletons();

//...

·  preInstantiateSingletons()

@Override

public void preInstantiateSingletons() throws BeansException {

//...

   //获取需要加载的所有beanName,进行遍历
List beanNames = new ArrayList(this.beanDefinitionNames);

for (String beanName : beanNames) {

//根据beanDefinitionMap获取beanDefinition

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

//不是抽象类 并且 是单例 并且 不是懒加载 才可以在这时候被初始化

if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

//判断是否是factoryBean

if (isFactoryBean(beanName)) {final FactoryBean> factory = (FactoryBean>) getBean(FACTORY_BEAN_PREFIX + beanName); boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction() {@Override public Boolean run() {return ((SmartFactoryBean>) factory).isEagerInit(); }
}, getAccessControlContext()); }else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean>) factory).isEagerInit()); }if (isEagerInit) {
getBean(beanName); }

}

else {

            //testService会走进这里来初始化bean

getBean(beanName); }
}
} //...
}

    上述的方法其实很简单,遍历所有需要初始化的bean,就是遍历存储beanName的list,并根据beanName作为key去查询beanDefinitionMap中的beanDefinition,校验对应的类,只有不是抽象类、是单例、不是懒加载的类才可以在spring容器初始化时被初始化。

    bean初始化的方法在getBean(beanName)中。

四、核心方法doGetBean()

protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)throws BeansException {

   //校验beanName合法性

   final String beanName = transformedBeanName(name); Object bean;

//查询缓存池中是否存在该bean

//当前处于spring加载时,所以testService肯定为null

Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) {

if (logger.isDebugEnabled()) {

         //校验是否bean在初始化中

if (isSingletonCurrentlyInCreation(beanName)) {logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference"); }else {logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); }
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); }else {//bean是否在初始化中,循环依赖会涉及到 if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName); }
//...try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // 查询该bean是否存在depend on依赖的其他bean String[] dependsOn = mbd.getDependsOn();

if (dependsOn != null) {

            // 遍历依赖的bean,对bean进行查找与初始化
for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); }
registerDependentBean(dep, beanName); try {
getBean(dep); }catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); }
}
}
//bean是否为单例,testService为单例,所以会走进这个分支 if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory() {@Override public Object getObject() throws BeansException {

try {

//创建bean

return createBean(beanName, mbd, args); }catch (BeansException ex) { destroySingleton(beanName); throw ex; }
}
}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
//bean作用域为原型的会进这个分支初始化beanelse if (mbd.isPrototype()) { Object prototypeInstance = null; try {
beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); }finally {
afterPrototypeCreation(beanName); }
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);

}

         //...
}

    doGetBean()中存放许多核心的方法,比如getSingleton、createBean等,doGetBean大致的流程为上述源码展示的那样:

(1)会去缓存池中查找该bean是否被加载过,如果被加载过,返回。由于此时spring加载时,会加载testService,因此testService该bean不存在缓存池中。

(2)进行单例作用域与原型作用域bean的创建,由于testService为单例因此,我们只需要关注单例的创建即可。

    除此之外我们需要关注几个核心的方法:

·  getSingleton

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName); 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) {
singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); }
}
}
}return (singletonObject != NULL_OBJECT ? singletonObject : null);}

    大致的意思为:

(1)先从singletonObjects一级缓存中查询是否有bean,如果有则返回。

(2)判断是否bean处于正在初始化中,这个条件主要是为了循环依赖使用,循环依赖情况下,可能存在bean正在创建中的情况。这种情况今天的初始化过程先不涉及。

(3)去三级缓存earlySingletonObjects中查询是否有bean,如果有则返回。

(4)去二级缓存singletonFactories中查询是否存在bean的工厂,如果存在,获取该bean工厂对应的bean,放到三级缓存中。返回。

    上述代码中展示了spring容器总共有三级缓存,来保证获取bean的性能。

·  createBean

@Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
//...
   // 从beanDefinition中取出该bean的class类型
   Class> resolvedClass = resolveBeanClass(mbd, beanName);   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); }
//...
//debug到此处运行完毕后发现,运行了构造函数的日志打印
Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isDebugEnabled()) {logger.debug("Finished creating instance of bean '" + beanName + "'"); }return beanInstance;}

·  doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)throws BeanCreationException {
//...

if (instanceWrapper == null) {

//实例化对象

instanceWrapper = createBeanInstance(beanName, mbd, args); }
//...}

    调用完createBeanInstance后对象被new出来,并执行了构造函数中的日志打印。createBeanInstance中做的事情就是推断出合适的构造函数,通过反射来构造对象。

    此时的instanceWrapper只是一个普通的对象,但是并不是一个bean。

    修改代码如下图所示:

@Componentpublic class TestService {@Autowired    private UserService userService;    public TestService() {
System.out.println("testService创建结束"); }
}

    通过@Autowired自动注入userSerivce,debug到createBeanInstance执行完毕后发现此时的userSerivce并没有被注入。只是对象被构造完成,执行完了构造函数。

1c2408a89d728bcb37d601ed7200c6c0.png

    此时的testSerivce还不是bean,也可以使用指定testService的初始化方法来观察,有没有执行bean的初始化方法。

a627faebf6353fcf46c1cce15419f03d.png

    运行到createBeanInstance后,没有执行bean的初始化方法。

    继续执行代码:

//判断是否允许循环依赖

//条件一:单例

//条件二:私有属性默认为true,不过可以修改,私有属性在beanFactory中

//条件三:是否处于正在创建队列中。在调用createBean方法之前,已经放在了创建队列中。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isDebugEnabled()) {logger.debug("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");

}

//将bean生成的bean工厂放到二级缓存中

addSingletonFactory(beanName, new ObjectFactory() {@Override

public Object getObject() throws BeansException {

//这里主要判断是否存在aop,在后面的spring aop中会讲到,这里不做阐述

return getEarlyBeanReference(beanName, mbd, bean); }
});}
//...

    继续执行代码,发现在populateBean之后完成了userService的注入:

//...

bject exposedObject = bean;

try {
populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd); }

}

//...

·  populateBean

    populateBean主要会解析注入时使用的注解比如@Autowired或@Resource或者Xml文件注入来进行依赖注入。最后都会调用beanFactory.getBean对需要依赖注入的bean进行初始化。

·  回到doGetBean继续执行getSingleton方法

7d687747aa3f1b515b298099328c8be2.png

    源码如下:

public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");

synchronized (this.singletonObjects) {

      //先从一级缓存中获取,是否存在bean
Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) {
//...
         //这里会将该bean放到集合中,表示正在创建该bean

beforeSingletonCreation(beanName)

//...

try {

//该singletonFactory表示,调用createBean创建出来的beanFactory

//根据bean工厂获取工厂中的bean
singletonObject = singletonFactory.getObject()

newSingleton = true; }
//...finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;

}

//从正在创建bean的集合中将该bean移除,意味着bean马上就要创建成功了

afterSingletonCreation(beanName); }

if (newSingleton) {

//将bean放入到一级缓存中

addSingleton(beanName, singletonObject); }
}return (singletonObject != NULL_OBJECT ? singletonObject : null); }
}

五、普通bean的初始化过程-总结

    以上就是普通spring bean的初始化过程,跟着debug阅读bean的初始化过程其实并不复杂,期间有比较多的参数以及为什么需要用到三级缓存,在接下来的循环依赖和aop中都会涉及到,先可以放下这些细节,学习下在没有循环依赖情况下bean的初始化过程。

    用画图来总结bean的过程其实是这样的:

98ed90ce4c3df71cb8cad219f3dd4721.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值