在展开 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调试经过。
工程结构解析
• 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 定义的 内容。
优先级按上面的顺序由高到低,数字越小优先级越高。