天天看點

SpringBoot的初體驗及原理分析

SpringBoot的初體驗及原理分析

一、前言

​  ​​上篇文章​​,我們聊到了SpringBoot得以實作的幕後推手,這次我們來用SpringBoot開始HelloWorld之旅。SpringBoot是Spring架構對“約定大于配置(Convention over Configuration)”理念的最佳實踐。SpringBoot應用本質上就是一個基于Spring架構的應用。我們大多數程式猿已經對Spring特别熟悉了,那随着我們的深入挖掘,會發現SpringBoot中并沒有什麼新鮮事,如果你不信,那就一起走着瞧呗!

二、SpringBoot初體驗

首先,我們按照下圖中的步驟生成一個SpringBoot項目:

  

SpringBoot的初體驗及原理分析

解壓後的項目檔案在idea中打開以後,我們會看到如下的項目結構:

  

SpringBoot的初體驗及原理分析

這時候,我們在​

​com.hafiz.springbootdemo​

​​包下建立​

​controller​

​​包,然後再在該包下面建立​

​DemoController.java​

​檔案,内容如下:

package com.hafiz.springbootdemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author hafiz.zhang
 * @description: first spring boot controller
 * @date Created in 2018/6/3 16:49.
 */
@RestController
public class DemoController {

    @GetMapping("/hello")
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}      

然後我們運作​

​SpringbootDemoApplication.java​

​這個main方法,啟動成功後,在控制台中會出現以下日志,證明項目啟動成功:

  

SpringBoot的初體驗及原理分析

我們從日志中可以看出,已經注冊了get方式的​

​/hello​

​​的路徑以及名為​

​/error​

​​的路徑,并且項目在tomat中運作在8080端口,然後我們在浏覽器中通路​

​localhost:8080/hello?name=hafiz.zhang​

​會看到如下資訊:

SpringBoot的初體驗及原理分析
這樣我們就完成了demo項目的開發,先不用這裡面到底發生了啥,我就想問問你:是不是很爽?完全沒有傳統項目的繁瑣的xml配置,爽到爆有木有?搭建一個項目骨架隻要幾秒鐘!這就是SpringBoot帶給我們的便利~ 就是這麼神奇!就是這麼牛逼!

三、項目簡單解析

首先我們來看,生成好的項目中的​

​SpringbootDemoApplication.java​

​檔案,内容如下:

package com.hafiz.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDemoApplication {

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

代碼很簡單,但最耀眼的就是​

​@SpringBootApplication​

​​注解以及在main方法中運作的​

​SpringAppliation.run()了​

​,那我們要揭開SpringBoot應用的奧秘,很明顯就要拿這二位開刀了!

​@SpringBootApplication​

​注解解析

先看看​

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

我們看到​

​@SpringBootApplication​

​​其實是一個複合的注解,起主要作用的就是​

​@SpringBootConfiguration​

​​、​

​@EnableAutoConfiguration​

​​以及​

​@ComponentScan​

​ 三個注解組成,是以如果我們把SpringBoot啟動類改寫成如下方式,整個SpringBoot應用依然可以與之前的啟動類功能一樣:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class SpringbootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}      

因為我們每次建立項目時都要寫上三個注解來完成配置,這顯然太繁瑣了,SpringBoot就為我們提供了​

​@SpringBootApplication​

​這樣一個複合注解來簡化我們的操作。簡直不能再貼心一點了!

​@SpringBootConfiguration​

​注解解析

接着我們來看​

​@SpringBootConfiguration​

​注解的源碼:

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

我們可以看到,這裡面依舊沒有什麼新東西,它SpringBoot為了差別​

​@Configuration​

​而新提供的專屬于SpringBoot的注解,功能和​

​@Configuration​

​一模一樣。而這裡的​

​@Configuration​

​​注解對于我們來說并不陌生,我們在​​上篇文章​​中進行了詳細的介紹。SpringBoot的啟動類标注了這個注解,毫無疑問,它本身也是個IoC容器的配置類。了解到了這裡,我們其實可以把SpringBoot的啟動類來拆成兩個類,拆完以後就非常清楚明了了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {

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

是以呢,啟動類DemoApplication其實就是一個标準的Standalone類型的Java程式的main函數啟動類,也并沒有什麼特殊的東西。

​@EnableAutoConfiguration​

​ 的神奇之處

看到這貨,我們不僅聯想出Spring 中很多以“@Enable”開頭的注解,比如:​

​@EnableScheduling​

​​、​

​@EnableCaching​

​​以及​

​@EnableMBeanExport​

​​等,​

​@EnableAutoConfiguration​

​​注解的理念和工作原理和它們其實一脈相承。簡單的來說,就是該注解借助​

​@Import​

​注解的支援,Spring的IoC容器收集和注冊特定場景相關的Bean定義:

  • ​@EnableScheduling​

    ​​是通過​

    ​@Import​

    ​将Spring排程架構相關的bean都加載到IoC容器。
  • ​@EnableMBeanExport​

    ​​是通過​

    ​@Import​

    ​将JMX相關的bean定義加載到IoC容器。

而​

​@EnableAutoConfiguration​

​​注解也是借助​

​@Import​

​​将所有複合配置條件的bean定義加載到IoC容器,僅此而已!而​

​@EnableAutoConfiguration​

​注解的源碼如下:

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

這其中最關鍵的就是​

​@Import(EnableAutoConfigurationImportSelector.class)​

​​了,它借助​

​EnableAutoConfigurationImportSelector.class​

​可以幫助SpringBoot應用将所有符合條件的@Configuration配置類都加載到目前SpringBoot建立并使用的IoC容器,就像一個“八爪魚”一樣。

  

SpringBoot的初體驗及原理分析

下面我們給出​

​EnableAutoConfigurationImportSelector.java​

​​的父類​

​AutoConfigurationImportSelector.java​

​的部分源碼,來解釋和驗證上圖:

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader);
    }
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
                this.beanClassLoader);
    }
}      

以上源碼可以看出,​

​@EnableAutoConfiguration​

​​正是借助​

​SpringFactoriesLoader​

​的支援,才能完成如此偉大的壯舉!

幕後英雄​

​SpringFactoriesLoader​

​詳解

SpringFactoriesLoader屬于Spring架構專屬的一種擴充方案(其功能和使用方式類似于Java的SPI方案:java.util.ServiceLoader),它的主要功能就是從指定的配置檔案​

​META-INF/spring.factories​

​中加載配置,spring.factories是一個非常經典的java properties檔案,内容格式是Key=Value形式,隻不過這Key以及Value都非常特殊,為Java類的完整類名(Fully qualified name),比如:

com.hafiz.service.DemoService=com.hafiz.service.impl.DemoServiceImpl,com.hafiz.service.impl.DemoServiceImpl2      

然後Spring架構就可以根據某個類型作為Key來查找對應的類型名稱清單了,SpringFactories源碼如下:

public abstract class SpringFactoriesLoader {

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
        ...
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}      

對于​

​@EnableAutoConfiguraion​

​​來說,SpringFactoriesLoader的用途和其本意稍微不同,它本意是為了提供SPI擴充,而在​

​@EnableAutoConfiguration​

​​這個場景下,它更多的是提供了一種配置查找的功能的支援,也就是根據​

​@EnableAutoConfiguration​

​​的完整類名​

​org.springframework.boot.autoconfigure.EnableAutoConfiguration​

​作為Key來擷取一組對應的@Configuration類:

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,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
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,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration      

在SpringBoot的autoconfigure依賴包中的META-INF檔案下的spring.factories檔案中,我們可以找到以上内容,這就很好的解釋了為什麼。

總結來說,​

​@EnableAutoConfiguration​

​​能實作自動配置的原理就是:SpringFactoriesLoader從classpath中搜尋所有META-INF/spring.fatories檔案,并将其中Key[​

​org.springframework.boot.autoconfigure.EnableAutoConfiguration​

​​]對應的Value配置項通過反射的方式執行個體化為對應的标注了​

​@Configuration​

​的JavaConfig形式的IoC容器配置類,然後彙總到目前使用的IoC容器中。

非必須的​

​@ComponentScan​

​解析

為什麼說這個注解是非必需的呢?因為我們知道作為Spring架構裡的老成員,@ComponentScan的功能就是自動掃描并加載複合條件的元件或Bean定義,最終将這些bean定義加載到目前使用的容器中。這個過程,我們可以手工單個進行注冊,不是一定要通過這個注解批量掃描和注冊,是以說​

​@ComponentScan​

​是非必需的。

是以,如果我們目前應用沒有任何bean定義需要通過​

​@ComponentScan​

​​加載到目前SpringBoot應用對應的IoC容器,那麼,去掉​

​@ComponentScan​

​注解,目前的SpringBoot應用依舊可以完美運作!那是不是到目前為止,依舊沒有什麼新鮮的事物出現?

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}      
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    ...
}