#### 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中