Just watch the video, Lei wrote a Starter, I want to customize a Starter, and look at the source code to see when the AutoConfiguration class runs, when the components are loaded into the container.

The first step is to establish an empty project and add two modules respectively

The first project project, the initiator referenced to the outside world, mainly refers to the automatic configuration package

The pom.xml file depends on the following:

<dependencies>
        <dependency>
            <groupId>com.yanxin</groupId>
            <artifactId>myhello-spring-boot-autoconfigurer</artifactId>
            <version>0.0.1 - the SNAPSHOT</version>
        </dependency>
    </dependencies>
Copy the code

The second project, automatic configuration packageThe pom.xml file depends on the following:

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>
Copy the code

Second, add auto configuration to the auto configuration package

Consider a component whose methods call some properties of another component, which we expect to be automatically configured when SpringBoot runs. Using components to bind properties, we can write this class first:

@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String prefix;
    private String suffix;

    public String getPrefix(a) {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix(a) {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix; }}Copy the code

The @ConfigurationProperties annotation is used to bind the configuration file’s configuration to the class.

The functional component classes we need are as follows:

public class HelloService {
    
    HelloProperties hello;

    public HelloProperties getHello(a) {
        return hello;
    }

    public void setHello(HelloProperties hello) {
        this.hello = hello;
    }

    public String sayHello(String name){
        String reply = hello.getPrefix() + name + hello.getSuffix();
        System.out.println(reply);
        returnreply; }}Copy the code

We add setter and getter methods to the HelloProperties property so that we can inject HelloProperties values into the functional component.

The automatic configuration classes are as follows:

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Autowired
    HelloProperties hello;

    @Bean
    public HelloService getService(a){
        HelloService ser = new HelloService();
        ser.setHello(hello);
        returnser; }}Copy the code

The autoconfiguration class has the @Configuration annotation that automatically loads the class into the container. (When does the SpringBoot program load the HelloAutoConfiguration class?)

Configured in/meta-INF /spring.factories to read auto-configured classes

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.yanxin.myhellospringbootautoconfigurer.HelloAutoConfiguration
Copy the code

Import them into the Maven repository and install both modules separately.

Third, create a New Web project that references the custom starter

Create the test-starter Module and import the POM. XML file

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.yanxin</groupId>
			<artifactId>myhello-spring-boot-starter</artifactId>
			<version>1.0 the SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
        	</dependency>
Copy the code

Configure it in application.properties

hello.prefix=zy
hello.suffix=yz
Copy the code

Create a new Controller class HelloController

@RestController
public class HelloController {

    @Autowired
    HelloService service;

    @GetMapping("/hello")
    public String getCon(String name){
        returnservice.sayHello(name); }}Copy the code

Run the SpringBoot project, access /hello, and get

You can see that zy– prefix, yz– suffix, and zys–name are returned.

The automatic configuration class is successfully configured. So, are auto-configuration classes appropriate to load? We follow the source code.

Principle of Automatic configuration

We clicked on the @SpringBootApplication annotation and found @SpringBootConfiguration and @EnableAutoConfiguration, which, as the name implies, is responsible for enabling automatic configuration, So how does that work? Let’s go on:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
Copy the code

We noticed that two annotations @ AutoConfigurationPackage and @ Import ({AutoConfigurationImportSelector. Class}), Click on @AutoConfigurationPackage and know that the class has two properties: basePackages and basePackageClasses, and import the component Registrar.class

It opens at the Registrar. The class

static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(newAutoConfigurationPackages.PackageImports(metadata)); }}Copy the code

See what the registerBeanDefinitions method does at this breakpoint

Method of entry,

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2); registry.registerBeanDefinition(BEAN, beanDefinition); }}Copy the code

This seems to be injecting the package name information of the annotation into a bean, and then making the configuration for that name scan by default.

To summarize, @autoConfigurationPackage is the package that automatically configures the main configuration class by default.

Look at below, the imported components AutoConfigurationImportSelector. It opens at the class can see

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }}Copy the code

This method is used to import some configuration. We order intogetAutoConfigurationEntry()Method,

Enclosing getCandidateConfigurations (annotationMetadata, attributes) is through the annotation information acquisition configuration class, point to open to see,

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;
    }
Copy the code

Now look at the loadFactoryNames method,

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

Now look at the loadSpringFactories method,

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if(result ! =null) {
            return result;
        } else {
            try{ Enumeration<URL> urls = classLoader ! =null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) { Entry<? ,? > entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); }}Copy the code

Knowing that the auto-configuration class is loaded here,

Our auto-configuration class is added to cache Result here

Then return to the superior

private <T> List<T> createSpringFactoriesInstances(Class
       
         type, Class
        [] parameterTypes, ClassLoader classLoader, Object[] args, Set
        
          names)
        
        {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();

        while(var7.hasNext()) {
            String name = (String)var7.next();

            try{ Class<? > instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<? > constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = BeanUtils.instantiateClass(constructor, args); instances.add(instance); }catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + ":"+ name, var12); }}return instances;
    }
Copy the code

This step gets 5ContextInitializerInitialize the container. Thus will be/META-INF/spring.factoriesThe AutoConfigration class is loaded into the container and initialized.