iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Software Development

從零開始構建能理解語義的 Linebot 架構系列 第 21

使用 Spring Boot 開發 Backend Bot Server: Bean的組合與 ComponentScan 機制

  • 分享至 

  • xImage
  •  

概述

在上一篇文章中,我們講完了Spring如何透過容器(Container)來託管Beans,也就是商業邏輯所需的依賴物件,將實例化的過程反轉(Inverse Control),抽離主流程。
這不僅使程式結構更加簡潔,還有助於依賴的抽換和測試。

講完了理論,這篇文章將以本專案的Bot Server為例,說明開發者在實際開發中需要提供哪些資訊,以及如何組合程式結構,使上述過程得以實現。我們將涵蓋以下幾個重點:

  • Spring如何辨識要託管的物件
  • 專案進入點: Application Class
  • 用於標注實例的註解(Annotation): BeanComponent
  • Bean 和 Component 的掃描機制
  • 特化注解 (Stereotype Annotations)

Spring如何辨識要託管的物件

"在物件導向中的一切都是物件,在Spring中的一切都是Beans。"
本來要拿這句看似通順有力的話來當引言,還好沒有,直接被AI打臉。
第一句話可能沒太大問題,第二句的問題就不只有語病了。
雖然在IoC的過程中,會將實依賴例化成物件的過程託付給容器,但並不是所有物件都會交由容器來建造或託管。

標注要交給容器的物件

  • 簡單來說,在Spring,你還是可以跟原本寫Java的時候一樣,自己透過new來把類別(Class)實例化成物件。new出來物件並不會被Spring容器託管。
  • 開發者需要透過一些方式,來告訴Spring哪些物件是要交給容器創建並託管。
  • 早期的Spring需要把所有要託管的物件(也就是Bean),以及實例化時要做的設定,通通寫在外部.xml檔案裡面。這其實是個相當麻煩的過程,因為要把程式語言表達的內容,改寫成xml的<tag>描述,多了一個了解<tag>定義以及轉換的過程。
  • 目前的主流是使用Annotation(註解),直接在實例化的程式碼區段做標註。也就是告訴容器: 這段程式碼產生的物件是Bean,請在程式啟動時幫我實例化,並交給你進行DI及其他的管理。

Annotation

  • Java 的Annotation(註解)是一種元數據(Metadata),以@作為前綴來表示。
  • 它為程式碼提供額外的資訊,但並不直接改變程式邏輯。這些額外資訊會在編譯時期由編譯器(IDE)來使用,或者被執行容器所用。
  • 註解通常用來修飾類別(Class)、方法(Function),變數(Variable)等,並且常與框架(如Spring)搭配使用,提供配置和行為控制。

常見的 Java Annotation

  • @Override
    • 用在Function上。
    • 一旦Function被標注了@Override,IDE會去檢查這個Function有沒有正確的改寫某個父類別的Function。
    • IDE檢查時,會去尋找父類別有沒有同樣Method Signature的Function。
      • Method Signature指的是Function的名稱 + 引數(Parameters)型態,可以作為辨識Function的依據
  • @Deprecated
    • 表示某個Class、Function,或Variable已經過時,建議不要再使用。使用這些元素時,IDE會發出警告。

此外,Java 也允許開發者自定義註解。在Spring中就有很多非Java原生的Annotation,例如後續會提到的@Bean@Component

專案進入點: Application Class

  • 在Spring Boot中,標注了@SpringBootApplication的Class通稱Application Class,也是Spring Boot的程式進入點。如果使用前一章介紹的Spring Initializr,會在下載的初始專案中找到DemoApplication,他有兩個特色:
    • main function
      • 跟Java的角色一樣,當執行Spring Boot build好的程式時,會從main function開始執行
    • 此Class的所在位置,為所有Bean的根目錄
      根目錄的意義在之後會說明,下面我們先說明BeanComponet的意義和使用

用於標注實例的Annotation: @Bean@Component

  • 在Spring中有兩種方式可以宣告Bean:
    • 1.在Class標示@Configuration,並且在這個Class的Method標示@Bean
    • 2.在Class標示@Component

@Configuration + @Bean

  • 前面有提到,早期的Spring是用.xml設定檔裡面tab宣告Bean。而使用Annotation時則是:
    • 用標示為@Configuration的Class取代xml設定檔,我們暫稱Configuration Class
    • 在Configuration Class用標示為@Bean的Function,宣告Bean,該Function return的物件就是要宣告的Bean

例如:

@Configuration
public class LinebotConfig {  
    @Bean
    LineSignatureValidator lineSignatureValidator() {
	return new LineSignatureValidator(channelSecret.getBytes(StandardCharsets.UTF_8));
    }
  • 上面的程式宣告了一個叫LinebotConfig的Configuration Class
  • 並且在lineSignatureValidator 這個Function會return一個已經設定好ChannelSecret的LineSignatureValidator的物件,因為標注了@Bean,所以會是一個將要被Spring容器託管的Bean

在Class標示@Component: @Component 及掃描機制

  • 除了@Configuration + @Bean以外,Spring還提供一個更簡單暴力的方法: 把Class標註為@Component (以下暫稱為Component Class)。
  • 要讓一個Class被宣告成一個將要被Spring容器託管的Bean需滿足兩個條件:
    • 1.在Class加上@Component。
    • 2.讓Component處在被掃瞄的位置

@Component

@Component
public class OpenAiApiService {

    @Value("${openai.api.key}")
    private String token;


    public String sendMessage(String pre, String userInputPrompt) {
    ...
  • 上面的程式直接把OpenAiApiService宣告為將要被Spring容器託管的Bean。

讓Component處在被掃瞄的位置

  • 前面在說明專案進入點時有提到,被標注了@SpringBootApplication的Class的所在目錄,會是所有Bean的根目錄,這是為什麼呢? 原理如下:
  • @SpringBootApplication的作用等同於3個Annotation: @ComponentScan*@Configuration, @EnableAutoConfiguration
    其中,@ComponentScan 代表了 Component的根目錄。

Component的根目錄: @ComponentScan

  • @ComponentScan意旨告訴Spring,"掃描這個Class所在的目錄(Package),以及所有的子目錄(sub Packages),只要有找到被標示為@Component的Class,就把它當成要給容器託管的Bean"。
  • 所以任何Component Class,只要放在被@ComponentScan標注的Class的目錄或子目錄,就會被當成要給容器託管的Bean
  • 而事實上,前面提到的@Configuration也是@Component的一種特化,它同樣需要處於@ComponentScan的掃描範圍才會生效。

Spring容器託管的Bean

  • 綜上所述,無論是1.@Configuration + @Bean,還是2.@Component,這兩種方式宣告的Bean,都必須處在@SpringBootApplication,或者 @ComponentScan Class所在的目錄或子目錄,才會被容器託管。
  • 結構如下:
    https://ithelp.ithome.com.tw/upload/images/20241005/20105227GVV8tWqOO5.png

圖: 由於Application Class(或者@ComponentScan宣告的Class)的位置在根目錄,
所有位於根目錄,或者子目錄的:

  • @Component
  • @Configuration + @Bean
    都在Component Scan的範圍內,可以順利的作為Bean被Spring容器託管。

取用Spring容器託管的Bean

  • 我們可以使用@Autowired這個Annotation,來取用已經被Spring容器託管的Bean:
class KafkaConsumer {
    @Autowired
    LineSignatureValidator lineSignatureValidator;
  • 由於前面我們已經託管了一個LineSignatureValidator,這邊只要加上@Autowired,就可以直接拿到設定好channelSecret的LineSignatureValidator物件。
  • 事實上@Autowired就是前面提到的Dependency Injection(DI)的實作。
  • 除了@Autowired@Inject這個Annotation也有一樣的作用,差別僅在於後者是來自JSR-330規範,@Autowired是Spring自己特有的Annotation。

@SpringBootApplication的其他作用

@Configuration

  • @SpringBootApplication的Class本身也可以宣告Bean。

@EnableAutoConfiguration

  • 啟用Spring boot的自動配置,意思是要求Spring Boot根據目前專案的依賴,自動配置所需要的Bean,甚至是外部系統。
  • 這個Annotation大幅減少了開發者手動配置Bean和設置外部系統的的需求,使得開發過程更簡單快捷,但因為真的太自動了,需要視情況來使用。
  • 例如:
    • 1.使用了spring-boot-starter-data-jpa依賴時,Spring會自動配置 JPA 所需的 Bean,如 DataSource、EntityManager 等。
    • 2.使用了spring-boot-starter-web依賴時,Spring甚至會自動配置一個內嵌的 Tomcat伺服器。

特化注解 (Stereotype Annotations)

  • 除了上面Spring boot的Annotation以外,還有一些特殊用途的Annotation,例如,@Controller會自動將標註的Function映射為處理指定URL的Web request的對應方法。

總結

總的來說:

  • 讓Spring容器託管Bean

    • 1.需要有Class被宣告成Bean的根目錄,該Class稱為Application Class

      • 可以把Class標註為@ComponentScan,或者 @SpringBootApplication
    • 2.所有託管的Bean都要在Application Class所在目錄,或者子目錄中

    • 3.要託管的Bean可以用兩種方式宣告:

      • 1.@Configuration + @Bean
      • 2.在Class加上@Component
  • 取用Spring容器託管的Bean

    • 使用@Autowired@Inject 來做Dependency Injection(DI)。
  • 本專案用到的Stereotype Annotation及其作用,將在之後的章節介紹。


上一篇
使用 Spring Boot 開發 Backend Bot Server: 從 IoC 容器到 DI 實踐
下一篇
使用 Spring Boot 開發 Backend Bot Server: 程式結構及Spring Boot Starter
系列文
從零開始構建能理解語義的 Linebot 架構30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言