[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 的生命周期


<<:  Day05 X Code Minimize & Uglify

>>:  Day 5 - Remove Element

API

今天先来看一段MuleSoft公司介绍API的影片吧! 从影片中我们能够很清楚的知道API其实就是扮...

CI/CD:使用Jenkins(Docker image)自动部署+bitbucket

流程 开发本机将新的commit push到bitbucket bitbucket的指定专案变动,透...

Day4 VPC & Security Group

从地端 On-Premise的传统资讯部署,再到云端 Cloud的新形态部署模式,在这个转型过程初...

[Day12] JavaScript 的动态型别

我们可以在变数做宣告时,是否绑定型别来判定程序语言是动态型别或是静态型别,而两者区别如下: 动态型别...

[Day 2] 快来探索AI的世界

快来探索AI的世界 Day 2 学习目标 人工智慧的演进 人工智慧的分级 机器是如何学习的 人工智慧...