从servlet到springboot(10) springboot 自动化配置原理

56 篇文章 2 订阅

Spring Boot中引入了自动配置,让开发者利用起来更加的简便、快捷。这一节我们将来分析springboot如何做到springboot的自动化配置的

在前面几篇中的某一篇,我们曾经说到过ConfigFileApplicationListener这个Listeners,这个Listeners当时说的是解决了一个读取哪一个application.properties的功能,我们细说了这个功能。其他这个组件还解决了读取properties文件内容的功能。

先找到这个类的onApplicationEvent方法

@Override
public void onApplicationEvent(ApplicationEvent event) {
   if (event instanceof ApplicationEnvironmentPreparedEvent) {
      onApplicationEnvironmentPreparedEvent(
            (ApplicationEnvironmentPreparedEvent) event);
   }
  。。。。
}

进入 onApplicationEnvironmentPreparedEvent

private void onApplicationEnvironmentPreparedEvent(
      ApplicationEnvironmentPreparedEvent event) {
   List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
   postProcessors.add(this);
   AnnotationAwareOrderComparator.sort(postProcessors);
   for (EnvironmentPostProcessor postProcessor : postProcessors) {
      postProcessor.postProcessEnvironment(event.getEnvironment(),
            event.getSpringApplication());
   }
}

这里获取到的postProcessors有三个

我们忽视上面两个,直接看ConfigFileApplicationListener

进入这个类的postProcessEnvironment 方法

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
      SpringApplication application) {
   addPropertySources(environment, application.getResourceLoader());
   configureIgnoreBeanInfo(environment);
   bindToSpringApplication(environment, application);
}

进入addPropertySources(environment, application.getResourceLoader());方法

直接看load方法

this.propertiesLoader = new PropertySourcesLoader();
this.activatedProfiles = false;
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<Profile>();

// Pre-existing active profiles set via Environment.setActiveProfiles()
// are additional profiles and config files are allowed to add more if
// they want to, so don't call addActiveProfiles() here.
Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) {
   for (String defaultProfileName : this.environment.getDefaultProfiles()) {
      Profile defaultProfile = new Profile(defaultProfileName, true);
      if (!this.profiles.contains(defaultProfile)) {
         this.profiles.add(defaultProfile);
      }
   }
}

上面这一大串方法其实就是找到application.properties的一个前缀,如果没有就是default

while (!this.profiles.isEmpty()) {
   Profile profile = this.profiles.poll();
   for (String location : getSearchLocations()) {
      if (!location.endsWith("/")) {
         // location is a filename already, so don't search for more
         // filenames
         load(location, null, profile);
      }
      else {
         for (String name : getSearchNames()) {
            load(location, name, profile);
         }
      }
   }
   this.processedProfiles.add(profile);
}
getSearchLocations()是获取到application可能得位置

我们进入else的load方法

 

private void load(String location, String name, Profile profile) {
   String group = "profile=" + (profile == null ? "" : profile);
   if (!StringUtils.hasText(name)) {
      // Try to load directly from the location
      loadIntoGroup(group, location, profile);
   }
   else {
      // Search for a file with the given name
      for (String ext : this.propertiesLoader.getAllFileExtensions()) {
         if (profile != null) {
            // Try the profile-specific file
            loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                  null);
            for (Profile processedProfile : this.processedProfiles) {
               if (processedProfile != null) {
                  loadIntoGroup(group, location + name + "-"
                        + processedProfile + "." + ext, profile);
               }
            }
            // Sometimes people put "spring.profiles: dev" in
            // application-dev.yml (gh-340). Arguably we should try and error
            // out on that, but we can be kind and load it anyway.
            loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                  profile);
         }
         // Also try the profile-specific section (if any) of the normal file
         loadIntoGroup(group, location + name + "." + ext, profile);
      }
   }
}

this.propertiesLoader.getAllFileExtensions()这里是获取到所有可以解析的文件名后缀

loadIntoGroup(group, location + name + "." + ext, profile);这里是主要解析动作

直接看

private PropertySource<?> doLoadIntoGroup(String identifier, String location,
      Profile profile) throws IOException {
   Resource resource = this.resourceLoader.getResource(location);
   PropertySource<?> propertySource = null;
   StringBuilder msg = new StringBuilder();
   if (resource != null && resource.exists()) {
      String name = "applicationConfig: [" + location + "]";
      String group = "applicationConfig: [" + identifier + "]";
      propertySource = this.propertiesLoader.load(resource, group, name,
            (profile == null ? null : profile.getName()));
      if (propertySource != null) {
         msg.append("Loaded ");
         handleProfileProperties(propertySource);
      }
      else {
         msg.append("Skipped (empty) ");
      }
   }
   else {
      msg.append("Skipped ");
   }
   msg.append("config file ");
   msg.append(getResourceDescription(location, resource));
   if (profile != null) {
      msg.append(" for profile ").append(profile);
   }
   if (resource == null || !resource.exists()) {
      msg.append(" resource not found");
      this.logger.trace(msg);
   }
   else {
      this.logger.debug(msg);
   }
   return propertySource;
}

这个方法主要是做了解析动作,最终会吧application中的配置项转化成一个propertySource

addConfigurationProperties(this.propertiesLoader.getPropertySources());最后通过这句话把propertySource加入到Enviroment中propertySources属性集合中

到这里解析properties文件的内容已经分析完了

还有一个比较重要的组件我们之前在分析创建tomat实例的时候已经说了解析@Configuration配置类的一个后置处理器ConfigurationClassPostProcessor

我们分析另外一个组件@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ EnableAutoConfigurationImportSelector.class,
        AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {
 
    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};
 
}
这里注解里Import了 EnableAutoConfigurationImportSelector.class,AutoConfigurationPackages.Registrar.class

这里说明下,@Import添加执行selectImports方法返回的数组。我们直接进入 EnableAutoConfigurationImportSelector.class

@Override
public String[] selectImports(AnnotationMetadata metadata) {
   if (!isEnabled(metadata)) {
      return NO_IMPORTS;
   }
   try {
      AnnotationAttributes attributes = getAttributes(metadata);
      List<String> configurations = getCandidateConfigurations(metadata,
            attributes);
      configurations = removeDuplicates(configurations);
      Set<String> exclusions = getExclusions(metadata, attributes);
      configurations.removeAll(exclusions);
      configurations = sort(configurations);
      recordWithConditionEvaluationReport(configurations, exclusions);
      return configurations.toArray(new String[configurations.size()]);
   }
   catch (IOException ex) {
      throw new IllegalStateException(ex);
   }
}

List<String> configurations = getCandidateConfigurations(metadata, attributes);然后根据mian类上的注解获取到所有符合条件的上面标注@EnableAutoConfiguration的类

我们用RedisAutoConfiguration

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration

一次解释下三个注解。第一个不用说了,第二个是说只有在类路径下有JedisConnection   RedisOperations  Jedis三个Class文件才会解析RedisAutoConfiguration,如果我们没有导入含有这三个Class的包,就自动跳过

@EnableConfigurationProperties:表示对@ConfigurationProperties的内嵌支持,默认会将对应Class这是为bean,例如这里值为RabbitProperties.class,其定义为:

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

   /**
    * Database index used by the connection factory.
    */
   private int database = 0;

   /**
    * Redis server host.
    */
   private String host = "localhost";

   /**
    * Login password of the redis server.
    */
   private String password;

忽略部分

RedisProperties提供对redis的配置信息,其前缀为spring.redis,因此在properties中配置的信息会放到这个bean上,具体如何解析properties我们在说明configFileApplicationListeners已经说明了,随后@EnableConfigurationProperties会将RedisProperties注册为一个bean。

 

我们分析一下@EnableConfigurationProperties是如何工作的

@EnableConfigurationProperties这个注解是这样的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

我们又看到了一个@Import,进入EnableConfigurationPropertiesImportSelector的selectImports方法

@Override
public String[] selectImports(AnnotationMetadata metadata) {
   MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
         EnableConfigurationProperties.class.getName(), false);
   Object[] type = attributes == null ? null
         : (Object[]) attributes.getFirst("value");
   if (type == null || type.length == 0) {
      return new String[] {
            ConfigurationPropertiesBindingPostProcessorRegistrar.class
                  .getName() };
   }
   return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
         ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
}

看到这里会返回一个ConfigurationPropertiesBindingPostProcessorRegistrar,而这个类的

registerBeanDefinitions会在spring容器中注入一个
ConfigurationPropertiesBindingPostProcessor的后置处理器,这又是一个熟悉的影子,我们直接分析这个处理器的

postProcessBeforeInitialization方法

private void postProcessBeforeInitialization(Object bean, String beanName,
      ConfigurationProperties annotation) {
   Object target = bean;
   PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
         target);
   if (annotation != null && annotation.locations().length != 0) {
      factory.setPropertySources(
            loadPropertySources(annotation.locations(), annotation.merge()));
   }
   else {
      factory.setPropertySources(this.propertySources);
   }
   factory.setValidator(determineValidator(bean));
   // If no explicit conversion service is provided we add one so that (at least)
   // comma-separated arrays of convertibles can be bound automatically
   factory.setConversionService(this.conversionService == null
         ? getDefaultConversionService() : this.conversionService);
   if (annotation != null) {
      factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
      factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
      factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
      factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
      if (StringUtils.hasLength(annotation.prefix())) {
         factory.setTargetName(annotation.prefix());
      }
   }
   try {
      factory.bindPropertiesToTarget();
   }
   catch (Exception ex) {
      String targetClass = ClassUtils.getShortName(target.getClass());
      throw new BeanCreationException(beanName, "Could not bind properties to "
            + targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
   }
}

PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>( target); 这句话实例化了一个工厂bean,这个工厂bean实现了FactoryBean, MessageSourceAware, InitializingBean 接口,并进行一些属性的设置

factory.setPropertySources(this.propertySources);这句话就是把第一步中解析到的properties文件中的内容封装成的propertySources加入到工厂bean中

factory.bindPropertiesToTarget()这局话从字面上也能看出是把相应的的属性绑定到相应的target上,target就是xxxProperties

我们跟踪下factory.bindPropertiesToTarget()这句代码

直接到BeanWrapperImpl中的setValue方法,我们可以看出是

@Override
   public void setValue(final Object object, Object valueToApply) throws Exception {
      final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
            ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
            this.pd.getWriteMethod());
      if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
         if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
               @Override
               public Object run() {
                  writeMethod.setAccessible(true);
                  return null;
               }
            });
         }
         else {
            writeMethod.setAccessible(true);
         }
      }
      final Object value = valueToApply;
      if (System.getSecurityManager() != null) {
         try {
            AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
               @Override
               public Object run() throws Exception {
                  writeMethod.invoke(object, value);
                  return null;
               }
            }, acc);
         }
         catch (PrivilegedActionException ex) {
            throw ex.getException();
         }
      }
      else {
         writeMethod.invoke(getWrappedInstance(), value);
      }
   }
}

可以看到时通过反射来实现赋值的

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值