Spring Boot 實踐

#### 1、Spring Boot重要特性

1. 獨立的Spring應用程序,嵌入式Tomcat/Jetty容器,無需部署War包
2. 儘量使用自動化配置,Spring Auto Configuration
3. 提供一批'starter' POM 簡化Maven及Gradle配置
4. 提供一系列能夠用到生產環境的應用度量、健康檢查等特性(Actuator)

#### 2、Spring Boot 快速上手

訪問http://start.spring.io/,使用SPRING INITIALIZR選擇須要的模塊快速初始化

![spring start](images/start.spring.io.jpg "start.spring.io")

選擇Web模塊,快速建立項目

```xml

<?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.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>x
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

```
生成的項目結構

```

myproject
 +- pom.xml
 +- src
    +- main
       +- java
       |   +- com.example.myproject
       |      +- Application.Java
       |   
       +- resources
       |   +- application.properties
       |
    +- test
       +- java
       |   +- com.example.myproject
       |      +- ApplicationTests.java


```


#### 3、項目分層結構及模塊劃分方式

![項目分層](images/springbootlayer.jpg "項目分層")

##### 1. 名稱規範

- **包名規範**

```
com.pingan.haofang.${產品}.${模塊}.${層次}.${className}.java
```

- **特殊類名規範** 

Spring配置類: configuration/*Configuration,例如WebConfiguration/DatasourceConfiguration

Properties類: properties/*Properties,例如FtpProperties

dao類:dao/*Dao或者dao/*Repository

service類:service/*Service

- ** 分層命名規範 ** 

domain: 數據庫PO

dao/repository:數據庫訪問層

service:業務邏輯層

dto:數據傳輸對象

constants:枚舉常量

controller:web控制器

form:web請求對象

vo:web響應對象

validator:校驗器

batch:批處理Job類


##### 2. 項目劃分

對於每一個項目能夠按照以下方式進行劃分爲4個模塊,獨立爲4個maven模塊

- **Parent**

負責依賴管理,公用的maven依賴

- **Service (lib)**

包含整個項目的業務邏輯/數據訪問代碼


```

com.pingan.haofang
     +- myproject
         +- customer
            +- domain
            |   +- Customer.java
            |   
            +- dao
            |  +- CustomerDao.java
            |
            +- service
            |   +- CustomerService.java
            |   +- impl
            |       +- CustomerServiceImpl.java
            |
            +- dto
            |   +- CustomerDto.java
             |
             +- constants
             |   +- CustomerConstants.java
             |   +- CustomerStatus.java

```

- **Exportapi (lib 或 app)** 

項目對外API提供,RPC等。依賴Service,可單獨部署也可打進web包進行部署。

- **Web (app)**

項目web接口暴露代碼,包括先後端接口暴露,文檔,攔截器。依賴service、exportapi

```
com.pingan.haofang
     +- myproject
         +- WebApplication.java
         +- customer
            +- controller
            |   +- CustomerController.java
            |   
            +- form
            |  +- CustomerForm.java
            +- vo
            |   +- CustomerVo.java
            |
            +- validator
            |   +- CustomerValidator.java

```

- **Batch (app)** 

項目批處理任務,常駐進程任務或者定時任務,依賴service

```
com.pingan.haofang
     +- myproject
         +- BatchApplication.java
         +- customer
            +- batch
            |   +- CustomerExportTask.java
            |   +- CustomerExportRunner.java

```


#### 4、Spring經常使用模塊應用

##### 1. Spring MVC

- **接口定義規範** 

```
Http Method

GET:讀取數據,不容許有數據的修改等操做
   列表URL設計:GET:/web/custmer
   單條數據URL設計:GET:/web/customer/{custmerId}
POST:新建數據
   POST:/web/custmer
PUT:修改數據
   PUT:/web/custmer/{custormId}
   PUT:/web/custmer/status/{custormId}
DELTE:刪除數據
   DELETE:/web/custmer/{custormId}

請求體與響應體

請求與響應除QueryString及PathVariable外,其他數據交互應以Json格式進行交互

Controller配置爲@RestController, 先後端ContentType:application/json; charset=UTF8

```

- **Swagger應用** 

全部controller都用swagger annotation進行註解,springfox嵌入以提供接口文檔及try out調試功能

- **TraceFilter** 

添加TraceFilter,對於每一個請求隨機生成RequestID並放入MDC進行日誌打印,便於排查

- **異常消息定義及ExceptionHandler** 

自定義完善的異常處理器,按照和前端定義好的接口產生異常消息體。經過HTTP CODE定義各種狀態

```
200 成功
409 校驗失敗,例如非空、長度、格式等
400 客戶端請求格式錯誤,例如不是合法的Json
401 未受權即未登陸
403 無權限訪問
404 不存在,未找到響應對象
500 服務器內部錯誤

```

異常消息體定義

```json
{
  "errorCode": 1, // 保留錯誤碼字段
  "message": "全局異常消息",
  "fieldErrors": [
    {
      "name": "名稱不容許重複"
    },
    {
      "desc": "描述過短"
    }
  ]
}

```
##### 2. JPA用法

- **數據源及數據庫鏈接池配置**

建議使用Alibaba Druid鏈接池配置,見
com.pingan.haofang.myproject.common.configuration.DataSourceConfiguration,

同時建議配置DruidStat,便可經過web管理數據源監測數據,見

```java

    @Bean
    public ServletRegistrationBean druidServlet(DruidStatProperties druidStatProperties) {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", druidStatProperties.getUsername());
        reg.addInitParameter("loginPassword", druidStatProperties.getPassword());
        return reg;
    }

```


- **繼承Repository接口**
  
  查詢可使用QueryMethod/@Query/Example/Specification四種方式,前面三種適合作簡單查詢時用,Specification(即CriteriaQuery)建議在複雜查詢,例如分業列表有較多篩選條件時使用

  demo見com.pingan.haofang.myproject.customer.service.impl#CustomerServiceImpl#queryList,CustomerSpecs.pageListSpec(dto)

- **合理使用實體關聯**

- **使用Auditing**
   
  Auditing提供了以下四個annotation能夠方便設置建立人、最後修改人、建立時間、最後修改時間。須要和@EnableJpaAuditing,AuditingEntityListener配合使用
      
  @CreatedBy,@LastModifiedBy,@CreatedDate,@LastModifiedDate


```java

@Bean
public AuditorAware<Long> auditorProvider() {
    return () -> Optional.ofNullable(SearchThreadContext.getSessionVisitor())
            .map(Visitor::getUserId)
            .orElse(0L);
}


@MappedSuperclass
public abstract class BaseDomain {

    @Column(name = "create_time")
    @CreatedDate
    private Date createTime;

    @Column(name = "create_by")
    @CreatedBy
    private Long createBy;

    @Column(name = "update_time")
    @LastModifiedDate
    private Date updateTime;

    @Column(name = "update_by")
    @LastModifiedBy
    private Long updateBy;

```

##### 3. 使用Spring Boot Actuator/Spring Boot Admin

Spring Boot actuator能夠幫助咱們提供方便的健康頁面、jmx等監控和排查功能。故指望全部項目的Actuator知足以下規範。

- **Spring Boot Actuator的Context-path爲/actuator**

context-path配置爲統一前綴,方便將來配置內部管理域名時proxy的統一配置。

```properties

endpoints.sensitive=false
endpoints.enabled=true
endpoints.actuator.enabled=true
endpoints.shutdown.enabled=true
endpoints.shutdown.sensitive=false

management.security.enabled=false
management.context-path=/actuator
management.address=127.0.0.1

```

```xml

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

```

- **爲便於管理和排查Spring Boot App,每一個APP都配置spring boot admin**

引入Jar

```xml

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>1.5.0</version>
</dependency>
  
<!--以下plugin也建議配置,主要用於生成buildinfo,方便/actuator/info返回項目的基本信息-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <!--生成build-info文件-->
                        <goal>build-info</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <!--以下插件生成git信息,包括構建的git分支,最後提交人及註釋,版本號-->
        <plugin>
            <groupId>pl.project13.maven</groupId>
            <artifactId>git-commit-id-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>revision</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!--日期格式;默認值:dd.MM.yyyy '@' HH:mm:ss z;-->
                <dateFormat>yyyyMMddHHmmss</dateFormat>
                <!--,構建過程當中,是否打印詳細信息;默認值:false;-->
                <verbose>true</verbose>
                <!-- ".git"文件路徑;默認值:${project.basedir}/.git; -->
                <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
                <!--若項目打包類型爲pom,是否取消構建;默認值:true;-->
                <skipPoms>false</skipPoms>
                <!--是否生成"git.properties"文件;默認值:false;-->
                <generateGitPropertiesFile>true</generateGitPropertiesFile>
                <!--指定"git.properties"文件的存放路徑(相對於${project.basedir}的一個路徑);-->
                <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties
                </generateGitPropertiesFilename>
                <!--".git"文件夾未找到時,構建是否失敗;若設置true,則構建失敗;若設置false,則跳過執行該目標;默認值:true;-->
                <failOnNoGitDirectory>true</failOnNoGitDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

```

配置註冊admin server地址

```properties

#spring admin
##目前st/ci將註冊到ci環境的admin,ga將註冊到ga環境的admin
spring.boot.admin.url=http://actuator.a.pa.com/
##若是dev及其餘開發機可能網絡不通,請使用下面配置
#spring.boot.admin.url=http://10.59.72.187:9596
  
##下面配置是spring-boot的name,配置後才能在admin有漂亮的名稱,請自行取名
spring.application.name=${applicationName}

##默認若是hosts中配置了當前IP的hostname可能沒法訪問,因此能夠加上以下設置
spring.boot.admin.client.prefer-ip=true

management.info.git.mode=full

```

- **訪問SpringAdminServer進行管理**

```

st/ci/開發:
http://actuator.anhouse.com.cn/
用戶名:admin
密碼:admin-st
 
anhouse
http://actuator.an2.ipo.com/
 
ga:
http://actuator.proxy.ipo.com/

```

![spring boot admin ui](images/springbootadminui.png "spring boot admin ui")
![spring boot admin ui](images/springbootadminui-logging.png "spring boot admin ui")


##### 4. 校驗

jsr303

functional validation

fail fast/ fail over

##### 5. 單測

- **內存數據庫H2**

請使用內存數據庫模擬數據庫,初始化腳本請添加SchemaSQL,初始化數據可以使用對應的data.sql,或者使用testEntityManager

詳見myproject-service/src/test

- **Mock/MockBean**

須要配置MockitoTestExecutionListener,見BaseTest

當單測測試邏輯類有依賴其餘的邏輯類,這個時候若是隻想測試本身的邏輯,可使用@MockBean,mock掉依賴的邏輯類,這裏mock的對象若是沒有使用mockito指定
相應邏輯,則都會返回null

見com.pingan.haofang.myproject.customer.service.impl.CustomerServiceImplTest#isCustomerBuyProduct

- **Spy/SpyBean**

須要配置MockitoTestExecutionListener,見BaseTest

與Mock和MockBean不一樣之處在於,mock的對象若是沒有對方法使用mockito指定相應邏輯,則會執行真實代碼,可是@Spy中若是先when,再ThenReturn則仍是會先執行
一次mock方法的真實邏輯,可能會由於不可預知的錯誤而失敗。

```java

// when去設置模擬返回值時,裏面的方法object.callMethod()會先執行一次
when(object.callMethod()).thenReturn("result");

// 使用doReturn則不會產生上面的問題
doReturn("result").when(object).callMethod();

```

更多請參照com.pingan.haofang.myproject.customer.service.impl.CustomerServiceImplTest#getOne

##### 6. Spring Session

@EnableRedisHttpSession

爲避免redis共享時出現問題,建議設置redisNamespace區分key, 不然默認都是spring:session,很差區分, 同時按照須要設置session過時時間

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 43200, redisNamespace = "search_cloud")

```
配置redisNamespace後的rediskey
"spring:session:search_cloud:sessions:5259b7fb-c882-4f57-8d32-d967148b1338"

未配置namespace後的rediskey
"spring:session:sessions:expires:c1698de0-618b-455d-a63b-b4809decb1fd"

```

##### 7. ThreadContext

線程上下文,請擴展使用com.pingan.haofang.module.common.ThreadContext

##### 8. 日誌規範

日誌請使用Spring-Boot默認提供的模板,springboot默認提供的模板已經預約義了變量,能夠進行賦值擴展,主要分console-pattern和file-pattern,
二者格式相同,console-pattern還包含顏色美化,便於閱讀

若是無額外appender配置,能夠直接在application.properties中配置,SpringBoot提供的日誌級別自定義
可按照以下配置示例擴展

```
logging.pattern.level=%X{REQUEST_ID} %p

```

但若是日誌比較複雜,可以使用SpringBoot提供的logback base.xml,defaults.xml進行組合配置,spring boot base提供的fileAppender不支持按時間滾動,
這塊能夠本身寫

```xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <jmxConfigurator/>
    <property name="LOG_FILE" value="${LOG_PATH}/myproject.log"/>

    <property name="ADDITIONAL" value="%X{REQUEST_ID} %X{TRACE_ID}"/>
    <property name="LOG_LEVEL_PATTERN" value="${ADDITIONAL} %5p"/>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}</fileNamePattern>
        </rollingPolicy>
    </appender>

    <!--customer專用appender-->
    <appender name="customerAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/customer.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/customer-%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>${ADDITIONAL} %d{HH:mm:ss.SSS} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.pingan.haofang.myproject.customer.controller.CustomerController" level="INFO" additivity="false">
        <appender-ref ref="customerAppender"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

</configuration>

```

日誌文件路徑指定,對於主程序日誌,請經過配置文件或者Jvm參數指定logging.path和logging.file

#### 5、好房Spring Boot Starter

基於各類開發場景,好房framework模塊開發了若干開發組件,例如歷史操做記錄,批處理框架,校驗工具等。須要使用請先引入以下pom


```xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.pingan.haofang.framework</groupId>
            <artifactId>pinganfang-framework-dependencies</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

```


##### 1. pinganfang-common-module,通用工具模塊

該模塊主要封裝了各類經常使用UTIL類庫,Exception定義,例如StringUtils,ThreadContext,BaseException等。

```mvn

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-common-module</artifactId>
</dependency>

```

##### 2. pinganfang-rpc-starter, RPC封裝

該模塊封裝了好房各業務模塊通訊的RPC,包括服務端開放RPC服務,以及RPC客戶端調用。

```mvn

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-rpc-starter</artifactId>
</dependency>

```

要啓用RPC,請在Application.java,或者配置類上面添加@EnableHaofangRPC,並定義以下Filter

```java

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public FilterRegistrationBean rpcFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new RPCFilter());
    filterRegistrationBean.addUrlPatterns("/rpc");
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;
}

```


**聲明RPC服務**,詳見com.pingan.haofang.myproject.demo.rpc.DemoRPCExportService

```java

@RPCExporter(value = "findByIds", defaultErrorMessage = "rpc error", successCode = "0")
public List<DemoDTO> findByIds(List<Integer> ids, int type) {
    List<DemoDTO> list = new ArrayList<DemoDTO>();
    list.add(new DemoDTO(101, "demo1", Arrays.asList(1, 2, 3, 4, 5)));
    list.add(new DemoDTO(102, "demo2", Arrays.asList(1, 2, 3, 4, 5)));
    list.add(new DemoDTO(103, "demo3", Arrays.asList(1, 2, 3, 4, 5)));
    return list;
}

```


**聲明RPC客戶端**,詳見com.pingan.haofang.myproject.demo.rpc.DemoRPCService

```java

@RPCClient(value = "User\\User.getMobileByUserIDs", config = "rpc.user", successCode = "0000")
public Map<Integer, String> getUserInfo(List<Integer> ids);

```




##### 3. pinganfang-validator-starter, 校驗器封裝

該模塊封裝了jsr303校驗器,擴展了現有的校驗器,既支持hibernate validator,同時校驗器能夠配置fail over/fail fast等高級特性

```xml

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-validator-starter</artifactId>
</dependency>

```

若要使用,請先在Application.java上或者配置類配置@EnableHaofangValidator

在須要校驗的controller方法上配置以下註解

```java

@Valid(CustomerValidator.class)
public List<CustomerVO> queryList(CustomerQueryForm form) {

```

同時寫好validator

```java

@Component
public class CustomerValidator {

    @ValidHandler
    public void queryList(ValidationResult result, CustomerQueryForm form) {

        /**
         * countryId爲40的時候cityId不能>20
         */
        if (form.getCountryId() == 40 && form.getCityId() > 20) {
            result.addError(ValidationError.of("cityId", "cityId > 20"));
        }
    }
}

```

demo見com.pingan.haofang.myproject.customer.controller.CustomerController#queryList

##### 4. pinganfang-jpa-starter, JPA封裝

該模塊封裝了BaseDomain, BaseRepository,對Spring Data Jpa 進行了進一步擴展

```xml

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-validator-starter</artifactId>
</dependency>

```

- BaseDomain封裝了createTime,createBy,updateTime,updateBy,推薦在定義domain時繼承

- 對於Repository,能夠繼承BaseRepository,提供了衆多新的數據庫操做方法,例如返回Map, listMap, Java 8支持等

- 提供了PageQueryDTO等基礎類

##### 5. pinganfang-history-starter, 歷史操做記錄封裝

history封裝了歷史操做記錄handler bean註冊,相應切面等邏輯,可是按照何種格式記錄日誌,則由具體業務而定,在HistoryOpHandler中實現便可。

```xml

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-history-starter</artifactId>
</dependency>


```
 - 1.在項目中配置註解@EnableHaofangHistory,啓用history功能

 - 2.定義HistoryOpHandler
 
 - 3.在須要記錄日誌的地方配置註解HistoryOpLog,這裏註解配置參數以下,須要注意

```

value: 對應的處理器方法名稱
beanName: 處理器在spring中的BeanName
errMessage: 異常消息
ignoreError: 若是爲true,則寫入日誌時失敗會拋出異常,不然會忽略繼續執行主體流程
force: 若是爲true,則不論請求是否成功均會記錄日誌,不然只會在正常返回時才記錄日誌

```

示例見
com.pingan.haofang.myproject.customer.service.impl.CustomerLogService,
com.pingan.haofang.myproject.customer.service.impl.CustomerServiceImpl



##### 6. pinganfang-batch-starter, 批處理封裝

batch封裝了批處理定義基礎類庫,支持一次性任務,常駐任務類型兩種

```xml

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-batch-starter</artifactId>
</dependency>

```

若是使用batch,請配置@EnableHaofangBatch

batch 建議兩種用法

- 一次性任務

見com.pingan.haofang.myproject.demo.batch.DemoCronTaskRunner

- 常駐進程任務

見com.pingan.haofang.myproject.demo.batch.DemoScheduleTaskRunner

如上任務啓動類均爲BatchMain,如要啓動某做業,啓動JVM參數爲-DrunnerName=${batchName}


##### 7. pinganfang-web-common, web基礎工具類庫

該模塊主要提供web程序要的通用基礎類庫Utils等

```

<dependency>
    <groupId>com.pingan.haofang.framework</groupId>
    <artifactId>pinganfang-web-common</artifactId>
</dependency>

```

目前提供的基礎類有

- ContextFilter,提供請求ID生成並寫入MDC,可在logback中打印


#### 6、項目構建及部署

##### 打包方式

```
sh build.sh ${mvn_profile}

```

打包爲

```
output/myproject-web.tar.gz
output/myproject-batch.tar.gz
```

包結構爲

```

tar.gz
    +- bin
    |  app_control.bash
   +- conf
   |  logback.xml
   |  application.properties
   +- myproject-web.jar


```

##### App啓動方式

```
bash app_control.bash start|shutdown|kill|force|restart|status

start     啓動app
shutdown   關閉app
kill      殺掉app進程
force     強制殺掉app進程
restart    重啓app
status        查看app狀態

```


##### war 包使用方式

見項目myproject-web-war,目前須要將配置文件打進war包,相關配置都配置在application.properties中
相關文章
相關標籤/搜索