# 《Spring核心技术》第05章-驱动型注解:深度解析@Import注解

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码地址:https://github.com/binghe001/spring-annotation-book/tree/master/spring-annotation-chapter-05 (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

大家好,我是冰河~~


  • 本章难度:★★★★☆

  • 本章重点:进一步学习并掌握@Import注解向Spring IOC容器中注入Bean的示例与流程,从源码级别彻底掌握@Import注解在Spring底层的执行流程。


本节目录如下所示:

  • 学习指引
  • 注解说明
    • 注解源码
    • 注解使用场景
  • 使用案例
    • 引入普通类案例
    • 引入实现了ImportSelector接口的类案例
    • 引入实现了ImportBeanDefinitionRegistrar接口的类案例
  • 源码时序图
  • 源码解析
  • 总结
  • 思考
  • VIP服务

# 一、学习指引

@Import注解是什么?

想深入学习一项技术并不是一朝一夕就能够完成的,它需要我们花费大量的时间和精力,塌下心来深入研究,从不知道,到了解,再到熟悉,最后到精通,这需要一个不断深入研究,不断实践的过程。学习Spring亦是如此,要想掌握好Spring的核心技术,同样需要塌下心来不断研究和实践。

# 二、注解说明

关于@Import注解的一点点说明~~

@Import注解可以将第三方包中的类对象注入到IOC容器中。使用Spring开发业务系统时,@Import注解的使用频率不及@Bean注解,@Import注解往往在一些中间件或者框架项目中使用的比较多。在Spring底层,也大量使用了@Import注解来向IOC容器中注入Bean对象。当然,如果在开发业务系统时,也可以使用@Import注解向IOC容器中注入Bean对象。@Import注解相比于@Bean注解来讲,在使用上会更加灵活。

# 2.1 注解源码

@Import注解只能标注到类或其他注解上,通常与配置类一起使用的,使用此注解引入的类上可以不再使用@Configuration,@Component等注解标注。本节,就对@Import注解的源码进行简单的剖析。

@Import注解的源码详见:org.springframework.context.annotation.Import,如下所示。

/**
 * Since: 3.0
 * @author Chris Beams
 * @author Juergen Hoeller
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	Class<?>[] value();
}
1
2
3
4
5
6
7
8
9
10
11

从@Import源码的注释可以看出,@Import是Spring从3.0版本开始提供的注解,注解中只有一个Class数组类型的value属性。含义如下所示。

  • value:Class数组类型,用于指定其他配置类的字节码,支持指定多个配置类。另外,使用value属性指定的有一定的条件,必须是普通类、实现了ImportSelector接口的类和实现了ImportBeanDefinitionRegistrar接口的类。

注意:@Import注解只能标注到类上。

# 2.2 注解使用场景

在使用Spring进行开发时,如果涉及到的配置项比较多,要是将所有的配置项都写到一个类里,则配置结构和配置内容将会变得非常杂乱,如果此时使用@Import注解,则可以将配置项进行分类管理。另外,如果在项目中需要引入第三方的类,并且需要将这些类的对象注入到IOC容器中,也可以使用@Import注解。

# 三、使用案例

@Import注解案例实战~~

@Import注解可以引入三种类,分别如下所示。

  • 引入普通类,将Bean对象注入到IOC容器。
  • 引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器,但是实现了ImportSelector接口的类对象本身不会被注册到IOC容器中。
  • 引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中,但是实现了ImportBeanDefinitionRegistrar接口的类对象本身不会被注册到IOC容器中。

# 3.1 引入普通类案例

本节,主要实现使用@Import注解实现引入普通类,并且将Bean对象注入到IOC容器中的案例。具体实现步骤如下所示。

(1)新建User类

User类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.User,如下所示。

public class User {
    private Long userId;
    private String userName;
    //#############省略getter/serrer方法############
}
1
2
3
4
5

可以看到,User类就是一个普通的类对象,后续会通过@Import注解引入User类,并且将User类的对象注入到IOC容器中。

(2)新建Spring配置类ImportConfig

ImportConfig类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.class})
@Configuration
public class ImportConfig {
}
1
2
3
4

可以看到,ImportConfig类主要是Spring的配置类,会在ImportConfig类上标注@Configuration注解和@Import注解,并且会通过@Import注解引入User类,将User类的对象注入到IOC容器中。

(3)新建ImportTest类

ImportTest类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,如下所示。

public class ImportTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
        String[] definitionNames = context.getBeanDefinitionNames();
        Arrays.stream(definitionNames).forEach((definitionName) -> System.out.println(definitionName));
    }
}
1
2
3
4
5
6
7

可以看到,ImportTest类主要是案例的测试类,在ImportTest类的main()方法中,主要打印了Bean定义的名称。

(4)运行ImportTest类

运行ImportTest类的main()方法,输出的结果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
1
2
3
4
5
6

其中,以org.springframework包命名的Bean是Spring内部的Bean。另外,可以看到,结果信息中也输出了ImportConfig类的Bean名称和User类的Bean名称。

说明:使用@Import注解可以引入普通的类,并且能够将类对象注入到Spring容器中。

# 3.2 引入实现了ImportSelector接口的类案例

本节,主要实现使用@Import注解引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器中的案例。具体的实现步骤如下所示。

注意:本节实现的案例是在3.1节的基础上实现的。

(1)新建ImportSelectorBean类

ImportSelectorBean类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean,如下所示。

public class ImportSelectorBean {
    private Long id;
    private String name;
    //########省略getter/setter方法#########
}
1
2
3
4
5

可以看到,ImportSelectorBean类是一个普通的类,ImportSelectorBean类的对象后续会通过ImportSelector接口的selectImports()注入到IOC容器中。

(2)新建MyImportSelector类

MyImportSelector类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.selector.MyImportSelector,如下所示。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ImportSelectorBean.class.getName()};
    }
}
1
2
3
4
5
6

可以看到,MyImportSelector类实现了ImportSelector接口,并实现了ImportSelector接口的selectImports()方法,在selectImports()中返回了包含ImportSelectorBean类的全类名的Spring数组。

(3)修改ImportConfig类

ImportConfig类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.class, MyImportSelector.class})
@Configuration
public class ImportConfig {
}
1
2
3
4

可以看到,在ImportConfig类上标注的@Import注解的value属性中,新增MyImportSelector类的Class对象。

(4)运行ImportTest类

运行ImportTest类的main()方法,输出的结果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
1
2
3
4
5
6
7

可以看到,在输出的结果信息中,除了有Spring内部的Bean对象的名称、ImportConfig类的Bean对象名称和User类的Bean对象名称外,还输出了ImportSelectorBean类的Bean对象名称。但是,并没有输出实现了ImportSelector接口的MyImportSelector类的Bean对象的名称。

说明:使用@Import注解可以引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器中,但是实现了ImportSelector接口的类对象本身不会被注册到IOC容器中。

# 3.3 引入实现了ImportBeanDefinitionRegistrar接口的类案例

本节,主要实现使用@Import注解引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中的案例。具体实现步骤如下所示。

(1)新增ImportBeanDefinitionRegistrarBean类

ImportBeanDefinitionRegistrarBean类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean,如下所示。

public class ImportBeanDefinitionRegistrarBean {
    private Long id;
    private String name;
	//#########省略getter/setter方法############
}
1
2
3
4
5

可以看到,ImportBeanDefinitionRegistrarBean类就是一个普通的类,后续会通过ImportBeanDefinitionRegistrar接口的实现类实现的registerBeanDefinitions()方法将ImportBeanDefinitionRegistrarBean类的Bean对象注入到IOC容器中。

(2)新增MyImportBeanDefinitionRegistrar类

MyImportBeanDefinitionRegistrar类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar,如下所示。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
        BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
}
1
2
3
4
5
6
7
8

可以看到,MyImportBeanDefinitionRegistrar类实现了ImportBeanDefinitionRegistrar接口,并实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法。在registerBeanDefinitions()方法中,获取ImportBeanDefinitionRegistrarBean类的全类名作为注入到IOC容器中的Bean名称。接下来,调用RootBeanDefinition类的构造方法传入ImportBeanDefinitionRegistrarBean类的Class对象创建BeanDefinition对象。最终,调用registry的registerBeanDefinition()方法将创建的BeanDefinition对象注入到IOC容器中。

(3)修改ImportConfig类

ImportConfig类的源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}
1
2
3
4

可以看到,在ImportConfig类上标注的@Import注解的value属性中,新增了实现了ImportBeanDefinitionRegistrar接口的MyImportBeanDefinitionRegistrar类的Class对象。

(4)运行ImportTest类

运行ImportTest类的main()方法,输出的结果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean
1
2
3
4
5
6
7
8

可以看到,在输出的结果信息中,除了Spring内部的Bean名称、ImportConfig类的Bean名称、User类的Bean名称和ImportSelectorBean类的Bean名称外,还输出了ImportBeanDefinitionRegistrarBean类的名称。但是并没有输出实现了ImportBeanDefinitionRegistrar接口的MyImportBeanDefinitionRegistrar类的Bean名称。

说明:使用@Import注解能够引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中,但是实现了ImportBeanDefinitionRegistrar接口的类对象本身不会被注册到IOC容器中。

# 四、源码时序图

结合时序图理解源码会事半功倍,你觉得呢?

本节,就以源码时序图的方式,直观的感受下@Import注解在Spring源码层面的执行流程。@Import注解在Spring源码层面的执行流程如图5-1~5-3所示。

图5-1

图5-2

图5-3

由图5-1~图5-3可以看出,@Import注解在Spring源码层面的执行流程会涉及到ImportTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、PostProcessorRegistrationDelegate类、ConfigurationClassPostProcessor类、ConfigurationClassParser类、MyImportSelector类、ConfigurationClassBeanDefinitionReader类、ImportBeanDefinitionRegistrar类、MyImportBeanDefinitionRegistrar类和DefaultListableBeanFactory类。具体的源码执行细节参见源码解析部分。

# 五、源码解析

源码时序图整清楚了,那就整源码解析呗!

@Bean注解在Spring源码层面的执行流程,结合源码执行的时序图,会理解的更加深刻。

(1)运行案例程序启动类

案例程序启动类源码详见:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,运行ImportTest类的main()方法。

在ImportTest类的main()方法中调用了AnnotationConfigApplicationContext类的构造方法,并传入了ImportConfig类的Class对象来创建IOC容器。接下来,会进入AnnotationConfigApplicationContext类的构造方法。

(2)解析AnnotationConfigApplicationContext类的AnnotationConfigApplicationContext(Class<?>... componentClasses)构造方法

源码详见:org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class<?>... componentClasses)。

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}
1
2
3
4
5

可以看到,在上述构造方法中,调用了refresh()方法来刷新IOC容器。

(3)解析AbstractApplicationContext类的refresh()方法

源码详见:org.springframework.context.support.AbstractApplicationContext#refresh()。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //############省略其他代码##############
        try {
            //############省略其他代码##############
            invokeBeanFactoryPostProcessors(beanFactory);
           //############省略其他代码##############
        }catch (BeansException ex) {
            //############省略其他代码##############
        }finally {
            //############省略其他代码##############
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

refresh()方法是Spring中一个非常重要的方法,很多重要的功能和特性都是通过refresh()方法进行注入的。可以看到,在refresh()方法中,调用了invokeBeanFactoryPostProcessors()方法。

(4)解析AbstractApplicationContext类的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)方法

源码详见:org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}
1
2
3
4
5
6
7

可以看到,在AbstractApplicationContext类的invokeBeanFactoryPostProcessors()方法中调用了PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors()方法。

(5)解析PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors)方法

源码详见:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors)。

由于方法的源码比较长,这里,只关注当前最核心的逻辑,如下所示。

public static void invokeBeanFactoryPostProcessors(
    ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

    //############省略其他代码##############
    List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

    // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
    String[] postProcessorNames =
        beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
        }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    registryProcessors.addAll(currentRegistryProcessors);
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
    currentRegistryProcessors.clear();

    // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
    postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
        }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    registryProcessors.addAll(currentRegistryProcessors);
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
    currentRegistryProcessors.clear();

    // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
    boolean reiterate = true;
    while (reiterate) {
        reiterate = false;
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
                reiterate = true;
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
        currentRegistryProcessors.clear();
    }
    //############省略其他代码##############
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

可以看到,在PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors)方法中,BeanDefinitionRegistryPostProcessor的实现类在执行逻辑上会有先后顺序,并且最终都会调用invokeBeanDefinitionRegistryPostProcessors()方法。

(6)解析PostProcessorRegistrationDelegate类的invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)方法

源码详见:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)。

private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
        postProcessor.postProcessBeanDefinitionRegistry(registry);
        postProcessBeanDefRegistry.end();
    }
}
1
2
3
4
5
6
7
8
9
10

可以看到,在invokeBeanDefinitionRegistryPostProcessors()方法中,会循环遍历postProcessors集合中的每个元素,调用postProcessBeanDefinitionRegistry()方法注册Bean的定义信息。

(7)解析ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法

源码详见:org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	//##########省略其他代码###################
    processConfigBeanDefinitions(registry);
}
1
2
3
4
5

可以看到,在postProcessBeanDefinitionRegistry()方法中,会调用processConfigBeanDefinitions()方法。

(8)解析ConfigurationClassPostProcessor类的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法

源码详见:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry registry)。

这里,重点关注方法中的如下逻辑。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //############省略其他代码#################
    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
        parser.parse(candidates);
        parser.validate();
        //############省略其他代码#################
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
        //############省略其他代码#################
    }
    while (!candidates.isEmpty());
    //############省略其他代码#################
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可以看到,在processConfigBeanDefinitions()方法中,创建了一个ConfigurationClassParser类型的对象parser,并且调用了parser的parse()方法来解析类的配置信息。

(9)解析ConfigurationClassParser类的parse(Set configCandidates)方法

源码详见:org.springframework.context.annotation.ConfigurationClassParser#parse(Set configCandidates),重点关注如下代码片段

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            //###############省略其他代码###############
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    this.deferredImportSelectorHandler.process();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

可以看到,在ConfigurationClassParser类的parse(Set configCandidates)方法中,调用了类中的另一个parse()方法。

(10)解析ConfigurationClassParser类的parse(AnnotationMetadata metadata, String beanName)方法

源码详见:org.springframework.context.annotation.ConfigurationClassParser#parse(AnnotationMetadata metadata, String beanName)

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
1
2
3

可以看到,上述parse()方法的实现比较简单,直接调用了processConfigurationClass()方法。

(11)解析ConfigurationClassParser类的processConfigurationClass(ConfigurationClass configClass, Predicate filter)方法

源码详见:org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass(ConfigurationClass configClass, Predicate filter)。

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    //###############省略其他代码####################
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);
    this.configurationClasses.put(configClass, configClass);
}
1
2
3
4
5
6
7
8
9

可以看到,在processConfigurationClass()方法中,会通过do-while()循环获取配置类和其父类的注解信息,SourceClass类中会封装配置类上注解的详细信息。在在processConfigurationClass()方法中,调用了doProcessConfigurationClass()方法。

(12)解析ConfigurationClassParser类的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)方法

源码详见:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate filter),重点关注如下代码片段。

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
	 //#############省略其他代码#############
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //#############省略其他代码#############
    // No superclass -> processing is complete
    return null;
}
1
2
3
4
5
6
7
8
9
10

可以看到,在doProcessConfigurationClass()方法中,会调用processImports()方法来解析@Import注解。

(13)解析ConfigurationClassParser类的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate exclusionFilter, boolean checkForCircularImports)方法。

源码详见:org.springframework.context.annotation.ConfigurationClassParser#processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate exclusionFilter, boolean checkForCircularImports)。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {
	//################省略其他代码#################
    this.importStack.push(configClass);
    try {
        for (SourceClass candidate : importCandidates) {
            if (candidate.isAssignable(ImportSelector.class)) {
                // Candidate class is an ImportSelector -> delegate to it to determine imports
                Class<?> candidateClass = candidate.loadClass();
                ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                Predicate<String> selectorFilter = selector.getExclusionFilter();
                if (selectorFilter != null) {
                    exclusionFilter = exclusionFilter.or(selectorFilter);
                }
                if (selector instanceof DeferredImportSelector) {
                    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                }
                else {
                    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                    processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                }
            }
            else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                // Candidate class is an ImportBeanDefinitionRegistrar ->
                // delegate to it to register additional bean definitions
                Class<?> candidateClass = candidate.loadClass();
                ImportBeanDefinitionRegistrar registrar =
                    ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                         this.environment, this.resourceLoader, this.registry);
                configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            }
            else {
                // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                // process it as an @Configuration class
                this.importStack.registerImport(
                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
            }
        }
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
            configClass.getMetadata().getClassName() + "]: " + ex.getMessage(), ex);
    }
    finally {
        this.importStack.pop();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

在processImports()方法中,如果使用@Import注解引入的是实现了ImportSelector接口的类,则执行的是if (candidate.isAssignable(ImportSelector.class))条件的逻辑。如果@Import注解引入的是实现了ImportBeanDefinitionRegistrar接口的类,则执行的是else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))条件的逻辑,否则执行的是else条件的逻辑。

其中,执行if (candidate.isAssignable(ImportSelector.class))条件的逻辑时,会调用ImportSelector接口的selectImports()方法获取要注入到IOC容器中的Bean名称数组,如下所示。

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
1

调用ImportSelector接口的selectImports()方法时,就会调用案例程序中的MyImportSelector类的selectImports()方法。

(14)解析MyImportSelector类的selectImports(AnnotationMetadata importingClassMetadata)方法

源码详见:io.binghe.spring.annotation.chapter05.selector.MyImportSelector#selectImports(AnnotationMetadata importingClassMetadata)

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    return new String[]{ImportSelectorBean.class.getName()};
}
1
2
3
4

可以看到,在MyImportSelector类的selectImports()方法中,会返回包含ImportSelectorBean类的全类名的String数组,后续会将ImportSelectorBean类的Bean对象注入IOC容器。

(15)回到ConfigurationClassParser类的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate exclusionFilter, boolean checkForCircularImports)方法。

如果@Import注解引入的是实现了ImportBeanDefinitionRegistrar接口的类,则执行的是else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))条件的逻辑,如下所示。

else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    Class<?> candidateClass = candidate.loadClass();
    ImportBeanDefinitionRegistrar registrar =
        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
1
2
3
4
5
6

可以看到,在上述代码逻辑中会调用configClass的addImportBeanDefinitionRegistrar()方法来添加ImportBeanDefinitionRegistrar对象。

(16)解析ConfigurationClass类的addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)方法

源码详见:org.springframework.context.annotation.ConfigurationClass#addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)。

void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
    this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
1
2
3

可以看到,在addImportBeanDefinitionRegistrar()方法中,会将传入的registrar参数作为Key,importingClassMetadata参数作为Value存储importBeanDefinitionRegistrars结构中。

其中,importBeanDefinitionRegistrars结构的定义如下所示。

private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
1

可以看到,importBeanDefinitionRegistrars是一个LinkedHashMap对象,也就是说,会将ImportBeanDefinitionRegistrar对象和AnnotationMetadata对象的映射关系存入一个LinkedHashMap对象中。

(17)再次回到ConfigurationClassParser类的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate exclusionFilter, boolean checkForCircularImports)方法。

如果@Import注解引入的类既没有实现ImportSelector接口,又没有实现ImportBeanDefinitionRegistrar接口,则执行else逻辑,如下所示。

else {
    this.importStack.registerImport(
        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
1
2
3
4
5

else逻辑中,会按照解析@Configuration注解的逻辑执行,有关@Configuration注解的执行流程,可以参见第1章的内容,这里不再赘述。

其实,在processImports()方法中,如果@Import注解引入的类实现了ImportSelector接口,并且没有实现DeferredImportSelector接口的话,最终还是会执行processImports()方法的else逻辑。

(18)回到ConfigurationClassPostProcessor类的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法。

在ConfigurationClassPostProcessor类的processConfigBeanDefinitions()方法中,执行完ConfigurationClassParser类的parse()方法后,会执行ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions()方法,如下所示。

this.reader.loadBeanDefinitions(configClasses);
1

(19)解析ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions(Set configurationModel)方法

源码详见:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(Set configurationModel),如下所示。

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}
1
2
3
4
5
6

可以看到,在loadBeanDefinitions()方法中,会循环遍历传入的configurationModel集合,并调用loadBeanDefinitionsForConfigurationClass()方法处理遍历的每个元素。

(20)解析ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法

源码详见:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)。

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    //################省略其他代码######################
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
1
2
3
4
5
6
7
8
9
10
11
12

在loadBeanDefinitionsForConfigurationClass()方法中,如果@Import注解引入的是普通的类,或者是实现了ImportSelector接口的类,则会执行if (configClass.isImported())条件的逻辑,此时会调用registerBeanDefinitionForImportedConfigurationClass()方法向IOC容器中注入配置类的BeanDefinition信息。

(21)解析ConfigurationClassBeanDefinitionReader类的registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass)方法

源码详见:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass),如下所示。

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
    //###############省略其他代码#################
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  this.registry.registerBeanDefinition(definitionHolder.getBeanName(),definitionHolder.getBeanDefinition());
    configClass.setBeanName(configBeanName);
	//###############省略其他代码#################
}
1
2
3
4
5
6
7
8

可以看到,在registerBeanDefinitionForImportedConfigurationClass()方法中会调用DefaultListableBeanFactory类的registerBeanDefinition()方法向IOC容器中注入BeanDefinition信息。最终,会将BeanDefinition信息保存到DefaultListableBeanFactory类的beanDefinitionMap中。

(22)回到ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法

如果@Import注解引入的是实现了ImportBeanDefinitionRegistrar接口的类,则在loadBeanDefinitionsForConfigurationClass()方法中调用loadBeanDefinitionsForConfigurationClass()方法时,会通过configClass的getImportBeanDefinitionRegistrars()方法获取第(16)步保存信息的LinkedHashMap对象。

(23)解析ConfigurationClass类的getImportBeanDefinitionRegistrars()方法

源码详见:org.springframework.context.annotation.ConfigurationClass#getImportBeanDefinitionRegistrars()。

Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
    return this.importBeanDefinitionRegistrars;
}
1
2
3

(24)再次ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法。

在loadBeanDefinitionsForConfigurationClass()会调用loadBeanDefinitionsFromRegistrars()方法从实现了ImportBeanDefinitionRegistrar接口的类中加载Bean定义信息。

(25)解析ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)方法

源码详见:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)。

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
1
2
3

可以看到,在loadBeanDefinitionsFromRegistrars()方法中,会遍历传入的registrars,并调用每个registrar的registerBeanDefinitions()方法注册BeanDefinition信息。

(26)解析ImportBeanDefinitionRegistrar接口的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)方法

源码详见:org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
    registerBeanDefinitions(importingClassMetadata, registry);
}
1
2
3

可以看到,registerBeanDefinitions()方法是ImportBeanDefinitionRegistrar接口的一个默认方法,并在方法中调用了另一个registerBeanDefinitions()方法。其中,调用的这个registerBeanDefinitions()方法就是我们自己写的案例中实现了ImportBeanDefinitionRegistrar接口的MyImportBeanDefinitionRegistrar类中的方法。

(27)解析MyImportBeanDefinitionRegistrar类的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法

源码详见:io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
    BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
    registry.registerBeanDefinition(beanName, beanDefinition);
}
1
2
3
4
5
6

可以看到,在registerBeanDefinitions()方法中,最终会调用DefaultListableBeanFactory类的registerBeanDefinition()方法向IOC容器中注入BeanDefinition信息。最终,会将BeanDefinition信息保存到DefaultListableBeanFactory类的beanDefinitionMap中。

至此,整个@Import注解在Spring源码层面的执行流程分析完毕。

# 六、总结

@Import注解讲完了,我们一起总结下吧!

本章,首先介绍了@Import注解的源码和使用场景,并列举了使用@Import注解向IOC容器中注入Bean对象的三种案例。接下来,详细分析了@Import注解的源码时序图和@Import注解在Spring源码层面的执行流程。

# 七、思考

既然学完了,就开始思考几个问题吧?

关于@Import注解,通常会有如下几个经典面试题:

  • 在ConfigurationClassParser类的processImports()中,如果@Import注解引入的是普通类或者引入的是实现了ImportSelector接口,并且没有实现DeferredImportSelector接口的类,最终还是会执行processImports()方法的else逻辑。那么,如果@Import注解引入的是实现了ImportSelector接口,并且没有实现DeferredImportSelector接口的类,最终是如何执行else逻辑的?
  • @Import注解的三种案例在Spring底层的源码执行流程分别是什么?
  • 使用@Import注解向IOC容器中注入Bean与使用@Bean注解有什么区别?
  • 在你自己负责的项目中,会在哪些场景下使用@Import注解向IOC容器中注入Bean?
  • 你从Spring的@Import注解的设计中得到了哪些启发?

# 星球服务

加入星球,你将获得:

1.项目学习:微服务入门必备的SpringCloud Alibaba实战项目、手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】、Seckill秒杀系统项目—进大厂必备高并发、高性能和高可用技能。

2.框架源码:手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】。

3.硬核技术:深入理解高并发系列(全册)、深入理解JVM系列(全册)、深入浅出Java设计模式(全册)、MySQL核心知识(全册)。

4.技术小册:深入理解高并发编程(第1版)、深入理解高并发编程(第2版)、从零开始手写RPC框架、SpringCloud Alibaba实战、冰河的渗透实战笔记、MySQL核心知识手册、Spring IOC核心技术、Nginx核心技术、面经手册等。

5.技术与就业指导:提供相关就业辅导和未来发展指引,冰河从初级程序员不断沉淀,成长,突破,一路成长为互联网资深技术专家,相信我的经历和经验对你有所帮助。

冰河的知识星球是一个简单、干净、纯粹交流技术的星球,不吹水,目前加入享5折优惠,价值远超门票。加入星球的用户,记得添加冰河微信:hacker_binghe,冰河拉你进星球专属VIP交流群。

# 星球重磅福利

跟冰河一起从根本上提升自己的技术能力,架构思维和设计思路,以及突破自身职场瓶颈,冰河特推出重大优惠活动,扫码领券进行星球,直接立减149元,相当于5折, 这已经是星球最大优惠力度!


领券加入星球,跟冰河一起学习《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》,更有已经上新的《大规模分布式Seckill秒杀系统》,从零开始介绍原理、设计架构、手撸代码。后续更有硬核中间件项目和业务项目,而这些都是你升职加薪必备的基础技能。

100多元就能学这么多硬核技术、中间件项目和大厂秒杀系统,如果是我,我会买他个终身会员!

# 其他方式加入星球

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

# 星球规划

后续冰河还会在星球更新大规模中间件项目和深度剖析核心技术的专栏,目前已经规划的专栏如下所示。

# 中间件项目

  • 《大规模分布式定时调度中间件项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式IM(即时通讯)项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式网关项目实战(非Demo)》:全程手撸代码。
  • 《手写Redis》:全程手撸代码。
  • 《手写JVM》全程手撸代码。

# 超硬核项目

  • 《从零落地秒杀系统项目》:全程手撸代码,在阿里云实现压测(已上新)。
  • 《大规模电商系统商品详情页项目》:全程手撸代码,在阿里云实现压测。
  • 其他待规划的实战项目,小伙伴们也可以提一些自己想学的,想一起手撸的实战项目。。。

既然星球规划了这么多内容,那么肯定就会有小伙伴们提出疑问:这么多内容,能更新完吗?我的回答就是:一个个攻破呗,咱这星球干就干真实中间件项目,剖析硬核技术和项目,不做Demo。初衷就是能够让小伙伴们学到真正的核心技术,不再只是简单的做CRUD开发。所以,每个专栏都会是硬核内容,像《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》就是很好的示例。后续的专栏只会比这些更加硬核,杜绝Demo开发。

小伙伴们跟着冰河认真学习,多动手,多思考,多分析,多总结,有问题及时在星球提问,相信在技术层面,都会有所提高。将学到的知识和技术及时运用到实际的工作当中,学以致用。星球中不少小伙伴都成为了公司的核心技术骨干,实现了升职加薪的目标。

# 联系冰河

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:星球编号

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。内容在 冰河技术 微信公众号首发,强烈建议大家关注。

公众号:冰河技术

# 视频号

定期分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

视频号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术