一、概念
电影销售系统的架构来对服务提供者和消费者进行一个定位:
可以看到,当用户访问电影购票系统时,首先访问的是前置系统“电影微服务”,而电影微服务在进行出票的过程中,需要获取用户之前注册的用户信息,此时就需要调用用户微服务的服务,来获取用户信息。这里电影微服务就是“服务消费者”,而用户微服务就是“服务提供者”,电影微服务需要消费用户查询服务,而用户微服务提供了用户查询服务。
有关“服务消费者”和“服务提供者”的定义:
二、编写一个服务提供者
首先我们来编写一个简单的服务提供者样例工程。这里我们直接通过Spring官网提供的构建工具来创建一个SpringBoot工程。打开https://start.spring.io/:
这里填写我们需要的信息,其中系统构建使用Maven,编程语言使用Java,SpringBoot使用最新2.2.0(SNAPSHOT快照)版本,Group和Artifact定义为微服务Demo的相关信息:“com.microserver.cloud”、“microserver-simple-provider-user”。
最后需要一些依赖,web、jpa以及H2:
其中web是我们需要的Spring-web,用于支持Web开发;jpa是Spring-jpa是持久层框架,用于操作数据库;H2是H2数据库,内置数据库,用来做一些数据的展示。
点击“Generate Project”按钮,来构建一个基于Spring boot的一个Project。此时会弹出一个Zip压缩包供我们下载,下载完毕后得到一个文件:
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.microserver.cloud</groupId>
<artifactId>microserver-simple-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>microserver-simple-provider-user</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
可以看到,SpringBoot的版本就使我们之前选择的2.2.0-SNAPSHOT版本,然后也引入了web、jpa和H2的相关依赖。最上面的src下,生成了一个默认的MicroserverSimpleProviderUserApplication.java类:
package com.microserver.cloud.microserversimpleprovideruser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MicroserverSimpleProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserverSimpleProviderUserApplication.class, args);
}
}
该类用于加载默认启动项,并启动SpringBoot工程。
在schema.sql中编写建表语句:
DROP TABLE USER IF EXISTS;
CREATE TABLE USER(
id BIGINT generated BY DEFAULT AS identity, -- h2数据库自增主键语法
username VARCHAR(40) COMMENT '账号',
NAME VARCHAR(20) COMMENT '姓名',
age INT(3) COMMENT '年龄',
balance DECIMAL(10,2) COMMENT '余额',
PRIMARY KEY(id)
);
然后创建一个data.sql,用于给user表插入测试数据:
在data.sql中编写插入数据:
INSERT INTO USER(id,username,NAME,age,balance) VALUES(1,'user1','张三',20,100.00);
INSERT INTO USER(id,username,NAME,age,balance) VALUES(2,'user2','李四',21,200.00);
INSERT INTO USER(id,username,NAME,age,balance) VALUES(3,'user3','王五',22,300.00);
INSERT INTO USER(id,username,NAME,age,balance) VALUES(4,'user4','赵六',23,400.00);
数据准备好之后,我们在src/main/java下的com.microserver.cloud.entity包下创建一个User实体类:
package com.microserver.cloud.entity;
import java.math.BigDecimal;
public class User implements Serializable{
private Long id;
private String username;
private String name;
private Short age;
private BigDecimal balance;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Short getAge() {
return age;
}
public void setAge(Short age) {
this.age = age;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
由于我们的项目是使用SpringData-JPA的,所以需要加上JPA的注解:
package com.microserver.cloud.entity;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User implements Serializable{
@Id //标记为主键
@GeneratedValue(strategy=GenerationType.AUTO) //主键自增
private Long id;
@Column //标记为数据库字段,下同
private String username;
@Column
private String name;
@Column
private Short age;
@Column
private BigDecimal balance;
//Get与Set方法省略
}
然后编写DAO层,在src/main/java下的com.microserver.cloud.dao包下创建一个UserRepository接口:
package com.microserver.cloud.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.microserver.cloud.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long>{
}
这里方法体不需要实现其他方法定义,JpaRepository父接口已经定义了常用的增删改查操作方法:
package org.springframework.data.jpa.repository;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
@NoRepositoryBean
public abstract interface JpaRepository<T, ID>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>
{
public abstract List<T> findAll();
public abstract List<T> findAll(Sort paramSort);
public abstract List<T> findAllById(Iterable<ID> paramIterable);
public abstract <S extends T> List<S> saveAll(Iterable<S> paramIterable);
public abstract void flush();
public abstract <S extends T> S saveAndFlush(S paramS);
public abstract void deleteInBatch(Iterable<T> paramIterable);
public abstract void deleteAllInBatch();
public abstract T getOne(ID paramID);
public abstract <S extends T> List<S> findAll(Example<S> paramExample);
public abstract <S extends T> List<S> findAll(Example<S> paramExample, Sort paramSort);
}
这个例子很简单,我们这里就不再编写Service层了,直接Controller层调用DAO层。我们在src/main/java下的com.microserver.cloud.controller包下创建一个UserController类:
package com.microserver.cloud.controller;
import java.util.Optional;
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 com.microserver.cloud.dao.UserRepository;
import com.microserver.cloud.entity.User;
@RestController
public class UserController {
@Autowired
private UserRepository userDao;
@GetMapping("/findById/{id}")
public User findById(@PathVariable Long id){
Optional<User> userOptional = this.userDao.findById(id);
return userOptional.get();
}
}
其中@RestController定该Controller对外暴露REST风格的服务,@RestController为@Controller与@ResponseBody的组合注解,旨在提供json格式的REST服务。
在application.yml文件中加入以下配置:
server:
port: 7900//在application.yml文件中加入以下配置:
spring:
jpa:
generate-ddl: false//为启动时是否生成DDL语句,这里我们已经自己编写好了,所以false不生成;
show-sql: true//为是否打印SQL语句,这里设置为true
hibernate:
ddl-auto: none//为hibernate每次启动时是否自动创建表单(jpa依赖hibernate),这里要求启动时不做DDL的处理,所以设置为none;
datasource:
platform: h2//用于设置数据源使用的数据库类型,这里为H2;
schema: classpath:schema.sql//用于设置数据库启动时的建表语句文件位置
data: classpath:data.sql//用于设置数据库启动时的数据库信息插入语句文件位置
logging:
level:
root: info//用于设置根目录的日志级别
org.hibernate: INFO//用于设置hibernate的日志级别
org.hibernate.type.descripter.sql.BasicBinder: TRACE
org.hibernate.type.descripter.sql.BasicExtractor: TRACE//用于设置hibernate输出SQL语句到日志,与上面的“spring.jpa.show-sql”配合
com.microserver: DEBUG//设置我们自己业务代码的日志级别为DEBUG,用于体现细粒度的日志,方便查找Bug。
这里需要将MicroserverSimpleProviderUserApplication移到com.microserver.cloud包中,即是在SpringBoot中有个约定俗成的规矩,因为Application要扫描子包的注解,所以要求Application启动类要在业务包的最顶层:
所有准备工作完毕,我们来启动该工程。执行MicroserverSimpleProviderUserApplication启动类的main方法:
控制台次最后出现MicroserverSimpleProviderUserApplication : Started字样,即启动成功。
我们在浏览器中访问刚刚编写的服务,来获取id为1的用户信息:
二、编写一个服务消费者
我们来编写一个服务消费者来调用服务提供者的服务。新构建一个名为“microserver-simple-consumer-movie”(Artifact同名)的工程,和之前的构建方式一样(外部依赖只需要web,jpa和h2不需要),这里不再赘述。
User类和microserver-simple-provider-user中的一样,这是要把有关JPA的所有注解去除:
package com.microserver.cloud.entity;
import java.io.Serializable;
import java.math.BigDecimal;
public class User implements Serializable{
private Long id;
private String username;
private String name;
private Short age;
private BigDecimal balance;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Short getAge() {
return age;
}
public void setAge(Short age) {
this.age = age;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
我们编写一个MovieController,用来调用服务提供者的服务:
package com.microserver.cloud.controller;
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 org.springframework.web.client.RestTemplate;
import com.microserver.cloud.entity.User;
@RestController
public class MovieController {
@Autowired
private RestTemplate restRemplate;
@GetMapping("/movie/{id}")
public User findUserById(@PathVariable Long id){
return this.restRemplate.getForObject("http://localhost:7900/findById/"+id, User.class);
}
}
这里使用RestTemplate类来调用Http服务,使用的是getForObject,分别传入请求的HTTP服务的URL、返回的实体类类型。
由于RestTemplate需要开发人员来配置其构造函数的参数SimpleClientHttpRequestFactory对象,所以这里只写@Autowired不进行配置,是不能自动注入的。新建RestTemplateConfig类,添加@Configuration作为配置类,类似在XML中去编写了RestTemplate类的注入配置:
package com.microserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置
* 这是一种JavaConfig的容器配置,用于spring容器的bean收集与注册,并通过参数传递的方式实现依赖注入。
* "@Configuration"注解标注的配置类,都是spring容器配置类,springboot通过"@EnableAutoConfiguration"
* 注解将所有标注了"@Configuration"注解的配置类,"一股脑儿"全部注入spring容器中。
*
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);//在Spring容器中注入RestTemplate对象
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
//初始化RestTemplate对象需要的Factory工厂类,biang注入到Spring容器中
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//读取反馈超时时间5000ms
factory.setConnectTimeout(15000);//连接超时时间15000ms
return factory;
}
}
然后在src/main/resource下创建一个application.yml,里面只需要配置端口号就行了:
server:
port: 7901
最后别忘记把MicroserverSimpleConsumerMovieApplication类移到包的最顶层。
分别先后启动服务提供者和服务消费者,然后访问服务消费者的movie请求:
拿到了userId为1的用户信息。至此我们的消费者创建成功并且成功消费了服务提供者提供的服务信息。
总结
Spring Cloud 专注于为典型用例提供良好的开箱即用体验,并提供涵盖其他用例的扩展机制。
- 分布式/版本化配置
- 服务注册和发现
- 路由
- 服务到服务呼叫
- 负载平衡
- 断路器
- 全局锁
- 领导层选举和集群状态
- 分布式消息传递