Spring之B:spring初始化顺序 您所在的位置:网站首页 对象初始化的顺序 Spring之B:spring初始化顺序

Spring之B:spring初始化顺序

2024-07-14 15:43| 来源: 网络整理| 查看: 265

首先,Spring bean的默认加载顺序是怎么控制的

工程中有2个bean,A和B,其中必须先初始化A再初始化B,但是没有depend-on或者Order等方式去保证,只不过恰好刚好这么运行着没出事,但是突然增加了一个C之后,就先初始化B再初始化A导致问题,但是在主干版本上却没问题。

    解决这个问题其实很简单,depend-on即可,但是为什么会分支版本上会增加C后就改变AB的初始化顺序?为什么主干版本上同样添加没问题呢?可以看spring的源码 DefaultListableBeanFactory 类中有

private final Map beanDefinitionMap = new ConcurrentHashMap(256);这个map保存的就是xml中定义的bean结构,spring解析定义的bean之后,保存到这里,其中key为beanid,value为bean的详细信息,根据beanid算出hashCode分别为1505068002,1682286698,从定义可以看到这是一个ConcurrentHashMap,在获取到定义后,spring在初始化的时候是怎么初始化的呢?显然也是从这个定义里取值,为了简化问题,我们理解为是类似如下方式

for(Entry entry : beanDefinitionMap)去遍历bean然后根据BeanDefinition来初始化,考虑分支版本的是用JDK1.6,主干是用JDK1.8,不同ConcurrentHashMap的实现是否会导致取出来的顺序不一样呢?

    JDK1.8中,ConcurrentHashMap 的实现是这样的,数组Node[]table,每个元素为一个Node,每个元素直接落到Node上,去追加链表或者在红黑树上,总之是一次hash

    JDK1.6中,ConcurrentHashMap 的实现是这样的,先Segment[]segments来进行一个分段,然后每个Segment里再包含元素 HashEntry[] table,即,会对每个元素会有2次hash,第一次定位到Segment,第二次在Segment内部定位到自己的位置userService

 可以知道,在JDK1.8中2个bean的位置是固定的(所以主干版本同样添加但是AB初始化顺序不变),但是在JDK1.6中可能会发生这种情况:B在第一个Segment中,A在第二个Segment中,导致取出来的时候先获取到B,后取出来A,所以出现了空指针,同样,也可以解释,为什么新增了1个id为C的bean就导致了问题,但是之前没问题,因为新增bean导致ConcurrentHashMap中部分bean所在的Segment发生变化。

    当然,对有依赖关系显然是显式指定出来的好,不然像这样坑后来人就不好了

使用Spring @DependsOn控制bean加载顺序

spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范。但spring保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载。但如果beanA不直接依赖B,我们如何让B仍先加载呢?

控制bean初始化顺序可能有些场景中,bean A 间接依赖 bean B。如Bean B应该需要更新一些全局缓存,可能通过单例模式实现且没有在spring容器注册,bean A需要使用该缓存;因此,如果bean B没有准备好,bean A无法访问。

另一个场景中,bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。

简言之,有很多场景需要bean B应该被先于bean A被初始化,从而避免各种负面影响。我们可以在bean A上使用@DependsOn注解,告诉容器bean B应该先被初始化。下面通过示例来说明。

示例说明示例通过事件机制说明,发布者和监听者,然后通过spring配置运行。为了方便说明,示例进行了简化。

EventManager.java 事件管理类,维护监听器列表,通过单例方法获取事件管理器,可以增加监听器或发布事件。

import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; public class EventManager { private final List listeners = new ArrayList(); private EventManager() { } private static class SingletonHolder { private static final EventManager INSTANCE = new EventManager(); } public static EventManager getInstance() { return SingletonHolder.INSTANCE; } public void publish(final String message) { listeners.forEach(l -> l.accept(message)); } public void addListener(Consumer eventConsumer) { listeners.add(eventConsumer); } }

EventPublisherBean.java 事件发布类,通过EventManager类发布事件。

import com.logicbig.example.EventManager; public class EventPublisherBean { public void initialize() { System.out.println("EventPublisherBean initializing"); EventManager.getInstance().publish("event published from EventPublisherBean"); } }

EventListenerBean.java 事件监听者,可以增加监听器。

import com.logicbig.example.EventManager; public class EventListenerBean { private void initialize() { EventManager.getInstance(). addListener(s -> System.out.println("event received in EventListenerBean : " + s)); } }

AppConfig.java 配置运行类。

@Configuration @ComponentScan("com.logicbig.example") public class AppConfig { @Bean(initMethod = "initialize") @DependsOn("eventListener") public EventPublisherBean eventPublisherBean () { return new EventPublisherBean(); } @Bean(name = "eventListener", initMethod = "initialize") // @Lazy public EventListenerBean eventListenerBean () { return new EventListenerBean(); } public static void main (String... strings) { new AnnotationConfigApplicationContext(AppConfig.class); } }

运行AppConfig的main方法,输出结果为:

EventListenerBean initializing EventPublisherBean initializing event received in EventListenerBean : event published from EventPublisherBean

总结如果我们注释掉@DependsOn("eventListener"),我们可能不确定获得相同结果。尝试多次运行main方法,偶尔我们将看到EventListenerBean 没有收到事件。为什么是偶尔呢?因为容器启动过程中,spring按任意顺序加载bean。

那么当不使用@DependsOn可以让其100%确定吗?可以使用@Lazy注解放在eventListenerBean ()上。因为EventListenerBean在启动阶段不加载,当其他bean需要其时才加载。这次我们仅EventListenerBean被初始化。

EventPublisherBean initializing现在从新增加@DependsOn,也不删除@Lazy注解,输出结果和第一次一致,虽然我们使用了@Lazy注解,eventListenerBean在启动时仍然被加载,因为@DependsOn表明需要EventListenerBean。

@Lazy

@Lazy用于指定该Bean是否取消预初始化。主要用于修饰Spring Bean类,用于指定该Bean的预初始化行为,

使用该Annotation时可以指定一个boolean型的value属性,该属性决定是否要预初始化该Bean

lazy代表延时加载,lazy=false,代表不延时,如果对象A中还有对象B的引用,会在A的xml映射文件中配置b的对象引用,多对一或一对多,不延时代表查询出对象A的时候,会把B对象也查询出来放到A对象的引用中,A对象中的B对象是有值的。 lazy=true代表延时,查询A对象时,不会把B对象也查询出来,只会在用到A对象中B对象时才会去查询,默认好像是false,你可以看看后台的sql语句的变化就明白了,一般需要优化效率的时候会用到 @Lazy(true) @Component public class Chinese implements Person{ //codes here }

@DependsOn用于强制初始化其他Bean。可以修饰Bean类或方法,使用该Annotation时可以指定一个字符串数组作为参数,每个数组元素对应于一个强制初始化的Bean

@DependsOn({"steelAxe","abc"}) @Component public class Chinese implements Person{ //codes here } @Order

@Order注解的源码如下,value用例赋值,默认赋值Ordered.LOWEST_PRECEDENCE,即默认优先级最低:

@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) public @interface Order { /** * The order value. Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE; }

再来看下Order接口的源码如下,一目了然,值越小优先级越高,即被优先加载(precedence即优先级):

public interface Ordered { /** * Useful constant for the highest precedence value. * @see java.lang.Integer#MIN_VALUE */ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;//值越小,优先级越高 /** * Useful constant for the lowest precedence value. * @see java.lang.Integer#MAX_VALUE */ int LOWEST_PRECEDENCE = Integer.MAX_VALUE;//值越大,优先级越低

 

/** * Return the order value of this object, with a * higher value meaning greater in terms of sorting. *

Normally starting with 0, with {@code Integer.MAX_VALUE} * indicating the greatest value. Same order values will result * in arbitrary positions for the affected objects. *

Higher values can be interpreted as lower priority. As a * consequence, the object with the lowest value has highest priority * (somewhat analogous to Servlet "load-on-startup" values). * @return the order value */ int getOrder();

 

拓展①:为何在类上加@Order就可以对类(映射到容器中就是bean)有效排序了?

先了解下Java强大的注解功能,可以参考Java注解教程及自定义注解

拓展②:给类@Order注解后spring容器具体怎么给bean排序的呢?

在@Order注解的源码上,作者写了相关信息,感兴趣可以查看下

拓展③:@Order的作用域可以是类、方法、类成员,方法和类成员暂时没有测试,保留。

Springboot的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序

Springboot的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序。了解Spring内Bean的解析,加载和实例化顺序机制有助于我们更好的使用Spring/Springboot,避免手动的去干预Bean的加载过程,搭建更优雅的框架。

Spring容器在实例化时会加载容器内所有非延迟加载的单例类型Bean,看如下源码:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean { //刷新Spring容器,相当于初始化 public void refresh() throws BeansException, IllegalStateException { ...... // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); } } public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { /** List of bean definition names, in registration order */ private volatile List beanDefinitionNames = new ArrayList(256); public void preInstantiateSingletons() throws BeansException { List beanNames = new ArrayList(this.beanDefinitionNames); for (String beanName : beanNames) { ...... getBean(beanName); //实例化Bean } } }

ApplicationContext内置一个BeanFactory对象,作为实际的Bean工厂,和Bean相关业务都交给BeanFactory去处理。 在BeanFactory实例化所有非延迟加载的单例Bean时,遍历beanDefinitionNames 集合,按顺序实例化指定名称的Bean。beanDefinitionNames 属性是Spring在加载Bean Class生成的BeanDefinition时,为这些Bean预先定义好的名称,看如下代码:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { ...... this.beanDefinitionNames.add(beanName); } }

BeanFactory在加载一个BeanDefinition(也就是加载Bean Class)时,将相应的beanName存入beanDefinitionNames属性中,在加载完所有的BeanDefinition后,执行Bean实例化工作,此时会依据beanDefinitionNames的顺序来有序实例化Bean,也就是说Spring容器内Bean的加载和实例化是有顺序的,而且近似一致,当然仅是近似。

Spring在初始化容器时,会先解析和加载所有的Bean Class,如果符合要求则通过Class生成BeanDefinition,存入BeanFactory中,在加载完所有Bean Class后,开始有序的通过BeanDefinition实例化Bean。

我们先看加载Bean Class过程,零配置下Spring Bean的加载起始于ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法,我总结了下其加载解析Bean Class的流程:

配置类可以是Spring容器的起始配置类,也可以是通过@ComponentScan扫描得到的类,也可以是通过@Import引入的类。如果这个类上含有@Configuration,@Component,@ComponentScan,@Import,@ImportResource注解中的一个,或者内部含有@Bean标识的方法,那么这个类就是一个配置类,Spring就会按照一定流程去解析这个类上的信息。

在解析的第一步会校验当前类是否已经被解析过了,如果是,那么需要按照一定的规则处理(@ComponentScan得到的Bean能覆盖@Import得到的Bean,@Bean定义的优先级最高)。

如果未解析过,那么开始解析:

解析内部类,查看内部类是否应该被定义成一个Bean,如果是,递归解析。解析@PropertySource,也就是解析被引入的Properties文件。解析配置类上是否有@ComponentScan注解,如果有则执行扫描动作,通过扫描得到的Bean Class会被立即解析成BeanDefinition,添加进beanDefinitionNames属性中。之后查看扫描到的Bean Class是否是一个配置类(大部分情况是,因为标识@Component注解),如果是则递归解析这个Bean Class。解析@Import引入的类,如果这个类是一个配置类,则递归解析。解析@Bean标识的方法,此种形式定义的Bean Class不会被递归解析解析父类上的@ComponentScan,@Import,@Bean,父类不会被再次实例化,因为其子类能够做父类的工作,不需要额外的Bean了。在1,3,4,6中都有递归操作,也就是在解析一个Bean Class A时,发现其上能够获取到其他Bean Class B信息,此时会递归的解析Bean Class B,在解析完Bean Class B后再接着解析Bean Class A,可能在解析B时能够获取到C,那么也会先解析C再解析B,就这样不断的递归解析。

在第3步中,通过@ComponentScan扫描直接得到的Bean Class会被立即加载入beanDefinitionNames中,但@Import和@Bean形式定义的Bean Class则不会,也就是说正常情况下面@ComponentScan直接得到的Bean其实例化时机比其他两种形式的要早。

通过@Bean和@Import形式定义的Bean Class不会立即加载,他们会被放入一个ConfigurationClass类中,然后按照解析的顺序有序排列,就是图片上的 “将配置类有序排列“。一个ConfigurationClass代表一个配置类,这个类可能是被@ComponentScan扫描到的,则此类已经被加载过了;也可能是被@Import引入的,则此类还未被加载;此类中可能含有@Bean标识的方法。

Spring在解析完了所有Bean Class后,开始加载ConfigurationClass。如果这个ConfigurationClass是被Import的,也就是说在加载@ComponentScan时其未被加载,那么此时加载ConfigurationClass代表的Bean Class。然后加载ConfigurationClass内的@Bean方法。

顺序总结:@ComponentScan > @Import > @Bean

下面看实际的启动流程:

此图顺序验证小框架:Spring Bean解析,加载及实例化顺序验证小框架

Bean Class的结构图如上所示,A是配置类的入口,通过A能直接或间接的引入一个模块。

此时启动Spring容器,将A引入容器内。

如果A是通过@ComponentScan扫描到的,那么此时的加载顺序是: A > D > F > B > E > G > C

如果A是通过@Import形式引入的,那么此时的加载顺讯是: D > F > B > E > G > A > C

当然以上仅仅代表着加载Bean Class的顺序,实际实例化Bean的顺序和加载顺序大体相同,但还是会有一些差别。 Spring在通过getBean(beanName)形式实例化Bean时,会通过BeanDefinition去生成Bean对象。在这个过程中,如果BeanDefinition的DependsOn不为空,从字面理解就是依赖某个什么,其值一般是某个或多个beanName,也就是说依赖于其他Bean,此时Spring会将DependsOn指定的这些名称的Bean先实例化,也就是先调用getBean(dependsOn)方法。我们可以通过在Bean Class或者@Bean的方法上标识@DependsOn注解,来指定当前Bean实例化时需要触发哪些Bean的提前实例化。

当一个Bean A内部通过@Autowired或者@Resource注入Bean B,那么在实例化A时会触发B的提前实例化,此时会注册A>B的dependsOn依赖关系,实质和@DependsOn一样,这个是Spring自动为我们处理好的。

了解Spring Bean的解析,加载及实例化的顺序机制能够加深对Spring的理解,搭建更优雅简介的Spring框架。

 

 

 

 

 

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有