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中
相关文章
相关标签/搜索