Spring Cloud 应用分析(二):基础的测试用例

前言

[Spring Cloud 基础架构]一文中,我们已经知道一个完整的 Spring Cloud 所应当包含的组件以及各个组件和组件之间的基础功能;该篇博文便是根据这个基础架构来构建一个最简单的 Spring Cloud 应用,且包含 Spring Cloud 基础架构中的各个核心的组件;

本文为作者的原创作品,转载需注明出处;

测试用例

描述

思路,创建两个微服务,描述如下,

  • 微服务 A,订单系统;
  • 微服务 B,库存系统;

用户使用场景,通过订单系统生成订单,在生成订单的同时,通过库存系统扣减库存;该测试用例试图简单的将 Spring Cloud 的各个组件串联起来;

顶层设计

架构如 Spring Cloud 蓝图所示,

图中微服务 A 表示订单系统,微服务 B 表示库存系统;该测试用例通过用户下单并扣减库存的测试案例来描述一个完整的 Spring Cloud 程序的骨架(该骨架中暂时不包含 Config Server )

工程结构

项目的工程骨架如上所示;可以通过 Eclipse 直接进行创建,创建的步骤和相关工程描述大致如下,

  1. 先通过 Eclipse Maven Project 创建父类工程 springcloud-demo
    注意,如果要使得当前的 maven 工程成为 parent project,必须在 pom.xml 添加 <packaging>pom</packaging> 节点;配置如下

    1
    2
    3
    4
    5
    6
    7
    <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>org.shangyang.springcloud</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    </project>

    否则会报错:The parent project must have a packaging type of POM

  2. 然后再依次通过使用 maven module,包含如下的模块

    • eureka-server
      微服务注册中心
    • microservice-order
      一个微服务节点工程,注意,该工程包含两个模块,api 和 service;
      api: 对外提供公共访问接口以及 VO
      service: order 内部服务;
    • micorservice-stock
      类同于 microservice-order 微服务
    • zuul-server
      服务网关,服务发现中心;

相关的截图如下,

  • 如何创建 Maven Project / Maven Module

架构

部署图

上图描述了各个组建的部署结构,

  • zuul server
    提供了访问路由,认证等网关功能;如果要结合 Eureka 来实现路由,那么首先,需要将 zuul 自己注册到 Eureka 服务注册中心,然后通过组件 Ribbon 来发现服务,最后将客户端的访问转发到该服务上,实现路由功能;
  • eureka server
    服务注册中心;访问 eureka server 可以看到如下注册的信息
  • order
    注意,order 可以部署成多个节点,并通过 ZUUL 进行负载调度;
    • order api
      对外提供 order 的访问接口和 VO
    • order service
      • 生成订单操作需要引用 stock 微服务接口,所以,order service 模块需要引用 stock-api;
      • Feign 模块用于发现 stock 服务
  • stock
    注意,stock 可以部署成多个节点,并通过 ZUUL 进行负载调度;
    • stock api
      对外提供 stock 的访问接口和 VO;本案例中,该 api 主要提供给 Order 微服务使用。
    • stock service
      stock 内部服务实现;

流程图

如图,描述了客户创建订单的流程图;

  • 首先,所有的服务节点 Order、Stock 以及 ZUUL 都需要在 Eureka 服务中心进行注册;
  • 其次,客户端通过 ZUUL 路由到指定的 Order 节点;
  • 最后,Order 节点通过 Feign 调用 Stock 节点;

用例代码

microservice stock

api 模块

该模块提供 StockVO 和 IRemoteStock;StockVO 是 Order 远程调用 Stock 的 JSON 对象,IRemoteStock 是 Order 通过 Feign 远程调用时所需要的接口定义;

StockVO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.shangyang.springcloud.stock.api;

/**
* The Stock Business Visualize Object
*
* @author shangyang
*
*/
public class StockVO {

long productId;

String productName;

// how many products should be reduced?
int reduce;

public StockVO(){

}

public StockVO(long productId, String productName, int reduce){
this.productId = productId;
this.productName = productName;
this.reduce = reduce;
}

public long getProductId() {
return productId;
}

public void setProductId(long productId) {
this.productId = productId;
}

public String getProductName() {
return productName;
}

public void setProductName(String productName) {
this.productName = productName;
}

public int getReduce() {
return reduce;
}

public void setReduce(int reduce) {
this.reduce = reduce;
}

}

IRemoteStock.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.shangyang.springcloud.stock.api;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
*
* 该接口是放在 Order 工程中还是放在 Stock 工程中?最开始我觉得应该放到 Order 工程中,因为该接口是 HTTP Rest 接口,
* 按照常规思维,HTTP 是由调用发发起,那么自然应该放在 Order 工程中;但是,后来想想,这样做并不太好,因为,一旦 Stock 中该接口
* 发生变化了怎么办?1. 自然是由 Stock 开发团队来维护接口的变化更好;2. 另外,如果有其他的微服务工程,也恰好需要调用 Stock 该接口,
* 那么同时需要由其它开发组同时更新两个地方的变化;所以,综上所述,接口定义,还是应该由 Stock 自己来维护是最好的;
*
* @author shangyang
*
*/
@FeignClient("stock-service")
public interface IRemoteStock {

@RequestMapping(method=RequestMethod.PUT, value="/stock/{productid}")
ResponseEntity<String> reduce(@PathVariable(value="productid") long productid, @RequestBody StockVO stock );

}

该接口对应的就是 StockController#reduce 方法调用;

正如注释中所描述的那样,开始,该接口是放在 Order 微服务中还是放在 Stock 微服务中,我有过纠结;大部分教程是把类似这样的 Feign 接口是放在 Order 微服务中的;但是,如果这样做,有一个很大的问题,就是维护成本过高,试想,如果还有第二个、第三个微服务需要引用 Stock 的 Feign 接口,那不是要重复维护好几个相同的接口定义,这里既是 IRemoteStock 接口,而且,一旦 Stock 微服务中的 REST 接口放生变更,那么需要同时更新多个相同的分别位于不同微服务模块中的 IRemoteStock 接口;所以,最终,再三思考,最终决定,还是将 Feign 接口放在 Stock 模块中,远程调用接口都由自己来维护;

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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>
<parent>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>microservice-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>stock-api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

因为 api 模块的远程接口 IRemoteStock 使用到了 Feign,所以需要在 pom 中进行相关的配置;

service 模块

StockApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.shangyang.springcloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class StockApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(StockApplication.class).web(true).run(args);
}

}

StockController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.shangyang.springcloud.stock.web;

import org.apache.log4j.Logger;
import org.shangyang.springcloud.stock.api.StockVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
*
* 扣减库存操作
*
* @author shangyang
*
*/
@RestController
@RequestMapping(value="/stock")
public class StockController {

private final Logger logger = Logger.getLogger(getClass());

@Autowired
private DiscoveryClient client;

/**
* 扣减库存,模拟根据 productid 来进行扣减库存
*
* @param productName
* @param quantity
* @return true if reduce success.
*/
@RequestMapping(value = "/{productid}", method = RequestMethod.PUT )
public ResponseEntity<String> reduce(@PathVariable long productid, @RequestBody StockVO stock) {

ServiceInstance instance = client.getLocalServiceInstance();

logger.info("/reduce, host:" + instance.getHost() + ", service_id:" + instance.getServiceId());

logger.info("====> success reduced " + stock.getReduce() + " products with product id:"+productid);

return new ResponseEntity<String>(HttpStatus.OK);

}

}

application.properties

1
2
3
4
5
6
7
spring.application.name=stock-service

\# define more ports to start more application server
server.port=3000

\# Eureka 服务中心地址
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka
  • spring.application.name
    指明注册到 eureka 服务中心的注册名,这里是 stock-service
  • eureka.client.serviceUrl.defaultZone
    eureka 注册中心的地址;

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<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>
<parent>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>microservice-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>stock-service</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>stock-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

microservice order

reference 引用其它模块

因为需要远程调用 Stock 微服务,所以需要引用 Stock 微服务中的 api 模块

api 模块

该 api 只提供对外服务的 VO,OrderVO;该接口对象主要是由客户端通过 REST 调用的 JSON 对象接口;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package org.shangyang.springcloud.order.api;

/**
* 一个很粗略的 Order..
*
* @author shangyang
*
*/
public class OrderVO {

long orderId;

long productId;

String productName;

int quantity;

public OrderVO(){

}

public OrderVO(long orderId, long productId, String productName, int quantity ){

this.orderId = orderId;

this.productId = productId;

this.productName = productName;

this.quantity = quantity;

}

public long getOrderId() {
return orderId;
}

public void setOrderId(long orderId) {
this.orderId = orderId;
}

public long getProductId() {
return productId;
}

public void setProductId(long productId) {
this.productId = productId;
}

public String getProductName() {
return productName;
}

public void setProductName(String productName) {
this.productName = productName;
}

public int getQuantity() {
return quantity;
}

public void setQuantity(int quantity) {
this.quantity = quantity;
}

public String toString(){

return "Order Instance: id: "+orderId+"; productId: "+productId+"; product name:"+productName+"; quantity: "+quantity;

}

}

service 模块

OrderApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.shangyang.springcloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan
@EnableFeignClients
public class OrderApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(OrderApplication.class).web(true).run(args);
}

}

注意,如果 OrderApplication.java 是放在 package org.shangyang.springcloud.order 中会导致所引用的 Feign 接口 IRemoteStock.java 找不到,因为 IRemoteStock.java 是放在 org.shangyang.springcloud.stock.api 目录中的,OrderApplication.java 限定了 Component Scan 的路径在 org.shangyang.springcloud.order 中,自然就找不到 org.shangyang.springcloud.stock package 中的内容;所以,这里,我形成了一个不成文的规定,凡是 xxApplication,均放置在根 package org.shangyang.springcloud 中;

OrderController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package org.shangyang.springcloud.order.web;

import org.apache.log4j.Logger;
import org.shangyang.springcloud.order.api.OrderVO;
import org.shangyang.springcloud.stock.api.IRemoteStock;
import org.shangyang.springcloud.stock.api.StockVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
*
* 该用例模拟创建订单,并且在调用过程中 Order 微服务将会调用 Stock 微服务;
*
* @author shangyang
*
*/
@RestController
@RequestMapping(value="/order")
public class OrderController {

private final Logger logger = Logger.getLogger(getClass());

@Autowired
IRemoteStock stock;

@Autowired
private DiscoveryClient client;

/**
* 模拟生成订单接口,生成订单的同时会调用 stock 的远程接口进行库存扣减操作;
*
* @param orderid
* @param productid
* @param quantity
* @return
*/
@RequestMapping( method = RequestMethod.POST, consumes = "application/json") // 加上 consumes 表示该接口只接受 header 为 application/json 的接口调用
public ResponseEntity<OrderVO> create(@RequestBody OrderVO order) {

ServiceInstance instance = client.getLocalServiceInstance();

logger.info("/create order, host:" + instance.getHost() + ", service_id:" + instance.getServiceId());

// 调用 stock 微服务接口,进行库存扣减;

ResponseEntity<String> entity = stock.reduce( order.getProductId(), new StockVO( order.getProductId(), order.getProductName(), order.getQuantity()));

if( entity.getStatusCode().equals(HttpStatus.OK) ){
logger.info("====> success of creating the order with order id: " + order.getOrderId() );
}else{
logger.error("====> failed of creating the order with order id: " + order.getOrderId() );
}

return new ResponseEntity<OrderVO>(order, HttpStatus.CREATED);

}

}

注意这里饿返回类型 ResponseEntity(order, HttpStatus.CREATED); 如果是通过 RestTemplate#postForEntity 的方式进行远程调用,返回的是 ResponseEntity 对象,如果是通过 RestTemplate#postForObject 的方式进行远程调用,返回的是 OrderVO 对象;

application.properties

1
2
3
4
5
6
7
spring.application.name=order-service

\# define more ports to start more application server
server.port=2000

\# Eureka 注册中心地址
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<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>
<parent>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>microservice-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>order-service</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>stock-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>order-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

eureka server

EurekaApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.shangyang.springcloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(EurekaApplication.class).web(true).run(args);
}

}

application.properties

1
2
3
4
5
server.port=1111

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<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>

<parent>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>springcloud-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>eureka-server</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

zuul server

ZuulApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.shangyang.springcloud;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@SpringCloudApplication
public class ZuulApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(ZuulApplication.class).web(true).run(args);
}

}

application.properties

1
2
3
4
5
6
7
spring.application.name=api-gateway
server.port=8000

zuul.routes.order.path=/order-service/**
zuul.routes.order.serviceId=order-service

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

ZUUL 的路由是通过上述配置实现的,通过访问前缀 /order-service/** 就可以路由向到 order-service 节点;ZUUL 是通过 Ribbon 来实现服务发现的,从 Eureka 服务中心中找到 order-service 节点的 server 地址,然后路由到 order-service 节点;

例子,http://localhost:8000/order-service/order URL 将会被路由为 http://localhost:2000/order

pox.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<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>
<parent>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>springcloud-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>zuul-server</artifactId>

<dependencies>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.shangyang.springcloud</groupId>
<artifactId>order-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>

</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

client 调用测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package org.shangyang.springcloud.zuul;

import org.junit.Test;
import org.shangyang.springcloud.order.api.OrderVO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
* 模拟客户端进行测试;
*
* @author shangyang
*
*/
public class ClientTest {

static final int PORT = 8000; // ZUUL default port

@Test
public void testCreateOrder(){

// http://localhost:8000/order-service/order 将会被映射到 http://localhsot:2000/order
final String uri = "http://localhost:"+PORT+"/order-service/order";

OrderVO order = new OrderVO(1000, 2000, "macbook", 10);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity<?> result = restTemplate.postForEntity(uri, order, null);

System.out.println(result);

}

}

至此,一个完整的最为基础的 Spring Cloud 测试用例就搭建完成了;

代码下载

从我的 github 中下载,https://github.com/comedsh/spirngcloud-demo