Spring Boot 快速入門

學(xué)習(xí)內(nèi)容主要參考《Spring Boot 實戰(zhàn)》创南,本篇內(nèi)容結(jié)合項目實例伦忠,由淺入深,源碼級別了解自動配置的原理

一稿辙、Spring Boot簡介

Spring Boot背景

  • 痛點1:Spring 配置復(fù)雜:雖然Spring的組件代碼是輕量級的昆码,但它的配置卻是重量級的。一開始邻储,Spring用XML配置赋咽,而且是很多XML配置。Spring 2.5引入了基于注解的組件掃描吨娜,這消除了大量針對應(yīng)用程序自身組件的顯式XML配置脓匿。Spring 3.0引入了基于Java的配置,這是一種類型安全的可重構(gòu)配置方式宦赠,可以代替XML陪毡。
    組件掃描減少了配置量,Java配置讓它看上去簡潔不少袱瓮,但Spring還是需要不少配置缤骨。
  • 痛點2:依賴管理復(fù)雜:實現(xiàn)一個功能要考慮使用哪些依賴,依賴版本管理等尺借,是否會有依賴沖突绊起。

Spring Boot精要

  • 自動配置能力(核心):針對很多Spring應(yīng)用程序常見的應(yīng)用功能,Spring Boot能自動提供相關(guān)配置燎斩。
  • 起步依賴(核心):告訴Spring Boot需要什么功能虱歪,它就能引入需要的庫。
  • Actuator(核心):讓你能夠深入運行中的Spring Boot應(yīng)用程序栅表,一探究竟笋鄙。
  • 命令行界面:這是Spring Boot的可選特性,借此你只需寫代碼就能完成完整的應(yīng)用程序怪瓶,無需傳統(tǒng)項目構(gòu)建萧落。

二、由淺入深

快速搭建Spring Boot項目

作者習(xí)慣使用Idea + maven方式

  • idea -> File -> New -> Project洗贰,選擇Spring Initializr


    image.png
  • 創(chuàng)建項目基本信息


    image.png
  • 選擇Starters


    image.png
  • 查看項目結(jié)構(gòu)


    image.png
  • 啟動類
package com.example.readinglist;

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

@SpringBootApplication
public class ReadingListApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReadingListApplication.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 {
        ```
}

從定義可以看到該注解開啟了Spring的組件掃描和Spring Boot的自動配置功能找岖。實際上,@SpringBootApplication將三個有用的注解組合在了一起敛滋。

  • Spring的@Configuration:標(biāo)明該類使用Spring基于Java的配置许布。我們會更傾向于使用基于Java而不是XML的配置。
  • Spring的@ComponentScan:啟用組件掃描绎晃,這樣你寫的Web控制器類和其他組件才能被自動發(fā)現(xiàn)并注冊為Spring應(yīng)用程序上下文里的Bean蜜唾。本章稍后會寫一個簡單的Spring MVC控制器杂曲,使用@Controller進行注解,這樣組件掃描才能找到它袁余。
  • Spring Boot的@EnableAutoConfiguration:就是這個配置\color{red}{開啟了Spring Boot自動配置的魔力(下面深入講解)}擎勘,讓你不用再寫成篇的配置了。

Spring Boot生成的jar包

我們先繼續(xù)由淺入深的看一下Spring boot生成的jar包
pom.xml中會自動引入插件:spring-boot-maven-plugin

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

mvn clean install 會在項目的target目錄生成對應(yīng)的jar包(todo:上傳項目源碼)
jar xvf xxx.jar解壓生成的jar包泌霍,查看目錄結(jié)構(gòu)如下

image.png

cat META-INF/MANIFEST.MF

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: ReadingList
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.readinglist.ReadingListApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.2
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

todo:啟動類入口分析:org.springframework.boot.loader.JarLauncher

三货抄、自動配置能力分析

  • 注解@ EnableAutoConfiguration定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    /**
     * Environment property that can be used to override when auto-configuration is
     * enabled.
     */
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};

}

其中最重要的是 @Import(AutoConfigurationImportSelector.class)注解。借助AutoConfigurationImportSelector 朱转,@EnableAutoConfiguration 幫助Spring Boot 應(yīng)用將所有符合條件的 @Configuration 配置加載到當(dāng)前IoC容器中蟹地。而最主要的還是借助于 Spring 框架一的一個工具類:SpringFactoriesLoader 將 META-INF/spring.factories加載配置,spring.factories 文件是一個典型的properties配置文件藤为,配置的格式仍然是Key = Value 的形式怪与,中不過 Key 和 Value 都是Java的完整類名。比如:
org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.jpa.repository.support.JpaRepositoryFactory

  • AutoConfigurationImportSelector源碼分析
    核心方法selectImports
        @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//關(guān)鍵方法
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

getAutoConfigurationEntry

/**
     * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
     * of the importing {@link Configuration @Configuration} class.
     * @param annotationMetadata the annotation metadata of the configuration class
     * @return the auto-configurations that should be imported
     */
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);//獲取注解屬性
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//關(guān)鍵方法缅疟,從META-INF/spring.factories加載配置
        configurations = removeDuplicates(configurations);//去重
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);//去掉exclusions
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

getCandidateConfigurations

/**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());//關(guān)鍵方法
        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;
    }

/**
     * Return the class used by {@link SpringFactoriesLoader} to load configuration
     * candidates.
     * @return the factory class
     */
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }


loadFactoryNames

/**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * <p>As of Spring Framework 5.3, if a particular implementation class name
     * is discovered more than once for the given factory type, duplicates will
     * be ignored.
     * @param factoryType the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading resources; can be
     * {@code null} to use the default
     * @throws IllegalArgumentException if an error occurs while loading factory names
     * @see #loadFactories
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);//META-INF/spring.factories
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }

四分别、SpringBoot默認(rèn)提供了哪些自動配置類

在向應(yīng)用程序加入Spring Boot時,有個名為spring-boot-autoconfigure.jar文件存淫,其中包含了很多配置類耘斩。


image.png

這些配置類里有用于Thymeleaf的配置,有用于Spring Data JPA的配置桅咆,有用于Spiring MVC的配置括授,還有很多其他東西的配置,你可以自己選擇是否在Spring應(yīng)用程序里使用它們岩饼。共計130個左右


image.png

所有這些配置如此與眾不同荚虚,原因在于它們利用了Spring的條件化配置,這是Spring 4.0引入的新特性籍茧。
這里貼一個demo示例版述,講解了如何自動將configure類引入到容器。

  • Conditional
    上面說了SpringBoot如何引入Configuration寞冯,但是如何做到智能的引入對應(yīng)的bean呢渴析?這里就要講解一下Conditional了。
    讓我們以 ElasticsearchAutoConfiguration 這個配置類為例一探究竟:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Client.class, TransportClientFactoryBean.class })
@ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false)
@EnableConfigurationProperties(ElasticsearchProperties.class)
public class ElasticsearchAutoConfiguration {

    private final ElasticsearchProperties properties;

    public ElasticsearchAutoConfiguration(ElasticsearchProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean
    public TransportClient elasticsearchClient() throws Exception {
        TransportClientFactoryBean factory = new TransportClientFactoryBean();
        factory.setClusterNodes(this.properties.getClusterNodes());
        factory.setProperties(createProperties());
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    private Properties createProperties() {
        Properties properties = new Properties();
        properties.put("cluster.name", this.properties.getClusterName());
        properties.putAll(this.properties.getProperties());
        return properties;
    }

}

不難注意到上述代碼中出現(xiàn)的 @ConditionalOnClass吮龄、@ConditionalOnProperty檬某、@ConditionalOnMissingBean 等注解。它們都屬于 Spring 4.0 的新特性——Conditional 接口螟蝙。條件化配置允許配置存在于應(yīng)用程序中,但在滿足某些特定條件之前都忽略這個配置民傻。 Conditional 接口又有很多衍生條件胰默,以上面的 @ConditionalOnClass 為例场斑,代碼如下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    /**
     * The classes that must be present. Since this annotation is parsed by loading class
     * bytecode, it is safe to specify classes here that may ultimately not be on the
     * classpath, only if this annotation is directly on the affected component and
     * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
     * use this annotation as a meta-annotation, only use the {@link #name} attribute.
     * @return the classes that must be present
     */
    Class<?>[] value() default {};

    /**
     * The classes names that must be present.
     * @return the class names that must be present.
     */
    String[] name() default {};

}

它關(guān)聯(lián)到 OnClassCondition 去返回條件匹配結(jié)果。結(jié)合上文 ElasticsearchAutoConfiguration 的源碼來看牵署,該配置只有在 classpath 中有 org.elasticsearch.client.Client 及 org.elasticsearch.client.transport.TransportClient 時才會生效漏隐。

五、結(jié)語

本篇由淺入深逐漸了解Spring Boot的內(nèi)部機制奴迅,從項目創(chuàng)建青责、jar包分析、自動配置源碼講解以及如何智能的加載bean到IoC中取具。整篇梳理有對應(yīng)的代碼示例脖隶,方便理解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暇检,一起剝皮案震驚了整個濱河市产阱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌块仆,老刑警劉巖构蹬,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悔据,居然都是意外死亡庄敛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門科汗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藻烤,“玉大人,你說我怎么就攤上這事肛捍∫啵” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵拙毫,是天一觀的道長依许。 經(jīng)常有香客問我,道長缀蹄,這世上最難降的妖魔是什么峭跳? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮缺前,結(jié)果婚禮上蛀醉,老公的妹妹穿的比我還像新娘。我一直安慰自己衅码,他們只是感情好拯刁,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逝段,像睡著了一般垛玻。 火紅的嫁衣襯著肌膚如雪割捅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天帚桩,我揣著相機與錄音亿驾,去河邊找鬼。 笑死账嚎,一個胖子當(dāng)著我的面吹牛莫瞬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播郭蕉,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼疼邀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恳不?” 一聲冷哼從身側(cè)響起檩小,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烟勋,沒想到半個月后规求,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡卵惦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年阻肿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮尿。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡丛塌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畜疾,到底是詐尸還是另有隱情赴邻,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布啡捶,位于F島的核電站姥敛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瞎暑。R本人自食惡果不足惜彤敛,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望了赌。 院中可真熱鬧墨榄,春花似錦、人聲如沸勿她。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至播揪,卻和暖如春贮喧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猪狈。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辩恼,地道東北人雇庙。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像灶伊,于是被迫代替她去往敵國和親疆前。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容