大家好!
在本文中,我们将演示使用Consul服务注册表,用于所有支架的Spring Boot,依赖项注入,用于组装的Maven以及Spring REST和Java RESTful Jersey / JaxRS API创建RESTful微服务的基本组件。
微服务的主要优势:
- 微服务允许不同的团队使用独立技术来处理小型组件,从而提供更安全,更频繁的部署Spring Boot支持各种实现来创建REST API
如果您还没有使用微服务,那么您还没有成功地进入技术感知曲线上的早期追随者阶段,这可能是正确的开始时间。

在过去的二十年中,企业在SDLC流程中变得非常灵活,但通常,我们的应用程序仍然是整体的,其巨大的jar支持市场上的所有各种API和版本。 但是目前,人们希望有更多的精益,DevOps流程,并且该功能正变得“无服务器”。 重构为微服务可以减少代码和资源的纠缠,使程序集更小,发布更安全,API更稳定。
在本文中,我们将创建一个简单的股票市场投资组合管理应用程序,客户可以调用该应用程序来评估他们的股票投资组合(股票行情和价格)。 投资组合微服务将检索客户的投资组合,将其发送到定价微服务以应用最新价格,然后返回经过充分估值和小计的投资组合,并通过剩余呼叫进行演示。

在开始创建微服务之前,让我们通过设置Consul来准备环境。
下载领事
我们将使用Hashicorp Consul来发现服务,因此请访问
www.consul.io/downloads.html并下载适用于Windows,Linux,Mac等的Consul。 这将为您提供一个可执行文件,您需要将其添加到路径中。
启动领事
在命令提示符下,以开发人员模式启动Consul:
consul agent -dev
要验证它是否正在运行,请转到浏览器并访问Consul接口
http:// localhost:8500 。 如果一切顺利,领事必须报告他还健在。 通过单击领事服务(左侧),您将收到其他信息(右侧)。

如果目前有任何问题,请确保将Consul添加到执行路径,并且端口8500和8600可用。
编译一个SpringBoot应用程序
我们将使用集成在大多数IDE中的
Spring Initializr来构架我们的SpringBoot应用程序。 下面的屏幕截图使用IntelliJ IDEA。
选择“文件/新项目”以打开新项目模板,然后选择“ Spring Initializr”。

通常,您可以通过SpringBoot Initializr网页
start.spring.io填写在线表单来设置不带IDE的脚手架,该表单将为您的空项目创建一个zip文件,可供下载。
单击“下一步”并填写项目元数据。 使用以下配置:

单击“下一步”选择依赖项,然后在搜索依赖项中输入“ Jersey”和“ Consul Discovery”。 添加以下依赖项:

单击“下一步”以指示项目的名称及其位置。 保留默认名称“ portfolio”并指定项目的首选位置,然后单击“完成”以创建并打开项目:

我们可以使用生成的application.properties,但是SpringBoot也可以识别YAML格式,该格式更易于显示,因此我们将其重命名为application.yml。
我们称微服务为“投资组合服务”。 我们可以指定端口或使用端口0,以便应用程序使用可用端口。 在本例中,我们将使用57116。如果您将此服务作为Docker容器托管,则可以将其映射到您选择的任何端口。 为应用程序命名并通过将以下内容添加到application.yml中来指定我们的端口:
spring: application: name: portfolio-service server: port: 57116
为了使我们的服务可用,请在我们的SpringBoot应用程序类中添加注释。 打开PortfolioApplication应用程序,并在类声明上方添加@EnableDiscoveryClient。
确认进口。 该类应如下所示:
package com.restms.demo.portfolio; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; . . . @SpringBootApplication @EnableDiscoveryClient public class PortfolioApplication { public static void main(String[] args) { SpringApplication.run(PortfolioApplication.class, args); } }
(为了演示微服务如何由独立的平台组成,我们将使用Jersey来提供此服务,并使用Spring REST来提供)。
要在Jersey上配置RESTful Web服务,我们需要指定ResourceConfig配置类。 添加JerseyConfig类(为演示起见,我们将其保存在与应用程序类相同的包中)。 它应该看起来像这样,加上正确的包和导入:
@Configuration @ApplicationPath("portfolios") public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(PortfolioImpl.class); } }
请注意,它继承自ResourceConfig,以将其指定为Jersey配置类。 @ApplicationPath(“ portfolios”)属性定义了调用的上下文,这意味着调用必须以“ portfolioios”路径元素开始。 (如果省略,默认上下文为“ /”)。
PortfolioImpl类将服务于两个请求:投资组合/客户/ {customer-id}返回所有投资组合,投资组合/客户/ {customer-id} /投资组合/ {portfolio-id}返回一个投资组合。 投资组合由一组股票和该股票持有的股票数量组成。 (为演示起见,有三个标识为0、1和2的客户,每个客户都有三个标识为0、1和2的投资组合)。
您的IDE将要求您创建PortfolioImpl; 现在就做。 为了演示,将其添加到同一包中。 输入以下代码并确认所有导入:
@Component @Path("/") public class PortfolioImpl implements InitializingBean { private Object[][][][] clientPortfolios; @GET @Path("customer/{customer-id}") @Produces(MediaType.APPLICATION_JSON) // a portfolio consists of an array of arrays, each containing an array of // stock ticker and associated shares public Object[][][] getPortfolios(@PathParam("customer-id") int customerId) { return clientPortfolios[customerId]; } @GET @Path("customer/{customer-id}/portfolio/{portfolio-id}") @Produces(MediaType.APPLICATION_JSON) public Object[][] getPortfolio(@PathParam("customer-id") int customerId, @PathParam("portfolio-id") int portfolioId) { return getPortfolios(customerId)[portfolioId]; } @Override public void afterPropertiesSet() throws Exception { Object[][][][] clientPortfolios = { { // 3 customers, 3 portfolios each {new Object[]{"JPM", 10201}, new Object[]{"GE", 20400}, new Object[]{"UTX", 38892}}, {new Object[]{"KO", 12449}, new Object[]{"JPM", 23454}, new Object[]{"MRK", 45344}}, {new Object[]{"WMT", 39583}, new Object[]{"DIS", 95867}, new Object[]{"TRV", 384756}}, }, { {new Object[]{"GE", 38475}, new Object[]{"MCD", 12395}, new Object[]{"IBM", 91234}}, {new Object[]{"VZ", 22342}, new Object[]{"AXP", 385432}, new Object[]{"UTX", 23432}}, {new Object[]{"IBM", 18343}, new Object[]{"DIS", 45673}, new Object[]{"AAPL", 23456}}, }, { {new Object[]{"AXP", 34543}, new Object[]{"TRV", 55322}, new Object[]{"NKE", 45642}}, {new Object[]{"CVX", 44332}, new Object[]{"JPM", 12453}, new Object[]{"JNJ", 45433}}, {new Object[]{"MRK", 32346}, new Object[]{"UTX", 46532}, new Object[]{"TRV", 45663}}, } }; this.clientPortfolios = clientPortfolios; } }
Component注释将其指定为Spring组件的类,并将其公开为端点。 关于类声明的
路径注释声明该类是通过路径元素“ /”访问的,并且通过投资组合/客户/ {customer-id}和投资组合/ customer / {customer-id} /投资组合/ {portfolio- id},正如我们从方法的注释中看到的那样。 请注意,路径(“ /”)是默认路径,但我们留作参考。 通过@GETannotation将方法指定为HTTP GET。 我们的方法旨在返回一个数组,并带有注释以返回Json,因此它返回一个Json数组。 请注意,在方法签名中如何使用“
路径参数”批注从显示的查询中提取显示的参数。
(对于我们的演示,我们返回硬编码的值。当然,实际上,该实现将查询数据库或其他服务或数据源,而不是硬编码。)
现在创建一个项目并运行它。 如果使用IntelliJ,它将创建一个默认可执行文件,因此只需单击绿色的“运行”箭头。 您也可以使用
mvn spring-boot:run
或者,您可以执行maven安装并使用java -jar指向目标目录中生成的jar来运行应用程序:
java -jar target\portfolio-0.0.1-SNAPSHOT.jar
现在,我们应该在Consul中看到此服务,因此让我们回到浏览器,下载
http://本地主机:8500 / ui /#/ dc1 /服务 (如果已经存在,则进行更新)。

嗯,我们在那看到了我们的服务,但显示为失败。 这是因为领事期望我们的服务发出“健康”的心跳信号。
为了生成心跳信号,我们可以在应用程序的pom中添加对
Spring Actuator服务的依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
当我们处于pom状态时,请注意Consul启动程序和Jersey启动程序之间与Jersey发生版本冲突。 为了解决这个问题,将泽西岛首发指定为第一个瘾君子。
您的pom现在应包含以下依赖项:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </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> </dependencies>
通过重新启动Consul,投资组合服务将显示出令人满意的状态:

现在,在组合服务中有两个传输节点:一个是我们对组合服务的实现,另一个是心跳。
让我们检查已分配的端口。 您可以在应用程序的输出中看到:
INFO 19792 --- [ main] sbcetTomcatEmbeddedServletContainer : Tomcat started on port(s): 57116 (http)
您还可以直接在Consul用户界面中查看端口。 单击“客户服务”,然后选择“服务'客户服务'检查链接”链接,该链接显示服务端口,在这种情况下为57116。

请求
HTTP:// //本地主机:57116 /资产/客户/ 1 /资产/ 2 ,您将看到json数组[[“ IBM”,18343],[“ DIS”,45673],[“ AAPL”,23456]]
我们的第一个微服务开始营业!
定价服务
接下来,我们将使用Spring RestController而不是Jersey来创建定价服务。
定价服务将接受客户标识符和投资组合标识符作为参数,并将使用RestTemplate请求投资组合服务,接收报价器和股票,以及返回当前价格。 (我不需要告诉您这些值是假新闻,因此不要用它们来做出交易决策!)
使用以下信息创建一个新项目:

这次,选择Web,Consul Discovery和Actuator依赖项:

将项目的默认名称保留为“ pricing”,然后在您选择的目录中创建一个项目。
这次,我们将使用application.properties而不是application.yml。
在application.properties中将名称和端口设置为:
spring.application.name=pricing server.port=57216
用@EnableDiscoveryClient注释PricingApplication。 该类应该看起来像这样,加上包和导入。
@SpringBootApplication @EnableDiscoveryClient public class PricingApplication { public static void main(String[] args) { SpringApplication.run(PricingApplication.class, args); } }
然后,我们将创建PricingEndpoint类。 在这里,我将给出一个更详细的示例,因为它演示了几个重要功能,包括服务发现(搜索组合服务)以及使用RestTemplate进行查询:
@RestController @RequestMapping("/pricing") public class PricingEndpoint implements InitializingBean { @Autowired DiscoveryClient client; Map<String, Double> pricingMap = new HashMap<>(); RestTemplate restTemplate = new RestTemplate(); @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio( @PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId) { List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream() .findFirst() .orElseThrow(() -> new RuntimeException("not found")); String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId); // query for the portfolios, returned as an array of List // of size 2, containing a ticker and a position (
要找到投资组合服务,我们需要有权使用DiscoveryClient。 使用Spring的@Autowired批注很容易获得。
@Autowired DiscoveryClient client;
然后,此DiscoveryClient实例用于在调用中搜索服务:
List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream().findFirst().orElseThrow(() -> new RuntimeException("not found"));
找到该服务后,我们可以使用它来满足我们的请求,该请求根据我们在投资组合服务中创建的api调用组成。
String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId);
最后,我们使用RestTemplate执行GET请求。
Object[] portfolio = restTemplate.getForObject(url, Object[].class);
请注意,对于Rest控制器(以及Spring MVC请求控制器),使用
Path Variable注释检索
路径变量,这与我们看到的Jersey所使用的
Path Param不同。
这样就结束了我们与Spring RestController的定价。
该文件
为了创建我们的微服务,我们解决了所有这些问题,但是如果我们不向世界提供有关如何使用它们的知识,它们将不会带来足够的收益。
为此,我们使用了方便易用的
Swagger工具,该工具不仅记录了我们的API调用,而且还提供了一个方便的Web客户端来调用它们。
首先,让我们在pom中指定Swagger:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
然后,我们需要告诉Swagger我们要记录哪个类。 让我们介绍包含Swagger规范的新SwaggerConfig类。
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.regex("/pricing.*")) .build(); } }
让我们看看这个类做什么。 首先,我们将其指定为带有@ EnableSwagger2注释的Swagger配置。
然后,我们创建了一个Docket组件,该组件告诉Swagger应该显示哪些API。 在上面的示例中,我们告诉Swagger演示了以“ /定价”开头的所有路径。 另一种选择是为文档而不是路径指定类:
.apis(RequestHandlerSelectors.basePackage("com.restms.demo")) .paths(PathSelectors.any())
重新启动价格微服务,然后从浏览器中调用
http://本地主机:57216 / swagger-ui.html
单击列出操作以查看详细的服务操作。
单击“扩展操作”以基于表单创建请求。 设置一些参数,单击“试用!” 然后等待答案:

您可以通过在方法中添加Swagger注释来添加更多颜色。
例如,使用@ApiOperation批注装饰现有PricingImpl.getPricedPortfolio方法,如下所示:
@ApiOperation(value = "Retrieves a fully priced portfolio", notes = "Retrieves fully priced portfolio given customer id and portfolio id") @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio(@PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId)
重新加载并更新swagger-ui以查看新的更新文档:

这不是您使用Swagger所能做的全部,因此请查阅文档。
我们课程
“ Spring框架的开发者”课程的讲师
Yuri Dvorzhetsky将向您详细介绍Spring Boot的工作:
原始文章