SpringCloud 详细教程代码下载地址:https://download.csdn.net/download/zpcandzhj/10762209
1.SpringCloud教程目录
开发环境说明
- 细说微服务架构
- SpringCloud 概论
- SpringCloud 快速入门
- Eureka 服务注册中心
- 使用Spring Cloud Ribbon 实现负载均衡
- 使用Spring Cloud Hystrix 实现容错
- Feign声明式客户端(REST调用)
- 服务网关 SpringCloud Zuul
- 分布式配置中心SpringCloud Config
- 消息总线 SpringCloud Bus
- 整合consul、zookeeper作为注册中心
- 整合swagger 统一管理API
2.开发环境说明
- JDK: 1.8
- IDE: IntelliJ IDEA
- Maven:3.3.9
- OS: Windows 10 10.0
- Springboot版本:2.0+
3.细说微服务架构
- 目前微服务是非常火的架构或者说概念,也是在构建大型互联网项目时采用的架构方式。
3.1.单体架构
- 单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。
- 假设你正准备开发一款与Uber和滴滴竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于SpringBoot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
- 应用核心是业务逻辑,由定义服务、领域对象和事件的模块完成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。
- 尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,部署在Tomcat或者Jetty上,而另外一些Java应用会被打包成自包含的JAR格式,类似的,Rails和Node.js会被打包成层级目录。
- 这种应用开发风格很常见,因为IDE和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用Selenium链接UI就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这类应用运行的很好。
3.2.单体架构存在的问题
如何解决以上问题呢? – 使用微服务架构。使得应用由重变轻。
3.3.什么是微服务?
作者:Martin Fowler
3.4.微服务架构的特征
3.5.微服务架构示例
每一个应用功能区都使用微服务完成。
4.SpringCloud概论
4.1.简介
SpringCloud项目的官方网址:http://projects.spring.io/spring-cloud/
4.2.SpringCloud子项目
4.3.Springcloud版本说明
官方版本:
可见,目前Finchley.SR2版本是最新的稳定版,所以我们学习的过程中,就是使用的这个版本。
4.4.SpringCloud框架特点
5.使用 SpringBoot 实现微服务
在正式学习SpringCloud 之前我们先使用SpringBoot实现一个微服务。
业务非常简单:
1、商品微服务:通过商品id查询商品的服务;
2、订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;
图示:
说明:
1、对于商品微服务而言,商品微服务是服务的提供者,订单微服务是服务的消费者;
2、对于订单微服务而言,订单微服务是服务的提供者,人是服务的消费者。
5.1.实现商品微服务
5.1.1.创建maven工程
5.1.2.导入依赖
重点是导入SpringBoot的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<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>com.zpc.microservice</groupId>
<artifactId>microservice-item</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
</project>
5.1.3.创建实体Item
package com.zpc.item.entity;
public class Item {
private Long id;
private String title;
private String pic;
private String desc;
private Long price;
public Item(){}
public Item(long id, String title, String pic, String desc, Long price) {
this.id=id;
this.title=title;
this.pic=pic;
this.desc=desc;
this.price=price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
@Override
public String toString() {
return "Item [id=" + id + ", title=" + title + ", pic=" + pic + ", desc=" + desc + ", price=" + price + "]";
}
}
5.1.4.编写ItemService
编写ItemService用于实现具体的商品查询逻辑,为了演示方便,我们并不真正的连接数据库,而是做模拟实现。
package com.zpc.item.service;
import com.zpc.item.entity.Item;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class ItemService {
private static final Map<Long, Item> ITEM_MAP = new HashMap<Long, Item>();
static {// 准备一些静态数据,模拟数据库
ITEM_MAP.put(1L, new Item(1L, "商品1", "http://图片1", "商品描述1", 1000L));
ITEM_MAP.put(2L, new Item(2L, "商品2", "http://图片2", "商品描述2", 2000L));
ITEM_MAP.put(3L, new Item(3L, "商品3", "http://图片3", "商品描述3", 3000L));
ITEM_MAP.put(4L, new Item(4L, "商品4", "http://图片4", "商品描述4", 4000L));
ITEM_MAP.put(5L, new Item(5L, "商品5", "http://图片5", "商品描述5", 5000L));
ITEM_MAP.put(6L, new Item(6L, "商品6", "http://图片6", "商品描述6", 6000L));
ITEM_MAP.put(7L, new Item(7L, "商品7", "http://图片7", "商品描述7", 7000L));
ITEM_MAP.put(8L, new Item(8L, "商品8", "http://图片8", "商品描述8", 8000L));
ITEM_MAP.put(8L, new Item(9L, "商品9", "http://图片9", "商品描述9", 9000L));
ITEM_MAP.put(8L, new Item(10L, "商品10", "http://图片10", "商品描述10", 10000L));
}
/**
* 模拟实现商品查询
*
* @param id
* @return
*/
public Item queryItemById(Long id) {
return ITEM_MAP.get(id);
}
}
5.1.5.编写ItemController
package com.zpc.item.controller;
import com.zpc.item.entity.Item;
import com.zpc.item.service.ItemService;
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;
@RestController
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 对外提供接口服务,查询商品信息
*
* @param id
* @return
*/
@GetMapping(value = "item/{id}")
public Item queryItemById(@PathVariable("id") Long id) {
return this.itemService.queryItemById(id);
}
}
@RestController注解的说明:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
从源码可以看出,这是一个组合注解,组合了@Controller
和@Response
注解。相当于我们同时写了这2个注解。
@GetMapping注解的说明:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {...}
@GetMapping
注解是@RequestMapping(method = RequestMethod.GET)
简写方式。其功能都是一样的。
同理还有其它注解:
5.1.6.编写程序入口
package com.zpc.item.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* @author Evan
*/
@SpringBootApplication//申明这是一个Spring Boot项目
@ComponentScan(basePackages = {"com.zpc.item.controller","com.zpc.item.service"})//手动指定bean组件扫描范围
public class ItemApp {
public static void main(String[] args) {
SpringApplication.run(ItemApp.class, args);
}
}
5.1.7.创建application.yml配置文件
SpringBoot以及SpringCloud项目支持yml和properties格式的配置文件。
yml格式是YAML(Yet Another Markup Language)编写的格式,YAML和properties格式的文件是可以相互转化的。如:
server:
port: 8081 #服务端口
等价于properties文件的配置:
server.port=8081
配置文件的示例:
server:
port: 8081 #服务端口
5.1.8.启动程序测试
可以看到已经通过微服务查询到数据。
5.2.实现订单微服务
5.2.1.创建工程
5.2.2.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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>com.zpc.microservice</groupId>
<artifactId>microservice-item</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
</project>
5.2.3.创建订单Order实体
package com.zpc.order.entity;
import java.util.Date;
import java.util.List;
public class Order {
private String orderId;
private Long userId;
private Date createDate;
private Date updateDate;
private List<OrderDetail> orderDetails;
public Order() {
}
public Order(String orderId, Long userId, Date createDate, Date updateDate) {
this.orderId = orderId;
this.userId = userId;
this.createDate = createDate;
this.updateDate = updateDate;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public List<OrderDetail> getOrderDetails() {
return orderDetails;
}
public void setOrderDetails(List<OrderDetail> orderDetails) {
this.orderDetails = orderDetails;
}
@Override
public String toString() {
return "Order [orderId=" + orderId + ", userId=" + userId
+ ", createDate=" + createDate + ", updateDate=" + updateDate
+ "]";
}}
5.2.4.创建订单详情OrderDetail实体
订单与订单详情是一对多的关系。
package com.zpc.order.entity;
public class OrderDetail {
private String orderId;
private Item item = new Item();
public OrderDetail() {
}
public OrderDetail(String orderId, Item item) {
this.orderId = orderId;
this.item = item;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
@Override
public String toString() {
return "OrderDetail [orderId=" + orderId + ", item=" + item + "]";
}
}
5.2.5.将商品微服务项目中的Item类拷贝到当前工程
5.2.6.编写OrderService
该Service实现的根据订单Id查询订单的服务,为了方便测试,我们将构造数据实现,不采用查询数据库的方式。
package com.zpc.order.service;
import com.zpc.order.entity.Item;
import com.zpc.order.entity.Order;
import com.zpc.order.entity.OrderDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class OrderService {
private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
static {
// 模拟数据库,构造测试数据
Order order = new Order();
order.setOrderId("201810300001");
order.setCreateDate(new Date());
order.setUpdateDate(order.getCreateDate());
order.setUserId(1L);
List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();
Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
item.setId(1L);
orderDetails.add(new OrderDetail(order.getOrderId(), item));
item = new Item(); // 构造第二个商品数据
item.setId(2L);
orderDetails.add(new OrderDetail(order.getOrderId(), item));
order.setOrderDetails(orderDetails);
ORDER_DATA.put(order.getOrderId(), order);
}
@Autowired
private ItemService itemService;
/**
* 根据订单id查询订单数据
*
* @param orderId
* @return
*/
public Order queryOrderById(String orderId) {
Order order = ORDER_DATA.get(orderId);
if (null == order) {
return null;
}
List<OrderDetail> orderDetails = order.getOrderDetails();
for (OrderDetail orderDetail : orderDetails) {
// 通过商品微服务查询商品详细数据
Item item = this.itemService.queryItemById(orderDetail.getItem()
.getId());
if (null == item) {
continue;
}
orderDetail.setItem(item);
}
return order;
}
}
5.2.7.实现ItemService
package com.zpc.order.service;
import com.zpc.order.entity.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class ItemService {
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
public Item queryItemById(Long id) {
return this.restTemplate.getForObject("http://127.0.0.1:8081/item/"
+ id, Item.class);
}
}
5.2.8.编写OrderController
package com.zpc.order.controller;
import com.zpc.order.entity.Order;
import com.zpc.order.service.OrderService;
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;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping(value = "order/{orderId}")
public Order queryOrderById(@PathVariable("orderId") String orderId) {
return this.orderService.queryOrderById(orderId);
}
}
5.2.9.编写程序入口
package com.zpc.order.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
/**
* @author Evan
*/
@SpringBootApplication//申明这是一个Spring Boot项目
@ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service"})//手动指定bean扫描范围
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
/**
* 向Spring容器中定义RestTemplate对象
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
5.2.10.编写application.yml配置文件
server:
port: 8082 #服务端口
5.2.11.启动测试
测试结果可见,查询订单时,同时也将商品数据查询到。
5.3.添加okHttp的支持
okhttp是一个封装URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。
官网:http://square.github.io/okhttp/
RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate
的底层使用okhttp,非常简单:
1、添加okhttp依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.0</version>
</dependency>
2、设置requestFactory
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
测试:
结果(没有变化):
测试结果是一样的。
5.4.解决订单系统中的url硬编码问题
通过以上的测试我们发现,在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。
如何解决呢?
解决方案:将url地址写入到application.yml
配置文件中。
实现:
修改application.yml
文件:
server:
port: 8082 #服务端口
myspcloud:
item:
url: http://127.0.0.1:8081/item/
修改ItemService中的实现:
@Service
public class ItemService {
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
@Value("${myspcloud.item.url}")
private String itemUrl;
public Item queryItemById(Long id) {
return this.restTemplate.getForObject(itemUrl
+ id, Item.class);
}
}
测试(debug可以看到效果):
5.5.继续优化解决硬编码的问题
在SpringBoot中使用@ConfigurationProperties
注解可以非常简单的将配置文件中的值映射成对象。
第一步,创建ItemProperties类:
package com.zpc.order.properties;
public class ItemProperties {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
第二步,创建OrderProperties类:
package com.zpc.order.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 以myspcloud开头的配置被匹配到
* @author Evan
*/
@Component
@ConfigurationProperties(prefix="myspcloud")
public class OrderProperties {
private ItemProperties item = new ItemProperties();
public ItemProperties getItem() {
return item;
}
public void setItem(ItemProperties item) {
this.item = item;
}
}
第三步,在Itemservice中注入该对象:
@Service
public class ItemService {
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
//@Value("${myspcloud.item.url}")
//private String itemUrl;
@Autowired
OrderProperties orderProperties;
public Item queryItemById(Long id) {
return this.restTemplate.getForObject(orderProperties.getItem().getUrl()
+ id, Item.class);
}
第四步,启动类中增加bean扫描路径
@ComponentScan(basePackages = {"com.zpc.order.controller","com.zpc.order.service","com.zpc.order.properties"})//
手动指定bean扫描范围
第五步,debug运行
可以看出,这种解决方案比第一种好很多,更加的方便的。
思考:
这样是否还存在问题?如果商品的微服务有多个怎么办?
我们在订单微服务中使用了Item实体,直接采取从商品微服务拷贝代码的方式是否太生硬?
6.SpringCloud快速入门
6.1.分析硬编码的问题
通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
1、如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
2、如果商品微服务有多个,那么在订单微服务中又该如何写地址?
那应该怎么解决呢? – 通过服务注册、发现的机制来完成。
6.2.微服务注册与发现
原理示意图: