• Spring之B:spring初始化顺序


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

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

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

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

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

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

        JDK1.6中,ConcurrentHashMap 的实现是这样的,先Segment<K,V>[]segments来进行一个分段,然后每个Segment里再包含元素 HashEntry<K,V>[] 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<Consumer<String>> 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<String> 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.
         * <p>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.
         * <p>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<String> beanDefinitionNames = new ArrayList<String>(256);
    
        public void preInstantiateSingletons() throws BeansException {
            List<String> beanNames = new ArrayList<String>(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框架。

  • 相关阅读:
    Junit研究心得——准备研究Junit
    Nginx proxying for Tomcat applications
    将工厂模式升华为插件式框架【文摘】
    JIRA中输入验证时,将验证错误“InvalidInputException”写到对应字段顶部,而不是页面顶部。
    Junit研究心得——Junit中的Annotation
    Junit研究心得——Junit中的约定规则
    Java 编程的动态性【文摘】
    插件式设计开发心得(一)【文摘】
    Java SE 6 新特性: Instrumentation 新功能
    php实现首页自动选择语言跳转
  • 原文地址:https://www.cnblogs.com/duanxz/p/3668423.html
Copyright © 2020-2023  润新知