微服務構建: Spring Boot

在展開 Spring Cloud 的微服務架構部署以前, 咱們先了解一下用於構建微服務的基礎框架-Spring Boot。 因爲 Spring Cloud 的構建基於 Spring Boot 實現, 在後續的示例中我 們將大量使用 Spring Boot 來構建微服務架構中的基礎設施以及一些試驗中使用的微服務。 爲了可以輔助後續內容的介紹,確保讀者有必定的Spring Boot基礎,在這裏先對Spring Boot 作一個簡單的介紹, 以保證讀者可以有必定的基礎去理解後續介紹的內容並順利完成後續 的一些示例試驗。css

       在這裏介紹 Spring Boot 的目的除了它是 Spring Cloud 的基礎以外, 也因爲其自身的各 項優勢, 如自動化配置、 快速開發、 輕鬆部署等, 很是適合用做微服務架構中各項具體微 服務的開發框架。因此咱們強烈推薦使用 Spring Boot 來構建微服務, 它不只能夠幫助咱們 快速地構建微服務, 還能夠輕鬆簡單地整合 Spring Cloud 實現系統服務化, 而若是使用了 傳統的 Spring 構建方式的話, 在整合過程當中咱們還須要作更多的依賴管理工做才能讓它們 無缺地運行起來。java

       在本文中咱們將介紹下面這些與後續介紹有密切聯繫的內容: mysql

               • 如何構建 Spring Boot 項目 web

               • 如何實現 RESTfulAPI 接口 spring

               • 如何實現多環境的 Spring Boot 應用配置 sql

               • 深刻理解 Spring Boot 配置的啓動機制數據庫

               • Spring Boot 應用的監控與管理apache

一、框架介紹json

    對於不少Spring框架的初學者來講, 常常會由於其繁雜的配置文件而卻步。 而對於很 多老手來講, 每次新構建項目老是會重複複製粘貼一 些差很少的配置文件這樣枯燥乏味的事。服務器

   Spring Boot的出現 能夠有效改善這類問題,SpringBoot的宗旨並不是要重寫Spring或是 替代Spring, 而是但願經過設計大量的自動化配置等方式來簡化Spring原有樣板化的配置, 使得開發者能夠快速構建應用。 

    除了解決配置問題以外, Spring Boot還經過一系列StaiterPOMs的定義, 讓咱們整合 各項功能的時候, 不須要在 Maven的pom.xml中維護那些錯綜複雜的依賴關係, 而是通 過相似模塊化的Starter模塊定義來引用, 使得依賴管理工做變得更爲簡單。

     在現在容器化大行其道的時代,Spring Boot除了能夠很好融入Docker以外, 其自身就 支持嵌入式的 Tomcat、 Jetty 等容器。 因此, 經過Spring Boot 構建的應用再也不須要安裝 Tomcat, 將應用打包成war, 再部署到Tomcat 這樣複雜的構建與部署動做, 只需將Spring Boot應用打成jar包, 並經過java -jar命令直接運行就能啓動一個標準化的Web應用, 這使得Spring Boot應用變得很是輕便。

二、快速搭建SpringBoot項目

   在本文中, 咱們將逐步指引讀者建立一個Spring Boot的基礎項目, 而且實現 一個簡單 的RESTfulAPL 經過這個例子對Spring Boot有一個初步的瞭解, 並體驗其結構簡單、 開 發迅速的特性。

   項目構建與解析

         系統及工具版本要求 

                • Java 7及以上版本 

                • Spring Framework 4.2.7及以上版本 

                • Maven 3.2及以上版本/Gradle 1.12及以上版本 

        本文內容均採用Java 1.七、 Spring Boot 1.5.10調試經過。

https://img1.mukewang.com/5b28a12b0001ae2113220976.jpg

工程結構解析

• src/main/java: 主程序入口 DmsApplication, 能夠經過直接運行該類來 啓動Spring Boot應用。

• src/main/resources: 配置目錄, 該目錄用來存放應用的一些配置信息, 好比 應用名、服務端口、數據庫連接等。由千咱們引入了Web模塊,所以產生了static 目錄與templates目錄, 前者用於存放靜態資源, 如圖片、 css、JavaScript等; 後者用千存放Web頁面的模板文件, 這裏咱們主要演示提供RESTful APL因此這 兩個目錄並不會用到。 

• src/test/: 單元測試目錄, 生成的DmsApplicationTests經過JUnit 4實 現, 能夠直接用運行Spring Boot應用的測試。 後文中, 咱們會演示如何在該類中測 試RESTfulAPI。

    Maven配置分析 打開當前工程下的pom.xml文件, 看看生成的項目都引入了哪些依賴來構建Spring Boot工程, 內容大體以下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.baihe.dms</groupId>
    <artifactId>dms</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>dms</name>
    <description>Deduct Money System</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.7</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.14</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.5</version>
        </dependency>
         <dependency>
            <groupId>net.minidev</groupId>
               <artifactId>json-smart</artifactId>
                <version>1.2</version>
         </dependency>
    </dependencies>

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


</project>

 

   在基礎信息部分, groupid和 artifactId 對應生成項目時頁面上輸入的內容。 另 外, 咱們還能夠注意到, 打包形式爲 jar, 正如咱們 以前所介紹的,Spring Boot默認將該Web應用打包爲jar 的形式, 而非war 的形式, 由於 默認 的Web模塊依賴 會包含嵌入式的Tomcat , 這樣使得咱們的應用jar自身就具有了提供 Web服務的能力, 後續咱們會演示如何啓動它。 

    父項目parent配置指定爲 spring-boot-starter-parent的1. 5.10 版本, 該父項 目中定義了Spring Boot版本的基礎依賴以及 一 些默認配置內容 , 好比,配置文件application.properties的位置等。 在項目依賴 dependencies配置中, 包含了下面兩項。 

          • spring-boot-starter-web: 全棧Web開發模塊, 包含嵌入式Tomcat、 Spring MVC。 

          • spring-boot-starter-test: 通用測試模塊, 包含JUnit、 Hamcrest、 Mockito 。 

    這裏所引用的web和test 模塊,在SpringBoot 生態中被稱爲Starter POMs。Starter POMs 是一系列輕便的依賴 包, 是一套一站式的Spring相關技術的解決方案。 開發者在使用和整 合模塊時, 沒必要再去搜尋樣例代碼中的依賴配置來複制使用, 只須要引入對應的模塊包即 可 。 

    好比, 開發Web應用的時候, 就引入spring-boot-starter-web, 但願應用具有 訪問數據庫能力的時候, 那就再引入 spring-boot-starter-jdbc 或是更好用的 spring-boot-starter-data-jpa。 在使用SpringBoot構建應用的時候, 各項功能模 塊的整合再也不像傳統Spring應用的開發方式那樣,須要在 pom.xml中作大量的依賴配置, 而是經過使用StarterPOMs定義的依賴包,使得功能模塊整合變得很是輕巧, 易於理解與使用。 

三、實現RESTfulAPI

   在Spring Boot中建立一個RESTfulAPI的實現代碼同SpringMVC應用同樣, 只是不 須要像SpringMVC那樣先作不少配置, 而是像下面這樣直接開始編寫Controller內容:

• 新建package, 命名爲com.baihe.dms.controller.CmsController, 可根據實際的構建狀況修改爲自 己的路徑。

 • 新建CmsController類,內容以下所示。

package com.baihe.dms.controller;

import com.baihe.dms.entity.common.CmsException;
import com.baihe.dms.entity.common.ResponseData;
import com.baihe.dms.service.WithholdService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller("cmsController")
@RequestMapping(value = "/")
public class CmsController {

    private Logger logger = Logger.getLogger(this.getClass());

    private WithholdService withholdService;

    CmsController(@Autowired  WithholdService withholdService) {
        this.withholdService = withholdService;
    }

    @RequestMapping(value = "/withhold" , method =  RequestMethod.POST)
    @ResponseBody
    public ResponseData withhold(String contractNo, Long requestNo, Long userId, Long projectId,
    		Integer planStep, String bankCode, Double totalAmount, Integer overdueFlag, Integer incomeFree) {
        try {
            return withholdService.withhold(contractNo, requestNo, userId, projectId, planStep, bankCode, totalAmount, overdueFlag, incomeFree);
        } catch (CmsException e) {
            return ResponseData.no(e.getErrCode());
        } catch (Exception e) {
            logger.debug("withhold", e);
            return ResponseData.no(ResponseData.INTERNAL_ERROR);
        }
    }
    
    @GetMapping("/withholdCancelOrRecover")
    @ResponseBody
    public ResponseData withholdCancelOrRecover(Long requestNo, int status) {
        try {
            return withholdService.withholdCancelOrRecover(requestNo, status);
        } catch (CmsException e) {
            return ResponseData.no(e.getErrCode());
        } catch (Exception e) {
            logger.debug("withholdCancelOrRecover", e);
            return ResponseData.no(ResponseData.INTERNAL_ERROR);
        }
    }
    /*
     * 因爲豁免流程的存在
     * 會出現當即還款狀況
     * 參數 requestNo 還款計劃流水號
     * channelType  扣款渠道  寶付仍是易寶
     * */
    @RequestMapping("/atOnceWithhold")
    @ResponseBody
    public ResponseData atOnceWithhold(String requestNo, String channelType) {
        try {
            return withholdService.atOnceWithhold(Long.valueOf(requestNo), channelType);
        } catch (CmsException e) {
            return ResponseData.no(e.getErrCode());
        } catch (Exception e) {
            logger.debug("atOnceWithhold", e);
            return ResponseData.no(ResponseData.INTERNAL_ERROR);
        }
    }

}

  

ResponseData類:

package com.baihe.dms.entity.common;

import java.io.Serializable;

/**
 * 接口返回的數據
 */
public class ResponseData implements Serializable {

    private static final long serialVersionUID = 2047667816784695690L;

    public static final int OK = 200; // 非 OK 的都是失敗
    public static final int NO = -1;

    public static final int INTERNAL_ERROR = -99;
    public static final int INVALID_PARAMETER = -100;
    public static final int NULL_PARAMETER = -101;
    public static final int INVALID_AMOUNT = -102;
    public static final int INVALID_BANKCODE = -103;
    public static final int HAS_UNFINISHED_REQUEST = -104;
    public static final int INVALID_SPLIT_CONFIG = -105;
    public static final int SPLIT_TOO_MANY = -106;
    public static final int NOT_NEED_SPLIT = -107;

    private Integer code = NO;
    private String message = "";
    private Object data;


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    private void generateMessage(int errCode) {
        String message = "未知錯誤";

        switch (errCode) {
            case INTERNAL_ERROR:
                message = "內部錯誤";
                break;

            case INVALID_PARAMETER:
                message = "無效的參數";
                break;

            case NULL_PARAMETER:
                message = "參數不能爲空";
                break;

            case INVALID_AMOUNT:
                message = "無效的金額";
                break;

            case INVALID_BANKCODE:
                message = "無效的銀行編碼";
                break;

            case HAS_UNFINISHED_REQUEST:
                message = "用戶尚有未完成的交易";
                break;

            case INVALID_SPLIT_CONFIG:
                message = "拆分配置無效";
                break;

            case SPLIT_TOO_MANY:
                message = "拆分數目過多";
                break;
                
            case NOT_NEED_SPLIT:
                message = "金額沒有變化,無需拆分";
                break;

            case NO:
                message = "內部錯誤";
                break;

            case OK:
                message = "成功";
                break;
        }

        this.message = message;
    }

    public static ResponseData create(int code, Object data) {
        ResponseData responseData = new ResponseData();
        responseData.code = code;
        responseData.generateMessage(code);
        responseData.data = data;

        return responseData;
    }

    public static ResponseData create(int code) {
        return create(code, null);
    }

    public static ResponseData ok(Object data) {
        return ResponseData.create(ResponseData.OK, data);
    }

    public static ResponseData ok() {
        return ResponseData.create(ResponseData.OK, null);
    }

    public static ResponseData no(int code, Object data) {
        return ResponseData.create(code, data);
    }

    public static ResponseData no(int code) {
        return ResponseData.create(code);
    }

}

  

啓動Spring Boot應用的方式有不少種:

• 做爲一個 Java 應用程序, 能夠直接經過運行擁有 main 函數的類來啓動。

• 在 Maven 配置中, 以前提到了 spring-boot 插件, 可使用它來啓動, 好比執行 mvn spring-boot: run 命令。

• 在服務器上部署運行時, 一般先使用 mvn install 將應用打包成 jar 包, 再經過 java -jar xxx. jar 來啓動應用。

配置詳解

   在面咱們輕鬆地實現了一個簡單的RESTfulAPI應用, 體驗了SpringBoot的 諸多優勢。咱們用很是少的代碼就成功實現了一個Web應用, 這是傳統Spring應用沒法辦到的。雖然在實現Controller時用到的代碼是同樣的,可是在配置方面,相信你們也注意到了, 在上面的例子中, 除了Maven的配置以外, 沒有引入任何其餘配置。 

    這就是以前咱們提到的,SpringBoot針對經常使用的開發場景提供了一系列自動化配置來 減小本來複雜而又幾乎不多改動的模板化配置內容。可是,咱們仍是須要 瞭解如何在Spring Boot中修改這些自動化的配置內容, 以應對一些 特殊的場景需求, 好比, 咱們在同一臺主 機上須要啓動多個基千Spring Boot的Web應用, 若不爲每一個應用指定特別的端口號, 那 麼默認的8080 端口必將致使衝突。 後續咱們在使用SpringCloud的各個組件的時候, 其實有大量的工做都 會是針對配置 文件的。因此咱們有必要深刻了解一些關於SpringBoot中的配置文件的知識, 好比配置方 式、 如何實現多環境配置、 配置信息的加載順序等。

  • 配置文件

     

    在快速入門示例中, 咱們介紹Spring Boot 的工程結構時, 提到過 src/rnain/ resources 目錄是Spring Boot的配置目錄, 因此當要爲應用建立個性化配置時, 應在該 目錄下進行。

    Spring Boot 的默認配置文件位置爲 src/main/resources/application. properties 。關於SpringBoot應用的配置內容均可以集中在該文件中, 根據咱們引入的 不 同Starter模塊,能夠在這裏定義容器端口號、 數據庫鏈接信息、 日誌級別等各類配置信 息。好比, 咱們須要自定義Web模塊的服務端口號,能夠在application.properties  中添加 server.port=8888 來指定服務端口爲 8888 , 也可 以經過 spring.appliction.name =hello 來指定應用名(該名字在後續SpringCloud中會被 註冊爲服務名)。 

    Spring Boot的配置文件除了可使用傳統的 properties文件以外,還支持如今被普遍推 薦使用的YAML文件。

    YAML 採用的配置格式不像 properties 的配置那樣以單純的鍵值對形式來表示,而是以 相似大綱的縮進形式來表示。 下面是一段 YAML 配置信息:

server:
    port: 8081

spring:
    profiles:
        active: prod

mybatis:
    mapper-locations: classpath:mapping/*.xml
    type-aliases-package: com.baihe.dms.entity.common
    configLocation: classpath:mybatis-config.xml

logging:
    level:
        com.baihe.dms.mapper: debug

endpoints:
    shutdown:
        enabled: true
    sensitive:
      false

 

  • 自定義參數

    除了能夠在 Spring Boot 的配置文件中設置各個 Starter 模塊中預約義的配置屬性, 也可 以在配置文件中定義一些咱們須要的自定義屬性。 好比在 application.properties 中 添加: book.name=SpringCloudinAction

    book.author=ZhaiYongchao

    而後, 在應用中能夠經過@Value 註解來加載這些自定義的參數,

    好比:

    @Component

    public class Book {

    @Value("${book.name}")

    private String name;

    @Value("${book.author}")

    private String author;

    //省略getter和setter @Value 註解加載屬性值的時候能夠支持兩種表達式來進行配置,

    以下所示。

    • 一種是上面介紹的 PlaceHolder 方式, 格式爲${...}, 大括號內爲 PlaceHolder。

    • 另外一種是使用SpEL 表達式 (Spring Expression Language), 格式爲#{...}, 大括號 內爲 SpEL 表達式。

  • 使用隨機數

    在 一些特殊狀況下, 咱們但願有些參數每次被加載的時候不是 一個固定的值, 好比密 鑰、 服務端口等。 在 SpringBoot的屬性配置文件中, 能夠 經過 使用${random}配置來產 生隨機的int值、long值或者string字符串,這樣咱們就能夠容易地經過 配置隨機生成屬性, 而不是在程序中經過編碼來實現這些邏輯。

    #隨機字符串 com.didispace.blog.value=${random.value}

    #隨機int com.didispace.blog.number=${random.int}

    #隨機long com.didispace.blog.bignumber=${random.long}

    # 10之內的隨機數 com.didispace.blog.test1=${random.int(l0)}

    # 10-20的隨機數 com.didispace.blog.test2=${random.int[l0,20]}

  • 命令行參數

    在用命令行方式 啓 動 Spring Boot 應用時, 連續的兩個減號--就 是對 application.properties 中的屬性值進行賦值 的標識。 因此 , java -jar xxx.jar--server.port=8888命令, 等價千在 application.properties 中添加 屬性server.port= 8888。 經過命令行來修改屬性值是 SpringBoot很是重要的一個特性。 經過此特性, 理論上已經使得應用的屬性在啓動前是可變的, 因此其中的端口號也好、 數據庫鏈接也好, 都是可 以在應用啓動時發生改變的, 而不一樣於以往的Spring應用經過Maven的Profile在編譯器 中進行不一樣環境的構建。 SpringBoot的這種方式, 可讓應用程序的打包內容貫穿開發、 測試以及線上部署, 而Maven不一樣Profile的方案爲每一個環境所構建的包,其內容本質上是 不一樣的。 可是, 若是 每一個參數都須要經過命令行來指定, 這顯然也不是 一個好的方案, 所 如下面咱們看看如何在SpringBoot中實現多環境的配置。

  • 多環境配置

    咱們在開發應用的時候, 一般同一套程序會被應用和安裝到幾個不一樣的環境中, 好比 開發 、 測試、 生產等。 其中 每一個環境的數據庫地址、 服務器端口等配置都不一樣, 若是在爲 不一樣環境打包時都要頻繁修改配置文件的話, 那必將是個很是煩瑣且容易發生錯誤的事。 對於多環境的配置,各類項目構建工具或是框架的基本思路是 一致的, 經過配置多份 不一樣環境的配置文件,再經過打包命令指定須要打包的內容以後進行區分打包,SpringBoot 也不 例外, 或者說實現起來更加簡單。

    在 Spring Boot 中, 多環境配置的文件名須要知足 application-{profile}. proper巨es的格式, 其中{profile}對應你的環境標識,

    以下所示。

    • applicaction-dev.properties: 開發環境。

    • applicaction-test.properties: 測試環境。

    • application-prod.properties: 生產環境。

    至於具體哪一個配置文件會被加載, 須要在 application.properties 文件中經過 spring.profiles.active 屬性來設置, 其 值 對應配置文件中的{profile}值。 如 spring.profiles.active= test就會加載 application-test.properties配置 文件內容。

  • 加載順序

    爲了可以更合理地重寫各屬性的值,SpringBoot使用了下面這種較爲特別的屬性加載 順序:

    1 在命令行中傳入的參數。

    2. SPRING APPLICATION JSON中的屬性。 SPRING_APPLICATION—JSON是以 JSON格式配置在系統環境變量中的內容。

    3. java:comp/env中的JNDI 屬性。

    4. Java的系統屬性, 能夠經過System.getProperties()得到的內容。

    5 操做系統的環境變量 。

    6 經過random.*配置的隨機屬性。

    7 位於當前應用 jar 包以外,針對不一樣{profile}環境的配置文件內容,例如 application-{profile}.properties或是YAML定義的配置文件。

    8 位於當前應用 jar 包以內 ,針對不一樣{profile}環境的配置文件內容,例如 application-{profile}.properties或是YAML定義的配置文件。

    9 位於當前應用jar包以外的application.properties和YAML配置內容。

    10位於當前應用jar包以內的application.properties和YAML配置內容。

    11在@Configura巨on註解修改的類中,經過@PropertySource註解定義的屬性。

    12應用默認屬性,使用SpringApplication.setDefaultProperties 定義的 內容。

    優先級按上面的順序由高到低,數字越小優先級越高。

相關文章
相關標籤/搜索