Spring把「手动」的复杂裹成了「自动」的温柔

案例

案例一:@EnableXXX注解使用
在一个 Spring MVC 项目,通过给配置类加上一个 @EnableWebMvc 注解,加上之后 Spring 就会注册 Spring MVC 的一系列组件,包括:HandlerMapping,HandlerAdapter,ViewResolver 等。

案例二:Spring Boot自动配置
在一个 Spring Boot应用中会有 @SpringBootApplication 注解修饰启动类,当引入 spring-boot-starter-web 依赖之后,Spring 也会自动地注册 Spring MVC 的一系列组件。

那 Spring 中是如何实现自动注册的能力的呢?先说结论:
Spring 中提供了 @Import 注解可以引入一个配置类或者是配置类的选择器。

当使用一般的 @EnableXXX 注解时实际上是通过 @Import 注解引入了预先定义好的配置类,它会配置一些指定的 Bean 来实现对应的功能。

当使用 Spring Boot 的自动配置功能时实际上是通过 @Import 注解引入了一个配置类的选择器,它会读取配置文件中配置的所有配置类,然后判断该配置类的条件是否满足,如果满足,则引入,否则,则不引入,从而实现自动配置某些功能。

源码分析

@EnableXXX注解实现原理

先看一下 @EnableWebMvc 注解,该注解上通过 @Import 注解引用了一个 DelegatingWebMvcConfiguration 配置类,它上面有 @Configuration 注解修饰。代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

@Configuration(proxyBeanMethods = false)  
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}

在 DelegatingWebMvcConfiguration 这个配置类的父类 WebMvcConfigurationSupport 中定义了很多由 @Bean 注解修饰的方法,这些就是 Spring 会注册的 Spring MVC 组件类。代码如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    // 定义HandlerMapping组件Bean
    @Bean
    @SuppressWarnings("deprecation")
    public RequestMappingHandlerMapping requestMappingHandlerMapping(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

        // 省略代码
    }

    // 定义HandlerAdapter组件Bean
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcValidator") Validator validator) {

        // 省略代码
    }

    // 定义ViewResolver组件Bean
    @Bean
    public ViewResolver mvcViewResolver(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
        
        // 省略代码
    }
}

3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot文章中介绍了 Spring 中如何从 @Configuration 注解修饰的配置类的包扫描路径取扫描 Bean 的。主要是在ConfigurationClassParser 的 doProcessConfigurationClass() 方法中实现的,而对 @Import 注解引用的类也是在该方法中实现的。代码如下:

@Nullable
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

    // 省略代码

	// 这里处理@Import注解
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 省略代码

	// 这里处理@Bean注解修饰的方法
    // Process individual @Bean methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) {
            continue;
        }
        // 添加到配置类中
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 省略代码

    // No superclass -> processing is complete
    return null;
}

在处理 @Import 注解引用的且是 @Configuration 注解修饰的类时,把它当作配置类,递归调用解析配置类的方法 processConfigurationClass(),然后又进入到 doProcessConfigurationClass() 中,解析该类上 @Bean 注解修饰的方法添加到配置类中 。代码如下:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
    // 省略代码

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        // 省略代码
    }
    else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
				// 省略代码

                // 处理@Configuration注解修饰的类,就是去把它当作配置类继续解析它的配置
                this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                processConfigurationClass(candidate.asConfigClass(configClass), filter);
            }
        } finally {
            this.importStack.pop();
        }
    }
}

具体把配置类中的 Bean 方法解析为 Bean 定义则是在 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions() 中调用 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法实现的。代码如下:

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 = CollectionUtils.newHashSet(configCandidates.size());
    do {
	    // 省略代码
        parser.parse(candidates);
        parser.validate();

		// 省略代码

        // 这里调用loadBeanDefinitionsForBeanMethod()解析并注册Bean定义
        this.reader.loadBeanDefinitions(configClasses);
        
        // 省略代码
    }
    while (!candidates.isEmpty());

    // 省略代码
}

然后在 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法中从 @Bean 注解中获取 initMethoddestroyMethod 这些信息,然后注册 Bean 定义。代码如下:

private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // 省略代码
    
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    
    // 省略代码
}

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    ConfigurationClass configClass = beanMethod.getConfigurationClass();
    MethodMetadata metadata = beanMethod.getMetadata();
    String methodName = metadata.getMethodName();

    // 省略代码
    ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);

    String initMethodName = bean.getString("initMethod");
    if (StringUtils.hasText(initMethodName)) {
        beanDef.setInitMethodName(initMethodName);
    }

    String destroyMethodName = bean.getString("destroyMethod");
    beanDef.setDestroyMethodName(destroyMethodName);

	// 注册Bean定义
    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

Spring Boot 自动配置原理

对于一个 Spring Boot 应用上的 @SpringBootApplication 注解是一个组合注解,它上面有 @EnableAutoConfiguration 注解修饰,而这个注解则是实现自动配置的关键。代码如下:

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@SpringBootConfiguration  
@EnableAutoConfiguration  
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),  
       @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })  
public @interface SpringBootApplication {}

@EnableAutoConfiguration 注解和上面的 @EnableWebMvc 注解类似也是通过 @Import 注解引入了一个类 AutoConfigurationImportSelector,但是这个类却没有 @Configuration 注解修饰,而是实现了 ImportSelector 接口。代码如下:

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@AutoConfigurationPackage  
@Import(AutoConfigurationImportSelector.class)  
public @interface EnableAutoConfiguration {}

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,  
       ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}

在上面的 ConfigurationClassParser 类的 processImports() 方法中有一个分支就是判断 @Import 注解引入的类是不是 DeferredImportSelector 接口,如果是则会调用 DeferredImportSelectorHandler 的 handle() 方法进行处理。代码如下:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (selector instanceof DeferredImportSelector deferredImportSelector) {  
					    // 调用deferredImportSelectorHandler的handle()方法
					    this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);  
					}
            }
        } finally {
            this.importStack.pop();
        }
    }
}

而 DeferredImportSelectorHandler 的 handle 方法只是先把当前类加入到自己的 deferredImportSelectors 属性中。代码如下:

private class DeferredImportSelectorHandler {
    @Nullable
    private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

    void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
        DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
        if (this.deferredImportSelectors == null) {
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            handler.register(holder);
            handler.processGroupImports();
        }
        else {
            this.deferredImportSelectors.add(holder);
        }
    }
}

最后在 ConfigurationClassParser 的 parse() 方法最后调用它的 process() 方法。在 DeferredImportSelectorHandler 的 process() 方法中又调用了 DeferredImportSelectorHolder 的 processGroupImport() 方法。代码如下:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // 省略代码

    this.deferredImportSelectorHandler.process();
}

void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
    DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
    if (this.deferredImportSelectors == null) {
        DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
        handler.register(holder);
        handler.processGroupImports();
    }
    else {
        this.deferredImportSelectors.add(holder);
    }
}

private class DeferredImportSelectorGroupingHandler {
    @SuppressWarnings("NullAway")
    void processGroupImports() {
        for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
            Predicate<String> filter = grouping.getCandidateFilter();
            // 调用getImports()方法获取到配置类,然后在递归调用processImports()方法
            grouping.getImports().forEach(entry -> {
                ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
                try {
                    processImports(configurationClass, asSourceClass(configurationClass, filter),
                            Collections.singleton(asSourceClass(entry.getImportClassName(), filter)),
                            filter, false);
                }
                // 省略代码
            });
        }
    }
}

然后调用到了 AutoConfigurationGroup 的 process() 方法,在该方法中会调用 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法,这个里这个类就是通过 @EnableAutoConfiguration 引入的类了。代码如下:

private static final class AutoConfigurationGroup
    implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
    @Override
    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
        // 省略代码
        
        // 调用AutoConfigurationImportSelector的getAutoConfigurationEntry()方法
        AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
            .getAutoConfigurationEntry(annotationMetadata);
            
        this.autoConfigurationEntries.add(autoConfigurationEntry);
        for (String importClassName : autoConfigurationEntry.getConfigurations()) {
            this.entries.putIfAbsent(importClassName, annotationMetadata);
        }
    }
}

在 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法调用 ImportCandidates 读取默认值为 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列举的配置类,然后过滤掉不满足条件的配置类,过滤的方式可以是判断 CLASSPATH 路径下某些类是否存在。代码如下:

AutoConfigurationImportSelector{
        protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        // 省略代码
        
        // 获取所有配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        
        // 过滤掉不满足条件的配置类
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
                getBeanClassLoader());
        return configurations;
    }
}

public final class ImportCandidates implements Iterable<String> {

    private static final String LOCATION = "META-INF/spring/%s.imports";
    
    public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
        ClassLoader classLoaderToUse = decideClassloader(classLoader);
        // 这里就是配置类所在文件,默认是org.springframework.boot.autoconfigure.AutoConfiguration.imports
        String location = String.format(LOCATION, annotation.getName());
        Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
        List<String> importCandidates = new ArrayList<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            importCandidates.addAll(readCandidateConfigurations(url));
        }
        return new ImportCandidates(importCandidates);
    }
}

org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中内容如下:

image.png

这里以 WebMvcAutoConfiguration 配置类为例,它要不被过滤掉的条件是 CLASSPATH 路径下存在 ServletDispatcherServletWebMvcConfigurer 这些类,即这些类存在则会解析 WebMvcAutoConfiguration 配置类配置的 Bean,从而实现 Spring MVC 组件的 Bean 的注册。代码如下:

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,  
       ValidationAutoConfiguration.class })  
@ConditionalOnWebApplication(type = Type.SERVLET)  
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })  
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)  
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)  
@ImportRuntimeHints(WebResourcesRuntimeHints.class)  
public class WebMvcAutoConfiguration {}

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值