1、什么是Dubbo?
简单来说,dubbo就像是个看不见的手,负责专门从注册中心nacos调用注册到nacos上面的服务的,因为在微服务环境下不同的功能模块可能在不同的服务器上。dubbo调用服务就像是在调用本地的服务一样。
分布式调用与高并发处理 Dubbo分布式调用_分布式之间的调用-CSDN博客
2、Dubbo实现
(1)创建父项目
添加相关依赖
<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>com.zj</groupId>
<artifactId>Dubbo_demo2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Dubbo_demo2</name>
<url>http://maven.apache.org</url>
<properties>
<dubbo.version>3.2.4</dubbo.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.version>3.0.2</spring-boot.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<lombok.version>1.8.28</lombok.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>
<!-- SpringCloud Alibaba 微服务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot 依赖配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- lombok 依赖配置 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}}</version>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- bootstrap 启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2)创建子模块interface
添加下面的依赖文件,为啥要添加mybatis依赖呢?因为有些时候需要指定实体类对应的表和实体类和表字段之间的对应关系等。lombok就是为了生成实体类的get\set等方法。
<!-- Mybatis plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
在test数据库创建User表
create database test;
CREATE TABLE user
(
id
BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT
'姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年
龄',
PRIMARY KEY (id)
);
在interface下创建User实体类
@TableName("user")
@Data
public class User {
private Long id;
private String name;
private Integer age;
}
创建结果集
package com.zj.common;
import java.io.Serializable;
/*结果集*/
/*实现序列化因为dubbo传输的是二进制数据*/
public class CommonResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
}
创建IUserService接口
package com.zj.service;
import com.zj.common.CommonResult;
import com.zj.pojo.User;
/*用户接口*/
public interface IUserService {
CommonResult<User> createUser(User user);
CommonResult<User> findAllUser(User user);
CommonResult<User> updateUser(User user);
CommonResult<User> deleteUser(Long id);
}
(3)创建子模块user-service
这个模块就是对用户业务的具体实现,这里面的业务是需要注册到nacos上面的。
添加依赖文件
<dependencies>
<!--springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--dubbo整合spring boot的依赖包-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--dubbo注册到nacos上的依赖包-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<!-- Mybatis plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!--MySQL 数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--接口的依赖-->
<dependency>
<groupId>com.zj</groupId>
<artifactId>1interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
创建mapper层获取数据库的数据。
package com.zj.mapepr;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zj.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
别忘了在启动类上添加上这三个注解。
@EnableDubbo
@MapperScan("com.zj.mapper")
@SpringBootApplication
public class userService
{
public static void main( String[] args )
{
SpringApplication.run(userService.class, args);
}
}
@EnableDubbo
注解是使用Dubbo进行服务化开发的关键注解,它使得Spring Boot应用能够方便地集成和使用Dubbo框架提供的分布式服务能力。当你将这个注解添加到你的Spring Boot应用的配置类上时,它会做以下几件事情:
开启Dubbo自动配置:它会触发Spring Boot的自动配置机制,自动配置Dubbo相关的Bean。
服务暴露:在Spring容器中,标注了
@Service
(注意这里不是Spring的@Service
,而是Dubbo的@Service
注解)的类会被识别为Dubbo服务,并且会被注册到注册中心(比如Zookeeper),从而可以被其他服务发现和调用。服务引用:它允许你的应用通过Dubbo去引用其他服务。通常是通过
@Reference
注解来注入其他Dubbo服务。配置加载:它会加载Dubbo相关的配置,这些配置可以是写在
application.properties
或application.yml
文件中,也可以是通过其他方式定义的Dubbo配置类。依赖注入:它支持将Dubbo的Reference(服务引用)注入到Spring管理的Bean中,使得远程服务调用就像调用本地方法一样简单。
服务监控:
@EnableDubbo
还负责集成Dubbo的监控功能,比如可以通过配置将服务的调用次数、调用时间等信息发送到监控中心。
创建service层实现IUserService接口,并将该业务注册到nacos上。
package com.zj.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zj.common.CommonResult;
import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/*用户的业务层*/
@DubboService //将该业务发布到注册中心nacos
public class UserServiceImpl implements IUserService{
@Autowired
private UserMapper userMapper;
/*添加用户*/
@Override
public CommonResult<User> createUser(User user) {
CommonResult<User> userCommonResult = new CommonResult<>();
int insert = userMapper.insert(user);
if(insert > 0){
userCommonResult.setCode(200); //结果的编码
userCommonResult.setMsg("success"); //结果的描述
}else {
userCommonResult.setCode(500);
userCommonResult.setMsg("fail");
}
return userCommonResult;
}
/*查询用户,这个地方不能加泛型*/
@Override
public CommonResult findAllUser(User user) {
CommonResult userCommonResult = new CommonResult<>();
//查询条件构造器
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
//id不为空的话使用id查询
lqw.eq(user.getId() != null,User::getId,user.getId());
//name不为空的话使用id查询
lqw.eq(user.getName() != null,User::getName,user.getName());
//age不为空的话使用id查询
lqw.eq(user.getAge() != null,User::getAge,user.getAge());
//查询用户
List<User> users = userMapper.selectList(lqw);
userCommonResult.setCode(200);
userCommonResult.setMsg("success");
userCommonResult.setData(users);
return userCommonResult;
}
//更新用户
@Override
public CommonResult updateUser(User user) {
CommonResult userCommonResult = new CommonResult<>();
if(user.getId() == null){
userCommonResult.setCode(500);
userCommonResult.setMsg("id = null");
return userCommonResult;
}
/*条件构造器*/
LambdaUpdateWrapper<User> lqw = new LambdaUpdateWrapper<>();
lqw.set(user.getName() != null, User::getName, user.getName())
.set(user.getAge() != null, User::getAge, user.getAge())
.eq(User::getId, user.getId());
//更新
int update = userMapper.update(null, lqw);
if(update > 0){
userCommonResult.setCode(200);
userCommonResult.setMsg("success");
}else {
userCommonResult.setCode(500);
userCommonResult.setMsg("fail");
}
return userCommonResult;
}
//删除
@Override
public CommonResult<User> deleteUser(Long idr) {
CommonResult<User> userCommonResult = new CommonResult<>();
if(idr == null){
userCommonResult.setCode(500);
userCommonResult.setMsg("id = null");
return userCommonResult;
}
int i = userMapper.deleteById(idr);
if(i > 0){
userCommonResult.setCode(200);
userCommonResult.setMsg("success");
}else {
userCommonResult.setCode(500);
userCommonResult.setMsg("fail");
}
return userCommonResult;
}
}
需要注意的是查询全部的用户的时候不能写泛型,因为查询出来的不是User而是个List。
创建user-service模块的配置文件application.yml文件
dubbo:
application:
# 项目名称
name: user-service
# 通讯协议
protocol:
name: dubbo
# 端口号 设置端口为 -1 表示 dubbo 自动扫描并使用可用端口(从20880开始递增),避免了端口冲突的问题。
port: -1
registry:
# 服务的注册地址
address: nacos://192.168.66.100:8848
server:
port: 8001
#配置数据源
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.66.100:3306/test?serverTimezone=UTC
username: root
password: 123456
(4)服务接口测试
启动user-service模块
创建测试项目,使apifox测试,选择Dubbo类型的项目。
导入接口数据,注意导入的是nacos的数据。
(5)创建消费者模块
导入消费者模块的依赖
<dependencies>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--dubbo整合spring boot的依赖包-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--dubbo注册到nacos上的依赖包-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<!--这个不能忘记,因为在消费者模块中也就是consume中需要使用userService模块的服务-->
<dependency>
<groupId>com.zj</groupId>
<artifactId>1interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
添加配置文件。
消费者也是需要调用nacos中的服务的因此也需要将服务注册到nacos上。因此需要添加下面的配置,除此之外还需要添加Thymeleaf的配置。
spring-boot很多配置都有默认配置,比如默认页面映射路径为:classpath:/templates/*.html
同样静态文件路径为:classpath:/static/
thymeleaf是前端的页面因此这里创建子模块是consume是专门消费服务的,也就是前台发请求显示页面的模块。
在consume 模块的resource目录下创建template文件夹和static文件夹。
dubbo:
application:
name: consume-service
protocol:
name: dubbo
registry:
address: nacos://192.168.66.100:8848
server:
port: 8002
spring:
thymeleaf:
cache: false
mode: HTML5
encoding: utf-8
content-type: text/html
在templates下面创建视图页面。
首页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="/addUser">添加用户</a>
<a href="/user/showUser">查询用户</a>
</body>
</html>
添加用户
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<form action="/user/addUser" method="post">
<input type="hidden" name="id" value="0">
用户名字:<input type="text" name="name" placeholder="请输入名字">
用户年龄:<input type="text" name="age" placeholder="请输入年龄">
<input type="submit" value="添加用户">
</form>
</body>
</html>>
OK页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<head>
<meta charset="UTF-8">
<title>成功页面</title>
</head>
<body>
操作成功请<a href="/index">返回</a>
</body>
</html>
error页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
执行失败<a href="/index">返回</a>
</body>
</html>
显示用户信息
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>查询用户</title>
</head>
<body>
<table>
<tr>
<td>序号</td>
<td>名字</td>
<td>用户年龄</td>
<td>操作</td>
</tr>
<tr th:each="u : ${users}">
<td th:text="${u.id}"></td>
<td th:text="${u.name}"></td>
<td th:text="${u.age}"></td>
<td>
<a th:href="@{/user/delete(id=${u.id})}">删除</a>
<a th:href="@{/user/toUpdate(id=${u.id})}">更新</a>
</td>
</tr>
</table>
</body>
</html>
更新用户页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>更新用户</title>
</head>
<body>
<form action="/user/update" method="post">
<input type="hidden" name="id" th:value="${user.id}">
用户名字:<input type="text" name="name" placeholder="请输入名字" th:value="${user.name}">
用户年龄:<input type="text" name="age" placeholder="请输入年龄" th:value="${user.age}">
<input type="submit" value="更新用户">
</form>
</body>
</html>
然后创建两个controller,一个负责页面跳转的。
package com.zj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
/*视图层控制器*/
@Controller
public class IndexController {
/*页面跳转*/
@GetMapping("/{page}")
public String index(@PathVariable String page) {
return page;
}
/*忽略favicon*/
@GetMapping("favicon.ico")
@ResponseBody
void noFavicon() {
}
}
一个负责处理具体的业务请求的。
package com.zj.controller;
import com.zj.common.CommonResult;
import com.zj.pojo.User;
import com.zj.service.IUserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
//远程调用添加用户的操作,添加用户的服务是user-service模块提供的,需要注意的是要引入interface模块依赖
@DubboReference
private IUserService userService;
/*添加用户*/
@PostMapping("/addUser")
public String addUser(User user){
CommonResult<User> result = userService.createUser(user);
//判断是不是添加成功
if (result.getCode() == 200){
return "redirect:/ok";
}else {
return "redirect:/error";
}
}
/*查询用户*/
@GetMapping("/showUser")
public ModelAndView selectUser(){
//既要返回视图还需要返回结果
ModelAndView modelAndView = new ModelAndView();
User user1 = new User();
CommonResult<User> allUser = userService.findAllUser(user1);
//视图添加数据
modelAndView.addObject("users",allUser.getData());
//返回视图
modelAndView.setViewName("showUser");
return modelAndView;
}
/*根据id查询数据跳转到更新数据的页面*/
@GetMapping("/toUpdate")
public ModelAndView toUpdateUser(Long id){
ModelAndView modelAndView = new ModelAndView();
User user = new User();
user.setId(id);
CommonResult allUser = userService.findAllUser(user);
List<User> data = (List<User>)allUser.getData();
if (data.size() > 0){
modelAndView.addObject("user",data.get(0)); //取出第一个数据
modelAndView.setViewName("update");
}
return modelAndView;
}
/*更新用户*/
@PostMapping("/update")
public String updateUser(User user){
System.out.println("user:"+user);
CommonResult<User> userCommonResult = userService.updateUser(user);
if (userCommonResult.getCode() == 200){
return "redirect:/ok";
}else{
return "redirect:/error";
}
}
/*删除用户*/
@GetMapping("/delete")
public String deleteUser(Long id){
CommonResult<User> userCommonResult = userService.deleteUser(id);
if (userCommonResult.getCode() == 200){
return "redirect:/ok";
}else {
return "redirect:/error";
}
}
}
需要注意的就是这个@DubboReference注解,就是获取nacos的服务实例,注入到userService变量中。
另外根据id查询用户的时候需要注意的是查询结果是个List所以取出第一个来。
最后启动类加注解。
/*告诉 Spring Boot 不要自动配置数据源。使用我配置的数据源。*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDubbo
public class userConsume
{
public static void main( String[] args )
{
SpringApplication.run(userConsume.class, args);
}
}
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})第一次碰到这个注解,不叫的话会出错。
最后是配置文件。
dubbo:
application:
name: consume-service
protocol:
name: dubbo
registry:
address: nacos://192.168.66.100:8848
server:
port: 8002
#配置视图
spring:
thymeleaf:
cache: false
mode: HTML5
encoding: utf-8
content-type: text/html
最后启动user-service模块和user-consum模块,在浏览器输入locahost:8082/index就行啦。
详细的项目代码已经上传。