详解SpringBoot简化配置分析总结

网友投稿 656 2022-11-18


详解SpringBoot简化配置分析总结

在SpringBoot启动类中,该主类被@SpringBootApplication所修饰,跟踪该注解类,除元注解外,该注解类被如下自定注解修饰。

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

让我们简单叙述下它们各自的功能:

@ComponentScan:扫描需要被IoC容器管理下需要管理的Bean,默认当前根目录下的

@EnableAutoConfiguration:装载所有第三方的Bean

@SpringBootConfiguration 作用等同于@Configuration

我们来看下@SpringBootConfiguration

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration

public @interface SpringBootConfiguration {

@AliasFor(

annotation = Configuration.class

)

boolean proxyBeanMethods() default true;

}

可以看到该注解类内包含与@Configuration,其作用与@Configuration并无太大区别,只是多了层属性嵌套。

故: @SpringBootConfiguration + @ComponentScan

将根目录下所有被**@Controller、@Service、@Repository、@Component**等所修饰的类交给IoC容器管理。

那么重点来了,@EnableAutoConfiguration是如何装载第三方Bean的呢?让我们跟踪下它的源码。

首先我们可以看到该类被如下注解修饰:

@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

我们先关注下AutoConfigurationImportSelector这个组件。

// 批量导入第三方的一些Bean

@Import({AutoConfigurationImportSelector.class})

其中该组件的selectImports(AnnotationMetadata annotationMetadata)方法,我们先简述下它的作用:扫描所有需要被管理的第三方Bean并交给IoC容器进行管理。然后我们接着往下追踪。

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return NO_IMPORTS;

} else {

// 让我们跟踪到这个方法

AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);

return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

}

}

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return EMPTY_ENTRY;

} else {

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

// 获取所有AutoConfiguration的配置类

List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

// 下面就是对AutoConfiguration的去重、排除和过滤等操作

configurations = this.removeDuplicates(configurations);

Set exclusions = this.getExclusions(annotationMetadata, attributes);

this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = this.getConfigurationClassFilter().filter(configurations);

// 我们继续追踪这里

this.fireAutoConfigurationImportEvents(configurations, exclusions);

return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

}

}

private void fireAutoConfigurationImportEvents(List configurations, Set exclusions) {

List listeners = this.getAutoConfigurationImportListeners();

if (!listeners.isEmpty()) {

// 加了层包装

AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);

Iterator var5 = listeners.iterator();

while(var5.hasNext()) {

AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();

this.invokeAwareMethods(listener);

// 向ConditionEvaluationReport中导入所有AutoConfiguration

listener.onAutoConfigurationImportEvent(event);

}

}

}

可以猜想IoC容器在启动时会将这里的AutoConfiguration中的每个Bean都注入到容器中。这里的源码我们先跟踪到这里,大致了解了下该方法的作用。

那么SpringBoot又是如何取感知第三方的Bean文件呢?

SpringBoot和第三方Bean之间存在一定的规定。即通过对于相应依赖的Jar包中可能存在一个spring.factories文件,在该文件中就记录了需要被IoC容器管理的Bean文件路径,SpringBoot通过该文件确定需要IoC管理的Bean文件位置。对于spring-boot-autoconfiguration的spring.factories文件中,记录着大量xxxAutoConfiguration的类文件位置,这些类都被@Configuration注解标识,即这些配置类会配置多个Bean从而解决spring.factories可能产生的臃肿问题。

Tomcat的加载时机

对于SpringBoot来说它特点不仅是简化配置,还有内嵌容器等特点。那么就有必要探讨Tomcat容器的加载时机。在spring-boot-autoconfiguration的spring.factories文件中存在ServletWebServerFactoryAutoConfiguration配置类的路径,该类会在项目启动时将默认的Tomcat容器已@Bean的方式加载入IoC容器内部。

SpringBoot是如何集中配置呢?

谈论这个问题前我们不妨先按照之前yml或properties的文件配置下

server:

port: 8080

通过IDE,跟踪到port所配置的成员变量所在类,发现该类被@ConfigurationProperties所修饰,该注解就是将yml或properties中配置按照对应前缀注入到指定类的成员变量。该注解具体实现感兴趣的小伙伴们可以去如下链接学习。   @ConfigurationProperties实现原理与实战

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)

public class ServerProperties {

private Integer port;

*******

}

下面两个代码和前述作用大致相同

environment.getProperty("xxx");

@Value("${xxx}")

我们在使用SpringBoot时只需要做哪些事情?

通常我们再使用SpringBoot时只需要在Maven中引入类似如下的starter依赖。

org.springframework.boot

spring-boot-starter-data-redis

最多再需要配置一些类似mybatis这类框架的一些属性参数。而这些starter按照我们之前的逻辑其内部应该存有spring.factories文件,我们先去对应jar包查找下。

如果有些starter的jar包没有找到我们想要的spring.factories文件。我们可以去spring-boot-test-autoconfiguretion中的spring.factories查看下,SpringBoot内部其实已经定义好相当一定数量的AutoConfiguration。

果然该jar包内确实存在spring.factories文件,代码如下。

org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.redis.repository.support.RedisRepositoryFactory

这意味着我们已经简单地了解了SpringBoot如何简化配置,那么我们也应该可以自己来实现一个starter依赖交给SpringBoot来使用,只要在对应Jar包中添加spring.factories文件,在其中添加如下代码。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxAutoConfiguration

大家若有时间还请实现下自己的starter依赖,对加深这部分理解还是很有帮助的。感兴趣的小伙伴可以看下我做的一个简单的实现。 [自定义starter实现]

最后我们在说下最后@SpringBootApplication中@AutoConfigurationPackage这个注解类,发现其中导入了Registrar组件。

@Import({Registrar.class})

让我们重点关注registerBeanDefinitions这个方法,该方法最终会来到DefaultListableBeanFactory中registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,将AutoConfigurationPackages.class注册到IoC容器中,然后将主配置类所在包下所有组件导入到SpringIoC容器中

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

// 里面就这一个方法我们跟踪下

AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));

}

public static void register(BeanDefinitionRegistry registry, String... packageNames) {

// 判断beanDefinitionMap是否存在AutoConfigurationPackages

if (registry.containsBeanDefinition(BEAN)) {

BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);

ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();

constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));

} else {

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);

beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);

beanDefinition.setRole(2);

// 将设置好的AutoConfigurationPackages注册到beanDefinitionMap(是不是很熟悉这一步)

registry.registerBeanDefinition(BEAN, beanDefinition);

}

}

怎么样,在为我们简化了配置的同时,SpringBoot居然帮我们做了如此多的事情,而我们只需要简单地集中配置其中一部分的属性。关于SpirngBoot我们就探讨到这里,这些内容是阅读一些文章,观看部分讲解和源码的总结,若有错误还请接纳与指教。这是本人的第一篇文章,最后感谢各位的阅读。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:log4j与slf4j的使用与区别详解
下一篇:Java连接数据库,及增删改查的示例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~