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); } } }
可以看到时通过反射来实现赋值的