目录
现在开始一边学习一边实验的过程。
开发环境
我用的是
- jdk1.8 (Spring Boot 推荐jdk1.8及以上): java version "1.8.0_181"
- Maven 3.x (maven 3.2 以上版本):Apache Maven 3.3.9
- IntelliJ IDEA :IntelliJ IDEA 2019
- Spring Boot :Spring Boot 2.1.15
- Spring Cloud 使用稳定版本:Greenwich.SR6, 搭配 Spring Boot 2.1.15稳定版
Spring Cloud & Spring Boot 依赖关系
Release Train | Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
- GA : General Availability,正式发布的版本,官方推荐使用此版本。在国外都是用GA来说明release版本的;
- PRE : 预览版,内部测试版. 主要是给开发人员和测试人员测试和找BUG用的,不建议使用;
- SNAPSHOT : 快照版,可以稳定使用,且仍在继续改进版本。
maven配置文件
<!--开始处更改下载依赖的存放路径, 以下目录需要已经创建-->
<localRepository>D:\java\maven-repository</localRepository>
<!--在 mirrors 标签下 添加阿里云maven私服库-->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
<!-- 在 profiles 标签下指定jdk版本 -->
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
IDE中配置maven
项目练习
实现简单的产品生产者和消费者两个模块。以商品管理模块做一个微服务架构通用案例,消费者Consumer(Client)通过REST调用 提供者Provider(Server)提供的商品管理服务。
创建一个新的空工程
创建父工程

<?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.haogenmin.springcloud</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- spring boot 采用 2.1.15 版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<!--依赖声明-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<!--maven不支持多继承,使用import来依赖管理配置-->
<scope>import</scope>
</dependency>
<!--导入 mybatis 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
OK,现在父工程创建完毕 <dependencyManagement>指的是依赖声明,并不是真的导入了依赖,在子模块中再导入相应的依赖不必指出版本,其目的主要是为了固定版本。
构建公共模块
在model模块中创建实体类。
package com.haogenmin.model;
import java.io.Serializable;
/**
* @author :HaoGenmin
* @Title :Product
* @date :Created in 2020/6/23 19:02
* @description:
*/
public class Product implements Serializable {
private Long pid; //主键
private String productName; //产品名称
// 来自那个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
private String dbSource;
public Product() {
}
public Product(String productName) {
this.productName = productName;
}
public Product(Long pid, String productName, String dbSource) {
this.pid = pid;
this.productName = productName;
this.dbSource = dbSource;
}
public Long getPid() {
return pid;
}
public void setPid(Long pid) {
this.pid = pid;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getDbSource() {
return dbSource;
}
public void setDbSource(String dbSource) {
this.dbSource = dbSource;
}
}
构建服务提供者
同model模块一样创建一个maven项目。
pom文件如下:
<?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">
<parent>
<artifactId>parent</artifactId>
<groupId>com.haogenmin.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider_01</artifactId>
<dependencies>
<dependency>
<groupId>com.haogenmin.springcloud</groupId>
<artifactId>model</artifactId>
<version>${project.version}</version>
</dependency>
<!--springboot web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8001
spring:
application:
name: microservice-product #这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个name
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
url: jdbc:mysql://127.0.0.1:3306/springcloud_db01?serverTimezone=GMT%2B8 # 数据库名称
username: root
password: 123456
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 150 # 等待连接获取的最大超时时间
mybatis:
configuration:
map-underscore-to-camel-case: true #驼峰命名
项目目录
启动类
package com.haogenmin.provider1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author :HaoGenmin
* @Title :Provider_01_Application
* @date :Created in 2020/6/23 19:10
* @description:
*/
@SpringBootApplication
public class Provider_01_Application {
public static void main(String[] args) {
SpringApplication.run(Provider_01_Application.class,args);
}
}
Mapper
package com.haogenmin.provider1.mapper;
import com.haogenmin.model.Product;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper //或者在启动类上标识 @MapperScan
public interface ProductMapper {
@Select("select pid, product_name, db_source from product where pid=#{pid}")
Product findById(Long pid);
@Select("select pid, product_name, db_source from product")
List<Product> findAll();
@Options(useGeneratedKeys = true, keyProperty = "pid")
@Insert("INSERT INTO product(product_name, db_source) VALUES(#{productName}, DATABASE())")
boolean addProduct(Product product);
}
service
package com.haogenmin.provider1.service;
import com.haogenmin.model.Product;
import java.util.List;
public interface ProductService {
boolean add(Product product);
Product get(Long id);
List<Product> list();
}
package com.haogenmin.provider1.service.impl;
import com.haogenmin.model.Product;
import com.haogenmin.provider1.mapper.ProductMapper;
import com.haogenmin.provider1.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service //一定不要少了
public class ProductServiceImpl implements ProductService {
@Autowired
ProductMapper productMapper;
@Override
public boolean add(Product product) {
return productMapper.addProduct(product);
}
@Override
public Product get(Long id) {
return productMapper.findById(id);
}
@Override
public List<Product> list() {
return productMapper.findAll();
}
}
controller
package com.haogenmin.provider1.controller;
import com.haogenmin.model.Product;
import com.haogenmin.provider1.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(value = "/product/add", method = RequestMethod.POST)
public boolean add(@RequestBody Product product) {
return productService.add(product);
}
@RequestMapping(value = "/product/get/{id}", method = RequestMethod.GET)
public Product get(@PathVariable("id") Long id) {
return productService.get(id);
}
@RequestMapping(value = "/product/list", method = RequestMethod.GET)
public List<Product> list() {
return productService.list();
}
}
访问结果:
好,现在我们创建了一个生产着,接着创建一个消费者。
构建服务消费者
同上构建consumer子模块
pom文件
<?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">
<parent>
<artifactId>parent</artifactId>
<groupId>com.haogenmin.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer</artifactId>
<dependencies>
<dependency>
<groupId>com.haogenmin.springcloud</groupId>
<artifactId>model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
yml文件
server:
port: 80
目录结构:
启动类:
package com.haogenmin.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author :HaoGenmin
* @Title :ConsumerApplication
* @date :Created in 2020/6/23 19:31
* @description:
*/
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
config:
package com.haogenmin.consumer.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author :HaoGenmin
* @Title :MyConfig
* @date :Created in 2020/6/23 19:30
* @description:
*/
@Configuration
public class MyConfig {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller
package com.haogenmin.consumer.controller;
import com.haogenmin.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class ProductController_Consumer {
private static final String REST_URL_PREFIX = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/product/add")
public boolean add(Product product) {
return restTemplate.postForObject(REST_URL_PREFIX + "/product/add", product, Boolean.class);
}
@RequestMapping(value = "/consumer/product/get/{id}")
public Product get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "/product/get/" + id, Product.class);
}
@RequestMapping(value = "/consumer/product/list")
public List<Product> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/product/list", List.class);
}
}
测试一下,注意商品提供者也要启动起来。
至此,我们访问消费者,然后消费者通过rest形式访问提供者,得到数据再返回给客户端的整个流程已经跑通了。接下来我们要看看,什么是restful风格。
Restful
RESTful接口规范理解
RESTful = Representational State Transfer 即表现层状态转移 加 ful (即形容词后缀) 则表示是形容词性的,可以理解为表现层状态转移风格的。
Representational State Transfer这个词组,直译过来就是【表现层】【状态】【转移/转化/变换】
事实上,少了一个主语,这个主语叫做资源,应该叫做资源表现层状态转移,好了,看到这里有朋友要说了,你说什么玩意?鬼才看得懂。莫急……
以上词组我认为应该分为三个部分来解释,即,资源,表现层,状态转移
资源
什么是资源?任何事物,只要有被引用到的必要,它就是一个资源。我们在浏览器中看到的文本,视频,图片等等都是资源。这些都是实实在在存在的实体。资源可以是一个实体,也可以是抽象概念。比如
比如说吧:
-- Alex的个人信息
-- 沛齐的手机号
-- Alex跟沛齐的潜在关系
这些都是资源,可以是实体比如个人信息,手机号。也可以是抽象的概念,比如两个人的关系,那么在我们的网络中,我们要引用资源,资源一定要有一个标识,在web中的唯一标识就是URI,URI我们不常听说,我们经常用URL,那么两者区别是什么呢~
URI和URL
这里引用知乎上面的高赞回答。
作者:daixinye
链接:https://www.zhihu.com/question/21950864/answer/154309494
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。
拿人做例子,假设这个世界上所有人的名字都不能重复,那么名字就是URI的一个实例,通过名字这个字符串就可以标识出唯一的一个人。
现实当中名字当然是会重复的,所以身份证号才是URI,通过身份证号能让我们能且仅能确定一个人。
那统一资源定位符URL是什么呢。也拿人做例子然后跟HTTP的URL做类比,就可以有:
动物住址协议://地球/中国/浙江省/杭州市/西湖区/某大学/14号宿舍楼/525号寝/张三.人
可以看到,这个字符串同样标识出了唯一的一个人,起到了URI的作用,所以URL是URI的子集。URL是以描述人的位置来唯一确定一个人的。
在上文我们用身份证号也可以唯一确定一个人。对于这个在杭州的张三,我们也可以用:
身份证号:123456789
来标识他。
所以不论是用定位的方式还是用编号的方式,我们都可以唯一确定一个人,都是URl的一种实现,而URL就是用定位的方式实现的URI。
回到Web上,假设所有的Html文档都有唯一的编号,记作html:xxxxx,xxxxx是一串数字,即Html文档的身份证号码,这个能唯一标识一个Html文档,那么这个号码就是一个URI。
而URL则通过描述是哪个主机上哪个路径上的文件来唯一确定一个资源,也就是定位的方式来实现的URI。
对于现在网址我更倾向于叫它URL,毕竟它提供了资源的位置信息,如果有一天网址通过号码来标识变成了http://741236985.html,那感觉叫成URI更为合适,不过这样子的话还得想办法找到这个资源咯…
看到上面的解释相信朋友们对这两者应该有一个清晰的认识了,现在我们知道URL是统一资源定位符,那么他代表的是一个资源,很显然对于资源的表示,我们都是用名词,比如你喊你的朋友,你会喊,二狗,铁蛋。这是名字。难道你会喊他们抓二狗,换铁蛋这样的动词结构吗?同理那对于资源定位符URL为什么一定要加上一个动词呢?getProduct,updateProduct……,一个Product不就完了吗?用名词定位一个资源不好吗?世界不就清净了吗?这样对一个资源的所有操作都是同样一个URL了,这叫统一资源接口。那一个URL怎么区分增删改查巴拉巴拉各种操作呢?下面解释。
表现层
解释之前先安顺序说一下表现层,其实就是资源的一种描述,就是资源的一种展现形式,什么是资源,我认为就是存储在存储设备中的表示特定信息的一系列的二进制码。你说呢?除非我们直接看他们的存储的二进制形式否则你看到的都是它们的一种展现形式,可以解读成文本,可以进一步解读成xml,json。可以解读成PNG,JPG,MP3等等。我们客户端和服务端传输的都是资源的表述,而不是资源本身。因为它们传递的时候不单单是资源还被套上了格式(HTML,xml,json等等)。那么客户端如何知道服务端提供哪种表述形式呢?可以通过HTTP内容协商,客户端可以通过Accept头请求一种特定格式的表述,服务端则通过Content-Type告诉客户端资源的表述形式。这些资源的表述呈现在页面上,就是我们说的资源状态。
状态转移
什么叫状态转移?我认为就是状态改变?但是我在网上查阅的资料,对此有两种看法,一种认为是应用状态,一种认为是服务状态。在此我都列举出来,有助于大家理解。
服务状态
铁蛋昨天穿蓝色裤衩,今天穿红色裤衩了,那么他的状态就改变了。铁蛋是活的人,数据库的数据是固定的数据。那么这个数据改变了,我要不要更新它?这个数据没有了,我要不要删除它?这就是所谓的数据的状态转移了也可以理解为改变了。事实上,我们对数据的操作就是使数据发生状态转移,包括增删改查。可是单纯的查不会改变数据的状态啊,我想可能是为了对数据的所有操作有一个统一的解释。回到上面说的统一资源接口如何区分增删改查呢?答案是HTTP动词。
GET请求 返回查到所有或单条数据
POST请求 返回新增的数据
PUT请求 返回更新数据
PATCH请求 局部更新 返回更新整条数据
DELETE请求 返回值为空
也就是说,状态转移依靠的是HTTP动词,正是因为这一句话,才会认为状态转移是指服务端数据的状态发生变化。
应用状态
资源的表述呈现在页面上,就是我们说的资源状态。我们在看页面的时候,从当前资源的表述(也可以说状态或者表现层)会跳转到其他的资源状态。服务端通过超媒体告诉客户端当前状态有哪些后续状态可以进入。这些类似"下一页"之类的链接起的就是这种推进状态的作用——指引你如何从当前状态进入下一个可能的状态。我想这样的解释是因为REST词组的解释(表现层状态转移),既是表现层状态转移,那指的肯定是应用状态的转移了。
状态
我认为,如果我单纯的查看操作,从一个资源的表述形式,点击下一页等操作,转到另一个资源的表述形式,那么就是应用状态改变,如果点击修改,刷新页面是更新后的数据,那就是应用状态和服务状态一起改变。实际上不管怎么理解,我们只需要明白其本质是为了规范一个统一的接口。
RESTful接口规范
想要了解的朋友请参考:
RESTful接口优点
想来这种风格要求的是尽可能最大化的利用HTTP协议,而不是仅仅把它当做一个传输层协议来做,比如,URL中各种动词,所有的访问不是get就是post,表意混乱,比如,操作成功与否自己定义返回码,再自己解析等等。直接用TCP不好吗?
RESTful表示,我只想要两个东西,一个告诉我东西在哪?一个告诉我,找到东西要干嘛?OK,URL和HTTP动词。别的不需要了,响应结果?HTTP的设计者就说了,我定义那么多的响应类型,你用啊,你倒是用啊,我满足不了你吗………………
这种接口风格最大的优点想必是规范了统一接口,规范了响应状态,这样所有的客户端,web,Android,iOS等等应用都可以共用一个统一的接口,都可以用相同的响应解析策略。
从这里再看我们上面的代码,是不是需要改一改了?是的。
package com.haogenmin.consumer.controller;
import com.haogenmin.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/consumer")
public class ProductController_Consumer {
private static final String REST_URL_PREFIX = "http://localhost:8001/provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/products", method = RequestMethod.POST)
public boolean add(Product product) {
return restTemplate.postForObject(REST_URL_PREFIX + "/products", product, Boolean.class);
}
@RequestMapping(value = "/products/{id}", method = RequestMethod.GET)
public Product get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "/products/" + id, Product.class);
}
@RequestMapping(value = "/products",method = RequestMethod.GET)
public List<Product> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/products", List.class);
}
}
package com.haogenmin.provider1.controller;
import com.haogenmin.model.Product;
import com.haogenmin.provider1.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/provider")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(value = "products", method = RequestMethod.POST)
public boolean add(@RequestBody Product product) {
return productService.add(product);
}
@RequestMapping(value = "products/{id}", method = RequestMethod.GET)
public Product get(@PathVariable("id") Long id) {
return productService.get(id);
}
@RequestMapping(value = "products", method = RequestMethod.GET)
public List<Product> list() {
return productService.list();
}
}
这种不带动词的接口是不是看起来舒服多了。事实上,在restful的模型图中,做到这两部分只是达到了level2,不过这样已经很restful了,如果还想进一步了解,网上推荐的是读《Rest in Practice》第五章。模型图:
然后代码中使用了RestTemplate作为restful服务的客户端连接工具,相比于httpclient更加的简单粗暴,不过需要注意的是,在异步调用的时候请考虑另外一个简单模板WebClient,在异步非阻塞调用下会有更高的性能,官网标注:
关于RestTemplate模板工具的知识,回头好好学习之后再写……