1. 程式人生 > >詳解在Spring中進行整合測試

詳解在Spring中進行整合測試

在單元測試時,我們儘量在遮蔽模組間相互干擾的情況下,重點關注模組內部邏輯的正確性。而整合測試則是在將模組整合在一起後進行的測試,它的目的在於發現一些模組間整合的問題。有些功能很難通過模擬物件進行模擬,相反它們往往只能在真實模組整合後,才能真正執行起來,如事務管理就是其中比較典型的例子。 

按照Spring的推薦(原話:You should not normally use the Spring container for unit tests: simply populate your POJOs in plain JUnit tests!),在單元測試時,你不應該依賴於Spring容器。換言之,你不應該在單元測試時啟動ApplicatonContext並從中獲取Bean,相反你應該通過模擬物件完成單元測試。

而整合測試的前提則是事先裝配好模組和模組之間的關聯類,如將DAO層真實的UserDao和LoginLogDao裝配到UserServiceImpl再進行測試。具體裝配工作是在Spring配置檔案中完成的,因此在一般情況下,整合測試需要啟動Spring容器,你可以在測試類中簡單地從Spring容器中取出目標Bean進行測試。

需要測試的業務介面
假設我們的應用中擁有一個UserService業務層介面,它擁有4個業務方法,其程式碼如下所示: 
程式碼清單1 UserServie介面

    package com.baobaotao.service;
    import com.baobaotao.domain.User;
    import org.springframework.transaction.annotation.Transactional;
    @Transactional
    public interface UserService {
    boolean hasMatchUser(String userName,String password);
    User findUserByUserName(String userName);
    void loginSuccess(User user);
    void registerUser(User user);
    }

我們通過UserServiceImpl對UserService提供了實現: 
程式碼清單2 UserServiceImpl實現UserService介面

    package com.baobaotao.service;
    import com.baobaotao.dao.LoginLogDao;
    import com.baobaotao.dao.UserDao;
    import com.baobaotao.domain.LoginLog;
    import com.baobaotao.domain.User;
    public class UserServiceImpl implements UserService {
    private UserDao userDao;
    private LoginLogDao loginLogDao;
    public boolean hasMatchUser(String userName, String password) {
    int matchCount =userDao.getMatchCount(userName, password);
    return matchCount > 0;
    }
    public User findUserByUserName(String userName) {
    return userDao.findUserByUserName(userName);
    }
    public void loginSuccess(User user) {
    user.setCredits( 5 + user.getCredits());
    LoginLog loginLog = new LoginLog();
    loginLog.setUserId(user.getUserId());
    loginLog.setIp(user.getLastIp());
    loginLog.setLoginDate(user.getLastVisit());
    userDao.updateLoginInfo(user);
    loginLogDao.insertLoginLog(loginLog);
    }
    public void setLoginLogDao(LoginLogDao loginLogDao) {
    this.loginLogDao = loginLogDao;
    }
    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }
    }

UserServiceImpl引用了兩個DAO層的類(UserDao和LoginLogDao)共同實現UserService的介面,在UserServiceImpl開放使用之前,我們有必須對其進行整合測試,以保證實現邏輯的正確性。


使用傳統的方式進行整合測試

下面,我們通過傳統的方式為UserServiceImpl編寫了一個整合測試用例,測試程式碼如下所示: 
程式碼清單 3 TestUserService:UserService整合測試用例

   package com.baobaotao.service;
   …
   public class TestUserService extends TestCase {
   public ApplicationContext ctx = null; //①Spring容器引用
   private static String[] CONFIG_FILES = { //②Spring配置檔案
    "baobaotao-dao.xml",
    "baobaotao-service.xml" };
     protected void setUp() throws Exception { //③啟動Spring容器
    ctx = new FileSystemXmlApplicationContext(CONFIG_FILES);
   }
   public void testHasMatchUser() { //④測試方法一
  // ④-1從容器中獲取Bean
   UserService userService = (UserService) ctx.getBean("userService");
   boolean b1 = userService.hasMatchUser("admin", "123456");
   boolean b2 = userService.hasMatchUser("admin", "1111");
   assertTrue(b1);
   assertTrue(!b2);
   }
   public void testAddLoginLog() {⑤測試方法二
    //⑤-1從容器中獲取Bean
   UserService userService = (UserService) ctx.getBean("userService");
   User user = userService.findUserByUserName("admin");
   user.setUserId(1);
   user.setUserName("admin");
   user.setLastIp("192.168.12.7");
   user.setLastVisit(new Date());
   userService.loginSuccess(user);
   }
   …//省略其餘的測試方法 
   } 

在這個測試用例中,我們使用了最原始的JUnit的TestCase進行整合測試,乍一看並沒有多大的問題,但仔細分析一下,我們就可以總結出以下四點明顯的不足: 

1)導致多次Spring容器初始化問題:

根據JUnit測試方法的呼叫流程(參見錯誤!未找到引用源。小節的描述),每執行一個測試方法都會建立一個TestUserService例項並呼叫setUp()方法。由於我們在setUp()方法中初始化Spring容器,這意味著TestUserService有多少個測試方法,Spring容器就會被重複初始化多少次。雖然初始化Spring容器的速度並不會太慢,但由於可能會在Sprnig容器初始化時執行載入Hibernate對映檔案等耗時的操作,如果每執行一個測試方法都必須重複初始化Spring容器,則對測試效能的影響是不容忽視的; 

2)需要使用硬編碼方式手工獲取Bean:

在④-1和⑤-1處,我們通過ctx.getBean()方法從Spring容器中獲取需要測試的目標Bean,並且還要進行強制型別轉換的造型操作。這種乏味的操作迷漫在測試用例的程式碼中,讓人覺得繁瑣不堪; 

3)資料庫現場容易遭受破壞:

⑤處的測試方法會對資料庫記錄進行插入操作,雖然是針對開發資料庫進行操作,但如果資料操作的影響是持久的,可能會影響到後面的測試行為。舉個例子,你在測試方法中插入一條ID為1的User記錄,第一次執行不會有問題,第二次執行時,就會因為主鍵衝突而導致測試用例失敗。所以應該既能夠完成功能邏輯檢查,又能夠在測試完成後恢復現場,不會留下“後遺症”; 

4)沒有對資料操作正確性進行檢查:

⑤處我們向登入日誌表插入了一條成功登入日誌,可是我們卻沒有對t_login_log表中是否確實添加了一條記錄進行檢查。原來我們的方式是開啟資料庫,肉眼觀察是否插入了相應的記錄,但這嚴重違背了自動測試的原則。試想,你在測試包括成千上萬個數據操作行為的程式時,如何用肉眼進行檢查? 

既然使用傳統方式對Spring應用進行整合測試存在這麼多不足,Spring責無旁貸地擔當起革新之任。它通過擴充套件JUnit框架提供了一套專門測試Spring應用的有力工具。藉助Spring整合測試工具的幫助,以上所羅列的種種問題將冰消雪融、雲開霧散。Service:UserService整合測試用例

Spring在org.springframework.test包中為測試提供了幾個有用的類,它們都是JUnit TestCase的子類。通過層層擴充套件,不斷豐富測試的功能,我們可以通過下圖瞭解這些類的繼承關係:


圖1 Spring測試工具類

下面,我們來逐個瞭解這棵承繼類樹中每個節點測試類的功用,第一個要認識的是直接擴充套件於TestCase的ConditionalTestCase測試類。 

(1)ConditionalTestCase
如果你直接通過擴充套件TestCase建立測試用例,則所有帶test字首的測試方法都會被毫無例外地執行。ConditionalTestCase可以讓你在某些情況下,有選擇地關閉掉一些測試方法,不讓他們在測試用例中執行。這給開發者帶來了很大的靈活性,因為他們可以在某次測試中關閉掉一些測試方法,而僅運行當前特別關注的測試方法,將問題域聚集到一定範圍內。 
如果你要關閉某個測試方法行,僅需實現ConditionalTestCase的 isDisabledInThisEnvironment(String testMethodName)方法就可以了,ConditionalTestCase在執行每一個測試方法前會根據isDisabledInThisEnvironment()方法判斷是簡單放棄目標方法的執行,還是按正常方式執行之。該方法預設情況下對所有的測試方法都返回false,也即執行所有的測試方法。讓我們來看一個具體例子: 
程式碼清單 4 ConditionalTest1:有條件執行測試方法

package com.baobaotao.test;
    import org.springframework.test.ConditionalTestCase;
    public class ConditionalTest1 extends ConditionalTestCase {
    //(1)被忽略不執行的測試方法
    private static String[] IGNORED_METHODS = {"testMethod1","testMethod3"};
    @Override
    protected boolean isDisabledInThisEnvironment(String testMethodName) {// (2)所有在
    for (String method : IGNORED_METHODS) {                               // IGNORED_METHODS陣列中
    if (method.equals(testMethodName)) {                                  //的方法都忽略執行。
    return true;
    }
    }
    return false;
    }
    public void testMethod1(){ // (3)不執行
    System.out.println("method1");
    }
    public void testMethod2(){ // (4)執行
    System.out.println("method2");
    }
    public void testMethod3(){ // (5)不執行
    System.out.println("method3");
    }
    } 

如果我們直接承繼JUnit的TestCase,③、④及⑤處的三個測試方法都會被執行,但現在我們通過繼承ConditionalTestCase編寫測試類,並覆蓋了isDisabledInThisEnvironment()方法,當測試方法名位於IGNORED_METHODS陣列中時,測試方法就被旁路掉了。因此當執行ConditionalTest1時,你會發現只有④處的testMethod2()測試方法得到了執行,其它兩個測試方法看起來也被成功執行,只不過會程式日誌會給出報告,告訴你哪些測試方法是真正被執行,而哪些方法被“偽執行”的。 ConditionalTestCase其實可用於任何程式的單元測試中,它本身並沒有和Spring容器有任何關聯,它僅添加了一個按條件執行測試方法的功能。 
(2)AbstractSpringContextTests

AbstractSpringContextTests擴充套件於ConditionalTestCase,它維護了一個static型別的快取器(HashMap),它使用鍵儲存Spring ApplicationContext例項,這意味著Spring ApplicationContext是JVM級的,不同測試用例、不同測試方法都可以共享這個例項。也就是說,在執行多個測試用例和測試方法時,Spring容器僅需要例項化一次就可以了,極大地提高了基於Spring容器測試程式的執行效率。Spring通過這個測試幫助類解決了前面我們所指出的第1)個問題。

(3)AbstractSingleSpringContextTests 
AbstractSingleSpringContextTests繼承於AbstractSpringContextTests,它通過一些方法讓你方便地指定Spring配置檔案所在位置:
1) String[] getConfigLocations():該方法允許你在指定Spring配置檔案時使用資源型別字首,這些資源型別字首包括:classpath:、file:。以類似於“com/baobaotao/beans.xml”形式指定的資源被當成類路徑資源處理;
 2) String[] getConfigPaths():以“/”開頭的地址被當成類路徑處理,如“/com/baobaotao/beans.xml”,而未以“/”開頭的地址被當成相對於測試類所在包的檔案路徑,如“beans.xml”表示配置檔案在測試類所在類包的目錄下;
 3) String getConfigPath():和getConfigPaths()類似,在僅需指定一個配置檔案中使用。 以上三個方法,它們的優先順序和我們介紹的先後順序對應,也就是說,當你在子類中覆蓋了getConfigLocations()方法後,其它兩個方法就沒有意義了。所以你僅需選擇三者當中適合的方法進行覆蓋,而沒有必要同時覆蓋多個方法。

 AbstractSingleSpringContextTests將根據這些方法指定的Spring配置檔案初始化Spring容器,然後將Spring容器引用新增到static快取中。並通過getApplicationContext()向子類開放ApplicationContext的引用。 一般情況下,所有的測試類和測試方法都可以共享這個Spring容器直到測試完結,不過在某些極端情況下,測試方法可能會對Spring容器進行改動(比如通過程式改變Bean的配置定義),如果這種改變對於其它測試方法來說是有干擾的,這就相當於“弄髒”了作為測試現場的Spring容器,因此在下一個測試方法執行前必須“抹除”這個改變。你可以簡單地在會“弄髒”Spring容器的測試方法中新增setDirty()方法向AbstractSingleSpringContextTests報告這一行為,這樣在下一個測試方法執行前,AbstractSingleSpringContextTests就會重新載入Spring容器以修補被“弄髒”的部分。 雖然你可以直接繼承AbstractSpringContextTests或AbstractSingleSpringContextTests建立自己的整合測試用例,不過你大可不必如此著急。Spring已經提供了幾個功能齊全、實踐性更強的子類,讓我們繼續探索Spring整合測試工具類的精彩篇章吧。

一般整合測試
應該說,Spring通過AbstractSpringContextTests或AbstractSingleSpringContextTests準備好了整合測試的一些基礎設施,在建築學上,這叫夯實地基,而AbstractDependencyInjectionSpringContextTests是在此地基之上建起的第一幢樓房。 AbstractDependencyInjectionSpringContextTests所新添的主要功能是其子類的屬效能被Spring容器中的Bean自動裝配,你無需手工通過ApplicationContext#getBean()從容器中獲取目標Bean自行裝配。它很好回答了前面我們所指出第2)問題,下面我們通過例項進行學習:

    package com.baobaotao.test;
    import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
    import com.baobaotao.service.UserService;
    public class DependencyInjectionCtxTest
    extends AbstractDependencyInjectionSpringContextTests {
    private UserService userService;
    public void setUserService(UserService userService) {(1)該屬性設定方法會被自動調動
    this.userService = userService;
    }
    @Override
    protected String[] getConfigLocations() { (2)指定Spring配置檔案所在位置
    return new String[]{"baobaotao-service.xml","baobaotao-dao.xml"};
    }
    public void testHasMatchUser(){ (3)測試方法
    boolean match = userService.hasMatchUser("tom","123456");
    assertEquals(true, match);
    }
    …
    } 
程式碼清單 5 DependencyInjectionCtxTest
在②處,我們指定了Spring配置檔案所在的位置,AbstractDependencyInjectionSpringContextTests將使用這些配置檔案初始化好Spring容器,並將它們保存於static的快取中。然後馬上著手根據型別匹配機制(byType),
自動將Spring容器中匹配測試類屬性的Bean通過Setter注入到測試類中。為了方便說明這一重要的特性,我們先看一下baobaotao-service.xml的內容
    <beans>
    <tx:annotation-driven/>
    ①按型別匹配於DependencyInjectionCtxTest的userService屬性
    <bean id="userService" class="com.baobaotao.service.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="loginLogDao" ref="loginLogDao"/>
    </bean>
    …
    </beans> 
根據baobaotao-service.xml配置檔案的內容,我們知道Spring容器中有一個UserService Bean,AbstractDependencyInjectionSpringContextTests探測到Spring容器中存在一個匹配於userService屬性的Bean後,就將其注入到DependencyInjectionCtxTest的userService屬性中。userService是這個整合測試類的測試韌體,因此我們說AbstractDependencyInjectionSpringContextTests可以自己裝配測試韌體。

解決自動裝配問題
如果Spring容器中擁有多個匹配UserService型別的Bean,由於Spring沒有足夠的資訊做出取捨決策,因此會丟擲UnsatisfiedDependencyException異常。假設我們採用以下傳統的事務管理的配置方式對UserService進行配置,按型別匹配的自動裝配機制就會引發問題: 
①用於被代理的目標Bean,按型別匹配於UserService

   <bean id="userServiceTarget" class="com.baobaotao.service.UserServiceImpl">
   <property name="userDao" ref="userDao" />
   <property name="loginLogDao" ref="loginLogDao"></property>
   </bean>
②通過事務代理工廠為UserServiceImpl建立的代理Bean,也按匹配於UserService
    <bean id="userService"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager" />
    <property name="target" ref="userServiceTarget" />
    <property name="transactionAttributes">
    …
    </property>
    </bean>

由於①處和②處的Bean都按型別匹配於UserService,在對DependencyInjectionCtxTest的userService屬性進行自動裝配將會引發問題。有兩種針對該問題的解決辦法: 
(1) 調整配置檔案,使按型別匹配於UserService的Bean僅有一個,具體有以下兩個方法:
       1)將①處的Bean作為②處的內部Bean進行裝配; 
       2) 使用基於註解驅動的事務管理配置機制,這樣就無需在配置檔案中定義兩個UserService的Bean了。關於註解驅動事務管理配置的詳細資訊,請參見9.6小節的內容。
(2)改變DependencyInjectionCtxTest的自動裝配機制:Spring預設使用byType型別的自動裝配機制,但它允許你通過setAutowireMode()的方法改變預設自動裝配的機制,比如你可以呼叫setAutowireMode(AUTOWIRE_BY_NAME)方法啟用按名稱匹配的自動裝配機制。AbstractDependencyInjectionSpringContextTests定義了三個代表自動裝配機制型別的常量,分別說明如下: 
       1) AUTOWIRE_BY_TYPE:按型別匹配的方式進行自動裝配,這個預設的機制; 
       2) AUTOWIRE_BY_NAME:按名字匹配的方式進行自動裝配 
       3) AUTOWIRE_NO:不使用自動裝配機制,這意味著你需要手工呼叫getBean()進行裝配。 
現在我們解決了在自動裝配時,因Spring容器中存在多個匹配Bean而導致的問題,接下來讓我們考察另一個自動裝配的問題。

依賴檢查
假設我們在DependencyInjectionCtxTest新增一個User型別的屬性並提供Setter方法,而Spring容器中沒有匹配該屬性的Bean:

    package com.baobaotao.test;
    …
    import com.baobaotao.domain.User;
    public class DependencyInjectionCtxTest extends AbstractDependencyInjectionSpringContextTests {
    private User user;
    public void setUser(User user) {
    this.user = user;
    }
    …
   } 

猜想一下重新執行DependencyInjectionCtxTest將會發生什麼情況呢?答案可能讓你失望:UnsatisfiedDependencyException再次象黑幕一樣降臨。在預設情況下, AbstractDependencyInjectionSpringContextTests要求所有屬性都能在Spring容器中找到對應Bean,否則丟擲異常。 
仔細思考一下,這種執行機制並非沒有道理,因為既然你已經提供了Setter方法,
就相當於給出了這樣的暗示資訊:“這個屬性測試類自身建立不了,必須由外部提供”。而在使用自動裝配機制的情況下,測試類屬性自動從Spring容器中注入匹配的屬性,一般情況下不會手工去呼叫Setter方法準備屬性。 
如果你出於一些特殊的理由,希望在採用自動裝配的情況下,如果有屬性未得到裝配也不在乎,那麼你可以在測試類建構函式中呼叫setDependencyCheck(false)方法達到目的:

相關推薦

Spring進行整合測試

在單元測試時,我們儘量在遮蔽模組間相互干擾的情況下,重點關注模組內部邏輯的正確性。而整合測試則是在將模組整合在一起後進行的測試,它的目的在於發現一些模組間整合的問題。有些功能很難通過模擬物件進行模擬,相反它們往往只能在真實模組整合後,才能真正執行起來,如事務管理就是其中比較

RabbitMQ實例+Spring的MQ使用

方法 it is col 一致性 cli 服務器 發送請求 arguments restrict RabbitMQ實例詳解   消息隊列中間件是分布式系統中重要的組件,主要解決應用解耦,異步消息,流量削鋒等問題,實現高性能,高可用,可伸縮和最終一致性架構。 Queue Q

知識儲備:SpringAOP原理(基於註解版)2

接著上一篇部落格講,上一篇部落格地址:https://blog.csdn.net/qq_36625757/article/details/83652173 8.之前我們說過,測試方法在執行時new了一個AnnotationConfigApplicationContext傳入一個配置類,呼叫了re

spring的事務管理(程式設計式的事務管理,宣告式的事務管理)

spring提供的事務管理API 1. PlatformTransactionManager:平臺事務管理器. commit(TransactionStatus status) getTransaction(TransactionDefinition de

Spring 如何控制2個bean的初始化順序

開發過程中有這樣一個場景,2個 bean 初始化邏輯中有依賴關係,需要控制二者的初始化順序。實現方式可以有多種,本文結合目前對 Spring 的理解,嘗試列出幾種思路。 場景 假設A,B兩個 bean 都需要在初始化的時候從本地磁碟讀取檔案,其中B載入的檔案,依賴A中載入的全域性配置檔案中配

Spring的CharacterEncodingFilter過濾器,過濾url請求

 在專案中有很多讓人頭疼的問題,其中,編碼問題位列其一,那麼在Spring框架中是如何解決從頁面傳來的字串的編碼問題的呢?下面我們來看看Spring框架給我們提供過濾器CharacterEncodingFilter 1.看清結構:    可以看到其繼承Gener

Spring Boot 項目在 IDEA 進行單元測試

res sin run ng- targe clas 單元測試 2.0 就會 Spring Boot提供了許多實用程序和註釋來幫助您測試應用程序。 測試由兩個模塊提供支持:spring-boot-test包含核心項,spring-boot-test-autoconfigur

SSM框架整合配置(spring,spring mvc,mybatis)

當今SSM框架已經成為了一種主流,其中spring,spring mvc和mybatis框架的功能很強大,給我們程式設計師節省了很多力氣,可以說這三種框架簡直就是我們程式設計師的福音,但是我們都知道,框架在自身帶來便捷的同時,也存在很多的配置檔案,更別說當三個框架整合的時候那就更加的困難了,

spring整合shiro許可權管理與資料庫設計

現在基本上所有的後臺系統都逃不過許可權管理這一塊,這算是一個剛需了。現在我們來整合shiro來達到顆粒化許可權管理,也就是從連線選單到頁面功能按鈕,都進行許可權都驗證,從前端按鈕的顯示隱藏,到後臺具體功能方法的許可權驗證。 首先要先設計好我們的資料庫,先來看一張比較粗糙的資

Spring Boot的RabbitMQ

種類型 情況 pic att 嘗試 uem 種類 mpp 為什麽 RabbitMQ 即一個消息隊列,主要是用來實現應用程序的異步和解耦,同時也能起到消息緩沖,消息分發的作用。 消息中間件在互聯網公司的使用中越來越多,剛才還看到新聞阿裏將 RocketMQ 捐獻給了 Apac

【Oracle】OracleNLS_LANG變量的使用

make fault tro territory font pin onclick 添加 其中 目錄結構: // contents structure [-] 關於NLS_LANG參數 NSL_LANG常用的值 在MS-DOS模式和Batch模式中

Python的生成器表達式(generator expression)

新元素 括號 tuple 列表推導式 特點 解析式 表達式 但是 bracket      介紹     1、生成器表達式(generator expression)也叫生成器推導式或生成器解析式,用法與列表推導式非常相似,在形式上生成器推導式使用圓括號(parenth

Python的join()函數的用法

pre 說明 bsp 字符 指定 .net 絕對路徑 字典 -s 函數:string.join() Python中有join()和os.path.join()兩個函數,具體作用如下: join(): 連接字符串數組。將字符串、元組、列表中的元素以指定的字符(分

WordPress簡碼格式標簽編寫的基本方法

filter 所有 oot 執行 body 標簽 支持 script tro WordPress 簡碼是一種類似於論壇標簽的東西,格式類似於把尖括號換成中括號的 Html 標簽。簡碼很多人叫做短代碼,但官方的翻譯應該是簡碼,在這裏糾正一下。 簡碼的開發的邏輯比較簡單,主要就

javascript 的比較(==和===)

不一致 mit 如果 asc onu tin 算法 復雜 undefine 抽象相等比較算法 比較運算 x==y, 其中 x 和 y 是值,產生 true 或者 false。這樣的比較按如下方式進行: 若 Type(x) 與 Type(y) 相同, 則 若 Type(x)

舉例Python的split()函數的使用方法

使用方法 imp count say 文章 pri 參考 詳解 參數 這篇文章主要介紹了舉例詳解Python中的split()函數的使用方法,split()函數的使用是Python學習當中的基礎知識,通常用於將字符串切片並轉換為列表,需要的朋友可以參考下 函數:spl

(轉)C#的反射

typeof ref enc sin setvalue abs class 方法測試 strac (轉)http://www.cnblogs.com/Stephenchao/p/4481995.html 反射的用途: (1)使用Assembly定義和加載程序集,加載在

java的數據結構

span 通過 組成 ret hashcode p s 函數 arr 均衡   線性表,鏈表,哈希表是常用的數據結構,在進行Java開發時,JDK已經為我們提供了一系列相應的類來實現基本的數據結構。這些類均在java.util包中。本文試圖通過簡單的描述,向讀者闡述各個類的

Spring Boot配置文件之多環境配置

多個 提高效率 橫線 文件 style ica property form blog 一. 多環境配置的好處: 1.不同環境配置可以配置不同的參數~ 2.便於部署,提高效率,減少出錯~ 二. properties多環境配置 1. 配置激活選項 spring.profile

webpack的hash、chunkhash、contenthash區別

con tro 們的 tex trac extra lena fig files hash、chunkhash、contenthash hash一般是結合CDN緩存來使用,通過webpack構建之後,生成對應文件名自動帶上對應的MD5值。如果文件內容改變的話,那麽對應文件