Spring Boot Starter simplifies development principles

When I first learned Spring, I was impressed by all kinds of XML configuration, and many of the configuration had to be configured by this project, and the configuration of the old project was copied again when starting a new project, which was more prone to error and took a lot of time to debug. Starter simplifies these tedious configurations by following the Spring Boot “convention about configuration” philosophy by giving default properties when we don’t actively configure them, and in many cases they don’t need to change. How does this work? So in the SpringApplication class, the launch class, there’s an @SpringApplication annotation, This annotation is made up of @SpringBootConfiguration,@EnableAutoConfiguration, and @ComponentScan annotations

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, Classes = AutoConfigurationExcludeFilter. Class)}) public @ interface SpringBootApplication {/ / not important content to ignore}Copy the code

PM @ EnableAutoConfiguration, can see the annotations to import a AutoConfigurationImportSelector class

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.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 {}; }Copy the code

Let’s focus on this class:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// Override public String[] selectImports(AnnotationMetadata) {if (! isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }Copy the code

This class inherits DeferredImportSelector, and DeferredImportSelector inherits ImportSelector. Here’s a quick tidbit: When registering a bean with the @import annotation, the value of the Import annotation can be the implementation class of ImportSelector, which the Spring container instantiates and executes its selectImports method. Therefore, Spring calls the selectImports method of that class, and notice that this class inherits DeferredImportSelector, so it’s not loaded until the other @Configuration is loaded. Next, we focus on selectImports method, this method calls the getAutoConfigurationEntry (autoConfigurationMetadata, annotationMetadata), enter the method:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (! isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); / / on a List < String > configurations = getCandidateConfigurations (annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }Copy the code

Among them, the method to call the getCandidateConfigurations (annotationMetadata, attributes);

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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;
	}
Copy the code

Continue to enter the SpringFactoriesLoader. LoadFactoryNames (getSpringFactoriesLoaderFactoryClass (), getBeanClassLoader ());

public static List<String> loadFactoryNames(Class<? > factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }Copy the code

This method calls loadSpringFactories(classLoader).

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 = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

As you can see, this method gets the classes to be configured automatically from FACTORIES_RESOURCE_LOCATION, which contains the following:

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Copy the code

Therefore, we can conclude that Spring automatically configures SpringBoot by reading the classes that need to be configured automatically from the package’s “meta-INF/Spring.Factories” class. Let’s use a common starter to see if we’re right

Exploration of mybatis- Spring-boot-starter principle

Open the mybatis-spring-boot-starter jar and you can see only these files

Open poM file:

The < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion > 4.0.0 < / modelVersion > < the parent > < groupId > org. Mybatis. Spring. The boot < / groupId > < artifactId > mybatis - spring - the boot < / artifactId > < version > 2.1.0 < / version > < / parent > <artifactId>mybatis-spring-boot-starter</artifactId> <name>mybatis-spring-boot-starter</name> <properties> <module.name>org.mybatis.spring.boot.starter</module.name> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency>  <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> </dependencies> </project>Copy the code

The role of starter is two, one is dependency management, and the other is to start some functions. This POM file embodies the role of dependency management, and automatically introduces the required dependencies for this function. The following dependency is the key to myBatis auto-configuration

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
Copy the code

We mainly look at two classes, one is MybatisProperties, one is MybatisAutoConfiguration, these two classes have a standard name, end with AutoConfiguration, is a javaConfig form of configuration class, Classes that are configured as XML in Spring, and classes that end with Properties, do their job by reading Properties from the configuration file. The AutoConfiguration class takes the Properties from the corresponding Properties and configures them. Open the MybatisProperties class:

/** * Configuration properties for MyBatis. ** @author Eddu Melendez * @author Kazuki Shimizu */ @ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX) public class MybatisProperties { public static final  String MYBATIS_PREFIX = "mybatis"; private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); /** * Location of MyBatis xml config file. */ private String configLocation; /** * Locations of MyBatis mapper files. */ private String[] mapperLocations; // Ignore the following code}Copy the code

As you can see, this class uses the @ConfigurationProperties annotation, which reads properties prefixed with Mybatis from the configuration file and injects them into matching properties, such as mybatis. ConfigLocation. Is injected into a property of the class configLocation. Let’s look at the code for MyBatisAutoConfiguration

@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public class MybatisAutoConfiguration implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); private final MybatisProperties properties; private final Interceptor[] interceptors; private final TypeHandler[] typeHandlers; private final LanguageDriver[] languageDrivers; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List<ConfigurationCustomizer> configurationCustomizers; public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.typeHandlers = typeHandlersProvider.getIfAvailable(); this.languageDrivers = languageDriversProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); @bean@conditionalonmissingBean public SqlSessionFactory SqlSessionFactory (DataSource DataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } applyConfiguration(factory); 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 (this.properties.getTypeAliasesSuperType() ! = null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (! ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); } if (! ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } Set<String> factoryPropertyNames = Stream .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) .collect(Collectors.toSet()); Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers") && ! Objectutils.isempty (this.languagedRivers)) {// Need to mybatis-spring 2.0.2+ factory.setScriptingLanguageDrivers(this.languageDrivers); if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { defaultLanguageDriver = this.languageDrivers[0].getClass(); }} the if (factoryPropertyNames. The contains (" defaultScriptingLanguageDriver ")) {/ / Need to mybatis - spring 2.0.2 + factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } return factory.getObject(); } @conditionalonmissingBean public conditionlsessionTemplate SqlSessionTemplate (SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType ! = null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); }}}Copy the code

Let’s start with the notes: @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean. Class}) the annotation says only exist sqlSessionFactory and SqlSessionFactoryBean these two classes, to initialize the class; @ ConditionalOnSingleCandidate (DataSource. Class) this annotation, said only a DataSource. Only in the class or have more than one DataSource, but preferred specifies the DataSource, To initialize the class (which is specified in the configuration much data source to the preferred data source) @ EnableConfigurationProperties (MybatisProperties. Class) The function of this commentary is to MybatisProperties @ @ AutoConfigureAfter ConfigurationProperties takes effect on the ({DataSourceAutoConfiguration. Class, MybatisLanguageDriverAutoConfiguration. Class}) the role of the annotation is to specify the current class loading sequence The next is sqlSessionFactory method and sqlSessionTemplate method, Both methods have @bean and @conditionalonmissingBean annotations, indicating that Spring will automatically configure the SqlSessionFactory or SqlSessionTemplate if the user does not have a custom SqlSessionFactory or SqlSessionTemplate. Finally, open the meta-INF folder and see the familiar spring.factories file:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

Copy the code

Based on what we learned in the last section, Spring will automatically configure these two classes, so let’s take a look at the whole auto-loading process:

The @SpringBootApplication annotation is scanned at runtime by the SpringBootApplication bootapplication startup class. This annotation is a combination of multiple annotations. One of the annotations are @ Import (AutoConfigurationImportSelector. Class), the class implementation ImportSelector interface, when @ Import specified class is a ImportSelector implementation class, SpringbootApplication runs its selectImports method, which scans the Spring. factories files in the meta-INF folder of all jar packages. And then will to org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration as the key, get all the value, make a List, then will scan the List of classes

Because of the spring-.factories in mybatis-spring-boot-starter, So the spring will scan org. Mybatis. Spring. The boot. Autoconfigure. MybatisAutoConfiguration this class, and because of the annotation on the class, SqlSessionFactory and SqlSessionFactoryBean. Class class, or if there is a single DataSource and the preferred DataSource is specified, and because of the @autoConfigureAfter annotation The class of initialization is behind the DataSourceAutoConfiguration and MybatisLanguageDriverAutoConfiguration sequence At the same time, @ EnableConfigurationProperties annotation on the class can make MybatisProperties @ ConfigurationProperties takes effect on the class, so will read all mybatis configuration file for the prefix attribute, And matches to the properties of the class.

When initializing MybatisAutoConfiguration, Since sqlSessionFactory(DataSource DataSource) and sqlSessionTemplate(sqlSessionFactory sqlSessionFactory) have @bean and ConditionalOnMissingBean these two notes, So spring automatically initializes the sqlSessionFactory or sqlSessionTemplate class using the value MybatisProperties reads from the configuration file if the user does not define the sqlSessionFactory or sqlSessionTemplate class. The default value is used

4. In the end, we feel the effect is, as long as the introduction of mybatis – spring – the boot – starter this dependence, can use mybatis to access data, and the dataSource, sqlSessionFactory these things sometimes our own configuration can also, The reason why no configuration is possible.