广州网站设计制作,php大型网站开发视频,济南公交优化,备案系统网站前言
关于Bean注入Spring容器的方式网上也有很多相关文章#xff0c;但是很多文章可能会存在以下常见的问题 注入方式总结的不全 没有分析可以使用这些注入方式背后的原因 没有这些注入方式在源码中的应用示例 ...
所以本文就带着解决上述的问题的目的来重新梳理一下Bea…前言
关于Bean注入Spring容器的方式网上也有很多相关文章但是很多文章可能会存在以下常见的问题 注入方式总结的不全 没有分析可以使用这些注入方式背后的原因 没有这些注入方式在源码中的应用示例 ...
所以本文就带着解决上述的问题的目的来重新梳理一下Bean注入到Spring的那些姿势。
配置文件
配置文件的方式就是以外部化的配置方式来声明Spring Bean在Spring容器启动时指定配置文件。配置文件方式现在用的不多了但是为了文章的完整性和连续性这里我还是列出来了知道的小伙伴可以自行跳过这节。
配置文件的类型Spring主要支持xml和properties两种类型。
xml
在XmlBeanInjectionDemo.xml文件中声明一个class为类型为User的Bean
?xml version1.0 encodingUTF-8?
beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlnshttp://www.springframework.org/schema/beansxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdbean classcom.sanyou.spring.bean.injection.User//beansUser
Data
ToString
public class User {private String username;}测试
public class XmlBeanInjectionDemo {public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext new ClassPathXmlApplicationContext(classpath:XmlBeanInjectionDemo.xml);applicationContext.refresh();User user applicationContext.getBean(User.class);System.out.println(user);}}结果
User(usernamenull)可以看出成功将User注入到Spring中由于没有设置username属性值所以是null。
properties
除了xmlspring还支持properties配置文件声明Bean的方式。
如下在PropertiesBeanInjectionDemo.properties文件中声明了class类型为User的Bean并且设置User的username属性为sanyou。
user.(class) com.sanyou.spring.bean.injection.User
user.username sanyou测试
public class PropertiesBeanInjectionDemo {public static void main(String[] args) {GenericApplicationContext applicationContext new GenericApplicationContext();//创建一个PropertiesBeanDefinitionReader可以从properties读取Bean的信息将读到的Bean信息放到applicationContext中PropertiesBeanDefinitionReader propReader new PropertiesBeanDefinitionReader(applicationContext);//创建一个properties文件对应的Resource对象Resource classPathResource new ClassPathResource(PropertiesBeanInjectionDemo.properties);//加载配置文件propReader.loadBeanDefinitions(classPathResource);applicationContext.refresh();User user applicationContext.getBean(User.class);System.out.println(user);}}结果
User(usernamesanyou)成功获取到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的分页插件的时候就可以按如下方式这么来声明。
Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}此时就能将MybatisPlusInterceptor这个Bean注入到Spring容器中。
Import
Import注解也可以用来将Bean注入到Spring容器中Import注解导入的类可以分为三种情况 普通类 类实现了ImportSelector接口 类实现了ImportBeanDefinitionRegistrar接口
普通类
普通类其实就很简单就是将Import导入的类注入到Spring容器中这没什么好说的。
类实现了ImportSelector接口
public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);Nullabledefault PredicateString getExclusionFilter() {return null;}}当Import导入的类实现了ImportSelector接口的时候Spring就会调用selectImports方法的实现获取一批类的全限定名最终这些类就会被注册到Spring容器中。
比如如下代码中UserImportSelector实现了ImportSelectorselectImports方法返回User的全限定名
public class UserImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println(调用 UserImportSelector 的 selectImports 方法获取一批类限定名);return new String[]{com.sanyou.spring.bean.injection.User};}}当使用Import注解导入UserImportSelector这个类的时候其实最终就会把User注入到Spring容器中如下测试
Import(UserImportSelector.class)
public class ImportSelectorDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext();//将 ImportSelectorDemo 注册到容器中applicationContext.register(ImportSelectorDemo.class);applicationContext.refresh();User user applicationContext.getBean(User.class);System.out.println(user);}}运行结果
User(usernamenull)对于类实现了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创建过程中所需要的元信息。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {//设置Bean classNamevoid setBeanClassName(Nullable String beanClassName);//获取Bean classNameNullableString getBeanClassName();//设置是否是懒加载void setLazyInit(boolean lazyInit);//判断是否是懒加载boolean isLazyInit();//判断是否是单例boolean isSingleton();}如上代码是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的一种方式最终都会转换成BeanDefinitionSpring是基于BeanDefinition的信息来创建Bean。
既然Spring最终是基于BeanDefinition的信息来创建Bean那么我们是不是可以跳过配置文件和注解声明的方式直接通过手动创建和注册BeanDefinition的方式实现往Spring容器中注入呢
答案是可以的。
前面说过BeanDefinition最终会被注册到BeanDefinitionRegistry中那么如何拿到BeanDefinitionRegistry呢主要有以下两种方式 ImportBeanDefinitionRegistrar BeanDefinitionRegistryPostProcessor
ImportBeanDefinitionRegistrar
上面在说Import的时候关于导入的类实现了ImportBeanDefinitionRegistrar接口的情况没有说主要是因为在这里说比较合适
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}ImportBeanDefinitionRegistrar中有两个方法方法的参数就是BeanDefinitionRegistry。当Import导入的类实现了ImportBeanDefinitionRegistrar接口之后Spring就会调用registerBeanDefinitions方法传入BeanDefinitionRegistry。
来个Demo
UserImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar
public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {//构建一个 BeanDefinition , Bean的类型为 UserAbstractBeanDefinition beanDefinition BeanDefinitionBuilder.rootBeanDefinition(User.class)//设置User这个Bean的属性username的值为三友的java日记.addPropertyValue(username, 三友的java日记).getBeanDefinition();//把User的BeanDefinition注入到BeanDefinitionRegistry中registry.registerBeanDefinition(user, beanDefinition);}}测试类
Import(UserImportBeanDefinitionRegistrar.class)
public class UserImportBeanDefinitionRegistrarDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext();applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);applicationContext.refresh();User user 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的部分代码
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, MapString, Object attributes) {//构建BeanDefinitionclass类型为FeignClientFactoryBeanBeanDefinitionBuilder definition BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);String alias contextId FeignClient;AbstractBeanDefinition beanDefinition definition.getBeanDefinition();BeanDefinitionHolder holder new BeanDefinitionHolder(beanDefinition, className, new String[] { 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对象
public class RegisterUserBeanFactoryPostProcessor implements BeanFactoryPostProcessor {Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {//创建一个User对象User user new User();user.setUsername(三友的java日记);//将这个User对象注入到Spring容器中beanFactory.registerSingleton(user, user);}}测试
public class RegisterUserDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext();applicationContext.register(RegisterUserBeanFactoryPostProcessor.class);applicationContext.refresh();User user 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实现了FactoryBeangetObjectType返回了User类型所以这个UserFactoryBean会往Spring容器中注入User这个Bean并且User对象是通过getObject()方法的实现返回的。
public class UserFactoryBean implements FactoryBeanUser {Overridepublic User getObject() throws Exception {User user new User();user.setUsername(三友的java日记);return user;}Overridepublic Class? getObjectType() {return User.class;}
}测试
public class UserFactoryBeanDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext();//将UserFactoryBean注入到Spring容器中applicationContext.register(UserFactoryBean.class);applicationContext.refresh();User user applicationContext.getBean(User.class);System.out.println(user);}}结果
User(username三友的java日记)成功通过UserFactoryBean将User这个Bean注入到Spring容器中了。
FactoryBean这中注入的方式使用也是非常多的就拿上面举例的OpenFeign来说OpenFeign为每个FeignClient的接口创建的BeanDefinition的Bean的class类型FeignClientFactoryBean就是FactoryBean的实现。
class FeignClientFactoryBean implements FactoryBeanObject, InitializingBean, ApplicationContextAware {// FeignClient接口类型private Class? type;Overridepublic Object getObject() throws Exception {return getTarget();}Overridepublic Class? getObjectType() {return type;}
}getObject()方法就会返回接口的动态代理的对象并且这个代理对象是由Feign创建的这也就实现了Feign和Spring的整合。
总结
通过以上分析可以看出将Bean注入到Spring容器中大致可以分为5类 配置文件 注解声明 注册BeanDefinition 注册创建完成的Bean FactoryBean
以上几种注入的方式在日常业务开发中基本上都是使用注解声明的方式注入Spring中的在第三方框架在和Spring整合时注册BeanDefinition和FactoryBean这些注入方式也会使用的比较多至于配置文件和注册创建完成的Bean的方式有但是不多。
联系方式
关于文章中大家有任何疑问可以通过关注公众号《编程乐学》进行留言同时公众号还有更多有趣的项目以及关于学习编程的笔记资料大家可以看看欢迎大家进行留言。