@IT·互联网

SpringBoot源码解读与原理分析(二)组件装配

2023-12-31  本文已影响0人  灰色孤星

SpringBoot源码解读与原理分析(合集)

2.1 组件装配

2.1.1 组件

组件:IOC容器中的核心API对象
组件装配:将核心API配置到XML配置文件或注解配置类的行为
Spring Framework 只有一种组件装配方式,即手动装配;而 Spring Boot 基于原生的手动装配,通过模块装配+条件装配+SPI机制,完美实现组件的自动装配。

2.1.2 手动装配

手动装配,是指开发者在项目中通过编写XML配置文件、注解配置类、配合特定注解等方式,将所需的组件注册到IOC容器(即ApplicationContext)中。
三种手动装配方式(共性:需要手动编写配置信息):

<!-- 基于XML配置文件的手动配置 -->
<bean id="person" class="com.xiaowd.springboot.component.Person"/>

// 基于注解配置类的手动装配
@Configuration
public class ExampleConfiguration {
    @Bean
    public Person person() {
        return new Person();
    }
}

// 基于组件扫描的手动装配
@Component
public class DemoService {
}
@Configuration
@ComponentScan("com.xiaowd.springboot")
public class ExampleConfiguration {
}

2.1.3 自动装配

自动装配是 Spring Boot 的核心特性之一。
自动装配:本应该由开发者编写的配置,转为框架自动根据项目中整合的场景依赖,合理地做出判断并装配合适的Bean到IOC容器中。相比较于手动装配,自动装配关注的重点是整合的场景,而不是每个具体的场景中所需的组件。

2.2 Spring Framework的模块装配

模块装配是自动装配的核心,可以把一个模块所需的核心功能组件都装配到IOC容器中。
通过标注@EnableXXX注解,实现快速激活和装配对应的模块

2.2.1 模块

2.2.2 模块装配举例

模块装配的核心原则:自定义注解+@Import导入组件

1.模块装配场景

使用代码模拟构建一个酒馆,酒馆里有吧台、调酒师、服务员和老板4种不同的实体元素;酒馆可以看成IOC容器,4种不同的实体元素可以看成4个组件。
目的:通过一个注解,把以上元素全部填充到酒馆中。

2.声明自定义注解@EnableTavern

@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
public @interface EnableTavern {
}

3.声明老板类Boss

public class Boss {
}

4.在@EnableTavern增加@Import注解

@Import注解源码如下:


由源码可知,@Import注解可以导入配置类、ImportSelector的实现类、ImportBeanDefinitionRegistrar的实现类,以及普通类。
接下来在@EnableTavern的@Import注解中填入Boss类,这就意味着如果一个配置类上标注了@EnableTavern注解,就会触发@Import的效果,向容器中导入一个Boss类的Bean。
@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import(Boss.class)
public @interface EnableTavern {

}

5.创建配置类

@Configuration
@EnableTavern
public class TavernConfiguration {
}

6.编写启动类测试

public class TavernApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Boss boss = ctx.getBean(Boss.class);
        System.out.println(boss);
    }

}

运行结果显示,使用getBean可以正常获取Boss对象,说明Boss类已经被注册到了IOC容器,并创建了一个对象。

2.2.3 导入配置类

1.声明调酒师类

public class Bartender {
    
    private String name;

    public Bartender(String name) {
        this.name = name;
    }

    // getter and setter
}

2.声明注解配置类

@Configuration
public class BartenderConfiguration {
    
    @Bean
    public Bartender zhangsan() {
        return new Bartender("张三");
    }

    @Bean
    public Bartender lisi() {
        return new Bartender("李四");
    }
    
}

3.在@EnableTavern注解中添加BartenderConfiguration配置类

@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({Boss.class, BartenderConfiguration.class})
public @interface EnableTavern {

}

4.测试运行

public class TavernApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Map<String, Bartender> bartenders = ctx.getBeansOfType(Bartender.class);
        bartenders.forEach((name, bartender) -> System.out.println(name, bartender));
    }

}

运行结果显示,两个调酒师对象已经注册到了IOC容器。
注意:
配置类@Configuration还可以被组件扫描(ComponentScan)识别到,如果配置了组件扫描,不使用@Import导入配置类也可以在IOC容器中找到相应的组件。另外,本例中BartenderConfiguration本身也被注册到了IOC容器中成为一个Bean。

2.2.4 导入ImportSelector实现类

1.ImportSelector源码

Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.
ImportSelector是一个接口,它的实现类可以根据指定的筛选标准(通常是一个或多个注解)来决定那些配置类被导入。
被ImportSelector导入的类,最终会在IOC容器中以单实例Bean的形式创建并保存。

2.声明吧台类

public class Bar {
}

3.声明配置类

@Configuration
public class BarConfiguration {
    @Bean
    public Bar bar() {
        return new Bar();
    }
}

4.编写ImportSelector的实现类

public class BarImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
    }

}

selectImports方法源码:

Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.
Returns: the class names, or an empty array if none
根据导入的@Configuration类的注解元数据AnnotationMetadata选择并返回要导入的类的类名。
注意:返回的一组类名一定是全限定类名(可直接定位)

5.在@EnableTavern注解中添加BarImportSelector

@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {

}

6.测试运行

public class TavernApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Map<String, Bar> bars = ctx.getBeansOfType(Bar.class);
        bars.forEach((name, bar) -> System.out.println(name));
        System.out.println("=======");
        Map<String, BarConfiguration> barConfigurations = ctx.getBeansOfType(BarConfiguration.class);
        barConfigurations.forEach((name, barConfiguration) -> System.out.println(name));
        System.out.println("=======");
        Map<String, BarImportSelector> barImportSelectors = ctx.getBeansOfType(BarImportSelector.class);
        barImportSelectors.forEach((name, barImportSelector) -> System.out.println(name));
        System.out.println("=======");
    }

}

运行结果显示:
ImportSelector可以导入普通类(Bar),可以导入配置类(BarConfiguration),但没有导入BarImportSelector。

7.ImportSelector的灵活性

2.2.5 导入ImportBeanDefinitionRegistrar

以编程式向IOC容器中注册bean对象

1.声明服务员类

public class Waiter {
}

2.编写ImportBeanDefinitionRegistrar的实现类

public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("waiter222", new RootBeanDefinition(Waiter.class));
    }
    
}

第一个参数是Bean的名称(即ID)
第二个参数传入的RootBeanDefinition要指定Bean的字节码
这种方式相当于向IOC容器注册了一个普通的单实例bean(最终效果与组件扫描、@Bean注解的效果相同)

3.在@EnableTavern注解中添加WaiterRegistrar

@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {

}

4.测试运行

public class TavernApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Map<String, Waiter> waiters = ctx.getBeansOfType(Waiter.class);
        waiters.forEach((name, waiter) -> System.out.println(name));
        System.out.println("=======");
        Map<String, WaiterRegistrar> waiterRegistrars = ctx.getBeansOfType(WaiterRegistrar.class);
        waiterRegistrars.forEach((name, waiterRegistrar) -> System.out.println(name));
        System.out.println("=======");
    }

}

结果显示:服务员对象成功注册,WaiterRegistrar不会注册。

2.2.6 扩展:DeferredImportSelector

ImportSelector的子接口DeferredImportSelector,类似于ImportSelector,但执行时机比ImportSelector晚。
ImportSelector:在注解配置类的解析期间,此时配置类中的Bean方法还没有被解析
DeferredImportSelector:在注解配置类的解析完成之后
目的:配合条件装配(后面再深入)

1.编写WaiterDeferredImportSelector类

public class WaiterDeferredImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("DeferredImportSelector执行了...");
        return new String[] {Waiter.class.getName()};
    }
    
}

2.ImportSelector和ImportBeanDefinitionRegistrar也加上执行提示语

public class BarImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("ImportSelector执行了...");
        return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
    }

}
public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("ImportBeanDefinitionRegistrar执行了...");
        registry.registerBeanDefinition("waiter222", new RootBeanDefinition(Waiter.class));
    }

}

3.在@EnableTavern注解中添加WaiterDeferredImportSelector

@Documented
@Retention(RetentionPolicy.RUNTIME) //该注解在运行时起效
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class, WaiterDeferredImportSelector.class})
public @interface EnableTavern {

}

4.运行测试


DeferredImportSelector的运行时机比ImportSelector晚,但比ImportBeanDefinitionRegistrar早(这样设计的原理放到后面)。
另外,DeferredImportSelector还有分组的概念(DeferredImportSelector有一个方法getImportGroup),可以对不同的DeferredImportSelector加以区分(SpringBoot使用非常少,知道即可)。
SpringBoot源码解读与原理分析(合集)
上一篇下一篇

猜你喜欢

热点阅读