1、spring-boot-autoconfigure模块
我们在上一篇梳理整体SpringBoot源码的项目结构的时候有提到这个结构,这个项目应该算是自动注入的主要逻辑处理了,项目我们来梳理下这个模块。
1、condition包(spring-boot-autoconfigure模块)
要了解这个包,首先需要了解下Condition这个标签,这个可以搜索其他的博文、或者看下在前面有梳理的文章关于BeanDefintion的结构的文章,例如这篇。简单来说,如果一个Condition接口有经由@Conditional作用在要注入的Bean上面,首先就需要调用其matches方法,如果通过了,才会注入这个Bean,是不是有嗅到了自动注入的味道了^_^,好了本文到此结束。
------------------------------------------------------------------------------------------------------------------------------------------------
同时这个知识点也体现在了前面写的这篇文章。下面我们来看下在这个包下面有哪些接口与类。
这里的接口如@ConditionalOnClass、@ConditionalOnBean与下面的如OnBeaCondition、OnClassCondition这些是对应的,OnBeaCondition这种就是用来处理与之对应的@ConditionalOnBean注解的(可以搜索其他博文了解下这些注解的具体使用)。
下面我们来简单分析下这些以Conditional开头的注解,以及与之对应处理的类。
1、@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { ............. } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { ............... } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean { ............. }
可以看到这里的这些注解主要就是经由@Conditional引入了对应的处理类。例如OnBeanCondition、OnClassCondition,下面我们来看下这些类。
2、OnBeanCondition
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition { ............ @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class); MatchResult matchResult = getMatchingBeans(context, spec); if (!matchResult.isAllMatched()) { String reason = createOnBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnBean.class, spec).because(reason)); } matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec) .found("bean", "beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { ............ matchMessage = matchMessage .andCondition(ConditionalOnSingleCandidate.class, spec) .found("a primary bean from beans") .items(Style.QUOTE, matchResult.namesOfAllMatches); } if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { ............ matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec) .didNotFind("any beans").atAll(); } return ConditionOutcome.match(matchMessage); } } public class ConditionOutcome { private final boolean match; private final ConditionMessage message; } private static class ConditionContextImpl implements ConditionContext { @Nullable private final BeanDefinitionRegistry registry; @Nullable private final ConfigurableListableBeanFactory beanFactory; private final Environment environment; private final ResourceLoader resourceLoader;
可以看到这个ConditionOutcome包含两个信息,一个是是否匹配、还有一个就是匹配过程中产生的内容,这里的入参AnnotatedTypeMetadata 是描叙对应要注入的Bea对应Class的信息的,如一些注解,同时ConditionContext 的实现类有beanFactory、environment这些信息。同时其有继承SpringBootCondition,实现ConfigurationCondition。我们主要关注下SpringBootCondition注解(因为其实现了Condition接口的matchs方法)。
public abstract class SpringBootCondition implements Condition { private final Log logger = LogFactory.getLog(getClass()); @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } ............. } ........... public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata); ............ }
可以看到前面OnBeanCondition就是对SpringBootCondition的抽象方法getMatchOutcome的实现。然后在matches方法中调用(这里需要理解前面提到的这个matches方法的使用)。通过getMatchOutcome去处理获取ConditionOutcome 看配不配配。由此来判断这个要注入的Bean看注不注入。
然后上面提到的OnClassCondition与这个OnBeanCondition功能是类似的。
了解了这个我们来看下spring-boot-autoconfigure模块是怎样处理jdbc数据库操作相关的类注入的,例如DataSource、JdbcTemplate这些类是怎样处理注入的。
2、jdbc包(spring-boot-autoconfigure模块)
下面我们通过该包我们来梳理下主动注入的操作的:
1、DataSourceConfiguration
abstract class DataSourceConfiguration { @SuppressWarnings("unchecked") protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) { return (T) properties.initializeDataSourceBuilder().type(type).build(); } ................... @Configuration @ConditionalOnClass(HikariDataSource.class) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true) static class Hikari { @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari") public HikariDataSource dataSource(DataSourceProperties properties) { HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class); ........ return dataSource; } } @Configuration @ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource", matchIfMissing = true) static class Dbcp2 { @Bean @ConfigurationProperties(prefix = "spring.datasource.dbcp2") public org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) { return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class); } } ............. }
可以看到其是一个abstract类,然后要注入一个数据源,在对应方法上,就有注入@Configuration、@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty。
我们就以注入HikariDataSource威力(这个是SpringBoot默认的dataSource)为例。这些标签的意思是:通过@ConditionalOnClass判断在当前项目中有没有引入HikariDataSource.class,再通过@ConditionalOnMissingBean判断目前有没有注入DataSource.class对应的Bean,再通过@ConditionalOnProperty判断在配置文件中(如application.yml)有没有设置这个key及对应的value,如果有设置就会加载对应设置的DataSource。例如如果在配置文件设置"spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource"(同时也引入了dbcp2包),就会加载对应dbcp2的DataSource。但目前我们用的是默认的,没有在配置文件设置"spring.datasource.type"这个key,所以对于引入HikariDataSource.class来说,其@ConditionalOnProperty有设置matchIfMissing为true(不匹配也通过)。所以由上面这些标签就注入了对应的默认DataSource。
2、DataSourceProperties
然后上面的注入DataSource时有一个入参即DataSourceProperties,这个就是才有一些数据库连接信息,例如数据库地址、用户名、密码等在配置文件中配置的内容。
@ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { ............ private String driverClassName; private String url; private String username; private String password; ...........
可以看到其会通过@ConfigurationProperties读取在配置文件中配置的数据库信息:
spring.datasource.url: xxxx spring.datasource.username: xxxx spring.datasource.password: xxxx spring.datasource.driver-class-name: com.mysql.jdbc.Driver
2、DataSourceAutoConfiguration
@Configuration @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) @EnableConfigurationProperties(DataSourceProperties.class) @Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class}) public class DataSourceAutoConfiguration { @Configuration @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({DataSource.class, XADataSource.class}) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration { } @Configuration @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({DataSource.class, XADataSource.class}) @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class}) protected static class PooledDataSourceConfiguration { }
可以看到在这里通过EnableConfigurationProperties将DataSourceProperties注入到了Bean容器中,然后通过@Import将前面DataSourceConfiguration的内容去扫描添加到容器中,这个可以看下前面关于ConfigurationClassPostProcessor的描叙。
然后我们再梳理下@EnableConfigurationProperties注解的源码。
3、@EnableConfigurationProperties
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesImportSelector.class) public @interface EnableConfigurationProperties { Class<?>[] value() default {}; }
我们可以看到其主要是@Import(EnableConfigurationPropertiesImportSelector.class)
class EnableConfigurationPropertiesImportSelector implements ImportSelector { private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName()}; @Override public String[] selectImports(AnnotationMetadata metadata) { return IMPORTS; }
我们可以看到其是有实现ImportSelector接口,这个接口我们在前面关于ConfigurationClassPostProcessor也有梳理过。然后这里实质上是去注入ConfigurationPropertiesBeanRegistrar、ConfigurationPropertiesBindingPostProcessorRegistrar,这两个其是实现的ImportBeanDefinitionRegistrar,这个也是在ConfigurationClassPostProcessor中进行解析处理。
1、ConfigurationPropertiesBeanRegistrar
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); } private List<Class<?>> getTypes(AnnotationMetadata metadata) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(), false); return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList()); } .......... private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) { String name = getName(type); if (!containsBeanDefinition(beanFactory, name)) { registerBeanDefinition(registry, name, type); } } private String getName(Class<?> type) { ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class); ........... }
其主要是用来处理注入的Bean上面的@EnableConfigurationProperties标签中的值的。例如@EnableConfigurationProperties(DataSourceProperties.class),就是由ConfigurationPropertiesBeanRegistrar去处理将DataSourceProperties Bean注入到容器中。
2、ConfigurationPropertiesBindingPostProcessorRegistrar
这个Registrar就是用来给例如DataSourceProperties这种赋值的,其是用来处理@ConfigurationProperties注解的:
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition( ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { registerConfigurationPropertiesBindingPostProcessor(registry); ........ } } private void registerConfigurationPropertiesBindingPostProcessor( BeanDefinitionRegistry registry) { ............ registry.registerBeanDefinition( ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition); }
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { ............ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class); if (annotation != null) { bind(bean, beanName, annotation); } return bean; }
可以看到这里就是通过ConfigurationPropertiesBindingPostProcessorRegistrar来注入ConfigurationPropertiesBindingPostProcessor ,其有实现BeanPostProcessor接口,作用与所有的Bean,通过bind方法来处理这些bean对应的关于ConfigurationProperties 的逻辑。
4、JdbcTemplateAutoConfiguration
@Configuration @ConditionalOnClass({DataSource.class, JdbcTemplate.class}) @ConditionalOnSingleCandidate(DataSource.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) @EnableConfigurationProperties(JdbcProperties.class) public class JdbcTemplateAutoConfiguration { @Configuration static class JdbcTemplateConfiguration { private final DataSource dataSource; private final JdbcProperties properties; JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) { this.dataSource = dataSource; this.properties = properties; } @Bean @Primary @ConditionalOnMissingBean(JdbcOperations.class) public JdbcTemplate jdbcTemplate() { JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource); JdbcProperties.Template template = this.properties.getTemplate(); jdbcTemplate.setFetchSize(template.getFetchSize()); jdbcTemplate.setMaxRows(template.getMaxRows()); if (template.getQueryTimeout() != null) { jdbcTemplate .setQueryTimeout((int) template.getQueryTimeout().getSeconds()); } return jdbcTemplate; } }
上面这些就是注入对应DataSouurce、JdbcTemplate的代码逻辑。但是在这里我们还需要注意一个地方,就是@ConditionalOnClass注解,例如默认使用的DataSource--HikariDataSource,所以首先需要引入这个HikariDataSource,我们来看下spring-boot-autoconfigure的pom.xml
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <optional>true</optional> </dependency>
其就再这里引用了HikariCP来引入了HikariDataSource。但这里还有一个需要重要明白的点,就是<optional>true</optional>,可以看下其他的博文具体了解,这里简单说明下:首先是ProductA,然后ProductB引入了ProductA并且是以<optional>true</optional>的形式,则当ProductC引用ProductB的时候,没有主动去引入ProductA,那在引入ProductB的时候,是不会主动引入ProductA的。梳理这个对HikariCP来引入HikariDataSource的逻辑就是start-xxx该完成的内容了。
可以看到JdbcTemplate就是在这个JdbcTemplateAutoConfiguration中注入的。
2、spring-boot-starter-jdbc模块
我们通过上篇文章知道spring-boot-starter-jdbc的parent是spring-boot-starters,同时spring-boot-starter-jdbc没有代码逻辑,只有pom.xml逻辑,其注入逻辑就是在spring-boot-autoconfigure模块进行定义的,但我们在上面有提到要通过@ConditionalOnClass注解处理触发对应注入,现在我们来看下start-jdbc的pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> </dependencies>
可以看到start-xxx(start-jdbc)主要就是引入HikariCP,来触发spring-boot-autoconfigure模块定义关于DataSource的默认注入逻辑(其他的start-xxx也是与这个spring-boot-starter-jdbc类似)。
由此我们就梳理了当我们引入了spring-boot-starter-jdbc,其是怎样完成默认注入数据源DataSource或者自定义注入DataSource的过程,由此也可以推测到其他的start。不过这里还有一个注解@EnableAutoConfiguration,其起到了入口的作用,这个我们下一篇来梳理。