订阅微服务
上一节我们在Eureka上注册了一个名为APP-CARGO的微服务,现在我们就要新建一个微服务,经由Eureka访问到APP-CARGO微服务的接口。
Talk is cheap,show me the code!上代码。
新建模块
同样我们现在需要新建一个CargoAccesser模块用于访问这个Cargo模块提供的接口,新建模块的方法可以参考上一节的内容,新建一个项目也可以。
新建完基于Maven的CargoAccesser模块后,打开pom.xml,写入如下依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 资源文件拷贝插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
在java目录下新建runner、entity、controller、service四个包,首先我们先准备好实体类,在entity包下新建Cargo、CargoList、CargoListDetail三个实体类。
(其中Cargo实体在正式开发中是不必要的,微服务之间应该通过JSON来做数据传输,两个不同的微服务开发团队之间约定JSON中包含的内容即可)
。代码如下:
//Cargo.java
package entity;
public class Cargo {
private Long id;
private String name;
private String price;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public Cargo() {
}
public Cargo(Long id, String name, String price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Cargo{"
+ "id:"
+ id
+ ",name:'"
+ name + '\''
+ ",price:'"
+ price + '\''
+ "}";
}
}
//CargoList.java
package entity;
import java.util.List;
public class CargoList {
private String id;//货物清单ID
private List<CargoListDetail> detailList;
private String comment;//清单备注
public CargoList() {
}
public CargoList(String id, List<CargoListDetail> detailList, String comment) {
this.id = id;
this.detailList = detailList;
this.comment = comment;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<CargoListDetail> getDetailList() {
return detailList;
}
public void setDetailList(List<CargoListDetail> detailList) {
this.detailList = detailList;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
@Override
public String toString() {
return "CargoList [id=" + id + ",comment='"+comment+"'"
+ "]";
}
}
//CargoListDetail.java
package entity;
public class CargoListDetail {
private String id;
private Cargo cargo;
public CargoListDetail() {
}
public CargoListDetail(String id, Cargo cargo) {
this.id = id;
this.cargo = cargo;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Cargo getCargo() {
return cargo;
}
public void setCargo(Cargo cargo) {
this.cargo = cargo;
}
@Override
public String toString() {
return "CargoListDetail [id=" + id + ", cargo=" + cargo + "]";
}
}
准备好了实体类,接下来我们需要写一个调用先前已经注册的微服务的Service,在service包下新建CargoService类,代码如下:
package service;
import entity.Cargo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class CargoService {
@Autowired
private RestTemplate restTemplate;//SpringBoot对Rest请求做了封装,可以通过模板快速配置请求信息
public Cargo queryCargoById(Long id) {
//需要在RestTemplate对象上加上@LoadBalanced注解
String cargoUrl = "http://app-cargo/cargo/{id}";
Cargo cargo = this.restTemplate.getForObject(cargoUrl , Cargo.class , id);
System.out.println("CargoService is invoked,result:"+cargo);
return cargo;
}
}
接下来我们需要新建一个货物清单Service,来调用上面的CargoServce获取货物信息,来构成货物清单。代码如下:
package service;
import entity.Cargo;
import entity.CargoList;
import entity.CargoListDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class CargoListService {
private static final Map<String, CargoList> CARGO_LIST_DATA = new HashMap<String, CargoList>();
static {
// 模拟数据库,构造测试数据
CargoList cargoList = new CargoList();
cargoList.setId("123");
List<CargoListDetail> listDetails = new ArrayList<CargoListDetail>();
Cargo cargo = new Cargo();// 此处并没有货物的数据,仅有ID,需要通过微服务获取
cargo.setId(1L);
listDetails.add(new CargoListDetail(cargoList.getId(), cargo));
cargo = new Cargo(); // 构造第二个货物数据
cargo.setId(2L);
listDetails.add(new CargoListDetail(cargoList.getId(), cargo));
cargoList.setDetailList(listDetails);
cargoList.setComment("我是清单备注");
CARGO_LIST_DATA.put(cargoList.getId(), cargoList);
}
@Autowired
CargoService cargoService;
public CargoList queryCargoListById(String id) {
CargoList cargoList = CARGO_LIST_DATA.get(id);
if (null == cargoList) {
return null;
}
List<CargoListDetail> listDetails = cargoList.getDetailList();
for (CargoListDetail listDetail : listDetails) {
// 调用微服务查询货物信息
Cargo cargo = this.cargoService.queryCargoById(listDetail.getCargo()
.getId());
if (null == cargo) {
continue;
}
listDetail.setCargo(cargo);
}
return cargoList;
}
}
最后我们需要编写控制器、启动类和配置yml文件,启动模块,订阅者微服务就完成了。
控制器代码如下:
package controller;
import entity.CargoList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import service.CargoListService;
@RestController
public class CargoAccesserController {
@Autowired
private CargoListService cargoListService;
@GetMapping(value = "cargoList/{id}")
public CargoList queryOrderById(@PathVariable("id") String id) {
return this.cargoListService.queryCargoListById(id);
}
}
启动类代码如下:
package runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@ComponentScan({"controller","entity","service"})
@EnableEurekaClient//启动Eureka客户端服务
public class CargoAccesserApp {
public static void main(String[] args) {
SpringApplication.run(CargoAccesserApp.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
yml配置文件如下:
server:
port: 8095 #服务端口
spring:
application:
name: app-cargo-list
eureka:
client:
service-url:
defaultZone: http://localhost:9090/eureka/
###如果要提供服务则需要注册,因为这里是服务的订阅者,注不注册都可以
register-with-eureka: true
###是否需要从eureka上检索服务,这个服务需要调用别的服务,所以当然需要检索服务了
fetch-registry: true
启动该模块,访问http://localhost:8095/cargoList/123,查看程序执行结果。
通过结果可以发现,服务订阅者通过向http://app-cargo/cargo/xxx这个接口请求,成功获取到了货物信息,而没有使用传统硬编码的方式向localhost:8090/cargo/xxx来请求。这么做可以避免接口地址改变而带来不必要的试错成本,所有微服务都在注册中心上注册,让各个服务的开发团队都可以把注意力集中在自己的服务上,而不用去关心其他组件的实现。
由于在RestTemplate上使用了一个LoadBalance注解,为APP-CARGO服务启用了负载均衡,我们可以通过不同的端口启动多个APP-CARGO服务以达到假集群的效果,调用CargoList服务,会发现系统会使用轮询的方式逐个调用这些服务。