SpringBoot--------Restful([夢學谷]十三)

Spring Boot本身並不提供Spring框架的核心特性以及擴展功能,只是用於快速、敏捷地開發新一代基於Spring框架的應用程序。也就是說,它並不是用來替代Spring的解決方案,而是和Spring框架緊密結合用於提升Spring開發者體驗的工具。

Spring Boot繼承了Spring的優點,並新增了一些新功能和特性 
(0)從字面理解,Boot是引導的意思,因此SpringBoot幫助開發者快速搭建Spring框架,Spring 項目引導頁面可以在幾秒構建一個項目 
(1)Spring Boot 內置了多種種強大的測試框架,只需要在項目中引入spring-boot-start-test依賴包 
(2)Spring Boot 的自動配置讓配置變簡單; 
(3)SpringBoot的起步依賴讓依賴管理更加簡單; 
(4)Spring Boot Actuator對應用系統監控的集成功能; 
(5)現在spring boot和spring cloud是現在java開發框架的主流了

Spring Boot提供了一種新的編程範式,能在最小的阻力下開發Spring應用程序。有了它,你可以更加敏捷地開發Spring應用程序,專注於應用程序的功能,不用在Spring的配置上多花功夫,甚至完全不用配置。實際上,Spring Boot的一項重要工作就是讓Spring不再成爲你成功路上的絆腳石。    ——《SpringBoot實戰》

 

springboot如何做到

構建springboot項目 
訪問http://start.spring.io/ 
選擇構建工具Maven Project、Spring Boot版本以及一些工程基本信息 
 
下載壓縮包以後直接導入到ide中就可以了,大概就是這個樣子 
 
這個是我大致畫的一些spring的東西,先從代碼結構說吧 
http://naotu.baidu.com/file/e8972e3eff7fc8a1a54dd7c2d6d946d5

這是一般的主程序入口,當然在我們的項目中,因爲並沒有使用springboot內置的tomcat,所以啓動方式有所不同

 

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

不適用內置tomcat的情況,原理沒有區別

 

@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
              DataSourceTransactionManagerAutoConfiguration.class, })
@ComponentScan({ "com.fanli.*" })
public class Application extends SpringBootServletInitializer {
       @Override
       protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
              return application.sources(Application.class);
       }
}12345678910

按照下圖分別講一下源碼 
 
@SpringBootApplication

 

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.2.0
 */
@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 {
     ......
}1234567891011121314151617181920212223

@SpringBootApplication實際是@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan的組合

 

/**
 * Indicates that a class provides Spring Boot application
 * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
 * standard {@code @Configuration} annotation so that configuration can be found
 * automatically (for example in tests).
 * <p>
 * Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and
 * most idiomatic Spring Boot applications will inherit it from
 * {@code @SpringBootApplication}.
 *
 * @author Phillip Webb
 * @since 1.4.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}12345678910111213141516171819

@SpringBootConfiguration而@Configuration有相同的作用,配備了該註解的類就能夠以JavaConfig的方式完成一些配置,可以不再使用XML配置

@ComponentScan註解完成的是自動掃描的功能,相當於Spring XML配置文件中的

<context:component-scan >1

如果不設置的話默認掃描@ComponentScan註解所在類的同級類和同級目錄下的所有類,所以對於一個Spring Boot項目,一般會把入口類放在頂層目錄中,這樣就能夠保證源碼目錄下的所有類都能夠被掃描到。如果需要設置的話如下代碼

 

@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
              DataSourceTransactionManagerAutoConfiguration.class, })
@ComponentScan({ "com.fanli.*" })
public class Application extends SpringBootServletInitializer {。。。。。。12345

@EnableAutoConfiguration(核心) 
這個註釋告訴SpringBoot“猜”你將如何想配置Spring,基於你已經添加的依賴項,如果我們使用了spring-boot-starter-web並且已經添加Tomcat和Spring MVC,@EnableAutoConfiguration自動將假設您正在開發一個web應用程序並添加相應的Spring設置。 
    Spring Boot的自動配置是一個運行時(更準確地說,是應用程序啓動時)的過程,考慮了衆多因素,才決定Spring配置應該用哪個,不該用哪個。舉個例子。Spring的JdbcTemplate是不是在Classpath裏?如果是,並且有DataSource的Bean,則自動配置一個JdbcTemplate的Bean。每當應用程序啓動的時候,Spring Boot的自動配置都要做將近200個這樣的決定,涵蓋安全、集成、持久化、Web開發等諸多方面。所有這些自動配置就是爲了儘量不讓你自己寫配置。 
    在嚮應用程序加入Spring Boot時,有個名爲spring-boot-autoconfigure的JAR文件,其中包含了很多自動配置類(自動配置類就是普通的Spring @Configuration類,通過SpringFactoriesLoader機制完成加載,實現上通常使用@Conditional(比如@ConditionalOnClass或者@ConditionalOnMissingBean))。

 

 
每個配置類都在應用程序的Classpath裏,都有機會爲應用程序的配置添磚加瓦。這些配置類裏有用於AOP的配置,有用於Spring Data JPA的配置,有用於Spiring MVC的配置,還有很多其他東西的配置,你可以自己選擇是否在Spring應用程序裏使用它們。

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

這個@AutoConfigurationPackage僅限於引入@Import(AutoConfigurationPackages.Registrar.class)

 

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

 

  @Order(Ordered.HIGHEST_PRECEDENCE)
  static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
           @Override
           public void registerBeanDefinitions(AnnotationMetadata metadata,
                   BeanDefinitionRegistry registry) {
               register(registry, new PackageImport(metadata).getPackageName());
           }
           @Override
           public Set<Object> determineImports(AnnotationMetadata metadata) {
               return Collections.<Object>singleton(new PackageImport(metadata));
           }
   }123456789101112

 

       /**
        * Programmatically registers the auto-configuration package names. Subsequent
        * invocations will add the given package names to those that have already been
        * registered. You can use this method to manually define the base packages that will
        * be used for a given {@link BeanDefinitionRegistry}. Generally it's recommended that
        * you don't call this method directly, but instead rely on the default convention
        * where the package name is set from your {@code @EnableAutoConfiguration}
        * configuration class or classes.
        * @param registry the bean definition registry
        * @param packageNames the package names to set

        */
       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(BasePackages.class);
                     beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                                  packageNames);
                     beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                     registry.registerBeanDefinition(BEAN, beanDefinition);
              }
       }1234567891011121314151617181920212223242526272829

從註釋中可以看出,它的功能簡單說就是將應用的 package給註冊到Spring容器中,供後續使用,還說了不推薦手動來註冊 
然後debug的時候看一下,packageNames只有我們的rootpackage(com.example.demo),這裏也就是將應用的root package給註冊到Spring容器中,供後續使用。 
順便還可以看看調用棧,調用register方法的時間在容器刷新期間: 
refresh -> invokeBeanFactoryPostProcessors -> invokeBeanDefinitionRegistryPostProcessors -> postProcessBeanDefinitionRegistry -> processConfigBeanDefinitions(開始處理配置Bean的定義) -> loadBeanDefinitions -> loadBeanDefinitionsForConfigurationClass(讀取配置Class中的Bean定義) -> loadBeanDefinitionsFromRegistrars(這裏開始準備進入上面的register方法) -> registerBeanDefinitions(即上述方法)

 

@Import(AutoConfigurationImportSelector.class)

 

       /**
        * 最主要的方法
        * 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;
              }
              /**
               * 加載META-INF/spring-autoconfigure-metadata.properties,獲取所有支持自動配置的信息
               */
              AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                           .loadMetadata(this.beanClassLoader);
              /**
               * 得到註解中的所有屬性信息{excludeName=[], exclude=[]}
               */
              AnnotationAttributes attributes = getAttributes(annotationMetadata);
              /**
               * 獲取所有支持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);
                     }
              }
 }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener123

 

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

 

// 傳入的factoryClass:org.springframework.boot.autoconfigure.EnableAutoConfiguration
       public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
              String factoryClassName = factoryClass.getName();
              try {
                     Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
                                  : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                     List<String> result = new ArrayList<String>();
                     while (urls.hasMoreElements()) {
                           URL url = urls.nextElement();
                           Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                           String factoryClassNames = properties.getProperty(factoryClassName);
                            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
                     }
                     return result;
              } catch (IOException ex) {
                     throw new IllegalArgumentException("Unable to load [" + factoryClass.getName()
                                  + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
              }
       } 
// 相關常量 public static final String FACTORIES_RESOURCE_LOCATION =
              // "META-INF/spring.factories";123456789101112131415161718192021

這段代碼的意圖很明確,在第一篇文章討論Spring Boot啓動過程的時候就已經接觸到了。它會從類路徑中拿到所有名爲META-INF/spring.factories的配置文件,然後按照factoryClass的名稱取到對應的值。那麼我們就來找一個META-INF/spring.factories配置文件看看。

 

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
。。。。。。12345678910111213

列舉了非常多的自動配置候選項,挑一個AOP相關的AopAutoConfiguration看看究竟: 
/ 如果設置了spring.aop.auto=false,那麼AOP不會被配置  
// 需要檢測到@EnableAspectJAutoProxy註解存在纔會生效  
// 默認使用JdkDynamicAutoProxyConfiguration,如果設置了spring.aop.proxy-target-class=true,那麼使用CglibAutoProxyConfiguration 
@Configuration 
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class }) 
@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 
public class AopAutoConfiguration { 
       @Configuration 
       @EnableAspectJAutoProxy(proxyTargetClass = false) 
       @ConditionalOnProperty(prefix = “spring.aop”, name = “proxy-target-class”, havingValue = “false”, matchIfMissing = true) 
       public static class JdkDynamicAutoProxyConfiguration { 
       } 
       @Configuration 
       @EnableAspectJAutoProxy(proxyTargetClass = true) 
       @ConditionalOnProperty(prefix = “spring.aop”, name = “proxy-target-class”, havingValue = “true”, matchIfMissing = false) 
       public static class CglibAutoProxyConfiguration { 
       } 
}

如果存在並且值爲true的話使用基於CGLIB字節碼操作的動態代理方案,否則使用JDK自帶的動態代理機制。在這個配置類中,使用到了兩個全新的註解: 
@ConditionalOnClass 
@ConditionalOnProperty 
從這兩個註解的名稱,就大概能夠猜出它們的功能了: 
@ConditionalOnClass 
當類路徑上存在指定的類時,滿足條件。 
@ConditionalOnProperty 
當配置中存在指定的屬性時,滿足條件。 
其實除了這兩個註解之外,還有幾個類似的,它們都在org.springframework.boot.autoconfigure.condition這個包下,在具體介紹實現之前,下面先來看看Spring Boot對於@Conditional的擴展。

Spring Boot對於@Conditional的擴展Spring Boot提供了一個實現了Condition接口的抽象類SpringBootCondition。這個類的主要作用是打印一些用於診斷的日誌,告訴用戶哪些類型被自動配置了。它實現Condition接口的方法:

   @Override
   public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
          String classOrMethodName = getClassOrMethodName(metadata);
          try {
                 ConditionOutcome outcome = getMatchOutcome(context, metadata);
                 logOutcome(classOrMethodName, outcome);
                 recordEvaluation(context, classOrMethodName, outcome);
                 return outcome.isMatch();
          } catch (NoClassDefFoundError ex) {
                 throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
                              + ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on "
                              + "that class. This can also happen if you are "
                              + "@ComponentScanning a springframework package (e.g. if you "
                              + "put a @ComponentScan in the default package by mistake)", ex);
          } catch (RuntimeException ex) {
                 throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
          }
   }
   /**
    * * Determine the outcome of the match along with suitable log output. * @param
    * context the condition context * @param metadata the annotation metadata
    * * @return the condition outcome
    */
   public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
123456789101112131415161718192021222324

在Spring裏可以很方便地編寫你自己的條件,你所要做的就是實現Condition接口,覆蓋它的matches()方法

Spring Boot定義了很多更有趣的條件,並把它們運用到了配置類上,這些配置類構成了Spring Boot的自動配置。Spring Boot運用條件化配置的方法是,定義 
多個特殊的條件化註解,並將它們用到配置類上。

https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java

看一下這個calss的調用棧

 

在說一下springboot的啓動過程

 

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

 

       /**
        * Static helper that can be used to run a {@link SpringApplication} from the
        * specified sources using default settings and user supplied arguments.
        * @param primarySources the primary sources to load
        * @param args the application arguments (usually passed from a Java main method)
        * @return the running {@link ApplicationContext}
        */
       public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                     String[] args) {
              return new SpringApplication(primarySources).run(args);
       }1234567891011121314151617181920

它實際上會構造一個SpringApplication的實例,然後運行它的run方法,而run方法返回一個ConfigurableApplicationContext,看一下構造SpringApplication實列的地方

 

        /**
        * Create a new {@link SpringApplication} instance. The application context will load
        * beans from the specified primary sources (see {@link SpringApplication class-level}
        * documentation for details. The instance can be customized before calling
        * {@link #run(String...)}.
        * @param resourceLoader the resource loader to use
        * @param primarySources the primary bean sources
        * @see #run(Class, String[])
        * @see #setSources(Set)
        */
   @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
           this.resourceLoader = resourceLoader;//resourceLoader接口,提供資源導入,見鏈接https://www.cnblogs.com/doit8791/p/5774743.html 
           Assert.notNull(primarySources, "PrimarySources must not be null");
           this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //此處爲DemoApplication.class
           //推斷應用類型,Standard還是Web
           this.webApplicationType = deduceWebApplicationType();
           //
           /**
            * 設置初始化器(Initializer)
            * getSpringFactoriesInstances(ApplicationContextInitializer.class)
            * 從類路徑的META-INF/spring.factories處讀取相應配置文件,然後進行遍歷,讀取配置文件中Key爲:
            * org.springframework.context.ApplicationContextInitializer的value
            * value是一系列類名,實例化後this.initializers.addAll(initializers);全部set到SpringApplication中
            * 在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是激活Profiles。
            * Property Sources比較好理解,就是配置文件。Profiles是Spring爲了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體
            *ConfigurableApplicationContext.refresh()或SpringApplication.run()中用到
            *
            */
           setInitializers((Collection) getSpringFactoriesInstances(
                        ApplicationContextInitializer.class));
           /**
            * 設置setListeners,設置方式和初始化器一樣
            */
           setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
           /**
            * 獲取入口類的信息
            */
           this.mainApplicationClass = deduceMainApplicationClass();
    }
其實就是獲得容器ConfigurableApplicationContext之前初始化了一系列Initializers和Listeners

 

實際run方法:
          public ConfigurableApplicationContext run(String... args) {
              // 計時工具
              StopWatch stopWatch = new StopWatch();
              stopWatch.start();
              ConfigurableApplicationContext context = null;
              Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
              // 設置java.awt.headless系統屬性爲true - 沒有圖形化界面
              configureHeadlessProperty();
              /**
               * 獲取SpringApplicationRunListeners
               * getSpringFactoriesInstances方法,從META-INF/spring.factories中讀取
               * Key爲org.springframework.boot.SpringApplicationRunListener的Values
               * SpringApplicationEvent:它會利用一個內部的ApplicationEventMulticaster在上下文實際被刷新之前對事件進行處理
               * 它實際上是一個事件中轉器,它能夠感知到Spring Boot啓動過程中產生的事件,然後有選擇性的將事件進行中轉
               */
                //  Spring事件體系 https://blog.csdn.net/caihaijiang/article/details/7460888
              SpringApplicationRunListeners listeners = getRunListeners(args);
              // 發出開始執行的事件
              listeners.starting();
              try {
                     ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                     // 根據SpringApplicationRunListeners以及參數來準備環境
                     ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                     configureIgnoreBeanInfo(environment);
                     // 準備Banner打印器 - 就是啓動Spring Boot的時候打印在console上的ASCII藝術字體
                     Banner printedBanner = printBanner(environment);
                     // 創建Spring上下文,根據之前獲得的應用類型,創建ConfigurableApplicationContext實列可以看到實際上創建的是AnnotationConfigApplicationContext或AnnotationConfigEmbeddedWebApplicationContext。
                     context = createApplicationContext();
                     //SpringBootExceptionReporter,在spring.factories中能看到
                     exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                  new Class[] { ConfigurableApplicationContext.class }, context);
                     // Spring上下文前置處理,prepareContext方法中將會執行每個initializers的邏輯
                     prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                     // Spring上下文刷新
                     refreshContext(context);
                     // Spring上下文後置處理
                     afterRefresh(context, applicationArguments);
                     // 發出結束執行的事件
                     stopWatch.stop();
                     if (this.logStartupInfo) {
                           new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                     }
                     listeners.started(context);
                     callRunners(context, applicationArguments);
              } catch (Throwable ex) {
                     handleRunFailure(context, ex, exceptionReporters, listeners);
                     throw new IllegalStateException(ex);
              }
              try {
                     listeners.running(context);
              } catch (Throwable ex) {
                     handleRunFailure(context, ex, exceptionReporters, null);
                     throw new IllegalStateException(ex);
              }
              return context;
       }

 


private void prepareContext(ConfigurableApplicationContext context,
                     ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                     ApplicationArguments applicationArguments, Banner printedBanner) {
              context.setEnvironment(environment);
              postProcessApplicationContext(context);
            //調用之前準備好的ApplicationContextInitializer
              applyInitializers(context);
              listeners.contextPrepared(context);
              if (this.logStartupInfo) {
                     logStartupInfo(context.getParent() == null);
                     logStartupProfileInfo(context);
              }
              // Add boot specific singleton beans
              context.getBeanFactory().registerSingleton("springApplicationArguments",
                           applicationArguments);
              if (printedBanner != null) {
                     context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
              }
              // Load the sources
              Set<Object> sources = getAllSources();
              Assert.notEmpty(sources, "Sources must not be empty");
              load(context, sources.toArray(new Object[0]));
        //將SpringApplication自己擁有的ApplicationListener加入到ApplicationContext,發送ApplicationPreparedEvent
              listeners.contextLoaded(context);
 }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130

 

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\    // to report warnings for common misconfiguration,用來報告Spring容器的一些常見的錯誤配置的
org.springframework.boot.context.ContextIdApplicationContextInitializer,\//爲ApplicationContext設置id

org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\//委派處理ApplicationContext初始化器
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer//添加一個EmbeddedServletContainerInitializedEvent事件監聽,觸發設置嵌入的WEB服務啓動端口。通過屬性local.[namespace].port來設置啓動端口,其中namespace爲ApplicationContext指定的命名空間,如果命名空間爲空,則使用local.server.port屬性來表示配置的端口1234567

 

最終可以看到AbstractApplicationContext的refresh方法

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

在之前的的講解中可以看到調用棧 invokeBeanFactoryPostProcessors(beanFactory);這裏的時候,自動配置會起作用

SpringApplication實例的構建過程 
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都通過META-INF/spring.factories完成定義。 
SpringApplication實例run方法的執行過程 
其中主要有一個SpringApplicationRunListeners的概念,它作爲Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication實例的構建過程中得到的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴展性。

改造過程中遇到的坑 
1.原先項目改造中,有大量的xml配置文件,如果單純的改爲springboot項目的無xml配置,反而很麻煩,和springboot的某些目的背道而馳

 

@Configuration
@ImportResource(locations={"classpath:context/ehcache.xml"})
public class EncacheConfig {
}1234

2.在一個系統中,出現多個datasource時候出現的問題

自動裝配(autowiring)要求bean的匹配具備唯一性,否則就會產生歧義,從而拋出異常

 

@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
              DataSourceTransactionManagerAutoConfiguration.class, })12

3.spring項目啓動太慢的問題 
54607 ms 
刪除@SpringBootApplication  52118ms  幾乎沒有區別 
修改import  36238 ms 
最終改爲

 

@Configuration
@Import({
        DispatcherServletAutoConfiguration.class,
//        EmbeddedServletContainerAutoConfiguration.class,
//        ErrorMvcAutoConfiguration.class,
//        HttpEncodingAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
//        JacksonAutoConfiguration.class,
//        JmxAutoConfiguration.class,
//        MultipartAutoConfiguration.class,
        ServerPropertiesAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
//        WebSocketAutoConfiguration.class,
})
@ComponentScan({ "com.fanli.*" })
public class Application extends SpringBootServletInitializer {12345678910111213141516

@SpringBootApplication註解來自動獲取應用的配置信息,但這樣也會帶來一些副作用。使用這個註解後,會觸發自動配置(auto-configuration)和組件掃描(component scanning),這跟使用@Configuration、@EnableAutoConfiguration和@ComponentScan三個註解的作用是

可以通過Actuator的/autoconfig接口去看

 

4.多個springboot項目在同一臺機器上啓動(內測)時候的問題,多應用在同一個容器中

 

spring.jmx.default-domain=opendomain
endpoints.jmx.domain=opendomain
指定JMX domain name
https://www.zhihu.com/question/36688387
https://stackoverflow.com/questions/28197414/spring-cloud-error-deploying-two-eureka-clients-in-one-container12345

5.log日誌的問題

 

@Order(1)
public class CommonInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        //Log4jConfigListener
       servletContext.setInitParameter("webAppRootKey", "mall-open-api.root");
        servletContext.setInitParameter("log4jConfigLocation", "classpath:properties/log4j.properties");
        servletContext.addListener(Log4jConfigListener.class);
        //OpenSessionInViewFilter

    }
}12345678910111213

ServletContainerInitializer

Servlet 3.0引入的接口,用於在web應用啓動時動態添加servlet、filter和listener;

基於spi機制,META-INF/services/javax.servlet.ServletContainerInitializer文件中存放實現該接口的類,這些類會被容器調用

SpringServletContainerInitializer作爲ServletContainerInitializer的實現類,通過SPI機制,在web容器加載的時候會自動的被調用。(這個類上還有一個註解@HandlesTypes,它的作用是將感興趣的一些類注入到ServletContainerInitializerde), 而這個類的方法又會掃描找到WebApplicationInitializer的實現類,調用它的onStartup方法,從而起到啓動web.xml相同的作用。
 

------------------------------------------------ 我是低調的分隔線  ----------------------------------------------------

 

面向對象,面向卿;不負代碼,不負心... ...

 

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