1. 程式人生 > >Spring Boot學習筆記2——基本使用之最佳實踐[z]

Spring Boot學習筆記2——基本使用之最佳實踐[z]

前言

在上一篇文章Spring Boot 學習筆記1——初體驗之3分鐘啟動你的Web應用已經對Spring Boot的基本體系與基本使用進行了學習,本文主要目的是更加進一步的來說明對於Spring Boot使用上的具體的細節以及使用上的最佳實踐, 經過了幾天的文件閱讀和實驗,將自己這幾天的學習心得在這裡記錄下來。如果有不對的地方,請指正!

1.依賴管理的配置

1.1 依賴管理的原理及最佳實踐

我們在使用Spring Boot時,通常最好的方式是繼承spring-boot-starter-parent,然後指定其對應的版本。spring-boot-starter-parent主要完成如下工作:

  • 定義專案的中常用的依賴Jar包及其版本
  • 定義專案中常用的maven的外掛及其版本
  • 定義了預設的JDK編譯級別、原始檔字元編碼等通用資訊
  • 敏感資原始檔的過濾(敏感資原始檔是指:application.properties 和application.yml,同是也包括application-foo.properties、 application-foo.yml等)
  • 將maven中的佔位符進行了重新定義為@,為了防止和Spring中的$佔位符起衝突。
      spring-boot-starter-parent的pom檔案.png
    通過上面的截圖可以看到,Spring Boot也是通過maven專案之間的繼承從而完成依賴的繼承。
    spring-boot-dependencies
    檔案中幾乎將我們平時開發常見的依賴全部給定義了,有了它,媽媽再也不用擔心我找不到JAR包了。
      spring-boot-dependencies的pom檔案.png

這樣做除了幫助對依賴有管理,其還有2個優點:

  1. 如果之後想對依賴進行升級,只需修改spring-boot-starter-parent的版本即可,其管理的第三方依賴即可以完成自動化的一致性升級。
  2. 幫助我們解決第三方框架由於版本不相容而引發的衝突,稍微有點開發經驗的朋友可能都有這樣經歷,常常因為專案中引入了框架的JAR包,但是由於它們互相之間版本不相容而導致專案啟動失敗等問題,而現在這些版本之間管理都由Spring Boot來幫助我們完成,其互相之間出現衝突就基本上不復存在了。
    因此,Spring官方強烈建議使用者在使用Spring Boot,不要再對已經定義好的Spring版本進行重寫。
  Spring Boot官方的建議.png

ps:對於第三方依賴的版本重寫問題,Spring官方沒有給出強制的議要求不要重寫,但是個人建議無需重寫,除非你的專案中需要使用到第三方框架的新功能時再版本重寫,進行升級。

在繼承了spring-boot-starter-parent之後,如果我們的專案需要將spring和其他的第三方框架進行整合,則只需引入其對應的依賴描述符即可。比如我們要將Spring和JPA整合,則只需引入spring-boot-starter-data-jpa,在自己專案的POM檔案中加入如下該依賴

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

其會通過maven的依賴傳遞性,將所有和Spring以及JPA相關的JAR包都可以自動引入。

1.2 依賴描述符的說明

依賴描述符也被稱為Starter POM。所有由Spring官方所提供的Starter-POM檔案,都以spring-boot-starter-* 來進行命名,這樣的設計命名方式是幫助我們更加方便的進行定位查詢(我們在以後的設計中也可以對此進行借鑑)。
如果我們自己所編寫的框架和Spring整合構成一個類似spring-boot-starter-data-jpa這樣的一個模組,我們在命名時請使用*-spring-boot-starter。比如:Spring Boot官方並沒有提供Mybatis和Spring的快速整合框架,GitHub上就有老外提供了一個這樣的框架,其名字為mybatis-spring-boot-starter。所有官方和社群所提供的Starter POM都在 這裡。大家可以到這裡檢視

2.Spring Boot 程式碼的標準結構

Spring Boot並沒有強制我們將程式碼必須按照某種目錄格式進行存放,但是其提供了一種最佳實踐。官方建議將應用啟動類放在專案其他類的根包下面,其對應的專案結構如下:

com
 +- example
     +- myproject
         +- Application.java
         |
         +- domain
         |   +- Customer.java
         |   +- CustomerRepository.java
         |
         +- service
         |   +- CustomerService.java
         |
         +- web
             +- CustomerController.java

在應用的啟動類中使用@EnableAutoConfiguration註解來標識,該註解的作用我們之前說過,它可以自動根據當前classpath新增的JAR、和配置,自動進行執行環境的推斷,然後幫助我們自動建立專案中需要的物件並加以配置,然後將其加入Spring容器中(比如在前面文章提及的EmbeddedServletContainerFactory,該物件是執行內嵌Tomcat所需要建立的)。除了該功能,它還有一個更加牛X的功能,它可以自動掃描使用@Entity註解所標註的包,然後告訴JPA的我們所定義的實體類的位置。(注:前提是按照官方給出的程式碼結構進行類的存放)

  EnableAutoConfiguration的註釋說明.png

下面我們通過一個例子來證明一下@EnableAutoConfiguration是不是真的有文件中說的如此強大,我們以一個Spring和JPA整合的例子進行說明:

引入maven依賴:

<parent>
        <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent> <dependencies> <!-- 加入JPA所需要的依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- 加入單元測試需要的依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- 這裡只是簡單測試,因此我們使用h2資料庫,該資料庫是一個基於記憶體的關係型資料庫,非常方便我們測試。 我們無需對該資料庫進行安裝 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> 
  專案的目錄結構.png

建立實體類,方便測試就簡單定義一個City類:

package com.panlingxiao.springboot.jpa.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id; @Entity public class City implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; private String name; private String state; private String country; public City() { } public City(String name, String state, String country) { super(); this.name = name; this.state = state; this.country = country; } public String getName() { return this.name; } public String getState() { return this.state; } public String getCountry() { return this.country; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } public void setState(String state) { this.state = state; } public void setCountry(String country) { this.country = country; } @Override public String toString() { return getId()+","+getName() + "," + getState() + "," + getCountry(); } } 

編寫應用啟動類:

package com.panlingxiao.springboot.jpa; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; /** * 非常簡單,只需一個註解就搞定一切 */ @EnableAutoConfiguration public class JpaApplication { } 

編寫單元測試:

package com.panlingxiao.springboot.jpa.test;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;

import org.junit.Test;
import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.panlingxiao.springboot.jpa.JpaApplication; import com.panlingxiao.springboot.jpa.domain.City; /** * 使用Spring提供的Junit的執行器 */ @RunWith(SpringJUnit4ClassRunner.class) /** * 指定配置類,用於載入Spring容器,在標準的Spring環境下,使用的是 * @ContextConfiguration ,但是在Spring Boot中該@SpringApplicationConfiguration來進行代替。 */ @SpringApplicationConfiguration(classes=JpaApplication.class) public class TestJpaApplication { /** * 注入EntityManager,@EnableAutoConfiguration註解會自動幫助我們建立,就是這麼牛X */ @Autowired private EntityManager em; /** *@EnableAutoConfiguration註解會自動幫助我們建立資料來源 */ @Autowired private DataSource dataSource; private static Logger LOGGER = LoggerFactory.getLogger(TestJpaApplication.class); /** * JPA底層使用了Hibernate的參考實現,其執行必須使用事務管理器 * 因此這裡必須開啟事務,否則執行出錯。但是在單元測試換進下,預設 * 事務是回滾的,如果希望事務提交需要新增 @Rollback(false) */ @Transactional @Test public void testPersistCity(){ City city = new City("ShenZhen", "China","GuangDong"); em.persist(city); //這裡將資料來源輸入看看,它為我們提供的資料來源是什麼資料來源 LOGGER.info("dataSource:{}",dataSource); } } 

Spring Boot預設使用的日誌界別為INFO,我們為了更加清晰看到JPA執行的結果,因此我們需要修改一下Hibernate的SQL日誌級別。
在src/test/resources目錄下建立application.properties檔案,用於指定日誌界別。

logging.level.org.hibernate.SQL=debug
  測試結果.png

從上面的例子的執行結果可以看到,Spring Boot通過@EnableAutoConfiguration可以隱式地自動幫助我們建立資料來源、JPA的EntityManager,並且對其進行配置,同時還自動完成實體類所在包的搜尋,這一切都會讓我們的開發更簡單。

3. 基於註解的容器配置

3.1 使用@Configuration配置Spring容器

Spring Boot雖然可以支援通過xml進行對Spring容器,但是官方通常建議我們將容器的主要配置。並且推薦將@Configuration標註在應用的啟動類之上。

  Spring官方建議使用Java進行對容器配置.png

3.2 匯入其他配置類

使用過Spring的朋友都應用知道,通常我們不會將所有的配置資訊寫在一個檔案中,而是根據功能進行劃分成多個不同的配置,然後在主配置中匯入。使用@Configuration也同樣如此,我們同樣可以將Bean的定義根據功能和作用的不同,分別定義在不同的配置類中,然後通過@Import註解匯入其他額外的配置類,其所提供的功能與xml中定義的<import/>功能是一樣的。
下面通過一個例子來說明,其實這個例子的主要目的是為了說明@Import使用時的注意事項。

為了簡單測試功能,因此在這裡定義一個簡單的Bean:

package com.plx.spring.chapter3.annotation.ioc.bean;

public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } 

編寫主配置類:

package com.plx.spring.chapter3.annotation.ioc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.plx.spring.chapter3.annotation.ioc.bean.Person; import com.plx.spring.chapter3.annotation.ioc.sub.MyAppSubConfig; /** * 定義容器的主配置類 */ @Configuration //引入其他的配置類 @Import({MyAppSubConfig.class}) public class MyAppConfig { /** * 在主配置類中 * @return */ @Bean(name="p1") public Person getPerson(){ Person person = new Person(); person.setName("Main"); return person; } } 

編寫容器的其他配置類:

package com.plx.spring.chapter3.annotation.ioc.sub;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.plx.spring.chapter3.annotation.ioc.bean.Person;

@Configuration public class MyAppSubConfig { @Bean(name="p2") public Person getPerson(){ Person p =new Person(); p.setName("Sub"); return p; } } 

測試程式碼:

package com.plx.spring.chapter3.annotation.ioc;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.plx.spring.chapter3.annotation.ioc.bean.Person;


public class AnnotationIocTest { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( MyAppConfig.class); Person p1 = ctx.getBean("p1", Person.class); System.out.println(p1.getName()); Person p2 = ctx.getBean("p2", Person.class); System.out.println(p2.getName()); } } 

執行結果如下:

  @ImportConfig測試結果.png

需要注意的是,使用@Import匯入的類必須滿足符合以下的某一個條件:

  • 匯入的類使用@Configuration進行標註
  • 匯入的類中至少有一個使用@Bean標準的方法
  • 匯入的類實現了ImportSelector介面
  • 匯入的類實現了ImportBeanDefinitionRegistrar介面

否則執行將會出現如下異常:


  錯誤1.png
  錯誤2.png

另一種更加簡單的匯入的方式可以使用@ComponentScan去掃描指定包下的類,對於使用@Configuration所標識的類也會被@ComponentScan所掃描處理,由於比較簡單,這裡就不演示了。

3.3 匯入XML配置

即使我們將所有的Bean的定義都寫在了xml檔案中,Spring Boot還是建議我們我們使用@Configuration來完成配置檔案的載入。通過使用@ImportResource就可以完成xml檔案的載入。

  Spring官方對@ImportResource的建議.png

3.4 @EnableAutoConfiguration使用總結

前面已經好幾次使用到了@EnableAutoConfiguration,下面我們對其功能做一個比較全面的總結:

  1. @EnableAutoConfiguration的作用是開啟Spring的Application Context的自動配置功能,它會根據當前classpath下我們所新增的Jar包以及我們自己定義的類,然後進行推測我們可能所需要的Bean,最後建立這些我們需要的Bean,然後加入到Spring的Application Context中。

  2. Spring Boot的自動裝配功能做得非常靈活,它可以讓使用者手動指定對於哪些類不需要自動裝配,通過在註解上指定exclue或者excludeName來讓Spring對這裡類的自動建立,也可以在配置檔案中指定spring.autoconfigure.exclude屬性來進行排除;如果使用者在容器中自己定義了一個Spring容器將要會自動裝配物件,Spring容器將會取消對該Bean的自動裝配,而是使用使用者所提供的Bean,因為自動裝配的的過程是在使用者定義的Bean在容器中註冊完成後才發生的。

  3. 在使用時通常將該註解標註在主配置類上,並且主配置類放在根包中,方面對其他類以及子包中的類進行查詢和掃描。

前面我們在測試JPA的時候,看到Spring Boot預設情況下幫助我們建立的資料來源是使用Tomcat所提供的資料來源,目前我們現在用得比較多的可能是阿里的druid資料來源,下面通過這個例子來實現使用自己提供的資料來源來替換Spring Boot的預設配置。

我們首先需要引入Druid的依賴,在原來專案的POM中新增如下內容

        <dependency>
            <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency> 

將原來啟動程式進行修改:

package com.panlingxiao.springboot.jpa;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import com.alibaba.druid.pool.DruidDataSource; /** * 非常簡單,只需一個註解就搞定一切 */ @EnableAutoConfiguration public class JpaApplication { /** * application.proeprties會被Spring設定到Enviroment * 通過env可以讀取使用者配置的屬性資訊和系統屬性 */ @Autowired private Environment env; /** * 在配置類自定定義DataSource,在使用者自己定義了之後, * Spring Boot將不會實現對資料來源的自動配置功能 * @return */ @Bean public DataSource getDruidDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(env.getProperty("jdbc.driver")); dataSource.setUrl(env.getProperty("jdbc.url")); dataSource.setUsername(env.getProperty("jdbc.user")); dataSource.setPassword(env.getProperty("jdbc.password")); return dataSource; } } 

修改application.proeprties,設定資料庫的連線資訊,因為Spring Boot不再幫助我們建立資料來源,因此我們需要自己指定資料庫資訊。

logging.level.org.hibernate.SQL=debug
jdbc.driver=org.h2.Driver 
jdbc.url= jdbc:h2:mem:testdb
jdbc.username=sa
jdbc.password=sa

執行結果:

  配置自定義的資料來源.png

從上面的輸出結果可以看到,Spring Boot發現我們自己定義了資料來源後,就不會再做自動配置,而是使用我們自定義的資料來源,從這點我們也可以看出Spring Boot在設計的時候確實是非侵入式的設計。

3.5 擴充套件問題思考

既然Spring Boot可以發現完成當H2的JAR包出現在Classpath中,就可以自動建立資料來源,然後提供給JPA使用。那麼我們能否讓其發現我們需要的JAR包,也來完成自動配置,從而能夠達到一勞永逸的效果,我們該如何實現呢?
這個問題本人目前還沒有解決,但我的思路是首先看看其是否提供了預設的擴充套件機制,如果有是最理想的;如果沒有,則需要進一步原始碼其原始碼,通過修改其原始碼來實現。

3.6 使用@SpringBootApplication

因為我們通常會在應用啟動類上同時使用@Configuration, @EnableAutoConfiguration和@ComponentScan,Spring Boot為了方便將這3個註解整合在一起,形成了@SpringBootApplication,在上一節我們已經看過該註解的原始碼實現了,故這裡不再細說。@SpringApplication可以通過exclude和excludeName屬性排除自動裝配的物件,通過scanBasePackages屬性設定指定的包進行掃描。

4. 熱部署

由於Spring Boot應用通常都是一個普通的Java Application,如果我們在開發中修改了原始碼而不希望關閉重啟,Spring Boot也為我們提供了一個工具,完成應用的熱部署功能,我們只需要引入spring-boot-devtools模組就可以完成應用的熱部署效果。

<dependency> 
       <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> 

使用spring-boot-devtools會監控ClassPath下資源的改變,對於大多數資原始檔一旦發生變化,它就會觸發自動重啟功能,但是對於特殊的一些目錄下的資原始檔發生改變這些目錄分別為/META-INF/maven, /META-INF/resources ,/resources,/static,/public ,/templates(注意:這些目錄檔案都位於classpath下),spring-boot-devtools是不會觸發重啟應用的,但是會觸發這些資原始檔的重新載入。

 

  不觸發自動重啟的資源目錄.png
如果修改了static目錄下的a.txt檔案是不會觸發自動重啟功能的,使用者可以通過 spring.devtools.restart.exclude屬性自定義不觸發重啟的目錄。當在application.properties中加入如下內容:

 

spring.devtools.restart.exclude=public/**

此時,修改static目錄下的檔案就會自動觸發重啟,但是當修改public目錄下的檔案依舊不會觸發重啟。如果既想要保持預設的哪些目錄,並且同時還想新增一些目錄不觸發重啟,可以通過spring.devtools.restart.additional-exclude來進行指定。

5. 參考內容

  1. http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/
  2. https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples

至此,Spring Boot的基本使用以及使用上的最佳實踐已經討論完了,後面將進一步討論如何對Spring Boot進行定製化和細粒度的修改從而滿足我們自己的需求以及和各個模組的整合使用。用心寫作真的不易,這篇文章我放假整整寫了3天,如果覺得對您有點幫助,請點個喜歡,就我對我寫作最大的鼓勵,謝謝!




連結:https://www.jianshu.com/p/cb0f96958919