Spring Bean 詳解

word發表於2022-08-25

Spring Bean 詳解

Ioc例項化Bean的三種方式

1 建立Bean

1 使用無參建構函式

這也是我們常用的一種。在預設情況下,它會透過反射調⽤⽆參建構函式來建立物件。如果類中沒有⽆參建構函式,將建立 失敗。

  • class: 為需要註冊Bean類檔案的位置

applicationContext.xml配置檔案

image-20220825154455666

測試類:

/**
 * @author : look-word
 * 2022-08-25 11:36
 **/
public class IocTest {
    @Test
    public void testIoc() {
        ApplicationContext context = // 讀取配置檔案
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        // 容器中根據名稱獲取Bean
        ConnectionUtils connectionUtils
                = (ConnectionUtils) context.getBean("connectionUtils");
        System.out.println(connectionUtils);
    }
}

結果:

# 輸出了物件的地址
com.lagou.edu.utils.ConnectionUtils@3ecd23d9

2 使用靜態方法建立

  • 簡單來說,就是呼叫某個類的靜態方法建立物件

在實際開發中,我們使⽤的物件有些時候並不是直接透過建構函式就可以建立出來的,它可能在創 建的過程 中會做很多額外的操作。此時會提供⼀個建立物件的⽅法,恰好這個⽅法是static修飾的 ⽅法,即是此種情況:

例如,我們在做Jdbc操作時,會⽤到java.sql.Connection接⼝的實現類,如果是mysql資料庫,那 麼⽤的就 是JDBC4Connection,但是我們不會去寫 JDBC4Connection connection = new JDBC4Connection() ,因為我們要註冊驅動,還要提供URL和憑證資訊, ⽤ DriverManager.getConnection ⽅法來獲取連線。那麼在實際開發中,尤其早期的項⽬沒有使⽤Spring框架來管理物件的建立,但是在設計時使⽤了 ⼯⼚模式 解耦,那麼當接⼊spring之後,⼯⼚類建立物件就具有和上述例⼦相同特徵,即可採⽤ 此種⽅式配置。

CreateBeanFactory

/**
 * @author : look-word
 * 2022-08-25 15:50
 **/
public class CreateBeanFactory {
    /**
     * 使⽤靜態⽅法建立物件的配置⽅式
     */
    public static ConnectionUtils getInstanceStatic(){
        return new ConnectionUtils();
    }
}

applicationContext.xml配置檔案

image-20220825155323252


3 使⽤例項化呼叫成員⽅法建立

  • 簡單來說,就是建立一個類,然後再透過這個類的某個方法建立我們需要的物件。(不推薦,需要建立額外物件)

CreateBeanFactory:

    /**
     * 使⽤成員⽅法建立物件的配置⽅式
     */
    public  ConnectionUtils getInstance(){
        return new ConnectionUtils();
    }

applicationContext.xml配置檔案

image-20220825155855227


2 lazy—init 延遲載入

配置方式

xml配置延遲載入:

    <!--
        lazy-init:延遲載入,true代表延遲,false代表立即載入,預設是false 
    -->
    <bean id="lazyResult" class="com.lagou.edu.pojo.Result" lazy-init="true"/>

註解配值延遲載入:

可以配置到許多地方,如類似,方法上等等

@Lazy
@Component
public class XXXX {
    ...
}

載入原理

當使用上述三種配置後,Spring在掃描載入Bean時會讀取@Lazy和@Component註解相應值,並設定Bean定義的lazyInit屬性。讀取註解配置時最終會呼叫ClassPathBeanDefinitionScanner及其子類實現的doScan方法,在這個方法中完成註解的讀取配置。

public class ClassPathBeanDefinitionScanner 
        extends ClassPathScanningCandidateComponentProvider {
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
       // 不管是讀取註解或者XML配置方式bean,最終讀取載入Bean時都會進入到該方法
       // 對相應的包進行處理
       // beanDefinitions是儲存返回bean定義的集合
       Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
       // 遍歷多個包下的類
       for (String basePackage : basePackages) {
          // 獲取滿足條件的bean定義集合
          Set<BeanDefinition> candidates = 
                  findCandidateComponents(basePackage);
          // 對每個bean定義進行處理
          for (BeanDefinition candidate : candidates) {
             ScopeMetadata scopeMetadata = this.scopeMetadataResolver
                     .resolveScopeMetadata(candidate);
             candidate.setScope(scopeMetadata.getScopeName());
             String beanName = this.beanNameGenerator
                     .generateBeanName(candidate, this.registry);
             // 這個方法會處理@ComponentScan中的lazyInit值,因為在使用
             // @ComponentScan註解時會首先把該值賦值到beanDefinitionDefaults
             // 預設bean定義值的物件中,在postProcessBeanDefinition方法中
             // 會首先應用一次這些預設值,其中就包括lazyInit、autowireMode等
             if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition(
                        (AbstractBeanDefinition) candidate, beanName);
             }
             // 讀取@Lazy、@Primary和@DependsOn等註解值
             if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils
                        .processCommonDefinitionAnnotations(
                                (AnnotatedBeanDefinition) candidate);
             }
             // 如果候選者滿足要求則將其註冊到Bean定義中心
             if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = 
                        new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils
                        .applyScopedProxyMode(scopeMetadata, 
                            definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 註冊bean定義
                registerBeanDefinition(definitionHolder, this.registry);
             }
          }
       }
       return beanDefinitions;
    }
    protected void postProcessBeanDefinition(
            AbstractBeanDefinition beanDefinition, String beanName) {
       // 此處會應用預設值,如lazyInit、autowireMode、initMethod等
       beanDefinition.applyDefaults(this.beanDefinitionDefaults);
       if (this.autowireCandidatePatterns != null) {
          beanDefinition.setAutowireCandidate(PatternMatchUtils
                  .simpleMatch(this.autowireCandidatePatterns, beanName));
       }
    }
}

經過ClassPathBeanDefinitionScanner或子類實現的掃描讀取後,延遲載入的配置便被配置到了Bean定義中,等初始化時再使用該屬性,這裡需要注意的是@ComponentScan延遲載入屬性是可以被@Lazy覆蓋的,因為@Lazy是在@ComponentScan後面處理的。

使用細節

Spring框架延遲載入屬性在呼叫getBean之後將會失效,因為getBean方法是初始化bean的入口,這不難理解,那麼平時我們使用@Autowired等自動注入註解時能和@Lazy註解一起使用嗎?接下來我們從兩個例項來說明一下,這兩個例項都是使用平時的使用用法,在Component上新增@Lazy註解,且讓其實現InitializingBean介面,當Bean被載入時我們便能得知,看其是否會生效,示例如下:

@Lazy失效例項

宣告一個Controller控制器:

@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

再宣告一個Service服務類:

@Lazy
@Service
public class TestService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testService Initializing");
    }
}

啟動程式後控制檯輸出:

testService Initializing
testController Initializing

啟動完Spring程式後輸出了TestService裡面列印的字串。這就奇怪了,明明使用了@Lazy註解,但是卻並沒有其作用,在Spring啟動專案時還是載入了這個類?簡單來說,就是在DI注入的時候,獲取容器中獲取對應的Bean,Autowired按照預設型別獲取Resource按照預設名稱獲取,所以才會導致延遲載入失效問題。

@Lazy有效例項

修改先前的Controller

啟動後會發現,延遲載入失效問題解決了。

@Lazy
@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

3 BeanFactory和FactoryBean

  • BeanFactory是介面,提供了OC容器最基本的形式,給具體的IOC容器的實現提供了規範。
  • FactoryBean也是介面,為IOC容器中Bean的實現提供了更加靈活的方式,FactoryBean在IOC容器的基礎上給Bean的實現加上了一個簡單工廠模式和裝飾模式(如果想了解裝飾模式參考:修飾者模式(裝飾者模式,Decoration) 我們可以在getObject()方法中靈活配置。其實在Spring原始碼中有很多FactoryBean的實現類.

區別:

BeanFactory是個Factory,也就是IOC容器或物件工廠,FactoryBean是個Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似

4 後置處理器

Spring提供了兩種後處理bean的擴充套件接⼝:

  • BeanFactoryPostProcessor
    • 在BeanFactory初始化之後可以使⽤BeanFactoryPostProcessor進⾏後置處理做⼀些事情
  • BeanPostProcessor
    • 在Bean物件例項化(並不是Bean的整個⽣命週期完成)之後可以使⽤BeanPostProcessor進⾏後置處 理做⼀些事情

springBean 宣告週期

image-20220825214412887

springBean 宣告週期

相關文章