In depth spring boot annotation principle and use

Posted by sqlnoob on Tue, 16 Jun 2020 10:43:45 +0200

The main configuration class of SpringBoot

@SpringBootApplication
public class StartEurekaApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(StartEurekaApplication.class, args);
    }
}

Click @SpringBootApplication to see that @SpringBootApplication is a composite annotation.

@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 {
}

@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

You can see that this annotation has only one @ Configuration in addition to the meta annotation, that is to say, this annotation is equivalent to @ Configuration, so the two annotations have the same function. It allows us to register some additional beans and import some additional configurations. Then @ Configuration also has the function of turning this class into a Configuration class without additional XML Configuration. So @ SpringBootConfiguration is equivalent to @ Configuration. Enter @ Configuration and find that @ Configuration core is @ Component, indicating that Spring's Configuration class is also a Component of Spring.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

@EnableAutoConfiguration

The annotation enable autoconfiguration is to enable autoconfiguration.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

You can see that it is composed of @ AutoConfigurationPackage, @ import (enableautoconfigu rationImportSelector.class )The function of @ AutoConfigurationPackage is to enable the classes in the package and the classes in the sub package to be automatically scanned into the spring container.

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

Use @ Import to Import a component to the Spring container. Here, the imported component is Registrar.class . Let's look at the register:

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

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }

The path of the scanned package can be obtained by the above method. You can debug to see the specific value:

What's metadata? You can see that it's DemosbApplication marked on the @ SpringBootApplication annotation, which is our main configuration class Application:

In fact, scan and load all components in the package and sub package of the main configuration class (that is, the class marked by @ SpringBootApplication) into the Spring container. So we need to put DemoApplication at the top level of the project (the outermost directory).

Look at the annotation @ Import (autoconfigu rationImportSelector.class )The @ Import annotation is to Import some components to the Spring container. Here is a component selector: AutoConfigurationImportSelector.

You can see from the figure that AutoConfigurationImportSelector inherits the DeferredImportSelector inherits the ImportSelector, which has one method: selectImports. Return all components that need to be imported as full class names, and these components will be added to the container.

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = 
        this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

A lot of auto configuration classes (xxxAutoConfiguration) will be imported into the container, that is, all the components required for the scene will be imported into the container, and these components will be configured.

With the automatic configuration class, we can write configuration injection function components manually. How to get these configuration classes? Look at the following method:

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

We can see the getCandidateConfigurations() method. Its function is to introduce some classes that have been loaded by the system. Which classes are they

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;
}

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

From META-INF/spring.factories Get the resource in and load the resource through Properties:

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()) {
                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

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

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

You can know that SpringBoot starts from meta-inf under the classpath/ Spring.factories Get the values specified by EnableAutoConfiguration and import them into the container as an auto configuration class. The auto configuration class will take effect and help us with auto configuration. In the past, we need to configure our own things, and the automatic configuration class helps us to complete it. As shown in the figure below, you can see that some common Spring classes have been imported automatically.

Next, look at the @ ComponentScan annotation, @ ComponentScan (excludefilters = {@ filter (type= FilterType.CUSTOM , classes = TypeExcludeFilter.class ), @Filter(type = FilterType.CUSTOM , classes = AutoConfig urationExcludeFilter.class )The annotation is to scan the package and put it into the spring container.

@ComponentScan(excludeFilters = {
  @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), 
  @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
public @interface SpringBootApplication {}

In summary, @ SpringbootApplication: that is to say, he has prepared a lot of things. Whether to use depends on our program or configuration.

run method

public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
}

Let's see if there are any auto configuration things that are used to execute the run method. Let's click Run:

public ConfigurableApplicationContext run(String... args) {
    //timer
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    //monitor
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        //Prepare context
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, 
                      new Class[]{ConfigurableApplicationContext.class}, context);
        //Pre refresh context
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //Refresh context
        this.refreshContext(context);
        //context after refresh
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

Then we focus on refreshContext(context); refresh context, let's take a closer look.

private void refreshContext(ConfigurableApplicationContext context) {
   refresh(context);
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

We continue to click on refresh(context);

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}

He will call ((AbstractApplicationContext) applicationContext).refresh(); method, let's go further:

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();
      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);
         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);
         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);
         // Initialize message source for this context.
         initMessageSource();
         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();
         // Initialize other special beans in specific context subclasses.
         onRefresh();
         // Check for listener beans and register them.
         registerListeners();
         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);
         // Last step: publish corresponding event.
         finishRefresh();
      }catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();
         // Reset 'active' flag.
         cancelRefresh(ex);
         // Propagate exception to caller.
         throw ex;
      }finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

It can be seen that this is the loading process of a spring bean. Let's move on to a method called onRefresh():

protected void onRefresh() throws BeansException {
   // For subclasses: do nothing by default.
}

He doesn't realize it directly here, but we look for his specific realization:

For example, Tomcat is related to the web. We can see that there is a servlet web server application context:

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

You can see that there is a createWebServer(); method that creates a web container. Tomcat is not a web container. How to create it? Let's continue to see:

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",
               ex);
      }
   }
   initPropertySources();
}

factory.getWebServer(getSelfInitializer()); it was created in the factory way.

public interface ServletWebServerFactory {
   WebServer getWebServer(ServletContextInitializer... initializers);
}

You can see why it is an interface. Because we're not just Tomcat a web container.

We see that there is also Jetty. Let's look at TomcatServletWebServerFactory:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory
         : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}

That code is the built-in Tomcat we are looking for. In this process, we can see a process of creating Tomcat.

Extensions: Custom state

First, customize a state.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/>
</parent>
<groupId>com.zgw</groupId>
<artifactId>gw-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>

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

Let's first look at the version number of maven configuration write. If you want to customize a state, you must rely on the spring boot autoconfigure package. Let's look at the project directory first.

public class GwServiceImpl  implements GwService{
    @Autowired
    GwProperties properties;

    @Override
    public void Hello()
    {
        String name=properties.getName();
        System.out.println(name+"say:Hello");
    }
}

What we do is to customize name through configuration file, which is the specific implementation.

@Component
@ConfigurationProperties(prefix = "spring.gwname")
public class GwProperties {

    String name="zgw";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

This class can read the configuration file through @ ConfigurationProperties.

@Configuration
@ConditionalOnClass(GwService.class)  //Scanning class
@EnableConfigurationProperties(GwProperties.class) //Make configuration class effective
public class GwAutoConfiguration {

    /**
    * Function description hosted to spring
    * @author zgw
    * @return
    */
    @Bean
    @ConditionalOnMissingBean
    public GwService gwService()
    {
        return new GwServiceImpl();
    }
}

This is a configuration class. Why is it written like this? Because spring boot stats are written like this. We can copy stats by referring to them to achieve automatic configuration. Then we are passing spring.factories Also configure.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration

Then such a simple state is completed, and maven can be packaged and used in other projects.

Topics: Spring Tomcat SpringBoot Web Server