简介
Spring Cloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路 由,负载均衡,熔断器,控制总线,集群状态等等功能。
其主要涉及的组件包括:
Netflix
- Eureka:注册中心
- Zuul:服务网关
- Ribbon:负载均衡
- Feign:服务调用
- Hystrix:熔断器
- 等等
版本
Spring Clound 和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 |
创建父工程
微服务中需要同时创建多个项目,先创建一个父工程,后续的工程都以这个工程为父,使用 Maven的聚合和继承。统一管理子工程的版本和配置
<!--聚合父工程-->
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<packaging>pom</packaging>
<relativePath/>
</parent>
<!--这里指定版本信息-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<mapper.starter.version>2.1.5</mapper.starter.version>
<mysql.version>5.1.46</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<!--这里的import一定要记得加上,不然子工程报错-->
<scope>import</scope>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!--lombok简化代码-->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
子工程
版本号都由父工程进行统一的管理,子工程无需添加版本号,只需要简单的引入依赖
在微服务当中我们要有服务生产者,也需要有服务的消费者
REST风格Web服务
@RestController// 声明它是一个rest风格的控制器
@RequestMapping("/user")
@RefreshScope// 配置文件改变自动刷新属性
public class UserController {
// 注入service层
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userService.queryById(id);
}
}
微服务的启动器
@SpringBootApplication
@SpringBootApplication来标注这是一个springboot的应用,被标注的类是一个主程序, SpringApplication.run(App.class, args);传入的类App.class必须是被@SpringBootApplication标注的类。例如:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
@SpringBootApplication是一个组合注解,其作用相当于以下几个注解的组合:
1.@SprootbootConfiguration(或者 @Configuration) 配置类,对应配置文件,本质上是一个@Componet,只是更有意义,见名知意
2. @EnableAutoConfiguration:开启自动配置,将主配置类所在包及其下面所有后代包的所有注解扫描
3. @ComponentScan :配置需要扫描的包
@SpringBootConfiguration
这个注解的作用与@Configuration作用相同,都是用来声明当前类是一个配置类.可以通过@Bean注解生成IOC容器管理的bean.在ConsumerApplication中定义bean,并在UserController中注入使用
ConsumerApplication:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[]args){
SpringApplication.run(ConsumerApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
UserController:
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
String url = "http://localhost:9091/user/" + id;
return restTemplate.getForObject(url, User.class);
}
}
@EnableAutoConfiguration
@EnableAutoConfiguration是springboot实现自动化配置的核心注解,通过这个注解把spring应用所需的bean注入容器中.@EnableAutoConfiguration源码通过@Import注入了一个ImportSelector的实现类AutoConfigurationImportSelector
@Import({
AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector,这个ImportSelector最终实现根据我们的配置,动态加载所需的bean.
AutoConfigurationImportSelector的完成动态加载实现方法源码如下:
// annotationMetadata 是@Import所在的注解,这里指定是EnableAutoConfiguration
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
//加载XXConfiguration的元数据信息(包含了某些类被生成bean条件),继续跟进这个方法调用,就会发现加载的是:spring-boot-autoconfigure jar包里面META-INF的spring-autoconfigure-metadata.properties
AutoConfigurationMetadata autoConfigurationMetadata
= AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//获取注解里设置的属性,在@SpringBootApplication设置的exclude,excludeName属性值,其实就是设置@EnableAutoConfiguration的这两个属性值
AnnotationAttributes attributes
= this.getAttributes(annotationMetadata);
//从spring-boot-autoconfigure jar包里面META-INF/spring.factories加载配置类的名称,打开这个文件发现里面包含了springboot框架提供的所有配置类
List<String> configurations
= this.getCandidateConfigurations(
annotationMetadata,
attributes);
//去掉重复项
configurations = this.removeDuplicates(configurations);
configurations = this.sort(
configurations,
autoConfigurationMetadata);
//获取自己配置不需要生成bean的class
Set<String> exclusions = this.getExclusions(
annotationMetadata,
attributes);
//校验被exclude的类是否都是springboot自动化配置里的类,如果存在抛出异常
this.checkExcludedClasses(configurations, exclusions);
//删除被exclude掉的类
configurations.removeAll(exclusions);
//过滤刷选,满足OnClassCondition的类
configurations = this.filter(
configurations,
autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(
configurations,
exclusions);
//返回需要注入的bean的类路径
return StringUtils.toStringArray(configurations);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
@ComponentScan
使用过spring框架的小伙伴都知道,spring里有四大注解:
- @Service,
- @Repository,
- @Component,
- @Controller
用来定义一个bean.@ComponentScan注解就是用来自动扫描被这些注解标识的类,最终生成ioc容器里的bean.可以通过设置@ComponentScan basePackages,includeFilters,excludeFilters属性来动态确定自动扫描范围,类型已经不扫描的类型.默认情况下:它扫描所有类型,并且扫描范围是@ComponentScan注解所在配置类包及子包的类,
RestTemplate模板工具类
Spring提供了一个RestTemplate模板工具类,对基于HTTP的客户端进行了封装,并且实现了对象与json的序列化 和反序列化,非常方便。RestTemplate并没有限定HTTP的客户端类型,而是进行了抽象,目前常用的3种都有支持:
- HTTPClient
- OkHTTP
- JDK原生的URLConnection(默认的)
ConsumerApplication:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
UserController:
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
String url = "http://localhost:9091/user/" + id;
return restTemplate.getForObject(url, User.class);
}
}
Eureka注册中心
思考
user-service对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务 提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂 的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来 测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
架构图
Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
提供者:启动后向Eureka注册自己信息(地址,提供什么服务),可以是Spring Boot应用,也可以是其它任意技术实现,只要对外提供的是REST风格服务即 可。
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
心跳(续约):提供者定期通过HTTP方式向Eureka刷新自己的状态
搭建
Eureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建Web工程使用Eureka,可以 使用Spring Boot方式搭建。
eureka注册中心
服务端
添加依赖
<!-- Eureka服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动类
@EnableEurekaServer //声明当前应用时Eureka服务
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置文件
# 指定端口号
server.port=10086
# 给服务起个名字
spring.application.name=eureka-sevver
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
eureka.client.service-url.defaultZone=http://127.0.0.1:10086/eureka
# 不注册自己
eureka.client.register-with-eureka=false
# 不拉取服务
eureka.client.fetch-registry=false
客户端注册
在服务消费工程consumer-demo上添加Eureka客户端依赖;可以使用工具类DiscoveryClient根据服务名称获取对 应的服务地址列表。
添加依赖
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端
public class UserConsumerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
配置文件
# 给服务起个名字
spring.application.name=consumer-demo
# eureka服务地址
eureka.client.service-url.defaultZone=http://127.0.0.1:10086/eureka
修改代码
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
// String url = "http://localhost:9091/user/" + id;
// 拉取注册在eureka注册中心的所有叫user-service的服务
List<ServiceInstance> serviceInstances =
discoveryClient.getInstances("user-service");
// 我们只注册了一个,那么只需要获取0号下标的索引即可
ServiceInstance serviceInstance = serviceInstances.get(0);
// 拼接字符串形成一个url地址
String url = "http://" +
serviceInstance.getHost() +
":" +
serviceInstance.getPort() +
"/user/" +
id;
return restTemplate.getForObject(url, User.class);
}
}
高可用的eureka-server
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会 把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任 意一个节点,都可以获取到完整的服务列表信息。