Spring Boot、Spring MVC 和 Spring 有什么區別?

 

分別描述各自的特征:

 

Spring 框架就像一個家族,有眾多衍生產品例如 boot、security、jpa等等;但他們的基礎都是Spring 的ioc和 aop,ioc 提供了依賴注入的容器, aop解決了面向切面編程,然后在此兩者的基礎上實現了其他延伸產品的高級功能。

 

Spring MVC提供了一種輕度耦合的方式來開發web應用;它是Spring的一個模塊,是一個web框架;通過DispatcherServlet, ModelAndView 和 View Resolver,開發web應用變得很容易;解決的問題領域是網站應用程序或者服務開發——URL路由、Session、模板引擎、靜態Web資源等等。

 

Spring Boot實現了auto-configuration自動配置(另外三大神器actuator監控,cli命令行接口,starter依賴),降低了項目搭建的復雜度。它主要是為了解決使用Spring框架需要進行大量的配置太麻煩的問題,所以它並不是用來替代Spring的解決方案,而是和Spring框架緊密結合用於提升Spring開發者體驗的工具;同時它集成了大量常用的第三方庫配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot應用中這些第三方庫幾乎可以零配置的開箱即用(out-of-the-box)。

 

所以,用最簡練的語言概括就是:

 

Spring 是一個“引擎”;

 

Spring MVC 是基於Spring的一個 MVC 框架;

 

Spring Boot 是基於Spring4的條件注冊的一套快速開發整合包。

 

一 springboot啟動原理及相關流程概覽

  springboot是基於spring的新型的輕量級框架,最厲害的地方當屬自動配置。那我們就可以根據啟動流程和相關原理來看看,如何實現傳奇的自動配置。

 

 

二  springboot的啟動類入口

用過springboot的技術人員很顯而易見的兩者之間的差別就是視覺上很直觀的:springboot有自己獨立的啟動類(獨立程序)

?
1
2
3
4
5
6
@SpringBootApplication
public class Application {
     public static void main(String[] args) {
         SpringApplication.run(Application. class , args);
     }
}

從上面代碼可以看出,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼,所以要揭開SpringBoot的神秘面紗,我們要從這兩位開始就可以了。

 

三  單單是SpringBootApplication接口用到了這些注解

?
1
2
3
4
5
6
7
8
9
10
11
12
@Target (ElementType.TYPE) // 注解的適用范圍,其中TYPE用於描述類、接口(包括包注解類型)或enum聲明
@Retention (RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三個生命周期)
@Documented // 表明這個注解應該被javadoc記錄
@Inherited // 子類可以繼承該注解
@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類
@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助
@ComponentScan (excludeFilters = { // 掃描路徑設置(具體使用待確認)
@Filter (type = FilterType.CUSTOM, classes = TypeExcludeFilter. class ),
@Filter (type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter. class ) })
public @interface SpringBootApplication {
...
}  

 

  

 

在其中比較重要的有三個注解,分別是:

  1)@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類

  2)@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助

  3)@ComponentScan(excludeFilters = { // 掃描路徑設置(具體使用待確認)

   接下來對三個注解一一詳解,增加對springbootApplication的理解:

  1)@Configuration注解

  按照原來xml配置文件的形式,在springboot中我們大多用配置類來解決配置問題

   配置bean方式的不同: 

    a)xml配置文件的形式配置bean

?
1
2
3
4
5
6
7
<?xml version= "1.0" encoding= "UTF-8" ?>
<beans xmlns= "http://www.springframework.org/schema/beans"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default -lazy-init= "true" >
<!--bean定義-->
</beans>

  b)java configuration的配置形式配置bean

?
1
2
3
4
@Configuration
public class MockConfiguration{
     //bean定義
}

  

  注入bean方式的不同:

    a)xml配置文件的形式注入bean

?
1
2
3
<bean id= "mockService" class = "..MockServiceImpl" >
...
</bean>

  b)java configuration的配置形式注入bean

?
1
2
3
4
5
6
7
@Configuration
public class MockConfiguration{
     @Bean
     public MockService mockService(){
         return new MockServiceImpl();
     }
}

  

任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。

  表達bean之間依賴關系的不同:

    a)xml配置文件的形式表達依賴關系

?
1
2
3
4
<bean id= "mockService" class = "..MockServiceImpl" >
  <propery name = "dependencyService" ref= "dependencyService" />
</bean>
<bean id= "dependencyService" class = "DependencyServiceImpl" ></bean>

    b)java configuration配置的形式表達依賴關系(重點)

    如果一個bean A的定義依賴其他bean B,則直接調用對應的JavaConfig類中依賴bean B的創建方法就可以了。

?
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MockConfiguration{
   @Bean
   public MockService mockService(){
        return new MockServiceImpl(dependencyService());
  }
   @Bean
   public DependencyService dependencyService(){
        return new DependencyServiceImpl();
  }
}

  

  2) @ComponentScan注解

  作用:a)對應xml配置中的元素;

     b) (重點)ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component和@Repository等)或者bean定義;

     c) 將這些bean定義加載到IoC容器中.

   我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。

  注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages

   

3) @EnableAutoConfiguration

    此注解顧名思義是可以自動配置,所以應該是springboot中最為重要的注解。

    在spring框架中就提供了各種以@Enable開頭的注解,例如: @EnableScheduling、@EnableCaching、@EnableMBeanExport等; @EnableAutoConfiguration的理念和做事方式其實一脈相承簡單概括一下就是,借助@Import的支持,收集和注冊特定場景相關的bean定義。  

    •   @EnableScheduling是通過@Import將Spring調度框架相關的bean定義都加載到IoC容器【定時任務、時間調度任務】
    •   @EnableMBeanExport是通過@Import將JMX相關的bean定義加載到IoC容器【監控JVM運行時狀態】

     @EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器。

     @EnableAutoConfiguration作為一個復合Annotation,其自身定義關鍵信息如下:

 

?
1
2
3
4
5
6
7
8
9
10
@SuppressWarnings ( "deprecation" )
@Target (ElementType.TYPE)
@Retention (RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 【重點注解】
@Import (AutoConfigurationImportSelector. class )【重點注解】
public @interface EnableAutoConfiguration {
...
}

 

  

其中最重要的兩個注解已經標注:1、@AutoConfigurationPackage【重點注解】2、@Import(AutoConfigurationImportSelector.class)【重點注解】

    當然還有其中比較重要的一個類就是:EnableAutoConfigurationImportSelector.class

AutoConfigurationPackage注解:

?
1
2
3
4
5
6
7
8
@Target (ElementType.TYPE)
@Retention (RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import (AutoConfigurationPackages.Registrar. class )
public @interface AutoConfigurationPackage {
 
}

通過@Import(AutoConfigurationPackages.Registrar.class)

?
1
2
3
4
5
6
7
8
9
10
11
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
 
         @Override
         public void registerBeanDefinitions(AnnotationMetadata metadata,
                 BeanDefinitionRegistry registry) {
             register(registry, new PackageImport(metadata).getPackageName());
         }
 
         ……
 
     }

  

它其實是注冊了一個Bean的定義;
new PackageImport(metadata).getPackageName(),它其實返回了 當前主程序類的 同級以及子級的包組件(重點);
(重點)那這總體就是注冊當前主程序類的同級以及子級的包中的符合條件的 Bean的定義

以上圖為例,DemoApplication是和demo包同級,但是demo2這個類是DemoApplication的父級,和example包同級

也就是說,DemoApplication啟動加載的Bean中,並不會加載demo2,這也就是為什么,我們要把DemoApplication放在項目的最高級中。

 

Import(AutoConfigurationImportSelector.class)注解

 

(重點)可以從圖中看出   AutoConfigurationImportSelector 實現了 DeferredImportSelector 從 ImportSelector繼承的方法: selectImports
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
     public String[] selectImports(AnnotationMetadata annotationMetadata) {
         if (!isEnabled(annotationMetadata)) {
             return NO_IMPORTS;
         }
         AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                 .loadMetadata( this .beanClassLoader);
         AnnotationAttributes attributes = getAttributes(annotationMetadata);
         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 StringUtils.toStringArray(configurations);
     }

  

第9行List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);其實是去加載各個組件jar下的   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。

該方法在springboot啟動流程——bean實例化前被執行,返回要實例化的類信息列表;

如果獲取到類信息,spring可以通過類加載器將類加載到jvm中,現在我們已經通過spring-boot的starter依賴方式依賴了我們需要的組件,那么這些組件的類信息在select方法中就可以被獲取到。

 

?
1
2
3
4
5
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;
  }

 

  

 

其返回一個自動配置類的類名列表,方法調用了loadFactoryNames方法,查看該方法

 

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

 

自動配置器會跟根據傳入的factoryClass.getName()到項目系統路徑下所有的spring.factories文件中找到相應的key,從而加載里面的類。

 

這個外部文件,有很多自動配置的類。如下:

 

(重點)其中,最關鍵的要屬@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件(spring.factories)的bean定義(如Java Config@Configuration配置)都加載到當前SpringBoot創建並使用的IoC容器。就像一只“八爪魚”一樣。

 

 

 

自動配置幕后英雄:SpringFactoriesLoader詳解

借助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!

SpringFactoriesLoader屬於Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,加載工廠類

SpringFactoriesLoader為Spring工廠加載器,該對象提供了loadFactoryNames方法,入參為factoryClass和classLoader即需要傳入工廠類名稱和對應的類加載器,方法會根據指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件;

傳入的工廠類為接口,而文件中對應的類則是接口的實現類,或最終作為實現類。

?
1
2
3
4
5
6
7
8
9
10
11
public abstract class SpringFactoriesLoader {
//...
   public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
    ...
  }
   
   
   public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    ....
  }
}

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應的一組@Configuration類  

 

上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內容,可以很好地說明問題。

(重點)所以,@EnableAutoConfiguration自動配置的魔法其實就變成了:

從classpath中搜尋所有的META-INF/spring.factories配置文件,並將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的配置項通過反射(Java Refletion)實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個並加載到IoC容器。

 

 

 

 

四  springboot啟動流程概覽圖

 

 

 

 

五 深入探索SpringApplication執行流程

 

 

 

 

 

 

?
1
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered

EventPublishingRunListener實現了SpringApplicationRunListener接口;

實現了方法,下面是部分方法源碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
     public void starting() {
         this .initialMulticaster.multicastEvent(
                 new ApplicationStartingEvent( this .application, this .args));
     }
 
     @Override
     public void environmentPrepared(ConfigurableEnvironment environment) {
         this .initialMulticaster.multicastEvent( new ApplicationEnvironmentPreparedEvent(
                 this .application, this .args, environment));
     }
 
     @Override
     public void contextPrepared(ConfigurableApplicationContext context) {
         this .initialMulticaster.multicastEvent( new ApplicationContextInitializedEvent(
                 this .application, this .args, context));
     }
 
     @Override
     public void contextLoaded(ConfigurableApplicationContext context) {
     .....................

  

簡單了解下Bean的生命周期

 

一個Bean的構造函數初始化時是最先執行的,這個時候,bean屬性還沒有被注入

@PostConstruct注解的方法優先於InitializingBean的afterPropertiesSet執行,這時Bean的屬性竟然被注入了;

spring很多組件的初始化都放在afterPropertiesSet做,想和spring一起啟動,可以放在這里啟動;

spring為bean提供了兩種初始化bean的方式,實現InitializingBean接口,實現afterPropertiesSet方法,或者在配置文件中同過init-method指定,兩種方式可以同時使用;

實現InitializingBean接口是直接調用afterPropertiesSet方法,比通過反射調用init-method指定的方法效率相對來說要高點;但是init-method方式消除了對spring的依賴;

如果調用afterPropertiesSet方法時出錯,則不調用init-method指定的方法。

Bean在實例化的過程中:

Constructor > @PostConstruct > InitializingBean > init-method

 

BeanFactory 和ApplicationContext的區別

BeanFactory和ApplicationContext都是接口,並且ApplicationContext間接繼承了BeanFactory。

BeanFactory是Spring中最底層的接口,提供了最簡單的容器的功能,只提供了實例化對象和獲取對象的功能,而ApplicationContext是Spring的一個更高級的容器,提供了更多的有用的功能。  

ApplicationContext提供的額外的功能:獲取Bean的詳細信息(如定義、類型)、國際化的功能、統一加載資源的功能、強大的事件機制、對Web應用的支持等等。

加載方式的區別:BeanFactory采用的是延遲加載的形式來注入Bean;ApplicationContext則相反的,它是在Ioc啟動時就一次性創建所有的Bean,好處是可以馬上發現Spring配置文件中的錯誤,壞處是造成浪費。

 

?
1
2
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
         MessageSource, ApplicationEventPublisher, ResourcePatternResolver

  

SpringMVC處理請求的流程

1、用戶發送請求至前端控制器DispatcherServlet

2、DispatcherServlet收到請求調用HandlerMapping處理器映射器。

3、處理器映射器根據請求url找到具體的處理器,生成處理器對象Handler及處理器攔截器(如果有則生成)一並返回給DispatcherServlet。

4、DispatcherServlet通過HandlerAdapter(讓Handler實現更加靈活)處理器適配器調用處理器

5、執行處理器(Controller,也叫后端控制器)。

6、Controller執行完成返回ModelAndView(連接業務邏輯層和展示層的橋梁,持有一個ModelMap對象和一個View對象)。

7、HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet

8、DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

9、ViewReslover解析后返回具體View

10、DispatcherServlet對View進行渲染視圖(將ModelMap模型數據填充至視圖中)。

11、DispatcherServlet響應用戶

 

BEANFACTORY和FACTORYBEAN的區別與聯系

  1. 兩者都是接口;
  2. BeanFactory主要是用來創建Bean和獲得Bean的;
  3. FactoryBean跟普通Bean不同,其返回的對象不是指定類的一個實例,而是該FactoryBean的getObject方法所返回的對象;
  4. 通過BeanFactory和beanName獲取bean時,如果beanName不加&則獲取到對應bean的實例;如果beanName加上&,則獲取到FactoryBean本身的實例
  5. FactoryBean 通常是用來創建比較復雜的bean(如創建mybatis的SqlSessionFactory很復雜),一般的bean 直接用xml配置即可,但如果創建一個bean的創建過程中涉及到很多其他的bean 和復雜的邏輯,用xml配置比較困難,這時可以考慮用FactoryBean。

 

Bean的循環依賴

https://blog.csdn.net/itmrchen/article/details/90201279

對於Spring中Bean的管理,下圖一目了然: 

 

 

先調用構造函數進行實例化,然后填充屬性,再接着進行其他附加操作和初始化,正是這樣的生命周期,才有了Spring的解決循環依賴,這樣的解決機制是根據Spring框架內定義的三級緩存來實現的,也就是說:三級緩存解決了Bean之間的循環依賴。我們從源碼中來說明。

先來看Spring中Bean工廠是怎么獲取Bean的(AbstractBeanFactory中):

 

 

 

 

 

 

 

 一級一級向下尋找,找出了前面提到的三級緩存,也就是三個Map集合類:

singletonObjects:第一級緩存,里面放置的是已經實例化好的單例對象;

earlySingletonObjects:第二級緩存,里面存放的是提前曝光的單例對象;

singletonFactories:第三級緩存,里面存放的是將要被實例化的對象的對象工廠。

所以當一個Bean調用構造函數進行實例化后,即使set屬性還未填充,就可以通過三級緩存向外暴露依賴的引用值進行set(所以循環依賴問題的解決也是基於Java的引用傳遞),這也說明了另外一點,基於構造函數的注入,如果有循環依賴,Spring是不能夠解決的。

還要說明一點,Spring默認的Bean Scope是單例的,而三級緩存中都包含singleton,可見是對於單例Bean之間的循環依賴的解決,Spring是通過三級緩存來實現的。

 

同一個類中調用 @Transaction注解的方法會有事務效果嗎?

沒有,可以Autowired注入自己,然后再調用注入的類中的方法,即自己依賴自己,循環依賴;

這里在一個內部調用應該是相當於單純的調用方法this.methodName(),並沒有AOP動態代理。