#### 一、Spring Boot重要特性
- 独立的Spring应用程序,嵌入式Tomcat/Jetty容器,无需部署War包
- 尽可能使用自动化配置,Spring Auto Configuration
- 提供一批'starter' POM 简化Maven及Gradle配置
- 提供一系列可以用到生产环境的应用度量、健康检查等特性(Actuator)
二、Spring Boot 快速上手
访问http://start.spring.io/,使用SPRING INITIALIZR选择需要的模块快速初始化
![spring start](images/start.spring.io.jpg "start.spring.io")
选择Web模块,快速创建项目
```xml
<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>
``` 生成的项目结构
```
myproject
+- pom.xml
+- src
+- main
+- java
| +- com.example.myproject
| +- Application.Java
|
+- resources
| +- application.properties
|
+- test
+- java
| +- com.example.myproject
| +- ApplicationTests.java
```
三、项目分层结构及模块划分方式
![项目分层](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
```
四、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
@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
```
- **为便于管理和排查Spring Boot App,每个APP都配置spring boot admin**
引入Jar
```xml
<!--如下插件生成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>
```
配置注册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
<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>
```
日志文件路径指定,对于主程序日志,请通过配置文件或者Jvm参数指定logging.path和logging.file
五、好房Spring Boot Starter
基于各种开发场景,好房framework模块开发了若干开发组件,例如历史操作记录,批处理框架,校验工具等。需要使用请先引入如下pom
```xml
```
1. pinganfang-common-module,通用工具模块
该模块主要封装了各种常用UTIL类库,Exception定义,例如StringUtils,ThreadContext,BaseException等。
```mvn
```
2. pinganfang-rpc-starter, RPC封装
该模块封装了好房各业务模块通信的RPC,包括服务端开放RPC服务,以及RPC客户端调用。
```mvn
```
要启用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
```
**声明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
```
3. pinganfang-validator-starter, 校验器封装
该模块封装了jsr303校验器,扩展了现有的校验器,既支持hibernate validator,同时校验器可以配置fail over/fail fast等高级特性
```xml
```
若要使用,请先在Application.java上或者配置类配置@EnableHaofangValidator
在需要校验的controller方法上配置如下注解
```java
@Valid(CustomerValidator.class)
public List
```
同时写好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
```
BaseDomain封装了createTime,createBy,updateTime,updateBy,推荐在定义domain时继承
对于Repository,可以继承BaseRepository,提供了众多新的数据库操作方法,例如返回Map, listMap, Java 8支持等
提供了PageQueryDTO等基础类
5. pinganfang-history-starter, 历史操作记录封装
history封装了历史操作记录handler bean注册,相应切面等逻辑,但是按照何种格式记录日志,则由具体业务而定,在HistoryOpHandler中实现即可。
```xml
```
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
```
如果使用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等
```
```
目前提供的基础类有
- ContextFilter,提供请求ID生成并写入MDC,可在logback中打印
六、项目构建及部署
打包方式
``` 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中