首页IT科技bean的注入方式(扒一扒Bean注入到Spring的那些姿势,你会几种?)

bean的注入方式(扒一扒Bean注入到Spring的那些姿势,你会几种?)

时间2025-08-01 19:53:32分类IT科技浏览4722
导读:大家好,我是三友~~...

大家好              ,我是三友~~

这篇文章我准备来扒一扒Bean注入到Spring的那些姿势              。

其实关于Bean注入Spring容器的方式网上也有很多相关文章                     ,但是很多文章可能会存在以下常见的问题

注入方式总结的不全 没有分析可以使用这些注入方式背后的原因 没有这些注入方式在源码中的应用示例 ...

所以本文就带着解决上述的问题的目的来重新梳理一下Bean注入到Spring的那些姿势                     。

配置文件

配置文件的方式就是以外部化的配置方式来声明Spring Bean      ,在Spring容器启动时指定配置文件      。配置文件方式现在用的不多了       ,但是为了文章的完整性和连续性                     ,这里我还是列出来了             ,知道的小伙伴可以自行跳过这节       。

配置文件的类型Spring主要支持xml和properties两种类型                     。

xml

在XmlBeanInjectionDemo.xml文件中声明一个class为类型为User的Bean

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><beanclass="com.sanyou.spring.bean.injection.User"/></beans>

User

@Data@ToStringpublicclassUser{privateStringusername;}

测试:

publicclassXmlBeanInjectionDemo{publicstaticvoidmain(String[]args){ClassPathXmlApplicationContextapplicationContext=newClassPathXmlApplicationContext("classpath:XmlBeanInjectionDemo.xml");applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

结果:

User(username=null)

可以看出成功将User注入到Spring中       ,由于没有设置username属性值                     ,所以是null             。

properties

除了xml             ,spring还支持properties配置文件声明Bean的方式       。

如下,在PropertiesBeanInjectionDemo.properties文件中声明了class类型为User的Bean                     ,并且设置User的username属性为sanyou                     。

user.(class) = com.sanyou.spring.bean.injection.User user.username = sanyou

测试:

publicclassPropertiesBeanInjectionDemo{publicstaticvoidmain(String[]args){GenericApplicationContextapplicationContext=newGenericApplicationContext();//创建一个PropertiesBeanDefinitionReader                    ,可以从properties读取Bean的信息,将读到的Bean信息放到applicationContext中PropertiesBeanDefinitionReaderpropReader=newPropertiesBeanDefinitionReader(applicationContext);//创建一个properties文件对应的Resource对象ResourceclassPathResource=newClassPathResource("PropertiesBeanInjectionDemo.properties");//加载配置文件propReader.loadBeanDefinitions(classPathResource);applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

结果:

User(username=sanyou)

成功获取到User对象              ,并且username的属性为properties设置的sanyou             。

除了可以配置属性之外还支持其它的配置                    ,如何配置可以查看PropertiesBeanDefinitionReader类上的注释。

注解声明

上一节介绍了通过配置文件的方式来声明Bean      ,但是配置文件这种方式最大的缺点就是不方便              ,因为随着项目的不断扩大                     ,可能会产生大量的配置文件                     。为了解决这个问题      ,Spring在2.x的版本中开始支持注解的方式来声明Bean                    。

@Component + @ComponentScan

这种方式其实就不用多说       ,在项目中自定义的业务类就是通过@Component及其派生注解(@Service              、@Controller等)来注入到Spring容器中的。

在SpringBoot环境底下                     ,一般情况下不需要我们主动调用@ComponentScan注解             ,因为@SpringBootApplication会调用@ComponentScan注解       ,扫描启动引导类(加了@SpringBootApplication注解的类)所在的包及其子包下所有加了@Component注解及其派生注解的类                     ,注入到Spring容器中              。

@Bean

虽然上面@Component + @ComponentScan的这种方式可以将Bean注入到Spring中             ,但是有个问题那就是对于第三方jar包来说,如果这个类没加@Component注解                     ,那么@ComponentScan就扫不到                    ,这样就无法注入到Spring容器中,所以Spring提供了一种@Bean的方式来声明Bean                    。

比如              ,在使用MybatisPlus的分页插件的时候                    ,就可以按如下方式这么来声明      。

@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}

此时就能将MybatisPlusInterceptor这个Bean注入到Spring容器中              。

@Import

@Import注解也可以用来将Bean注入到Spring容器中      ,@Import注解导入的类可以分为三种情况:

普通类 类实现了ImportSelector接口 类实现了ImportBeanDefinitionRegistrar接口 普通类

普通类其实就很简单              ,就是将@Import导入的类注入到Spring容器中                     ,这没什么好说的                     。

类实现了ImportSelector接口 publicinterfaceImportSelector{String[]selectImports(AnnotationMetadataimportingClassMetadata);@NullabledefaultPredicate<String>getExclusionFilter(){returnnull;}}

当@Import导入的类实现了ImportSelector接口的时候      ,Spring就会调用selectImports方法的实现       ,获取一批类的全限定名                     ,最终这些类就会被注册到Spring容器中      。

比如如下代码中             ,UserImportSelector实现了ImportSelector       ,selectImports方法返回User的全限定名

publicclassUserImportSelectorimplementsImportSelector{@OverridepublicString[]selectImports(AnnotationMetadataimportingClassMetadata){System.out.println("调用UserImportSelector的selectImports方法获取一批类限定名");returnnewString[]{"com.sanyou.spring.bean.injection.User"};}}

当使用@Import注解导入UserImportSelector这个类的时候                     ,其实最终就会把User注入到Spring容器中             ,如下测试

@Import(UserImportSelector.class)publicclassImportSelectorDemo{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext();//将ImportSelectorDemo注册到容器中applicationContext.register(ImportSelectorDemo.class);applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

运行结果

User(username=null)

对于类实现了ImportBeanDefinitionRegistrar接口的情况,这个后面说       。

一般来说                     ,@Import都是配合@EnableXX这类注解来使用的                    ,比如常见的@EnableScheduling                    、@EnableAsync注解等,其实最终都是靠@Import来实现的                     。

@EnableScheduling @EnableAsync

讲完通过注解的方式来声明Bean之后              ,可以来思考一个问题                    ,那就是既然注解方式这么简单      ,为什么Spring还写一堆代码来支持配置文件这种声明的方式?

其实答案很简单              ,跟Spring的发展历程有关             。Spring在创建之初Java还不支持注解                     ,所以只能通过配置文件的方式来声明Bean      ,在Java1.5版本开始支持注解之后       ,Spring才开始支持通过注解的方式来声明Bean       。

注册BeanDefinition

在说注册BeanDefinition之前                     ,先来聊聊什么是BeanDefinition?

BeanDefinition是Spring Bean创建环节中很重要的一个东西             ,它封装了Bean创建过程中所需要的元信息                     。

publicinterfaceBeanDefinitionextendsAttributeAccessor,BeanMetadataElement{//设置BeanclassNamevoidsetBeanClassName(@NullableStringbeanClassName);//获取BeanclassName@NullableStringgetBeanClassName();//设置是否是懒加载voidsetLazyInit(booleanlazyInit);//判断是否是懒加载booleanisLazyInit();//判断是否是单例booleanisSingleton();}

如上代码是BeanDefinition接口的部分方法       ,从这方法的定义名称可以看出                     ,一个Bean所创建过程中所需要的一些信息都可以从BeanDefinition中获取             ,比如这个Bean的class类型,这个Bean是否是懒加载                     ,这个Bean是否是单例的等等                    ,因为有了这些信息,Spring才知道要创建一个什么样的Bean             。

有了BeanDefinition这个概念之后              ,再来看一下配置文件和注解声明这些方式往Spring容器注入Bean的原理。

Bean注入到Spring原理

如图为Bean注入到Spring大致原理图                    ,整个过程大致分为以下几个步骤

通过BeanDefinitionReader组件读取配置文件或者注解的信息      ,为每一个Bean生成一个BeanDefinition BeanDefinition生成之后              ,添加到BeanDefinitionRegistry中                     ,BeanDefinitionRegistry就是用来保存BeanDefinition 当需要创建Bean对象时      ,会从BeanDefinitionRegistry中拿出需要创建的Bean对应的BeanDefinition       ,根据BeanDefinition的信息来生成Bean 当生成的Bean是单例的时候                     ,Spring会将Bean保存到SingletonBeanRegistry中             ,也就是平时说的三级缓存中的第一级缓存中       ,以免重复创建                     ,需要使用的时候直接从SingletonBeanRegistry中查找

好了             ,通过以上分析我们知道,配置文件和注解声明的方式其实都是声明Bean的一种方式                     ,最终都会转换成BeanDefinition                    ,Spring是基于BeanDefinition的信息来创建Bean                     。

既然Spring最终是基于BeanDefinition的信息来创建Bean,那么我们是不是可以跳过配置文件和注解声明的方式              ,直接通过手动创建和注册BeanDefinition的方式实现往Spring容器中注入呢?

答案是可以的                    。

前面说过                    ,BeanDefinition最终会被注册到BeanDefinitionRegistry中      ,那么如何拿到BeanDefinitionRegistry呢?主要有以下两种方式:

ImportBeanDefinitionRegistrar BeanDefinitionRegistryPostProcessor ImportBeanDefinitionRegistrar

上面在说@Import的时候              ,关于导入的类实现了ImportBeanDefinitionRegistrar接口的情况没有说                     ,主要是因为在这里说比较合适

publicinterfaceImportBeanDefinitionRegistrar{defaultvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry,BeanNameGeneratorimportBeanNameGenerator){registerBeanDefinitions(importingClassMetadata,registry);}defaultvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){}}

ImportBeanDefinitionRegistrar中有两个方法      ,方法的参数就是BeanDefinitionRegistry。当@Import导入的类实现了ImportBeanDefinitionRegistrar接口之后       ,Spring就会调用registerBeanDefinitions方法                     ,传入BeanDefinitionRegistry              。

来个Demo

UserImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar

publicclassUserImportBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry,BeanNameGeneratorimportBeanNameGenerator){//构建一个BeanDefinition,Bean的类型为UserAbstractBeanDefinitionbeanDefinition=BeanDefinitionBuilder.rootBeanDefinition(User.class)//设置User这个Bean的属性username的值为三友的java日记.addPropertyValue("username","三友的java日记").getBeanDefinition();//把User的BeanDefinition注入到BeanDefinitionRegistry中registry.registerBeanDefinition("user",beanDefinition);}}

测试类

@Import(UserImportBeanDefinitionRegistrar.class)publicclassUserImportBeanDefinitionRegistrarDemo{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext();applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

结果

User(username=三友的java日记)

从结果可以看出             ,成功将User注入到了Spring容器中                    。

上面的例子中有行代码

applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);

这行代码的意思就是把UserImportBeanDefinitionRegistrarDemo这个Bean注册到Spring容器中       ,所以这里其实也算一种将Bean注入到Spring的方式                     ,原理也跟上面一样             ,会为UserImportBeanDefinitionRegistrarDemo生成一个BeanDefinition注册到Spring容器中      。

BeanDefinitionRegistryPostProcessor

除了ImportBeanDefinitionRegistrar可以拿到BeanDefinitionRegistry之外,还可以通过BeanDefinitionRegistryPostProcessor拿到BeanDefinitionRegistry

BeanDefinitionRegistryPostProcessor

这种方式就不演示了              。

手动注册BeanDefinition这种方式还是比较常见的                     。就比如说OpenFeign在启用过程中                     ,会为每个标注了@FeignClient注解的接口创建一个BeanDefinition                    ,然后再往Spring中的注册的,如下是OpenFeign注册FeignClient的部分代码

classFeignClientsRegistrarimplementsImportBeanDefinitionRegistrar,ResourceLoaderAware,EnvironmentAware{privatevoidregisterFeignClient(BeanDefinitionRegistryregistry,AnnotationMetadataannotationMetadata,Map<String,Object>attributes){//构建BeanDefinition              ,class类型为FeignClientFactoryBeanBeanDefinitionBuilderdefinition=BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);Stringalias=contextId+"FeignClient";AbstractBeanDefinitionbeanDefinition=definition.getBeanDefinition();BeanDefinitionHolderholder=newBeanDefinitionHolder(beanDefinition,className,newString[]{alias});//注册BeanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(holder,registry);}}

注册创建完成的Bean

上一节说可以跳过配置文件或者是注解                    ,直接通过注册BeanDefinition以达到将Bean注入到Spring中的目的      。

既然已经可以跳过配置文件或者是注解      ,那么我们可不可以更激进一步              ,跳过注册BeanDefinition这一步                     ,直接往Spring中注册一个已经创建好的Bean呢?

答案依然是可以的       。

因为上面在提到当创建的Bean是单例的时候      ,会将这个创建完成的Bean保存到SingletonBeanRegistry中       ,需要用到直接从SingletonBeanRegistry中查找                     。既然最终是从SingletonBeanRegistry中查找的Bean                     ,那么直接注入一个创建好的Bean有什么不可以呢?

既然可以             ,那么如何拿到SingletonBeanRegistry呢?

其实拿到SingletonBeanRegistry的方法其实很多       ,因为ConfigurableListableBeanFactory就继承了SingletonBeanRegistry接口                     ,所以只要能拿到ConfigurableListableBeanFactory就相当于拿到了SingletonBeanRegistry             。

ConfigurableListableBeanFactory类图

而ConfigurableListableBeanFactory可以通过BeanFactoryPostProcessor来获取

BeanFactoryPostProcessor

来个Demo

RegisterUserBeanFactoryPostProcessor实现BeanFactoryPostProcessor             ,

往Spring容器中添加一个手动创建的User对象 publicclassRegisterUserBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor{@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{//创建一个User对象Useruser=newUser();user.setUsername("三友的java日记");//将这个User对象注入到Spring容器中beanFactory.registerSingleton("user",user);}}

测试

publicclassRegisterUserDemo{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext();applicationContext.register(RegisterUserBeanFactoryPostProcessor.class);applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

结果

User(username=三友的java日记)

从结果还是可以看出,成功从Spring容器中获取到了User对象       。

这种直接将创建好的Bean注入到Spring容器中在Spring框架内部使用的还是比较多的                     ,Spring的一些内建的Bean就是通过这个方式注入到Spring中的                     。

如上图                    ,在SpringBoot项目启动的过程中会往Spring容器中添加两个创建好的Bean,如果你的程序需要使用到这些Bean              ,就可以通过依赖注入的方式获取到             。

虽然基于这种方式可以将Bean注入到Spring容器                    ,但是这种方式注入的Bean是不经过Bean的生命周期的      ,也就是说这个Bean中诸如@Autowired等注解和Bean生命周期相关的回调都不会生效的              ,注入到Spring时Bean是什么样就是什么样                     ,Spring不做处理      ,仅仅只是做一个保存作用。

FactoryBean

FactoryBean是一种特殊的Bean的类型       ,通过FactoryBean也可以将Bean注入到Spring容器中                     。

FactoryBean

当我们通过配置文件       、注解声明或者是注册BeanDenifition的方式                     ,往Spring容器中注入了一个class类型为FactoryBean类型的Bean时候             ,其实真正注入的Bean类型为getObjectType方法返回的类型       ,并且Bean的对象是通过getObject方法返回的                    。

来个Demo

UserFactoryBean实现了FactoryBean                     ,getObjectType返回了User类型             ,所以这个UserFactoryBean会往Spring容器中注入User这个Bean,并且User对象是通过getObject()方法的实现返回的。

publicclassUserFactoryBeanimplementsFactoryBean<User>{@OverridepublicUsergetObject()throwsException{Useruser=newUser();user.setUsername("三友的java日记");returnuser;}@OverridepublicClass<?>getObjectType(){returnUser.class;}}

测试

publicclassUserFactoryBeanDemo{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext();//将UserFactoryBean注入到Spring容器中applicationContext.register(UserFactoryBean.class);applicationContext.refresh();Useruser=applicationContext.getBean(User.class);System.out.println(user);}}

结果

User(username=三友的java日记)

成功通过UserFactoryBean将User这个Bean注入到Spring容器中了              。

FactoryBean这中注入的方式使用也是非常多的                     ,就拿上面举例的OpenFeign来说                    ,OpenFeign为每个FeignClient的接口创建的BeanDefinition的Bean的class类型FeignClientFactoryBean就是FactoryBean的实现                    。

classFeignClientFactoryBeanimplementsFactoryBean<Object>,InitializingBean,ApplicationContextAware{//FeignClient接口类型privateClass<?>type;@OverridepublicObjectgetObject()throwsException{returngetTarget();}@OverridepublicClass<?>getObjectType(){returntype;}}

getObject()方法就会返回接口的动态代理的对象,并且这个代理对象是由Feign创建的              ,这也就实现了Feign和Spring的整合      。

总结

通过以上分析可以看出                    ,将Bean注入到Spring容器中大致可以分为5类:

配置文件 注解声明 注册BeanDefinition 注册创建完成的Bean FactoryBean

以上几种注入的方式      ,在日常业务开发中              ,基本上都是使用注解声明的方式注入Spring中的;在第三方框架在和Spring整合时                     ,注册BeanDefinition和FactoryBean这些注入方式也会使用的比较多;至于配置文件和注册创建完成的Bean的方式      ,有但是不多              。

最后       ,本文所有的示例代码地址:

https://github.com/sanyou3/spring-bean-injection.git

往期热门文章推荐

RocketMQ消息短暂而又精彩的一生

写出漂亮代码的45个小技巧

两万字盘点那些被玩烂了的设计模式

RocketMQ保姆级教程

三万字盘点Spring/Boot的那些常用扩展点

@Async注解的坑                     ,小心

扫码或者搜索关注公众号 三友的java日记              ,及时干货不错过       ,公众号致力于通过画图加上通俗易懂的语言讲解技术                     ,让技术更加容易学习             ,回复 面试 即可获得一套面试真题                     。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
联想笔记本vt怎么打开(联想笔记本电脑vt开启教程) 搜索引擎关键字排名优化(关键词优化推广排名——让你的网站闪耀搜索引擎之巅)