SpringBoot 源码解读系列(2)--自动注入的源码分析(以注入jdbc所需类为案例)

    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,其起到了入口的作用,这个我们下一篇来梳理。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值