Java微服务
微服务概述
微服务是一种架构风格,把单个项目拆分成多个服务(比如页面展示、数据库操作),每个服务都能独立运行,各服务单元使用网络通信协议进行协作,最终构建起完整的系统。
为什么需要微服务?
传统软件工程中项目都是单体架构,这种方法在业务初期十分高效,开发简单,部署便捷,可随着业务的持续扩展,项目的可扩展性、可维护性和可迭代性都会变得越来越差,并且无法通过引入框架(如Spring)彻底解决,必须采取一种新的架构模式,对项目进行更细粒度的拆分,实现解耦业务模块,服务独立迭代,微服务架构就此应运而生了。
微服务是如何解决这些问题的?
微服务解决单体架构的核心逻辑在于解耦与自治重构系统,将原本单体架构下相互纠缠的各个功能模块,拆解为可独立运行的服务进程,避免原本单个程序的改动影响全局的情况,增强隔离性,便于维护;同时服务进程可以分别采用不同的存储形式,甚至技术栈,极大增强了开发模式的灵活性。
微服务的特征
微服务的主要特征包括:
单一职责:每个服务只完成指定业务,代码边界清晰;
自治:每个服务拥有独立存储模式,扩展和维护系统方便;
隔离性强:每个服务可以独立扩展、维护,而不影响其他服务或组件;
灵活性高:微服务可使用不同的语言、数据库和环境开发,可以为不同需求选择合适的工具。
微服务的应用场景
微服务降低程序耦合度,提升可扩展性的同时也带来了更高的开发、部署和维护成本,首先多个服务的通信成本就是项目的额外开销;并且管理多个交互的服务也远比管理单个程序更复杂,加上多个服务独立部署,使用微服务的运维成本也更高;同时要求每个服务能独立运行,这进一步要求每个服务的开发者都要具备全栈开发能力,抬高了团队合作的门槛。
由此可见,微服务并非可以完全替代单体架构的新模式,而是针对特殊需求的解决方案,比如业务复杂,但模块边界清晰的场景,如电商系统(可分为用户、订单、支付等独立服务);需要弹性扩展,流量波动的场景,如直播(随观众增多扩展服务);以及技术栈多样和多团队协同开发的场景,而传统的简单需求仍然适合单体架构。
微服务与分布式
因为之前做过spark下的电影推荐系统,其中用到分布式的思想和微服务十分类似,分布式是指利用多个计算或存储资源处理数据,最后整合结果,达到突破单机限制,扩展硬件性能的效果,而微服务是将单体架构拆分成多个独立运行的服务,将服务组合成完整项目,其本质都是分治思想的运用。
微服务作为一种分治架构,一定要使用分布式落地,但分布式不一定是微服务,简单来说,微服务是程序设计层面的“分布式”,其底层一定依赖分布式,更详细的对比可参考分布式与微服务的关系。
微服务的挑战
- 服务拆分粒度。拆分过粗会导致程序耦合性过高,拆分过细则会极大提高通信开销,通常要先粗后细经过几次拆分才能达到合理范围。
- 集群地址维护。微服务集群可能会频繁扩容缩容,传统静态地址完全失效,通常需引入注册中心对所有服务进行管理,实现动态配置同步。
- 健康状态感知。集群中单个服务可能因各种原因处于存活但不可用状态,其他服务无法感知,此时若不断向该服务发起请求,会导致故障扩散,需要建立熔断机制。
- 服务远程调用。跨网络调用会带来许多问题,通信成本,通信协议与序列化统一,调用链路追踪等。
- 分布式事务一致性。微服务中每个服务都有独立数据库,订单创建和扣库存无法通过事务保证一致性,高并发场景下可使用可靠消息队列同步管理事务,但不可避免的会带来延时。
- 日志追踪难。微服务中日志分布在多台机器上,一次请求出错需逐台机器排查,效率极低,同时难以感知整体流程耗时分布。
SpringBoot
SpringBoot入门
SpringBoot是Spring框架的进一步封装,内部整合了Spring和SpringMVC,同时内嵌了Tomcat服务器,抛弃了原本复杂的XML配置文件,全部采用注解标识,该框架通过启动器starter引入和管理依赖,并可以将整个项目打包为jar格式,实现独立部署,与微服务的自治特征高度契合,所以是微服务开发的主流选择之一。
SpringBoot的核心设计理念是约定大于配置,即框架预先定义一套设计规则,开发者遵循该配置时无需额外指定配置,只有当配置方式与预先定义不同时才需手动指定。
比如将控制层Controller、逻辑层Service等类放在启动类的子包下;默认读取application.properties或application.yml作为配置文件;引入启动器依赖自动加入容器,框架自动配置Servlet和Tomcat。
下面是快速创建SpringBoot的方法
1. 创建SpringBoot工程
因为网络原因,要将服务器URL替换为阿里云的start.aliyun.com

2. 引入web启动器
在pom.xml文件中添加如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
也可以点击添加启动器:

选择添加web

3. 创建控制层文件
在启动器文件SpringootApplication同级文件夹下创建控制层包,新建控制层文件,项目结构如下:

控制层文件简单测试方法为,使用注解映射URL路径,在返回的消息体中提示测试方法,示例代码如下:
package org.test.springboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@org.springframework.stereotype.Controller
public class Controller {
@RequestMapping("/test")
@ResponseBody
public String test() {
return "springboot 测试方法";
}
}
4. 启动主程序并测试
启动类的代码是自动生成的,其中@SpringBootApplication注解是标识当前工程为springboot,随后调用类.run方法即启动web项目,同时启动服务器,无需手动配置。
// 声明当前工程为Springboot
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
出现下图说明项目启动成功


5. 打包独立部署
首先注释掉pom.xml中带skip的一行,该标识告诉maven构建生命周期时跳过这个插件的运行,这会导致打包后的文件找不到主清单。

随后使用maven的打包操作

执行完毕后在项目的target目录下会生成jar包,进入目录在地址栏输入cmd,同时输入命令java -jar 包名即可启动该包独立运行。

此时在地址栏输入http://localhost:8080/test,仍然可以得到结果,证明springboot打包后的jar文件可以独立运行。
初体验小结
相比以往先配置beans.xml,配置springmvc-config.xml,再添加web项目和tomcat服务器的流程,使用springboot构建网络项目确实简单太多,另外基于springboot的项目打包后可以独立运行,部署为服务也很方便,为微服务的实现提供了技术支持。
整合MyBatis
1. 引入MyBatis启动器和数据库驱动
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 配置文件中创建数据源对象
#配置数据源application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db118?serverTimezone=GMT&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
#配置mapper映射文件的位置,如果使用xml映射的话
mybatis.mapper-locations=classpath:org.test.springboot.dao*.xml
3. 新建接口,实例加入容器
前面的文章写过很多次User类了,这里省略,直接展示接口映射方法,SpringBoot虽然全面采用注解方案,但MyBatis仍然可用xml文件的方式,仍然可以在resources文件夹下新建与接口文件相同的目录并配置xml。
public interface UserDao {
@Select("select * from userinfo")
@Results({
// 主键映射
@Result(column = "id",property = "uid",id = true),
@Result(column = "name",property = "name")
})
List<User> findAll();
}
控制层展示数据,扫描持久层接口并将其实例注入 Spring 容器,在控制层中通过自动注入获取接口实例,示例代码如下:
// 扫描持久层接口,将实例添加到容器
@MapperScan("org.test.springboot.dao")
// 控制层实例,类内所有方法加@ResponseBody
@RestController
public class Controller {
@Autowired
private UserDao userDao;
@RequestMapping("/test")
public String test() {
List<User> users = userDao.findAll();
System.out.println(users);
return users.toString();
}
}
页面输出如下:

使用FreeMarker模板引擎
以前用过的jsp要先将jsp代码编译成servlet,再使用servlet输出内容,而SpringBoot自带的嵌入式servlet容器对jsp的解析有使用限制,我们通常使用其他模板引擎,这里介绍一种新的模板引擎Freemarker。
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
模板视图应该放在resources/templates下,在配置文件中添加前后缀匹配:
#视图的前缀
spring.freemarker.template-loader-path=classpath:/templates
#视图的后缀
spring.freemarker.suffix=.ftl
2. 控制层返回查询对象
控制层借助Model对象,向域中添加对象列表,修改注解为@Controller,将返回值跳转到指定find页面,修改示例代码如下:
@org.springframework.stereotype.Controller
public class Controller {
@Autowired
private UserDao userDao;
@RequestMapping("/test")
public String test(Model model) {
List<User> users = userDao.findAll();
model.addAttribute("users", users);
return "find";
}
}
3. find.ftl展示数据
FreeMarker功能不如jsp对Java支持的全面,集合对象是没法一次识别的,需要使用循环遍历读取每个元素再展示,示例如下:
<html>
<head>
<meta charset="UTF-8">
<title>Freemarker</title>
</head>
<body>
Hello!
<#list users as user>
${user}
</#list>
</body>
</html>
如果要引入其他文件如jquery,则需要在主目录下新建webapp文件夹,整体目录结构如图:

使用Redis数据库
相比Mysql等有表结构的关系型数据库,Redis是大数据背景下常用的非关系数据库,其中数据大多通过Key-Value形式存储,其底层使用C实现,又因为redis其实是内存型的数据库,所以其访问速度要远远快于关系型数据库,所以项目中通常将其作为缓存使用,用于减轻数据库的访问压力,有关更底层更细节的性能分析可见Redis抗住百万高并发的秘密。
因为最新版本的Redis都停止了对Windows的支持,所以本文不做示例,仅记录使用方法。
基本使用方法
Redis默认端口6379,服务端可存储操作多种不同类型的数据,String类型使用set key value进行赋值或修改,使用get key查询,append追加;hash表使用hset 名称 key value的形式存储或修改,使用hgetall获取所有元素,hget 名称获取指定元素,list使用lpush 名称 值将元素添加到表头,rpush元素添加到表尾,lrange 名称 start end返回指定范围内的元素。
SpringBoot整合redis
首先导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件中写入配置,此时redis服务端应启动
# 指定IP和端口
spring.redis.host=localhost
spring.redis.port=6379
redis作为缓存
控制层中聚合RedisTemplate并自动注入,若要将对象存入缓存,需确保对象实现java.io.Serializable接口(开启对象序列化支持),获取绑定指定名称后的缓存,遇到查询先检查缓存内是否有数据,有则从中取,否则到数据库查询并加入缓存,修改后的控制层代码如下:
@org.springframework.stereotype.Controller
public class Controller {
private RedisTemplate redisTemplate;
@Autowired
private UserDao userDao;
@RequestMapping("/test")
public String test(Model model) {
List<User> users=null;
// 绑定缓存名称,获取缓存
BoundListOperations boundListOperations=redisTemplate.boundListOps("buffer");
if(boundListOperations.size()>0){
System.out.println("从缓存中取出数据");
// 取出全部数据
users=boundListOperations.range(0,-1);
}else{
System.out.println("从数据库中取出数据");
users = userDao.findAll();
// 将结果追加到缓存中
boundListOperations.leftPush("buffer",users);
}
model.addAttribute("users", users);
return "find";
}
}
Dubbo框架
SpringBoot是开发具体服务的框架,Dubbo则是阿里整合服务的框架(现在是apache的顶级框架,真还得是大厂),负责解决多个服务间的通信问题,服务提供者将自己的地址注册到注册中心如ZooKeeper,消费者从服务中心获取服务者地址,封装了HTTP等底层协议,实现跨服务调用,同时提供了复杂均衡等治理手段。
安装注册中心ZooKeeper
ZooKeeper是开源的分布式协调服务中心,可用于管理分布式系统中的数据和服务,服务提供方注册地址和服务到该中心,供消费者查询服务地址,详细介绍可见ZooKeeper看这一篇。
安装Java环境和tomcat
本次使用Linux作为注册中心,首先执行更新一下源,避免源错误产生问题,然后直接使用命令安装jdk8,命令如下:
sudo apt-get update
sudo apt install openjdk-8-jdk
使用java -version发现有输出就说明安装成功,随后配置环境:
# 打开配置文件
nano ~/.bashrc
# 将如下内容复制到文件末尾
# 配置 JAVA_HOME(OpenJDK 8 安装路径)
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
# 配置 JRE_HOME(可选,一般无需单独配置)
export JRE_HOME=${JAVA_HOME}/jre
# 配置 CLASSPATH(Java 类路径,. 表示当前目录)
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
# 将 Java 的 bin 目录加入系统 PATH
export PATH=${JAVA_HOME}/bin:$PATH
# 最后ctrl+x保存退出
到tomcat9下载服务器,复制到Linux服务器中解压,可以修改conf/server.xml中Connector标签的端口号,避免冲突。

最后到/bin目录下执行./startup.sh,启动服务器,最后访问127.0.0.1:8088出现如下界面说明安装成功。

注意版本对应!!!
折腾好几遍,最后因为下载的是tomcat10,支持jdk11以上的版本,还是尽量用老版本,jdk8+tomcat9,仍然更新且稳定。
安装配置ZooKeeper
到apache的zookeeper官网下载zookeeper安装包,本次示例使用的是稳定的3.8.5包。

下载并解压后将其中的配置文件示例/conf/zoo_sample.cfg赋值为zoo.cfg文件作为真实配置文件,其中dataDir可设置为自己的输出文件路径。

修改完成后进入bin目录,使用./zkServer.sh start启动注册中心服务
安装可视化工具dubbo-admin
注册中心启动后没有任何提示信息,很不方便,所以需要一种可视化管理服务的方法。
新版本其实不支持这种方式查看,但是因为相关资料比较少,只有这个能实现,并且支持本文的ZooKeeper版本。
首先到war包下载地址下载要使用的war包,把该war包放到tomcat/webapp下,重新启动服务,使用127.0.0.1:8088/dubbo-admin可进入如下页面,登录用户名和密码默认都是root。

服务提供者注册服务
传统的调用方法是基于接口的,即提供者实现接口,重写其中方法,消费者聚合接口类,容器自动注入,通过接口类调用提供者重写的接口方法。
首先创建接口类,并通过maven的install部署到本地仓库。
public interface TestInterface {
public void getMessage();
}
随后复制本项目坐标,引入服务提供者。
<groupId>org.test</groupId>
<artifactId>Interface</artifactId>
<version>1.0-SNAPSHOT</version>
<!--此外还需要引入日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!--dubbo的启动器-->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>1.0.0</version>
</dependency>
在包路径com.Service下新建Service类,继承中间类TestInterface并重写getMessage方法,最后用导入dubbo包的注解Service修饰,测试的简单代码如下:
package com.Service;
import org.test.TestService;
// 修饰服务的注解
@com.alibaba.dubbo.config.annotation.Service
public class Service implements TestService {
@Override
public String getMessage() {
return "dubbo service is running";
}
}
resource目录下新建application.properties配置文件,用于配置与注册中心ZooKeeper的交互,规定如下:
# 修改本项目地址避免冲突
server.port=8082
# dubbo提供者配置,注册中心名称
spring.dubbo.application.name=dubbo-server
# 到注册中心登记自己的信息,注册中心IP加默认端口2181
spring.dubbo.registry.address=zookeeper://192.168.145.129:2181
# 声明dubbo通信协议和本机服务端口,提供者将持续监控20880端口
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
#暴露服务所在包的位置
spring.dubbo.scan=com.Service
主函数中运行,可在部署的ZooKeeper见到服务数和应用数的变化。

消费者订阅服务
消费者首先引入依赖,包括中间接口、dubbo启动器,日志以及本次示例要用到的web启动器支持。
<dependency>
<groupId>org.test</groupId>
<artifactId>Interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 此外还需要引入日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!--dubbo的启动器-->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后配置文件,包括注册中心的名称地址,以及订阅服务类的包所在位置
server.port=8081
#dubbo消费者配置
spring.dubbo.application.name=dubbo-client
spring.dubbo.registry.address=zookeeper://192.168.145.129:2181
#订阅服务所在包的位置
spring.dubbo.scan=com.Controller
订阅类使用集成接口类的方法实现远程调用,订阅类使用Reference注解修饰,实现类似自动填充Autowired的效果,但底层原理完全不同。
Reference会先借助Dubbo将方法调用转换为RPC远程请求,通过注册中心找到服务者提供的地址,再通过网络发送请求并接收结果。
消费者类的示例代码为:
package com.Controller;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.test.TestService;
@org.springframework.stereotype.Controller
public class Controller {
@Reference
private TestService testService;
@RequestMapping("/service")
@ResponseBody
public String getMessage() {
return this.testService.getMessage();
}
}
服务端和消费端都启动时,可通过消费端地址访问服务端的方法。

Spring Cloud
Dubbo是微服务框架的落地,还有很多不足,比如需要额外配置注册中心,Spring Cloud则是分布式微服务架构的一站式解决方案,它将市面上成熟的微服务框架整合起来,使用Spring Boot的思想再次封装,提供的功能有:
- 分布式/版本化配置
- 服务注册与发现
- 服务间调用
- 负载均衡
详细文档可见官网:https://springframework.org.cn/projects/spring-cloud/,以及SpringCloud从入门到精通。
不同版本的Spring Cloud使用伦敦地铁站命名,与Spring Boot的版本对应如图:

继承父工程
微服务项目通常要有多个模块,为了简化配置通常使用父工程指定依赖的版本,不同服务继承父工程获取依赖版本,简化配置的同时也保证版本对应,该工程只用于约束子模块依赖版本,故其中存放src的代码目录可直接删掉。
新建普通maven工程的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.test</groupId>
<artifactId>cloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 声明工程为 依赖管理容器,不编译生成 jar/war 包 -->
<packaging>pom</packaging>
<repositories>
<repository>
<id>aliyun maven</id> <!-- 与镜像的mirrorOf="aliyun maven"匹配 -->
<url>https://maven.aliyun.com/repository/central</url>
</repository>
</repositories>
<!-- 继承父工程,其中规定了常用依赖的适配版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/>
</parent>
<!-- 指定版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<mysql.version>8.0.22</mysql.version>
<mybatis.version>2.1.1</mybatis.version>
</properties>
<!--子模块继承时,使用下面的依赖会自动拉取父工程规定的版本-->
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
上述配置首先继承自父工程spring-boot-starter-parent,该工程作为Spring Boot的父POM,其核心功能就是预定义了常用的依赖版本,这些依赖与当前parent版本对应。
后续通过<properties>指定依赖版本,使用<dependencyManagement>通过el表达式获取版本信息,继承的子工程需要显式调用,在导入相同依赖时会自动使用父工程的版本,若父工程使用<dependencies>声明依赖,子工程会自动继承。
微服务的节点就是Spring Boot模块,所有的微服务都在一个工程下,所以我们在当前工程下新建Spring Boot的cloud-server模块,作为服务提供者,在pom.xml中引入父工程坐标即可获得所有依赖,添加成功后可在左侧发现maven的向上提示,点击即可跳入父工程pom.xml文件。

子模块引入MyBatis、web等启动器依赖,点击左侧版本跳入,有的版本由父工程cloud-parent指定,有的由父工程继承的spring-boot-starter-parent指定,可见继承控制版本由父工程和指定配置spring-boot-starter-parent共同决定。
父工程依赖测试
本文以两个简单表作持久层查询为例学习Spring Cloud的运行流程,实现消费者向提供者拉取查询服务的场景,两个表的结构如下:


首先封装一个maven类型的中间模块store,用于存储查询得到的信息,其pom.xml会自动继承父工程。
使用父工程的lombok依赖类的相关方法,其中@Data注解用于生成get、set和toString方法,@AllArgsConstructor用于生成全参构造,@NoArgsConstructor用于生成无参构造,两个类的实例代码如下:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String sex;
private String phone;
public String hobby;
public String description;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private int id;
private String address;
private int uid;
// 使用下面代码测试可见构造函数和set方法及toString均成功生成
public static void main(String[] args) {
Address address = new Address();
address.setId(1);
address.setAddress("address");
address.setUid(1);
System.out.println(address);
}
}
将中间模块通过pom.xml依赖引入后,使用简单示例测试父工程依赖是否可用,首先生成MyBatis接口如下:
package com.mapper;
import com.pojo.User;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
@Repository
public interface UserMapper {
@Select("select * from userinfo")
ArrayList<User> AllUsers();
}
随后在控制层建立映射:
package com.controller;
import com.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/test")
@ResponseBody
public String test() {
return "connect success";
}
@GetMapping("/getall")
@ResponseBody
public String AllUsers() {
return userMapper.AllUsers().toString();
}
}
在启动器中指定要扫描的包位置
package org.test.serveruser;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@MapperScan("com.mapper")
@SpringBootApplication
@ComponentScan("com.controller")
public class ServerUserApplication {
public static void main(String[] args) {
SpringApplication.run(ServerUserApplication.class, args);
}
}
最后通过yml配置文件绑定设置,如端口号和数据库连接信息:
server:
port: 8081
spring:
application:
name: user-server
datasource:
url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package:
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.eagles: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
相比xml,yml可读性更强,是现代Spring Boot和Spring Cloud的主流配置文件方案。
此时访问指定URL可见数据库信息如下图,说明依赖成功继承:

服务拉取
服务开放
服务端新增根据id查找方法,Mapper接口中,id参数使用el表达式,即#{变量名}填充,示例为:
@Select("select * from userinfo where id=#{id}")
User getUserById(int id);
控制层映射路径使用单括号{变量名}表示路径变量,参数可使用注解@PathVariable("路径变量")修饰参数,将路径变量赋值给参数,示例如下:
@GetMapping("/user/{uid}")
@ResponseBody
public User FindUserById(@PathVariable("uid")int id) {
return userMapper.getUserById(id);
}
此时访问http://localhost:8081/user/1即可返回查找id为1的数据。
远程调用——RestTemplate
基本的拉取服务需要借助RestTemplate类完成,该类可基于核心配置类的方法创建,即在核心配置类中使用@Bean修饰返回实例的方法添加到容器中,启动器注解@SpringBootApplication也用于声明核心配置类。
故启动器部分代码为:
package org.test.customer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
@ComponentScan("com.controller")
@SpringBootApplication
public class CustomerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
控制层聚合RestTemplate类并自动注入,调用类的getForObject方法,并传入URL和返回类的字节码,使用实例如下:
@Controller
public class customer {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/customer/{id}")
@ResponseBody
// 远程调用方法
public User findUserById(@PathVariable("id")int id) {
return restTemplate.getForObject("http://localhost:8081/user/"+id, User.class);
}
}
此时访问调用者URLhttp://localhost:8082/customer/1即可查询到用户信息。
RestTemplate是Spring MVC提供的方法,使用这种方法进行远程调用无需语言一致,只需要IP、端口和请求参数,但这种方法已经即将过时,在高并发和复杂场景下有很大不足,并且服务提供者的URL还需要开发者维护拼接,代码冗余且容易出错。
Spring Cloud微服务生态中,多使用注册中心EUREKA和远程调用Feign的搭配,实现远程调用。
配置注册中心EUREKA
注册中心EUREKA实现了服务治理功能,用于实现服务的自动注册与发现,同时为了保证服务可用性,注册中心使用30s的心跳检测机制,服务每隔30s向服务中心发送状态信息,超时过长会将认为该服务已经下线。
与Dubbo搭配的ZooKeeper相比,Spring Cloud最大的优点是具备 “一站式、开箱即用的微服务生态”,与 Spring 技术栈无缝兼容的同时,还提供了服务管理、网关和熔断等功能,详细介绍可见:Eureka原理和使用。
新建模块Eureka在pom.xml中导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
配置文件yml中使用如下字段声明地址:
server:
port: 10001
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka/
最后给启动器添加注解@EnableEurekaServer,启动后可访问http://localhost:10001,进入如下页面:

经过注册中心拉取服务
服务提供者引入客户端依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件中增加注册配置:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10001/eureka/
启动启动器后可在注册中心中看到注册的服务:

服务名称就是配置中spring下的application:name。
可修改启动器配置实现多个服务:

在虚拟机选项中增加修改端口的参数-Dserver.port=8083避免端口冲突:

运行后可在注册中心发现两个不同端口的同名服务:

客户端配置一致,拉取时最大的不同是使用服务名替换原本的IP+端口号,同时需在RestTemplate添加负载均衡注解,使该类能识别服务器,实例代码如下:
@GetMapping("/customer/{id}")
@ResponseBody
// 远程调用方法
public User findUserById(@PathVariable("id")int id) {
// return restTemplate.getForObject("http://localhost:8081/user/"+id, User.class);
String service="http://USER-SERVER/user/";
return restTemplate.getForObject(service+id, User.class);
}
// 启动器代码
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
可服务名称仅仅代替IP+端口号,其实际意义会不会小了一些?
传统RestTemplate远程调用方法最大的缺陷是需要将URL写死,但在微服务中:
- 服务可能部署多实例(集群),地址可能是 8081、8082、8083 等;
- 服务可能动态扩缩容(新增 / 下线实例),地址会频繁变化;
- 服务可能迁移(IP 变更),URL 也会失效。
通过服务名称,该类会自动向服务中心查询所有可用地址,而无需开发者指定IP和端口,同时结合Spring Cloud LoadBalancer(或 Ribbon)完成负载均衡调用。
远程调用组件Feign
经过服务中心的传递,原有服务提供方的IP+端口已经被换为服务名称,但是调用者仍然需要使用URL的方式调用服务,参数复杂时往往难以维护,远程调用组件Feign提供了优雅发送http请求的方法,通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程。
首先新增模块cloud-feign并引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
新增接口类UserClient用于确认远程调用信息,通过注解@FeignClient("服务名")声明当前类为客户端并绑定服务,方法内可使用@GetMapping("url")的方式指定拉取服务的具体参数,完整示例代码如下:
import com.eagles.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("USER-SERVER")
public interface UserClient {
@GetMapping("/user/{uid}")
User findUserById(@PathVariable("uid") int uid);
}
用户模块引入Feign组件依赖后只需继承示例并自动注入,即可调用指定服务,而无需关注具体使用方法与url,示例代码如下:
@Service
public class cotroller{
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@GetMapping ("/order/{oid}")
@ResponseBody
public User findOrderById(int id) {
//远程调用 根据用户id查询用户信息
//User user = restTemplate.getForObject("http://USER-SERVER/user/" + order.getUserId(), User.class);
User user = userClient.findUserById(order.getUserId());
return user;
}
}
这么看来这个远程调用组件并没有解决URL与服务的维护问题,只是将这些问题剥离出来成为一个单独组件,真正调用时无需关注具体细节,Feign与服务名+RestTemplate相比,只是封装程度不同。
另外Spring Cloud还有负载均衡和网关功能,目前还不涉及企业级并发需求,所以不作详细展开,可见:
Spring Cloud负载均衡原理
Spring Cloud网关Gateway
总结
本文围绕微服务技术落地展开,从基础支撑到框架选型层层递进:首先以 SpringBoot 为基石,其通过将依赖封装为 “启动器”(Starter),打包生成的可执行 JAR 包内置内嵌服务器,无需额外部署容器,为微服务拆分与独立部署提供关键支撑。
随后介绍分布式远程调用框架 Dubbo:它以 RPC 为核心,借助 ZooKeeper 实现服务注册与发现 —— 提供者自动注册服务,消费者动态获取地址,ZooKeeper 还能监控服务健康;但 Dubbo 聚焦服务通信,需额外整合其他组件才能满足完整微服务需求。
最后聚焦主流的 Spring Cloud 生态:针对微服务配置复杂、版本冲突问题,它通过父工程dependencyManagement统一声明依赖版本,子模块继承后无需重复指定版本;在此基础上,基于 Eureka 实现服务治理:提供者自动注册,消费者按服务名拉取服务并实现负载均衡,充分体现其 “一站式微服务生态” 优势,无需额外整合即可覆盖核心需求。
更多推荐


所有评论(0)