
spring中的bean对象和java对象是有些许差别的,spring中的bean包含了java对象,并且是基于java对象在spring中做了一些列的加工,所以说spring中的bean比java对象更加丰富。在spring中bean的实例化有2个时机:
下面从spring ioc容器初始化的时候,预实例化的bean为线索来追溯bean的实例化和依赖注入过程,这个过程涵盖了getBean方法。
在spring ioc容器初始化的时候,触发了所有预实例化的bean的加载,这里必须是非抽象、单例和非懒加载的bean才符合条件进行预实例化。具体bean的实例化是在getBean方法中。
这里通过getSingleton先从缓存中获取bean实例。
从缓存中获取很好理解,分别从spring容器的一级缓存singletonObjects、二级缓存earlySingletonObjects和三级缓存singletonFactories中获取bean实例。在初次获取bean的时候,这里的缓存肯定为空的,但是对于存在循环依赖的bean,这里的一级或二级缓存就不是空的。在有循环依赖的bean中,这里一级缓存会存在不为空的情况,这个时候通过singletonFactory.getObject的时候,返回的可能是一个bean实例,也有可能是一个提前进行aop的代理对象( 正常情况下aop是发生在bean初始化的时候完成的 ),对于有循环依赖并且需要进行aop的bean,在这里会进行提前aop代理对象的生成。
当缓存中没有找到bean实例的时候:
通过singletonFactory.getObject回调前面的createBean方法获取bean实例,然后对于是新创建的bean实例添加到spring容器中的一级缓存singletonObjects中。下面从createBean中看bean是如何实例化的。
在这里可以看到整个bean实例化的全貌,也可以看出bean在spring中的生命周期:
可以看到在spring中是通过SimpleInstantiationStrategy类来进行bean对象的创建,有java反射和cglib两种方式进行bean的创建,判断依据是,在BeanDefinition中有动态代理或增强的时候会用cglib去创建bean对象。
根据bean是单例,并且当前bean是在创建中的时候,会去添加一个ObjectFactory到spring容器的三级缓存singletonFactories中。这里会有可能提前进行aop代理对象的生成。
这里把一个ObjectFactory添加到spring容器的三级缓存中去,在前面通过缓存获取bean的时候,会执行这里的getEarlyBeanReference方法,在这个方法中会去执行一些后置处理器的方法。 AbstractAutoProxyCreator会生成一个代理对象,这里就是提前进行了aop代理对象的生成。
在这里会去执行一些后置处理器的postProcessPropertyValues方法, @Autowired注解的AutowiredAnnotationBeanPostProcessor就是在这里生效的 。下面来看看AutowiredAnnotationBeanPostProcessor对属性依赖注入的处理
在执行后置处理器的postProcessAfterInitialization方法时,会去执行AbstractAutoProxyCreator类的postProcessAfterInitialization方法,这里会去根据需要生成当前bean的aop代理对象。
通常情况下,应用程序开发人员不需要对ApplicationContext实现类进行子类化。相反,Spring IoC容器可以通过插入特殊集成接口的实现来进行扩展。接下来的几节将描述这些集成接口。
BeanPostProcessor接口定义了回调方法,你可以实现这些方法来提供你自己的(或覆盖容器的默认)实例化逻辑、依赖性解析逻辑等等。如果你想在Spring容器完成实例化、配置和初始化Bean之后实现一些自定义逻辑,你可以插入一个或多个自定义BeanPostProcessor实现。
你可以配置多个BeanPostProcessor实例,你可以通过设置order属性控制这些BeanPostProcessor实例的运行顺序。只有当BeanPostProcessor实现了Ordered接口时,你才能设置这个属性。如果你编写自己的BeanPostProcessor,你也应该考虑实现Ordered接口。更多细节,请参见BeanPostProcessor和Ordered接口的javadoc。也请看关于BeanPostProcessor实例的程序化注册的说明。
BeanPostProcessor实例对Bean实例进行 *** 作。也就是说,Spring IoC容器实例化一个Bean实例,然后由BeanPostProcessor实例进行工作。BeanPostProcessor实例在每个容器中都有作用域。这只有在你使用容器层次结构时才有意义。如果你在一个容器中定义了一个BeanPostProcessor,它只对该容器中的Bean进行后处理。换句话说,在一个容器中定义的Bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器是同一层次结构的一部分。
ApplicationContext会自动检测配置元数据中定义了实现BeanPostProcessor接口的Bean。ApplicationContext 将这些 bean 注册为后处理器,以便以后在创建 bean 时可以调用它们。Bean后处理器可以像其他Bean一样被部署在容器中。
下面的例子展示了如何在ApplicationContext的上下文中编写、注册和使用BeanPostProcessors
Output:
BeanFactoryPostProcessor的实现被用来读取配置元数据,并在IOC容器实例化Bean之前对其进行修改。我们可以配置多个BeanFactoryPostProcessor,你还可以通过设置order属性来控制这些BeanFactoryPostProcessor的执行顺序。只有当BeanFactoryPostProcessor实现了Ordered接口时,你才能设置order属性。
BeanFactoryPostProcessor是一个功能接口,它有一个抽象方法postProcessBeanFactory(),实现这个方法,可以自定义修改Bean定义。请注意,当这个方法被调用时,所有的Bean定义都已经被加载,但还没有Bean被实例化。它允许重写或添加属性,即使是对急于初始化的Bean。BeanFactoryPostProcessor定义如下:
BeanFactoryPostProcessor示例
举个简单的例子,假如我们已经在properties配置文件中为数据库配置设置了属性,但在运行时我们想更改url,其它属性保持不变,我们可以用BeanFactoryPostProcessor访问bean定义,并修改属性值。当然,我们可以配置多个properties文件,并进行切换,这里仅做一个示例。
Output:
在Spring的bean容器中,有两种bean:普通bean和工厂bean。Spring直接使用前者,而后者可以自己产生对象,由框架来管理。简单地说,我们可以通过实现org.springframework.beans.factory.FactoryBean接口来建立一个工厂bean。
FactoryBean 接口提供了三种方法:
示例:
当我们要获取FactoryBean实例时,需要在bean的id前面加上 & 符号,比如getBean('&tool')
在如日中天的 Spring 架构体系下,不管是什么样的组件,不管它采用的接入方式如何眼花缭乱,它们永远只有一个目的:
Spring 容器内可以认为只有一种东西,那就是 bean,但是围绕bean的生命周期,Spring 添加了许多东西
实例化 bean 实例是 spring 针对 bean 作的拓展最多的周期
它包括:
常见扫描相关内容:
@Component、@Service、@Controller、@Configuration、applicationContext.xml
spring/springboot 在启动的时候,会扫描到这些注解或配置文件修饰的类信息
根据拿到的类信息,经过第二步解析后,转换成 BeanDefintion存入到 spring 容器当中,BeanDefintion描述 bean 的 class、scop、beanName 等信息
在 bean 的解析过程中,我们常用到的 Properties 读取 、 @Configuration配置类的处理 会在这一步完成
bean 的实例化实际有自动完成和调用 getBean()时候完成,还有容器初始化完毕之后实例化 bean ,他们都是根据 bean 的定义BeanDefintion来反射目标 bean 类,并放到 bean 容器当中
这就是大名鼎鼎的 bean 容器,就是一个 Map
这一阶段是 @Value、@Autowired、@Resource注解起作用的阶段
BeanPostProcessor前置处理方法
@PostConstruct注解起作用的阶段
BeanPostProcessor后置处理方法
@PreDestroy注解起作用的阶段
bean的销毁过程中,主要的作用就是释放一些需要手动释放的资源和一些收尾工作,如文件归并、连接池释放等
在了解了 Spring bean 的生命周期后,我们接下来介绍自建 Spring 组件的接入方式
使用配置类接入 Spring ,一般需要搭配 PostConstruct来使用,并且要确保 Spring 能扫描到配置
如,在组件 quartz-configable1.0 版本当中,就是使用的这种方式
quartz-configable需要扫描用户自定义的job来注册到quartz-configable自动创建的调度器Scheduler当中,并启动调度器Scheduler
在注册 Job的过程当中,又添加了自定义的TriggerListener监听器,来监听配置的变动,以动态调整Job执行时机
QuartzInitConfig类的作用是把扫描到的任务类放入调度器当中,并添加自定义监听(用于动态修改 cron 表达式)
此类加载有两个过程:
步骤 1所需要的功能与 Spring 的注入功能完美契合,而恰好@Configuration修饰的类也被当作了一个Spring bean,所以才能顺利注入组件需要的资源
步骤 2的初始化任务,极为契合Spring bean创建完毕后的初始化动作@PostConstruct当中,它同样是资源注入完毕后的初始化动作。
有时候,我们希望通过开关或者特定的配置来启用应用内具备的功能,这时候,我们可以使用 @ConditionalOnProperty来解决问题
risk组件扫描出符合规则的切点,在切点执行之前,去执行发送风控数据到风控平台的动作
虽然类 RiskAspectConfig是一个 Spring 配置类,方法defaultPointcutAdvisor()创建了一个切点顾问,用来在切点方法处实现风控的功能,但是,并不是应用启动之后,切点就会生效,这是因为有@ConditionalOnProperty的存在
@ConditionalOnProperty的作用:
根据提供的条件判断对应的属性是否存在,存在,则加载此配置类,不存在,则忽略。
当应用中存在如下配置时:
RiskAspectConfig配置类才会被加载,才会生成切点顾问DefaultPointcutAdvisor,因此切点就会生效
当需要的配置逐渐增多的时候,一条条添加进 @ConditionalOnProperty显得比较冗长复杂,这时候该如何处理呢?
在项目 fastdfs-spring-boot-starter当中,像上述需要的配置有很多,那么它是怎么处理的呢?
它把需要的配置放到了一个 Java 类里
其中, @ConfigurationProperties指定了配置的prefix,上述配置相当于
通常的,在开发组件的时候,我们使用第二种方式,把 Properties 的启用,交给 @Configuration配置类来管理,大家可以想想为什么
@EnableConfigurationProperties(FastDfsProperties.class)启用了括号内的 Properties 类,并把它们注入到 Spring 容器当中,使其可以被其他Spring bean导入
有时候,我们开发的组件的类路径和应用的类路径不同,比如,应用类路径常常为 com.xxx.xxx,而组件的类路径常常为com.xxx.yyy,这时候,经常需要为 Spring 指定扫描路径,才能把我们的组件加载进去,如果在自己项目当中加载上述quartz-configable组件,组件类路径为com.xxx.yyy:
如果新增了类似这样的 quartz-configable组件,就需要改动@ComponentScan代码,这对启动类是有侵入性的,也是繁琐的,也极有可能写错,当组件路径有改动的时候也需要跟着改动
怎样避免这种硬编码形式的注入呢?
Springboot 在加载类的时候,会扫描 classpath下的META-INF/spring.factories文件,当发现了spring.factories文件后,根据文件中的配置来加载类
借用这个规则,现在来升级我们的 quartz-configable组件
我们在组件项目 resources目录下添加META-INF/spring.factories文件,文件内容如下
然后在应用启动类当中删除已经无用的 @Component注解即可
此时, quartz-configable依然能生效
使用 META-INF/spring.factories虽然带来了简洁和便利,但是它总是去自动加载配置类,所以我们在设计组件的时候,一定要搭配@ConditionOnxxxx注解,有条件的去加载我们的组件
就像上面说的一样,使用 META-INF/spring.factories总会去加载配置类,自定义扫描路径有可能会写错类路径,那么,还有没有其他方式呢?
有,使用自定义注解来注入自己的组件,就像 dubbo的 starter 组件一样,我们自己造一个@EnableXxx注解
自定义注解的核心是 Spring 的 @Import注解,它基于@Import注解来注入组件自身需要的资源和初始化组件自身
@Import注解是 Spring 用来注入Spring bean的一种方式,可以用来修饰别的注解,也可以直接在 Springboot 配置类上使用。
它只有一个value属性需要设置,来看一下源码
这里的 value属性只接受三种类型的Class:
下面针对三种类型的 Class 分别做简单介绍,中间穿插自定义注解与外部配置的结合使用方式。
像 Springboot 中的配置类一样正常使用,需要注意的是,如果该类的包路径已在 Springboot 启动类上配置的扫描路径下,则不需要再重新使用 @Import导入了,因为@Import的目的是注入 bean,但是 Springboot 启动类自动扫描已经可以注入你想通过@Import导入的 bean 了。
当 @Import修饰自定义注解时候,通常会导入这个接口的实现类。
来看一下接口定义
通过这种方式,我们可以根据自定义注解配置的属性值来注入 Spring Bean 信息。
来看如下案例,我们通过一个注解,启动 RocketMq 的消息发送器:
这是一个服务项目的启动类,这个服务开启了 RocketMq 的一个发送器,并且分到 xxx 组里。
来看一下 @EnableMqProducer注解
这里使用 @Import导入了两个配置类,第一个是接口org.springframework.context.annotation.ImportBeanDefinitionRegistrar的实现类,第二个是被@Configuration修饰的配置类
我们看第一个类 XXXRegistrar,这个类的功能是注入一个自定义的DefaultMQProducer到Spring 容器中,使业务方可以直接通过@Autowired注入DefaultMQProducer使用
到这里,我们已经注入了一个 DefaultMQProducer的实例到 Spring 容器中,但是这个实例,还不完整,比如:
但是好消息是,我们已经可以通过注入来使用这个未完成的实例了。
上面遗留的问题,就是第二个类接下来要做的事。
来看第二个配置类
到这里,通过自定义注解和外部配置的结合,一个完整的消息发送器就可以使用了,但方式有取巧之嫌,因为在消息发送器启动之前,不知道还有没有别的类使用了这个实例,这是不安全的。
首先看一下接口
这个接口的实现类如果没有进行 Spring Aware接口拓展,功能比较单一,因为我们无法参与 Spring Bean 的构建过程,只是告诉 Spring 要注入的 Bean 的名字。不再详述。
综上所述,我们一共聊了三种形式的组件创建方式
其中穿插了 @ConditionOnXxxx选择性启动、Properties封装的技术,快去试一下吧
原文地址: https://www.cnblogs.com/qnlcy/p/15905682.html
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)