Spring Boot中的微服务入门

大家好!

在本文中,我们将演示使用Consul服务注册表,用于所有支架的Spring Boot,依赖项注入,用于组装的Maven以及Spring REST和Java RESTful Jersey / JaxRS API创建RESTful微服务的基本组件。

微服务的主要优势:

  • 微服务可帮助您放松代码

  • 微服务允许不同的团队使用独立技术来处理小型组件,从而提供更安全,更频繁的部署Spring Boot支持各种实现来创建REST API

  • 服务的发现和调用不依赖于服务平台

  • Swagger创建健壮的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 (# of shares) Object[] portfolio = restTemplate.getForObject(url, Object[].class); // Look up the share prices, and return a list of Strings, formatted as // ticker, shares, price, total List<String> collect = Arrays.stream(portfolio).map(position -> { String ticker = ((List<String>) position).get(0); int shares = ((List<Integer>) position).get(1); double price = getPrice(ticker); double total = shares * price; return String.format("%s %d %f %f", ticker, shares, price, total); }).collect(Collectors.toList()); return collect; } private double getPrice(String ticker) { return pricingMap.get(ticker); } @Override public void afterPropertiesSet() throws Exception { pricingMap.put("MMM",201.81); pricingMap.put("AXP",85.11); pricingMap.put("AAPL",161.04); pricingMap.put("BA",236.32); pricingMap.put("CAT",118.02); pricingMap.put("CVX",111.31); pricingMap.put("CSCO",31.7); pricingMap.put("KO",46.00); pricingMap.put("DIS",101.92); pricingMap.put("XOM",78.7); pricingMap.put("GE",24.9); pricingMap.put("GS",217.62); pricingMap.put("HD",155.82); pricingMap.put("IBM",144.29); pricingMap.put("INTC",35.66); pricingMap.put("JNJ",130.8); pricingMap.put("JPM",89.75); pricingMap.put("MCD",159.81); pricingMap.put("MRK",63.89); pricingMap.put("MSFT",73.65); pricingMap.put("NKE",52.78); pricingMap.put("PFE",33.92); pricingMap.put("PG",92.79); pricingMap.put("TRV",117.00); pricingMap.put("UTX",110.12); pricingMap.put("UNH",198.00); pricingMap.put("VZ",47.05); pricingMap.put("V",103.34); pricingMap.put("WMT", 80.05); } } 

要找到投资组合服务,我们需要有权使用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的工作:


原始文章

Source: https://habr.com/ru/post/zh-CN413567/


All Articles