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框架。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM