spring boot IDEA 开发微服务
本文是参考:https://blog.csdn.net/u011001084/article/details/79040701 的基础上自己实际操作编写。
在我们开始创建微服务之前,需要安装Consul来准备我们的环境。
一、下载Consul服务注册中心
我们将使用Hashicorp Consul来实现服务发现,所以请前往 https://www.consul.io/downloads.html 下载Consul,有Windows版、Linux版和Mac版等。这个链接将会提供一个可执行程序,你需要将这个程序添加到你的path环境变量中。
下载的版本是:consul_1.2.2_windows_amd64
二、启动Consul
从一个脚本弹出框以dev模式启动Consul:在命令行cmd模式下执行:
consul agent -dev
为了验证它确实已经在运行,可以打开浏览器,访问consul UI http://localhost:8500 。如果一切正常,consul应该会报告它的运行状态良好。点击(在左边的)consul服务,会(在右边)提供更多信息。
如果这个地方有什么问题,请确保你已经将consul添加到执行路径中而且8500和8600端口是可用的。
三、创建SpringBoot应用程序
我们将使用集成在主流IDE中的 Spring Initializr ,来创建我们的SpringBoot应用程序的脚手架。下面的截屏使用的是IntelliJ IDEA。
选择File/New Project,来打开新建项目模板弹出框,然后选择Spring Initializr。
事实上,你可以无需IDE就安装脚手架。通过SpringBoot Initializr网站 https://start.spring.io完成一个在线web表格,会产出一个可以下载的包含你的空项目的zip文件。
点击“Next”按钮,填写所有的项目元数据。使用下面的配置:
点击“Next”按钮来选择依赖,然后在依赖搜索栏输入Jersey和Consul Discovery。添加那些依赖:
点击“Next“按钮来指定你的项目名字和存放位置。使用在web表单中配置的默认名字“portfolio”,指定你希望存放项目的地址,然后点击“Finish”来生成并打开项目:
(点击图片放大)
你可以使用生成的application.properties文件,但是SpringBoot也接受YAML文件格式,YAML格式看起来更直观,因此可以将这个文件重命名为application.yml。
我们将这个微服务命名为“portfolio-service”。我们可以指定一个端口或者使用端口0来让应用程序使用一个可用的端口。在我们的例子中,我们使用端口57116。如果你将这个服务作为一个Docker container部署,你可以将它映射到任何你选中的端口。让我们通过添加如下配置到applicatin.yml文件,来为应用程序命名并指定端口:
spring:
application:
name: portfolio-service
server:
port: 57116
为了让我们的服务可以被发现,需要为SpringBoot的application类添加注解。打开PortfolioApplication,在这个类声明的上方添加@EnableDiscoveryClient。
接受imports。这个class看起来会是这样
SpringApplication.run(PortfolioApplication.class, args);
}
}
(为了演示如何由各种独立的平台组合微服务,我们将为这个服务使用Jersey,然后为下一服务使用Spring REST)。
为了安装Jersey REST风格Web Service,我们需要指定一个ResourceConfig Configuration类。增加JerseyConfig类(本例中,我们会把它放在相同的package下作为我们的application类。)它应该看起来像这样,加上适当的package和imports:
package com.restms.demo.portfolio; import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration; import javax.ws.rs.ApplicationPath; @Configuration
@ApplicationPath("portfolios")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig()
{
register(PortfolioImpl.class);
}
}
需要注意的是,它继承了ResourceConfig来表明它是一个Jersey的配置类。@ApplicationPath("portfolios")属性指定了调用的上下文,意味着调用路径应该以“portfolios”开头。(如果你没有指定,上下文默认为“/”。)
PortfolioImpl类将服务两种请求,其中portfolios/customer/{customer-id}返回所有的portfolios,而portfolios/customer/{customer-id}/portfolio/{portfolio-id}返回一个portfolio。一个portfolio包括一组股票代码和相应的持有份额。
(本例中,有3个客户,id分别为0、1、2,而且每一个客户都有3个portfolio,id分别为0、1、2)。
你的IDE会让你创建PortfolioImpl,照着做就行了。本例中,将它添加在相同的package。输入如下代码并接受所有imports:
@Component
@Path("/portfolios")
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组件类,将它暴露为一个端点。正如我们从方法的注解中看到的那样,@Path注解声明这个类可以通过“portfolios”路径访问到,两个支持的api调用可以通过portfolios/customer/{customer-id}和portfolios/customer/{customer-id}/portfolio/{portfolio-id}。这些方法通过@GET注解表明它服务HTTP GET请求,这个方法声明返回一个数组并注解为返回Json,因此它会返回一个Json数组。注意如何在方法声明中使用@PathParam注解来从request中提取映射的参数。
(本例中,我们返回硬编码的值。当然,在实际应用中,实现的服务在这里会查询数据库或其它一些服务或者数据源。)
现在构建这个项目,然后运行。如果你是在使用IntelliJ,它会创建一个默认的可运行程序,你只需点击绿色的“运行”箭头。你还可以使用
mvn spring-boot:run
或者,你可以运行一次maven install,然后使用java -jar并指定target目录下生成的jar文件来运行这个应用程序:
java -jar target\portfolio-0.0.1-SNAPSHOT.jar
我们现在应该可以在Consul中查看这个服务,所以返回浏览器,打开 http://localhost:8500/ui/#/dc1/services (如果你已经打开了这个地址,刷新就可以了)。
我们看到我们的portfolio-service在那里了,但是显示为failing(失败)。那是因为Consol在等待从我们的服务发送一个“健康”的心跳请求。
为了生成心跳请求,我们在应用程序的pom文件中增加SpringBoot “Actuator”服务 的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在pom文件中,请注意,Jersey版本在consul-starter和jersey-starter中有一个版本冲突。为了解决这个冲突,将jersey starter移为第一个依赖。
你的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,然后portfolio-service会显示正常:
现在在portfolio-service下有两个通过的节点,其中一个是我们实现的portfolio服务,另外一个是心跳服务。
检查分配的端口。你可以在应用程序输出台看到:
INFO 19792 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 57116 (http)
你也可以直接在consul UI中查看这个端口。点击portfolio-service,然后选择“Service 'portfolio-service'”链接,会显示该服务的端口,本例中为57116。
调用 http://localhost:57116/portfolios/customer/1/portfolio/2 ,然后你会看到json数组 [["IBM",18343],["DIS",45673],["AAPL",23456]]。
我们第一个微服务就正式开放了!(2018-11-20日批注:这里可能出现第二个服务不正常,其他显示数据都是正确的,问题根本原因待排查)
定价服务
接下来,我们会创建定价服务,这一次使用Spring RestController而不是Jersey。
定价服务会接受客户端id和portfolio id作为参数,然后会使用一个RestTemplate查询portfolio服务来获取股票代码和份额,随后返回当前的价格。(这些都是假数据,所以不要用这些数据来做交易决策!)
使用如下信息创建一个新项目:
这次选择Web、Consul Discovery和Actuator依赖:
(点击图片放大)
将项目命名为“pricing”,在你选中的目录中生成项目。
这次我们会使用application.properties而不是application.yml。
在application.properties中设置名字和端口如下:
- spring.application.name=pricing
- server.port=57216
用@EnableDiscoveryClient给PricingApplication注解。这个类应该看起来像这样,加上package和imports。
@SpringBootApplication
@EnableDiscoveryClient
public class PricingApplication {
public static void main(String[] args) {
SpringApplication.run(PricingApplication.class, args);
}
}
接下来,我们会创建PricingEndpoint类。这个类有一点冗长,因为它演示了一些重要的功能,包括服务发现(查找portfolio service)和使用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); }
}
为了发现portfolio服务,我们需要访问一个DiscoveryClient。这可以通过Spring的@Autowired注解轻松实现
@Autowired
DiscoveryClient client;
然后在服务调用中,用这个DiscoveryClient实例来寻址我们的服务:
List<ServiceInstance> instances = client.getInstances("portfolio-service");
ServiceInstance instance = instances.stream().findFirst().orElseThrow(() -> new RuntimeException("not found"));
一旦寻址到这个服务,我们可以用它来执行我们的请求。这个请求是我们根据在portflo-service中创建的api调用组合而成的。
String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId);
最终,我们使用一个RestTemplate来执行我们的GET请求。
Object[] portfolio = restTemplate.getForObject(url, Object[].class);
需要注意的是,对于RestControllers(和SpringMVC RequestController一样),路径变量可以从@PathVariable注解中提取,而不像Jersey那样从@PathParam中提取。
这里使用一个Spring RestController来将定价服务发布出去。
文档
我们已经克服所有困难创建了我们的微服务,但是如果不让世界知道如何使用它们,它们就不会产生任何价值。
为此,我们使用了一个称作 Swagger 的工具。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想要为哪些类生成文档。我们需要引入一个称为SwaggerConfig的新类,它包含Swagger的各种配置。
@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 bean,告诉Swagger要暴露哪些API。在上面的例子中,我们告诉Swagger暴露所有以“/pricing”开头的路径。还可以选择指定class文件而不是路径来生成文档:
- .apis(RequestHandlerSelectors.basePackage("com.restms.demo"))
- .paths(PathSelectors.any())
重启定价微服务,然后在浏览器上调用 http://localhost:57216/swagger-ui.html 。
点击“List Operations”按钮来查看详细的服务操作。
点击“Expand Opeartions”来创建一个基于form的查询调用。提供一些参数,点击“Try it out!”,然后等待响应结果:
(点击图片放大)
你可以通过给方法增加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 boot IDEA 开发微服务的更多相关文章
- spring boot IDEA 开发微服务(二)
https://www.cnblogs.com/spaceud/p/8097934.html 参考以上微博,自己实际操作步骤如下: 1~开发准备 JDK:1.8 Spring Boot:2.1.0 S ...
- Spring boot 零配置开发微服务
2018年12月29日星期六 体验Spring boot 零配置开发微服务 1.为什么要用Spring boot? 1.1 简单方便.配置少.整合了大多数框架 1.2 适用于微服务搭建,搭建的微服务 ...
- .net 与 java 开发微服务对比
java+spring boot+maven对比.net 优势: 1. spring 自身带的ioc 比.net 更简单易用. 2. spring actuator的健康检测等运行时状态查看功能很赞. ...
- 社区活动分享PPT:使用微软开源技术开发微服务
上周六在成都中生代技术社区线下活动进行了一个名为"微软爱开源-使用微软开源技术开发微服务"的技术分享. 也算是给很多不熟悉微软开源技术的朋友普及一下微软最近几年在开源方面所做的努力 ...
- 使用.NET Core+Docker 开发微服务
.NET Core发布很久了,因为近几年主要使用java,所以还没使用过.NET Core,今天正好有一个c#写的demo,需要做成服务,不想再转成java来实现,考虑使用.NET CORE来尝下鲜, ...
- Springboot监控之二:Spring Boot Admin对Springboot服务进行监控
概述 Spring Boot 监控核心是 spring-boot-starter-actuator 依赖,增加依赖后, Spring Boot 会默认配置一些通用的监控,比如 jvm 监控.类加载.健 ...
- Spring Boot 集成 WebSocket 实现服务端推送消息到客户端
假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的 ...
- [学习笔记]尝试go-micro开发微服务<第一波>
平时项目都是基于c++,lua,node, 现在打算开始自学开发微服务; 也顺带磨砺下go和docker 前期准备 1. 有golang编程基础 本系列文章是基于有golang编程基础,有过实际开 ...
- Spring Boot开发RESTful接⼝服务及单元测试
Spring Boot开发RESTful接⼝服务及单元测试 常用注解解释说明: @Controller :修饰class,⽤来创建处理http请求的对象 @RestController :Spring ...
随机推荐
- 20165205 2017-2018-2 《Java程序设计》实验三 敏捷开发与XP实践
20165205 2017-2018-2 <Java程序设计>实验三 敏捷开发与XP实践 实验内容 检查点1: 安装alibaba 插件,解决代码中的规范问题 首先把搭档加入到自己的项目中 ...
- Java课程作业之动手动脑(三)
1.以下代码为何无法通过编译?哪儿出错了? 在Foo类中已经有了一个Foo的含参构造方法,所以在定义Foo类对象时不能使用new Foo()方法.在Foo类中再写一个无参构造方法,就能编译了. 如果类 ...
- iOS 第三方应用调用safari
一,直接调用safari打开url [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www. ...
- 初窥GPFS文件系统(转)
原文地址:http://blog.csdn.net/jznsmail/article/details/5502840?reload 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化 ...
- Flutter实例一--底部规则导航栏制作
先来看看制作效果: 前置知识--StatefulWidget StatefulWidget具有可变状态(state)的窗口组件(widget).使用时要根据变化状态,调整State值, 能够快速初始 ...
- hive随机采样
hive> select * from account limit 10;OKaccount.accountname account.accid account.platid ac ...
- HTML+CSS盒模型
一.Padding 1.padding用来调整内容在容器中的位置关系:padding的属性要添加到父元素上. padding值是额外加在元素原有的大小之上的,若想保证元素大小不变,需从元素宽或高上 ...
- day06-三元表达式
python中没有其他语言中的三元表达式,不过有类似的实现方法 其他语言中,例如java的三元表达式是这样int a = 1;String b = "";b = a > 1? ...
- mysqldump备份时保持数据一致性
对MySQL数据进行备份,常见的方式如以下三种,可能有很多人对备份时数据一致性并不清楚 1.直接拷贝整个数据目录下的所有文件到新的机器.优点是简单.快速,只需要拷贝:缺点也很明显,在整个备份过程中新机 ...
- curl 超时设置<转>
PHP cURL 的超时设置有两个 CURLOPT_CONNECTTIMEOUT 和 CURLOPT_TIMEOUT,他们的区别是: CURLOPT_CONNECTTIMEOUT 用来告诉 PHP 在 ...