关系
Spring Boot是框架。将各个组件集合在一起,方便快速开发web应用。
Spring Cloud基于Spring Boot,限定了一组特定的组件,从而可以方便地进行微服务工程的开发。
Spring Cloud Alibaba在Spring Cloud的基础上进行了一些调整,将某些组件替换为阿里巴巴的组件,同样是为了方便微服务工程的开发。
也就是说,Spring Cloud Alibaba依赖于Spring Cloud。
故而,想要使用Spring Cloud Alibaba,必须在Spring Boot的基础上引入Spring Cloud,然后引入Spring Cloud Alibaba。
查看官方的版本说明:
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
其中的 毕业版本依赖关系(推荐使用) 当前内容为:
Spring Cloud Version | Spring Cloud Alibaba Version | Spring Boot Version |
---|---|---|
Spring Cloud 2020.0.0 | 2021.1 | 2.4.2 |
Spring Cloud Hoxton.SR8 | 2.2.5.RELEASE | 2.3.2.RELEASE |
Spring Cloud Greenwich.SR6 | 2.1.4.RELEASE | 2.1.13.RELEASE |
Spring Cloud Hoxton.SR3 | 2.2.1.RELEASE | 2.2.5.RELEASE |
Spring Cloud Hoxton.RELEASE | 2.2.0.RELEASE | 2.2.X.RELEASE |
Spring Cloud Greenwich | 2.1.2.RELEASE | 2.1.X.RELEASE |
Spring Cloud Finchley | 2.0.4.RELEASE(停止维护,建议升级) | 2.0.X.RELEASE |
Spring Cloud Edgware | 1.5.1.RELEASE(停止维护,建议升级) | 1.5.X.RELEASE |
该表指明了使用Spring Cloud Alibaba时,要同时引入的Spring Boot版本与Spring Cloud版本。
以表中第一行为例,其Spring Cloud Alibaba Versio为2021.1,那么:
- 需引入Spring Cloud Version的版本为Spring Cloud 2020.0.0。
- 需引入Spring Boot Version的版本为2.4.2。
- 一般来说版本向下兼容,因此可以使用比该指定更高的版本。但使用指定版本可能更稳定。
父子工程
一般地,使用SpringCloud开发往往会使用父子工程。
所谓父子工程,指的是先创建一个父工程,在pom.xml中用<dependencyManagement>
指定组件的<version>
;然后在其下创建子工程,修改pom.xml中的<parent>
来指向父工程,这样子工程的依赖将从父工程继承。
这样即可将依赖统一划归父工程管理,多个子工程使用的依赖将保持一致。
父工程仅仅负责对依赖的管理,不包含任何具体的逻辑。
创建父工程
- 点击File→New→Project,在左侧选择Spring Initializr,配置好后点Next。
- 左上角选择Spring Boot版本,左侧通常选择Developer Tools/Lombok,以及Web/Spring Web。然后点击Finish。
这样一个父工程就创建好了。
打开父工程的pom.xml,添加<dependencyManagement>
:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意其中的版本与官方文档中的 毕业版本依赖关系(推荐使用) 保持一致。
pom.xml源码:
<?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 https://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.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>alibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>SpringCloudAlibaba</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
其中的<parent>
,该标签下的多个属性指明了父工程。
而<parent></parent>
结束后紧跟的<groupId>
、<artifactId>
及<version>
3个标签则指明了本工程。这3个标签及内容会复制到子工程中。
特别注意其中的<packaging>
标签,需要将其设置为pom
。
创建子工程。
- 打开Project面板,在父工程上点右键,选择New→Module。
- 在左侧选择Spring Initializr,配置好后点Next。
- 左上角选择Spring Boot版本,左侧什么也不要选。然后点击Finish。
这样一个子工程就创建完毕了。
打开子工程的pom.xml,将其<parent>
标签下的内容删除,更换为上一步提到的父工程的3个标签。
nacos
打开父工程的pom.xml,按下Ctrl+鼠标左键来点击<artifactId>spring-cloud-alibaba-dependencies</artifactId>
。此时会显示 spring-cloud-alibaba-dependcies-2.2.1.RELEASE.pom 文件内容。
在其中分别搜索 nacos 和 nacos-discovery,可以找到内容:
<nacos.client.version>1.2.1</nacos.client.version>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
从而可知当前spring-cloud-alibaba所需的nacos客户端版本为1.2.1
,spring-cloud-starter-alibaba-nacos-discovery版本为2.2.1.RELEASE
。
nacos客户端
已知所需nacos客户端版本为1.2.1
,因此从官网下载对应版本客户端。官网的nacos目前已分为1.x与2.x两个分支,因此下载1.x最新版本1.4.2
。
下载后,运行nacos。
子工程
依赖
打开子工程的pom.xml,在其中引入nacos:
<dependencies>
...
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
由于父工程负责版本管理,因此子工程只需要引入即可,不需要再设置<version>
属性。
设置
需要在子工程的配置文件application.yml中设置工程名与nacos地址。
server:
port: 8081
spring:
application:
name: provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
一般来说也会在该处指定当前子工程的端口号。
服务提供者必须配置spring.application.name
和spring.cloud.nacos.discovery.server-addr
两个属性。但服务消费者可以不配置。
特别注意:若使用Ribbon,则服务名不能包含_
。
服务提供者与消费者
创建2个子工程,一个作为服务提供者,另一个作为服务消费者。
其pom.xml与application.yml设置相同,都使用上述设置。注意调试时端口号需要有所区别。
服务提供者
服务提供者定义一个简单的测试接口:
@RestController
public class ProviderTestController {
@RequestMapping("/test")
String test() {
return "this is the test string.";
}
}
运行工程。查看nacos后台,可以看到服务已顺利注册。
服务消费者
服务消费者根据访问方式不同,处理也不同。这里使用RestTemplate
访问。
可以使用Ribbon来自动负载均衡,仅需引入@LoadBalanced
注解。但需注意的是非Ribbon方式与Ribbon方式并不兼容,一旦使用了Ribbon,则RestTemplate
会被处理,以至于常规方式将无法使用。
非Ribbon方式
首先定义一个Configuration
类来注册RestTemplate
:
@Configuration
public class SpringCloudAlibabaConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
然后定义访问类:
@RestController
public class ConsumerTestController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test() {
// 获取指定服务下所有的实例。
List<ServiceInstance> serviceInstanceList = this.discoveryClient.getInstances("provider");
ServiceInstance serviceInstance = serviceInstanceList.get(0);
String uri = serviceInstance.getUri().toString();
String url = uri + "/test";
String r = restTemplate.getForObject(url, String.class);
return r;
}
}
其中RestTemplate.getForObject()
的第二个参数指明了返回值的类型。
运行后,调用/test
接口即可。
Ribbon方式
上述方法,需要人为获取所有服务实例,然后选择一个来调用。然后还需要人为拼接接口,非常烦琐。
Ribbon会自动按可配置策略选择一个实例来调用,且不再需要拼接接口:
注册RestTemplate
时添加@LoadBalanced
注解:
@Configuration
public class SpringCloudAlibabaConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
然后定义访问类:
@RestController
public class ConsumerTestController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test() {
String r = restTemplate.getForObject("http://provider/test", String.class);
return r;
}
}
其url使用"http://" + "服务名" + "接口名"
的形式。
访问即可。
请求传参
上述方式RestTemplate
转发请求,仅仅转发了请求的url,不包含参数。若要包含参数,根据请求的不同,方式不同:
get方式
对于get方式,直接将参数拼接在url后即可。
例如,要增加一个参数value
:
String value = "abc123";
String r = restTemplate.getForObject("http://provider/test" + "?value=" + value, String.class);
这样即可将value
参数传给服务提供者。
post方式
对于post方式,需将参数放入一个LinkedMultiValueMap
中:
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("value", "abc123");
String r = this.restTemplate.postForObject("http://provider/test", paramMap, String.class);
Feign
Feign
可替换RestTemplate
,用于转发请求。实际上,Feign
的本质就是生成一个RestTemplate
来转发请求。
使用Feign
的流程为:
-
引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
由于在父工程中已引入了
org.springframework.cloud
,故而引入依赖时不需要指定版本号。 -
在启动类上添加注解开启
Feign
:@EnableFeignClients
-
开发
Feign
客户端:@FeignClient("provider") public interface Client { @RequestMapping("/test") String test(); }
其中:
@FeignClient("provider")
指定了nacos的服务名为provider。@RequestMapping("/test")
指定了要访问的接口为/test
。
-
调用:
@Resource Client client; @RequestMapping("/test") public String test() { String r = client.test(); return r; }
这样即可请求成功。
Feign
传参,必须将参数放在接口定义中,并指明value属性。例如:
@FeignClient("provider")
public interface Client {
@RequestMapping("/testName")
String testName(@RequestParam(value = "name") String name);
@RequestMapping("/testUser")
String testUser(@RequestBody User user);
}
部署
上述测试,都是基于IDEA直接运行。而实际环境中,无论服务提供者还是服务消费者,都需要部署到Tomcat。在此过程中会遇到两个问题:
- 端口问题。
- 工程名问题。
端口问题
一个服务提供者,在yml文件中配置了server.port,使用IDEA运行一切正常。然而部署到Tomcat后,发现服务没有注册给naocs。
这是因为nacos的注册类绑定了一个监听事件,监听到容器端口后即会向注册中心注册。然而,当使用外部容器时,不能监听到事件,因此注册失败。
故而,解决方案为:容器初始化时人为地将服务提供者端口注册给注册中心。
考虑到部署在容器内的应用,必须通过容器指定的端口才能访问,因此注册的服务提供者端口必须与容器端口相同。
@Configuration
public class NacosConfig implements ApplicationContextAware {
@Autowired(required = false)
private NacosAutoServiceRegistration registration;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (registration != null) {
try {
Integer tomcatPort = new Integer(getTomcatPort());
registration.setPort(tomcatPort);
} catch (Exception e) {
e.printStackTrace();
}
registration.start();
}
}
/**
* 获取外部tomcat端口
*/
public String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return port;
}
}
该配置仅服务提供者需要,服务消费者是不需要的。
工程名问题
设服务提供者的工程名为myPorject,接口名为test
。
在IDEA中直接运行的工程,若没有在配置文件中设置server.servlet.context-path
,那么消费者访问的路径是不包含工程名的。此时访问接口的路径为:
http://127.0.0.1:8080/test
然而部署到Tomcat后,需带工程名访问,此时访问接口的路径为:
http://127.0.0.1:9080/myProject/test
因此,最佳实践应该是将工程名作为接口的一部分。即服务消费者获取到nacos实例后,拼接的接口名不再是/test
,而是/myProject/test
。
同时,为了IDEA调试与Tomcat的表现统一,需在服务提供者的配置文件中设置server.servlet.context-path
为工程名。
其他
nacos中显示的已注册服务地址不对:查看windows的网络和Internet,打开适配器选项,看是否有其他网卡,例如虚拟机或翻墙软件所使用的虚拟网卡。关闭即可。