1. 程式人生 > >淺談SpringBoot核心註解原理

淺談SpringBoot核心註解原理

SpringBoot核心註解原理

今天跟大家來探討下SpringBoot的核心註解@SpringBootApplication以及run方法,理解下springBoot為什麼不需要XML,達到零配置

首先我們先來看段程式碼

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

我們點進@SpringBootApplication來看

@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 @EnableAutoConfiguration 這兩個註解,到這裡我們知道 SpringBootApplication註解裡除了元註解,我們可以看到又是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan的組合註解,官網上也有詳細說明,那我們現在把目光投向這三個註解。

首先我們先來看 @SpringBootConfiguration,那我們點進來看

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

}

我們可以看到這個註解除了元註解以外,就只有一個@Configuration,那也就是說這個註解相當於@Configuration,所以這兩個註解作用是一樣的,那他是幹嘛的呢,相信很多人都知道,它是讓我們能夠去註冊一些額外的Bean,並且匯入一些額外的配置。那@Configuration還有一個作用就是把該類變成一個配置類,不需要額外的XML進行配置。所以@SpringBootConfiguration就相當於@Configuration。

那我們繼續來看下一個@EnableAutoConfiguration,這個註解官網說是 讓Spring自動去進行一些配置,那我們點進來看

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

可以看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)這兩個而組成的,我們先說@AutoConfigurationPackage,他是說:讓包中的類以及子包中的類能夠被自動掃描到spring容器中。 我們來看@Import(EnableAutoConfigurationImportSelector.class)這個是核心,之前我們說自動配置,那他到底幫我們配置了什麼,怎麼配置的?就和@Import(EnableAutoConfigurationImportSelector.class)息息相關,程式中預設使用的類就自動幫我們找到。我們來看EnableAutoConfigurationImportSelector.class

public class EnableAutoConfigurationImportSelector
      extends AutoConfigurationImportSelector {

   @Override
   protected boolean isEnabled(AnnotationMetadata metadata) {
      if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
         return getEnvironment().getProperty(
               EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
               true);
      }
      return true;
   }

}

可以看到他繼承了AutoConfigurationImportSelector我們繼續來看AutoConfigurationImportSelector,這個類有一個方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   try {
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
      configurations = removeDuplicates(configurations);
      configurations = sort(configurations, autoConfigurationMetadata);
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      configurations = filter(configurations, autoConfigurationMetadata);
      fireAutoConfigurationImportEvents(configurations, exclusions);
      return configurations.toArray(new String[configurations.size()]);
   }
   catch (IOException ex) {
      throw new IllegalStateException(ex);
   }
}

這個類會幫你掃描那些類自動去新增到程式當中。我們可以看到getCandidateConfigurations()這個方法,他的作用就是引入系統已經載入好的一些類,到底是那些類呢,我們點進去看一下

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

這個類回去尋找的一個目錄為META-INF/spring.factories,也就是說他幫你載入讓你去使用也就是在這個META-INF/spring.factories目錄裝配的,他在哪裡?

我們點進spring.factories來看 

 

我們可以發現幫我們配置了很多類的全路徑,比如你想整合activemq,或者說Servlet 

可以看到他都已經幫我們引入了進來,我看隨便拿幾個來看

org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\

比如我們經常用的security,可以看到已經幫你配置好,所以我們的EnableAutoConfiguration主要作用就是讓你自動去配置,但並不是所有都是建立好的,是根據你程式去進行決定。 那我們繼續來看

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

這個註解大家應該都不陌生,掃描包,放入spring容器,那他在springboot當中做了什麼策略呢?我們可以點跟煙去思考,幫我們做了一個排除策略,他在這裡結合SpringBootConfiguration去使用,為什麼是排除,因為不可能一上來全部載入,因為記憶體有限。

那麼我們來總結下@SpringbootApplication:就是說,他已經把很多東西準備好,具體是否使用取決於我們的程式或者說配置,那我們到底用不用?那我們繼續來看一行程式碼

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

那們來看下在執行run方法到底有沒有用到哪些自動配置的東西,比如說內建的Tomcat,那我們來找找內建Tomcat,我們點進run

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

然後他呼叫又一個run方法,我們點進來看

public ConfigurableApplicationContext run(String... args) {
   //計時器
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   //監聽器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      //準備上下文
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
         //預重新整理context
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
     //重新整理context
      refreshContext(context);
     //重新整理之後的context
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

那我們關注的就是 refreshContext(context); 重新整理context,我們點進來看

 

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

我們繼續點進refresh(context);

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

他會呼叫 ((AbstractApplicationContext) applicationContext).refresh();方法,我們點進來看

 

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

這點程式碼似曾相識啊 沒錯,就是一個spring的bean的載入過程我在,解析springIOC載入過程的時候介紹過這裡面的方法,如果你看過Spring原始碼的話 ,應該知道這些方法都是做什麼的。現在我們不關心其他的,我們來看一個方法叫做 onRefresh();方法

 

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

他在這裡並沒有實現,但是我們找他的其他實現,我們來找 

我們既然要找Tomcat那就肯定跟web有關,我們可以看到有個ServletWebServerApplicationContext

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

我們可以看到有一個createWebServer();方法他是建立web容器的,而Tomcat不就是web容器,那他是怎麼建立的呢,我們繼續看

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());他是通過工廠的方式建立的

public interface ServletWebServerFactory {

   WebServer getWebServer(ServletContextInitializer... initializers);

}

可以看到 它是一個介面,為什麼會是介面。因為我們不止是Tomcat一種web容器。

我們看到還有Jetty,那我們來看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);
}

那這塊程式碼,就是我們要尋找的內建Tomcat,在這個過程當中,我們可以看到建立Tomcat的一個流程。因為run方法裡面載入的東西很多,所以今天就淺談到這裡。如果不明白的話, 我們在用另一種方式來理解下,大家要應該都知道stater舉點例子

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

所以我們不防不定義一個stater來理解下,我們做一個需求,就是定製化不同的人跟大家說你們好,我們來看

<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-srater</artifactId>
<version>1.0-SNAPSHOT</version>

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

我們先來看maven配置寫入版本號,如果自定義一個stater的話必須依賴spring-boot-autoconfigure這個包,我們先看下專案目錄 

 

public class GwServiceImpl  implements GwService{
    @Autowired
    GwProperties properties;

    @Override
    public void Hello()
    {
        String name=properties.getName();
        System.out.println(name+"說:你們好啊");
    }
}

我們做的就是通過配置檔案來定製name這個是具體實現,

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

    String name="zgw";

    public String getName() {
        return name;
    }

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

這個類可以通過@ConfigurationProperties讀取配置檔案

@Configuration
@ConditionalOnClass(GwService.class)  //掃描類
@EnableConfigurationProperties(GwProperties.class) //讓配置類生效
public class GwAutoConfiguration {

    /**
    * 功能描述 託管給spring
    * @author zgw
    * @return
    */
    @Bean
    @ConditionalOnMissingBean
    public GwService gwService()
    {
        return new GwServiceImpl();
    }
}

這個為配置類,為什麼這麼寫因為,spring-boot的stater都是這麼寫的,我們可以參照他仿寫stater,以達到自動配置的目的,然後我們在通過spring.factories也來進行配置

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

然後這樣一個簡單的stater就完成了,然後可以進行maven的打包,在其他專案引入就可以使用,在這裡列出程式碼地址

https://github.com/zgw1469039806/gwspringbootsrater

到此本文結束,歡迎轉載,請標註出處和作者,謝