Springboot自动加载的原理

一.概括

我们用Springboot很方便的能将一个框架给搭建起来,是因为它将以前我们需要手动配置的地方都利用自动配置来代替,利用约定大于配置的思想简化了我们开发工作量。例如:在没有springboot之前,我们要在工程里面连接数据库的时候,我们需要在applicationContext.xml文件里面配置:

<bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
 
    <property name="driverClassName"  value="com.mysql.jdbc.Driver" />
 
     <property name="url" value="jdbc:mysql://localhost:3306/test" />
 
     <property name="username" value="root" />
 
     <property name="password" value="123456" />
 
    </bean>

但引入springboot后不需要声明DriverManagerDataSource,只需要在applicationContext.properties里面配置连接数据库的所需要的URL,用户名和密码就可以了,然后在pom文件里面引入相应的jar包就可以了,至于为什么可以这样,下面会讲解到

spring.datasource.druid.url=jdbc:mysql://127.0.01:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

所以自动加载对于springboot来说很重要。
springboot启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);
    }

}

@SpringBootApplication注解,@SpringBootApplication是一个复合型注解,里面包含了三个很重要的注解:
@SpringBootConfiguration:说明DemoApplication这个类是一个ioc容器配置类,可以在DemoApplication类中声明一些bean,然后通过@ComponentScan加载到ioc容器中,相当于我们配置文件里面的

<beans>
    <bean id="**" class="******">
    </bean>
    
    .......
</beans>

@ComponentScan:扫描当前包下的所有类和当前包下面子包所包含的类,将带有注解的类加载到ioc容器,@ComponentScan在springboot中的作用就是将代码中带有@Controller,@Service,@Repority等这些注解的类加载到ioc容器中。相当于我们在配置文件中写的这样一段代码

<context:component-scan base-package="***.***.***"/>

@EnableAutoConfiguration:基于你配置的依赖项,也就是引入的jar包,扫描所有jar包下面的META-INF/spring.factories,spring.factories中都是这个jar的配置类,配置类里面就有我们所需要的工具类。将所有复合自动配置条件的bean定义加载到ioc容器中,记住@EnableAutoConfiguration自动加载的是一些不需要我们自己去定义但是需要用到的“工具类”,例如上面提到的DriverManagerDataSource类。
@EnableAutoConfiguration能自动加载我们项目中所需要的“工具类”是用到了SpringFactoriesLoader,SpringFactoriesLoader能将指定的配置文件META-INF/spring.factories加载配置。

@Target({ElementType.TYPE})// 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME)// 注解的生命周期,保留到class文件中
@Documented// 表明这个注解应该被javadoc记录
@Inherited// 子类可以继承该注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

二.@EnableAutoConfiguration的基本原理
来看看@EnableAutoConfiguration这个注解是如何实现

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

在@EnableAutoConfiguration中有两个比较重要的注解,一个是@AutoConfigurationPackage,另外一个是@Import(EnableAutoConfigurationImportSelector.class)
@AutoConfigurationPackage的作用就是将当前启动类所在的包以bean的形式注册到ioc容器中,至于在后面起什么作用,我在网上查找了半天,也没有一个说清楚的,有谁知道的,请在下面留言告诉我。
@Import(EnableAutoConfigurationImportSelector.class):借助EnableAutoConfigurationImportSelector父类AutoConfigurationImportSelector的selectImports方法来读取所有依赖的jar包下面META-INF/spring.factories文件,并且根据加载条件来加载项目所需要的类,这样就完成了springboot的自动加载。
EnableAutoConfigurationImportSelector继承了AutoConfigurationImportSelector,看看AutoConfigurationImportSelector的selectImports方法

       /**
        * 最主要的方法
        * annotationMetadata
        * [@org.springframework.boot.autoconfigure.SpringBootApplication
        * (scanBasePackageClasses=[], excludeName=[], exclude=[], scanBasePackages=[])]
        * @param annotationMetadata
        * @return
        */
       @Override
       public String[] selectImports(AnnotationMetadata annotationMetadata) {
              if (!isEnabled(annotationMetadata)) {
                     return NO_IMPORTS;
              }
              /**
               * 
               */
              AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                           .loadMetadata(this.beanClassLoader);
              /**
               * 得到注解中的所有属性信息{excludeName=[], exclude=[]}
               */
              AnnotationAttributes attributes = getAttributes(annotationMetadata);
              /**
               *加载META-INF/spring-autoconfigure-metadata.properties,获取所有支持自动配置的信息
               * 获取所有支持EnableAutoConfiguration的组件信息,这部分信息配置在spring-boot-autoconfig包下的spring.factories下
               *
               *  使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
               *  META-INF\spring.factories,找出其中key为
               *  org.springframework.boot.autoconfigure.EnableAutoConfiguration
               *  的属性定义的工厂类名称。
               */
              List<String> configurations = getCandidateConfigurations(annotationMetadata,
                           attributes);
              configurations = removeDuplicates(configurations);
              /**
               * 去除不需要的
               * @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
                     DataSourceTransactionManagerAutoConfiguration.class, })
               */
              Set<String> exclusions = getExclusions(annotationMetadata, attributes);
              checkExcludedClasses(configurations, exclusions);
              configurations.removeAll(exclusions);
              /**
               * 然后使用AutoConfigurationImportFilter进行过滤,过滤的方式基本上是判断现有系统是否引入了某个组件,(系统是否使用哪个组件是在pom定义的时候就确定了的)
               * ,如果有的话则进行相关配置。比如ServletWebServerFactoryAutoConfiguration
               * ,会在ServletRequest.class等条件存在的情况下进行配置,
               * 而EmbeddedTomcat会在Servlet.class, Tomcat.class存在的情况下创建TomcatServletWebServerFactory
               *
               * org.springframework.boot.autoconfigure.condition.OnClassCondition
               * 总而言之,此过滤器会检查候选配置类的注解@ConditionalOnClass,如果要求的类在classpath 中不存在,则这个候选配置类会被排除掉
               */
              configurations = filter(configurations, autoConfigurationMetadata);
               /**
                * 现在已经找到所有需要被应用的候选配置类
                * 广播事件AutoConfigurationImportEvent
                */
              fireAutoConfigurationImportEvents(configurations, exclusions);
              return StringUtils.toStringArray(configurations);
       }


private void fireAutoConfigurationImportEvents(List<String> configurations,
                     Set<String> exclusions) {
              List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
              if (!listeners.isEmpty()) {
                     AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                                  configurations, exclusions);
                     for (AutoConfigurationImportListener listener : listeners) {
                           invokeAwareMethods(listener);
                           listener.onAutoConfigurationImportEvent(event);
                     }
              }
 }

getCandidateConfigurations方法会读取到所有依赖jar包下面的META-INF/spring.factories,并将spring.factories中的配置类的全名称获取到。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

重点来了,springboot之所以能拿到spring.factories就是通过SpringFactoriesLoader来读取的,SpringFactoriesLoader会将依赖包所有的spring.factories读取出来,并用一个map来封装读取出来的vaule。SpringFactoriesLoader是spring提供的一种扩张方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
    /**
     *在springboot启动的时候会将依赖包所有的spring.factories读取出来,spring.factories
     *中不仅包括配置类,还有监听器,初始化器等
     */
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //第一次就加载了所有的,后面每次需要的时候都是从缓存里面取的
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                //将文件路径转变成Properties
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

拿例如引入的mybatis举例,引入baomidou的mybaitis包后,springboot启动会去读取这个jar包下面的配置文件

 <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration

我们再看看MybatisPlusAutoConfiguration这个配置类里面的内容,可以看到MybatisPlusAutoConfiguration里面产生的bean都是以前需要我们去xml文件里面配置的类,当有了MybatisPlusAutoConfiguration后,我们不需要去xml文件利声明这些bean,springboot在启动的时候就将这些ban加载到了容器里。

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisPlusAutoConfiguration {
    private static final Log logger = LogFactory.getLog(MybatisPlusAutoConfiguration.class);
    private final MybatisPlusProperties properties;
    private final Interceptor[] interceptors;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
    }

    @PostConstruct
    public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
        }

    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        MybatisConfiguration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new MybatisConfiguration();
        }

        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            Iterator i$ = this.configurationCustomizers.iterator();

            while(i$.hasNext()) {
                ConfigurationCustomizer customizer = (ConfigurationCustomizer)i$.next();
                customizer.customize(configuration);
            }
        }

        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        factory.setConfiguration(configuration);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }

        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }

        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }

        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }

        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }

        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        if (!ObjectUtils.isEmpty(this.properties.getGlobalConfig())) {
            factory.setGlobalConfig(this.properties.getGlobalConfig().convertGlobalConfiguration());
        }

        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

    @Configuration
    @Import({MybatisPlusAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean({MapperFactoryBean.class})
    public static class MapperScannerRegistrarNotFoundConfiguration {
        public MapperScannerRegistrarNotFoundConfiguration() {
        }

        @PostConstruct
        public void afterPropertiesSet() {
            MybatisPlusAutoConfiguration.logger.debug("No " + MapperFactoryBean.class.getName() + " found.");
        }
    }

    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
        private BeanFactory beanFactory;
        private ResourceLoader resourceLoader;

        public AutoConfiguredMapperScannerRegistrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            MybatisPlusAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

            try {
                if (this.resourceLoader != null) {
                    scanner.setResourceLoader(this.resourceLoader);
                }

                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
                if (MybatisPlusAutoConfiguration.logger.isDebugEnabled()) {
                    Iterator i$ = packages.iterator();

                    while(i$.hasNext()) {
                        String pkg = (String)i$.next();
                        MybatisPlusAutoConfiguration.logger.debug("Using auto-configuration base package '" + pkg + "'");
                    }
                }

                scanner.setAnnotationClass(Mapper.class);
                scanner.registerFilters();
                scanner.doScan(StringUtils.toStringArray(packages));
            } catch (IllegalStateException var7) {
                MybatisPlusAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled." + var7);
            }

        }

        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    }
}

最后当springboot拿到这些配置文件里面的全性定类名的时候,会通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
当收集到所有在spring.factories中指定的bean的类路径,在processGroupImports方法中会以处理@Import注解一样的逻辑将其导入进容器。
 

public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        // getImports即上面得到的所有类路径的封装
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(
                    entry.getMetadata());
            try {
                // 和处理@Import注解一样
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configurationClass.getMetadata().getClassName() + "]", ex);
            }
        });
    }
}

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    ...
    // 遍历收集到的类路径
    for (SourceClass candidate : importCandidates) {
       ...
        //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        // 当作 @Configuration 处理         
        processConfigurationClass(candidate.asConfigClass(configClass));
   ...
}
            
    ...
}

springboot是如何让applicationcon.properties里面的配置生效的呢
以MybatisPlusAutoConfiguration举例,在MybatisPlusAutoConfiguration类上面有一个EnableConfigurationProperties注解,这个注解的作用就是让使用 @ConfigurationProperties 的类进行ioc注入,也就是将MybatisPlusProperties这个属性类生效,

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisPlusAutoConfiguration {

我们再看看MybatisPlusProperties这类。在这里看到了注解@ConfigurationProperties,这个注解的作用就是读取配置文件里面属性,将读到的值绑定到被注解的类中的属性。

@ConfigurationProperties(
    prefix = "mybatis-plus"
)
public class MybatisPlusProperties {
    public static final String MYBATIS_PLUS_PREFIX = "mybatis-plus";
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private String typeEnumsPackage;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private GlobalConfig globalConfig;
    @NestedConfigurationProperty
    private MybatisConfiguration configuration;

再看看项目中的配置文件application.yml,发现对mybatis的配置中是能和MybatisPlusProperties这个类一一对于对应的。

mybatis-plus:
# xml文件路径在package下面的 classpath:/com/yourpackage/*/mapper/*Mapper.xml
# xml文件路径在resources下面 classpath:/mapper/*Mapper.xml
    mapper-locations: classpath:/mappers/*Mapper.xml
#扫描的pojo对象所在包
    type-aliases-package: com.example.demo.bean
    global-config:
        #\u9A7C\u5CF0\u4E0B\u5212\u7EBF\u8F6C\u6362
        db-column-underline: true
        #\u903B\u8F91\u5220\u9664\u914D\u7F6E\uFF08\u4E0B\u97623\u4E2A\u914D\u7F6E\uFF09
        logic-delete-value: 1
        logic-not-delete-value: 0
        sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector

通过@ConfigurationProperties注解将配置文件里面的值读到配置bean里面,然后@EnableConfigurationProperties将注解bean注入到ioc容器中,这样当项目需要配置信息的时候可以直接从容器中去取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章