springcloud入门案例

简介

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集群中的任 意一个节点,都可以获取到完整的服务列表信息。

猜你喜欢

转载自blog.csdn.net/BrightZhuz/article/details/110244203