草庐IT

Spring: Bean的创建原理解析

Freedom3568 2024-04-28 原文

文章目录


一、Spring创建Bean流程


1.读取Bean的定义信息
通过BeanDefinitionReader这个接口解析xml配置、配置类或其他的一些方式定义的类,得到BeanDefinition(Bean定义信息)

2.实例化Bean
通过BeanPostProcessor这个接口(增强器)可以对我们的BeanDefinition进行一些修改,然后BeanFactory通过反射实例化Bean对象,但是此时的Bean对象还没有进行初始化,没有填充属性等操作。

3.初始化Bean
(1)自定义属性赋值是用 set 方法赋值(populateBean())
(2)容器对象的属性赋值是用实现Aware接口的方式来赋值(invokeAwareMethods()),如BeanNameAware
(3)调用BeanPostProcessor的前置处理方法
(4)调用init初始化方法:init-method
(5)调用BeanPostProcessor的后置处理方法(AOP在这里实现)
(6)获得一个完整的对象,并将对象放入map中(通过Context.getBean()可以获取到Bean对象并使用)

4.销毁Bean
Spring容器关闭时会调用DisposableBean的Destory()方法
如果你在这个Bean中配置了destory-method属性,会自动调用指定的销毁方法

二、Bean的整体创建流程

  • 1.利用该类的构造方法来实例化得到一个对象(但是如果一个类中有多个构造方法, Spring则会进行选择,这个叫做推断构造方法,下文在详细介绍)
  • 2.得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属 性,把这些属性找出来并由Spring进行赋值(依赖注入)
  • 3.依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前 对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数
  • 4.Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解 了,如果存在,Spring会调用当前对象的此方法(初始化前),上面的代码中初始化了一个adminUser的数据。
  • 5.紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就 表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当
    前对象中的afterPropertiesSet()方法(初始化)
  • 6.最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化 后)
    关于第6步控制台没办法打出日志,因为初始后涉及到spring的源码操作,但是可以通过断点看一下,提一句例子中UserService是被LogAspect切面切的。

这样,一个Bean就创建完了,如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可 以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就 是单例池)
如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次 getBean时会再次执行上述创建过程,得到一个新的Bean对象。

三、推断构造方法

至此,我们清楚了Bean的创建流程,那如果UserService中有多个构造函数呢?第一步还能顺利的创建一个普通对象吗?这里面涉及到一个概念推断构造方法,就是spring会去推断用哪个构造方法来创建出普通对象。

总结下:

  • 如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法。但是有参的构造方法,参数必须是spring的Bean这样spring才能拿到进行赋值。
  • 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参的构 造方法,因为无参构造方法本身表示了一种默认的构造方法。
  • 如果都没有构造方法,就是用默认的无参构造方法来创建。
  • 其实多个构造函数,也可以手动指定告诉spring用哪个构造函数来创建,那就是加了@Autowired注解

四、依赖注入流程

不管是属性注入还是构造方法注入,能提供的信息只有两个一个是类型OrderService ,一个是名字orderService。那到底是根据类型注入的还是根据名字注入的呢?
假设根据名字注入的那刚好有一个其他类型的Bean名字也叫orderService那注入的时候岂不是会类型不匹配异常。比如说刚好有一个OrderBaseService类但是beanName也叫orderService,如果根据名字注入的话拿到的是OrderBaseService对象显然类型不匹配。所以注入通常是先根据类型来查找的:

  • 先根据入参类型找,如果只找到一个不用管name,那就直接用来作为入参
  • 如果根据类型找到多个,则再根据入参名字来确定唯一
  • 最终如果没有找到,则会报错,无法创建当前Bean对象

五、代理对象生成

代理对象通常是AOP的时候会生成代理对象还有一种就是开启事务的时候也会生成代理对象。否则的话Bean都是直接根据构造函数生成对象在进行依赖注入和初始化等流程。

1.AOP代理对象生成

AOP就是进行动态代理,在创建一个Bean的过程中,Spring在最后一步会去判断当前正在 创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。

如何判断当前Bean对象需不需要进行AOP:

  • 1.找出所有的切面Bean
  • 2.遍历切面中的每个方法,看是否写了@Before、@After等注解
  • 3.如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
  • 4.如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP

利用cglib进行AOP的大致流程:

  • 1.生成代理类UserServiceProxy,代理类继承UserService
  • 2.代理类中重写了父类的方法,比如UserService中的test()方法
  • 3.代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过 UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的 对象)
  • 4.代理类中的test()方法被执行时的逻辑如下:
    • a. 执行切面逻辑(@Before)
    • b. 调用target.test()
      当我们从Spring容器得到UserService的Bean对象时,拿到的就是UserServiceProxy所生 成的对象,也就是代理对象。
      UserService代理对象.test()—>执行切面逻辑—>target.test(),注意target对象不是代理 对象,而是被代理对象。

2.事务代理对象生成

Spring事务 当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事 务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
Spring事务的代理对象执行某个方法时的步骤:

  • 1.判断当前执行的方法是否存在@Transactional注解
  • 2.如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接,
  • 3.修改数据库连接的autocommit为false
  • 4.执行target.test(),执行程序员所写的业务逻辑代码,也就是执行sql
  • 5.执行完了之后如果没有出现异常,则提交,否则回滚

注意:Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,要判 断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。

总结

Spring中Bean的创建过程其实就是从一个普通对象蜕变成Bean的一个过程,蜕变包括依赖注入,初始化等步骤。最后在看下这个类是否有被AOP或开启事务有的话会额外生成代理对象作为Bean。

其他

参考:https://www.jianshu.com/p/57ed586e258c
https://www.csdn.net/tags/MtjaYgxsOTU3NTMtYmxvZwO0O0OO0O0O.html

有关Spring: Bean的创建原理解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  6. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  8. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  9. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  10. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

随机推荐