iT邦幫忙

2021 iThome 鐵人賽

DAY 5
0
Software Development

Wow ! There is no doubt about Learn Spring framework in a month.系列 第 5

[Day - 05] - Spring Bean 運作與原理

Abstract

在前面章節我們已有進行模擬Spring框架之實作範例及介紹,使用者可理解到所有服務透過一項框架套件代理的過程,故本章節將提供使用者一些基本設置的套件,提供開發者了解Spring框架之控制反轉(IoC,Inverse of Control)與依賴注入(DI,Dependency Injection)兩項核心概念,運用此範例配置可提供給所有讀者當各類元件測試的基底,且讀者可快速學習到建立一套基礎的Spring Boot Starter服務架構需要運用到哪些套件與開發流程,而為什麼要選用Spring Boot Starter呢?因此套件的規範與設定優於Spring周遭套件,也整合許多外掛套件觀念,只需尋找spring-boot-starter-* 字母開頭套件當外掛,運用起來相當簡單,故在此推薦給眾多讀者做範例使用 !

Maven Repository Dependency list

    implementation ('org.springframework.boot:spring-boot-starter') 
    implementation ('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-jetty')
    compile('org.springframework.boot:spring-boot-starter-aop')
    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7'

    //unit test
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.springframework', name: 'spring-test'
    testCompile group: 'org.hamcrest', name: 'hamcrest-all', version : '1.3'
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.security:spring-security-test')

Principle Introduction

本篇全部將以註解(Annotation)進行各種Bean的配置,故我們先產生一個基本的模型(Model named: Chef),透過AppConfig元件進行建立兩項Bean類別元件,當我們服務啟動的時候,會自動將Bean載入Spring IoC容器中,故我們亦可透過ApplicationContext方式取得Bean類別,亦可透過註解方式(@Autowired)獲取Bean類別,詳細範例如下請參照。

啟動服務區段
   public static void main(String[] args) {
        SpringApplication.run(ApplicationBoot.class,args);
    }
ApplicationContext取得Bean方法,皆透過BeanFactory此頂層接口獲取Bean,前面我們已經敘述過我們都透過一個Map的變量持獲取Bean,故回傳給開發者依舊為一個Map集合,所以在範例中beansContainer為我們宣告為一個Map集合來獲取所有對應的Bean元件,便可透過定義名稱(namespace definition)取得對應的Bean,以便於提供開發者作後續的邏輯分析。
     Map<String,Object> beansContainer = null;
     
    @PostConstruct
    public void init() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        beansContainer =  ctx.getBeansWithAnnotation(Bean.class);
    }
    
    @Override
    public String getNameByChef(String name) {
        return ((Chef)beansContainer.get(name)).getName();
    }
@Autowird 註解方式獲取Bean,在這邊已預設將Chef-B作為優先獲取對象,故我們透過註解方式取得時,會優先以Chef-B作為回傳對象。

main.java.sw.sample.spring.service.ConfigServiceImpl.java

    @Primary
    @Bean(name= "chef-B")
    public Chef initChefB() {
        Chef chef = new Chef();
        long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
        chef.setId(generatedLong);
        chef.setName("Jyuh");
        chef.setRemark("Michelin-chef and dessert");
        List<String> specialSkills = new ArrayList<String>();
        specialSkills.add("Gold cake");
        specialSkills.add("noodle pie");
        specialSkills.add("chocolate pie");
        chef.setSpecialSkills(specialSkills);
        return chef;
    }

test.java.sw.sample.spring.ConfigServiceSuite.java

    @Autowired
    Chef chef;

    @Test
    public void validateChefBRemark() {
        assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
        System.out.println("validate Chef B Remark success!");
    }

透過以上程式碼區段敘述獲取Bean類別元件,便可進行各類程式邏輯運用與判斷。

Structure

在軟體開發週期定義上,無論何種元件都會有一個生命週期,以下是注入一個Bean的流程架構,Spring 會先取出相關屬性值,如:需註冊多個相同類別的Bean,將在每個Bean內建入不相同的變量參數,此時就會去對應不同的識別名稱去註冊Bean,此時會產生將配置給Bean的對映名稱,接下來進行確認此類別是否有配置初始化方法,若有會產生相關預設好的Bean,在進行觸發此方法進行初始化參數配置,最後將檢查是否有配置Destroy的方法,當進行系統關閉,每個Bean移除前都會觸發此方法,當配置完後會將此Bean存入BeanFactory變量池中,並將預設好的名稱作為索引值,以提供使用者隨時可呼叫配置使用。

圖一 Bean 生命週期
image

從此架構可以看出我們在範例中可以一次註冊多個Bean,每個Bean都可以連接@PostConstruct及@PreDestroy兩項方法,開發者可透過每個Bean的識別名稱進行獲取該元件類別,如何註冊多個Bean[chef-A、chef-B],請看下面代碼:

已註解方式註冊Bean

@Configuration
public class AppConfig {

    @Bean(name= "chef-A")
    public Chef initChefA() {
        Chef chef = new Chef();
        long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
        chef.setId(generatedLong);
        chef.setName("Weisting");
        chef.setRemark("Michelin-chef and main meal chef.");
        List<String> specialSkills = new ArrayList<String>();
        specialSkills.add("Gold chicken");
        specialSkills.add("Assorted fried noodles");
        specialSkills.add("Crab Fragrant Huangbao");
        chef.setSpecialSkills(specialSkills);
        return chef;
    }
    
    @Primary
    @Bean(name= "chef-B")
    public Chef initChefB() {
        Chef chef = new Chef();
        long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
        chef.setId(generatedLong);
        chef.setName("Jyuh");
        chef.setRemark("Michelin-chef and dessert");
        List<String> specialSkills = new ArrayList<String>();
        specialSkills.add("Gold cake");
        specialSkills.add("noodle pie");
        specialSkills.add("chocolate pie");
        chef.setSpecialSkills(specialSkills);
        return chef;
    }
}

獲取Chef Bean 方法如下

public class ConfigServiceSuite extends ServiceTestBase {

    @Autowired
    ConfigService configService;


    @Autowired
    Chef chef;
    
    @Test
    public void validateChefBRemark() {
        assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
        System.out.println("validate Chef B Remark success!");
    }

    @Test
    public void validateChefBName() {
        assertTrue(chef.getName().equalsIgnoreCase("Jyuh"));
        System.out.println("validate Chef B Remark success!");
    }

    ...
}

@Test

public void validateChefBRemark() {
    assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
    System.out.println("validate Chef B Remark success!");
}

@Test
public void validateChefBName() {
    assertTrue(chef.getName().equalsIgnoreCase("Jyuh"));
    System.out.println("validate Chef B Remark success!");
}

運作測試指令啟動步驟

Run test task

gradle test

Run open result html

open ./build/reports/tests/test/classes/sw.sample.spring.ConfigServiceSuite.html

Result

image

Sample Source

Spring boot starter base sample code

Reference Url

Spring 容器中Bean生命周期之@PostConstruct和@PreDestroy註解

23.14 Using the @PostConstruct and @PreDestroy Annotations with CDI Managed Bean Classes

Bean 的生命週期


上一篇
[Day-04] - Spring Boot Starter 環境配置馬上就上手
下一篇
[Day - 06 ] - Spring Conditional 運用與原理
系列文
Wow ! There is no doubt about Learn Spring framework in a month.30

尚未有邦友留言

立即登入留言