目录
什么是分布式系统
单机架构、集群架构、分布式架构
Dubbo的概念
Dubbo的核心组件
Dubbo的常用注解
Dubbo的高级特性:
序列化特性安全
地址缓存
超时机制
重试机制
多版本灰度发布
负载均衡
集群容错
服务降级
服务限流
结果缓存
Dubbo实战:
项目介绍
搭建架构
编写pojo实体类
编写mapper持久层接口
编写producer生产者代码
编写api模块代码
编写consumer消费者代码
引入相关依赖(dubbo、zookeeper、Dubbo_api)
编写service层代码(这里的@Service就是Spring的注解)
编写PageController(该控制层的作用就是跳转到对应页面)
编写UserController
编写index.html主页
配置dubbo和zookeeper
新增用户业务实现
添加用户测试
查询用户业务实现
查询用户测试
修改用户业务实现
修改用户测试
删除用户业务实现
部分图片来自百战尚学堂
什么是分布式系统
分布式就是很多“人”一起干不一样的事,合起来就是一件大事,意思就是一个大的业务系统,拆分成一个个小的业务模块,分别部署到不同的机器上
优点:解耦,代码复用性更高,独立部署,独立测试
单机架构、集群架构、分布式架构
单机架构就是一个“人”干所有事,集群架构就是多个“人”干相同的事,分布式架构就是不同的“人”干不同的事,但是合起来就是在干一件大事
单机架构的缺点就是当这个“人”出了一些故障,那么整个系统都将崩溃,代码耦合度较高,复用性不高,测试起来复杂。优点就是体系小,开发快,集群架构也是类似的
分布式架构的优点就是代码耦合度低,没有单点故障问题,一个“人”出错了,并不会影响其他“人”,代码复用性高,测试方便。缺点就是会导致体系变得庞大
Dubbo的概念
Dubbo是一个RPC框架,作用就是让远程调用像本地调用一样简单、方便,通常使用Zookeeper+Dubbo实现分布式系统
什么是RPC:RPC让你用别人家的东西就像自己家的一样。
RPC两个作用:
- 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法
- 隐藏底层网络通信的复杂性,让我们更加专注业务逻辑。
常用的RPC框架
RPC是一种技术思想而非一种规范或协议。
常见 RPC 技术和框架:
- 阿里的 Dubbo/Dubbox、Google gRPC、Spring Cloud。
Dubbo的核心组件
注册中心Registry
在Dubbo微服务体系中,注册中心是其核心组件之一。Dubbo通过注册中心实现了分布式环境中各服务之间的注册与发现,是各个分布式节点之间的纽带。
其主要作用如下:
- 动态加入:一个服务提供者通过注册中心可以动态地把自己暴露给其他消费者,无须消费者逐个去更新配置文件。
- 动态发现:一个消费者可以动态地感知新的配置、路由规则和新的服务提供者,无须重启服务使之生效。
- 动态调整:注册中心支持参数的动态调整,新参数自动更新到所有相关服务节点。
- 统一配置:避免了本地配置导致每个服务的配置不一致问题。
服务提供者Provider
服务的提供方
服务消费者Consumer
调用远程服务的服务消费方
监控中心Monitor
主要负责监控统计调用次数和调用时间等。
Dubbo的常用注解
@Service、@Reference
@Service:将类注册到注册中心
@Reference:将类从注册中心拉取到本地
Dubbo的高级特性
高级特性其实就是@Service、@Reference注解的一些属性
序列化特性安全
网络传输数据都是以二进制的形式进行传输的,但调用方请求的出入参数都是对象,此时就需要这些对象实现了Serializable方法,即可序列化,这样才能在网络中传输
打个比方:就像送快递一样,你的货物就相当于那个对象,货物需要打包相当于对象需要序列化为二进制,当对象到你手上的时候就是一串二进制,就像快递包到了你手上一样,此时就需要拆开快递包即可拿到自己的货物,就相当于反序列化一样将二进制变回对象
地址缓存
注册中心挂了,服务是否可以正常访问?
答案:
因为dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。服务提供者地址发生变化时,注册中心会通服务消费者。
超时机制
问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
- dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
服务生产者端配置超时时间
使用timeout属性配置超时时间,默认值1000,单位毫秒。
@Service(timeout = 3000) //当前服务3秒超时
消费端配置超时时间
@Reference(timeout=2000)// 远程注入
private IOrderService iOrderService;
重试机制
超时问题:
如果出现网络抖动,则会出现请求失败。
如何解决
Dubbo提供重试机制来避免类似问题的发生。
重试机制配置
@Service(timeout = 3000,retries = 2)
多版本灰度发布
Dubbo提供多版本的配置,方便我们做服务的灰度发布,或者是解决不兼容的问题。
灰度发布(金丝雀发布):
当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
版本迁移步骤
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
多版本配置
老版本服务提供者配置
@Service(version = "1.0.0") 设置版本为1.0
新版本服务提供者配置
@Service(version = "2.0.0") 设置版本为2.0
新版本服务消费者配置
@Reference(version="2.0.0") 代表拉取的是2.0版本的该对象
private IOrderService iOrderService;// 订单服务
如果不需要区分版本,可以按照以下的方式配置 :
@Reference(version="*")
private IOrderService iOrderService;// 订单服务
负载均衡
Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务通常会部署多个实例。
问题:
订单服务生产者会出现单点故障。
如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。
Dubbo内置负载均衡策略
- RandomLoadBalance:随机负载均衡,随机的选择一个,默认负载均衡。
- RoundRobinLoadBalance:轮询负载均衡。
- LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。
- ConsistentHashLoadBalance:一致性哈希负载均衡,相同参数的请求总是落在同一台机器上。
负载均衡策略配置
如果不指定负载均衡,默认使用随机负载均衡。我们也可以根据自己的需要,显式指定一个负载均衡。
生产者服务
@Service(timeout=3000,retries=3,loadbalance="roundrobin")
消费者服务
@Reference(timeout=2000,loadbalance="roundrobin")
参数:
- random:随机负载均衡
- leastactive:最少活跃调用数,相同活跃数的随机
- roundrobin:轮询负载均衡
- consistenthash:一致性哈希负载均衡
集群容错
Dubbo框架为服务集群容错提供了一系列好的解决方案,在此称为dubbo服务集群容错模式。
容错模式
- Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器,默认重试2次,使用retries配置。一般用于读操作
- Failfast Cluster : 快速失败,只发起一次调用,失败立即报错。通常用于写操作。
- Failsafe Cluster : 失败安全,出现异常时,直接忽略。返回一个空结果。日志不重要操作。
- Failback Cluster : 失败自动恢复,后台记录失败请求,定时重发。非常重要的操作。
- Forking Cluster:并行调用多个服务器,只要有一个成功即返回。
- Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。 同步要求高的可以使用这个模式。
集群容错配置
在消费者服务配置
@Reference(cluster = "failover")
private IOrderService iOrderService;
服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
两种场景:
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
为什么需要降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
服务降级方式
第一种
mock=force:return null
含义:
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
第二种
mock=fail:return null
含义:
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
服务降级演示
@Reference(timeout = 2000,mock = "force:return null")
private IOrderService iOrderService;
服务限流
并发控制
@Service(executes = 10)
注意:
服务端并发执行(或占用线程池线程数)不能超过10个
连接控制
@Service(actives= 10)
注意:
占用连接的请求的数不能超过10个。
结果缓存
//通过注解中的cache属性配置结果缓存机制
@Reference(cache="lru")
Dubbo实战
项目介绍
需求:完成用户的增删改查操作
技术栈:
前端:html、thymeleaf
分布式:Dubbo、Zookeeper、SpringMVC
持久化:MySql、MyBatisPlus
项目架构:
搭建架构
1、在Dubbo_father的pom文件中定义需要用的依赖的版本并配置jdk版本,在配置中有一个标签叫做dependencyManagement,这个标签的作用就是声明该依赖的版本,当子模块引入该依赖时,无需定义版本,可以直接使用父项目定义好的版本
<?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>com.itbaizhan</groupId>
<artifactId>Dubbo_father</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>Dubbo_producer</module>
<module>Dubbo_api</module>
<module>dubbo_consumer</module>
</modules>
<properties>
<dubbo.spring.starter.version>2.7.6</dubbo.spring.starter.version>
<dubbo.registry.zookeeper.version>2.7.6</dubbo.registry.zookeeper.version>
<mybatisplus.spring.starter.version>3.5.0</mybatisplus.spring.starter.version>
<mysql.connector.version>5.1.49</mysql.connector.version>
</properties>
<!-- 通过management提前声明这些依赖所使用的版本,等子项目使用的时候即可不需要定义版本,直接使用父项目声明的版本 -->
<dependencyManagement>
<dependencies>
<!-- Dubbo 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.spring.starter.version}</version>
</dependency>
<!-- zookeeper 注册中心 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo.registry.zookeeper.version}</version>
</dependency>
<!-- Mybatis plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.spring.starter.version}</version>
</dependency>
<!--MySQL 数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 设置jdk版本 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
因为Dubbo_consumer模块是SpringBoot项目,所以他的父项目是SpringBoot的起步依赖spring-boot-stater-parent,所以我们需要添加第二个父项目,通过dependencyManagement标签即可实现
<?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.7.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 引入第二个父亲 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>Dubbo_father</artifactId>
<type>pom</type>
<version>1.0-SNAPSHOT</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<groupId>com.itbaizhan</groupId>
<artifactId>dubbo_consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo_consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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-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>
编写pojo实体类
创建数据库和表
创建数据库
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)
);
创建User实体类(User实体类需要实现Serializable接口,前面有提到过)
package com.itbaizhan.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
public Long id;//用户id
public String name;//用户名字
public int age;//用户年龄
}
mapper模块引入pojo模块
编写mapper持久层接口
引入MybatisPlus和mysql依赖
<?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">
<parent>
<artifactId>Dubbo_producer</artifactId>
<groupId>com.itbaizhan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mapper</artifactId>
<dependencies>
<!-- 引入pojo -->
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- MyBatisPlus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
编写UserMapper
package com.itbaizhan.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itbaizhan.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
编写producer生产者代码
将producer修改为SpringBoot项目,因为SpringBoot项目的父类是spring-boot-stater-parent,而本身项目的父类是dubbo_producer,所以需要通过dependencyManagement引入第二个父项目
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<!-- 引入第二个父项目 -->
<dependency>
<artifactId>Dubbo_producer</artifactId>
<groupId>com.itbaizhan</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<modelVersion>4.0.0</modelVersion>
<artifactId>producer</artifactId>
</project>
引入mapper模块,通过依赖传递的方式引入pojo模块
<dependencies>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>mapper</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
编写启动类并扫描持久层接口创建相应的实现类放到spring容器中
package com.itbaizhan.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.itbaizhan.mapper")
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class,args);
}
}
配置数据源
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.138.101/test
username: root
password: 123456
编写api模块代码
添加pojo模块依赖
<?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">
<parent>
<artifactId>Dubbo_father</artifactId>
<groupId>com.itbaizhan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Dubbo_api</artifactId>
<dependencies>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
编写api模块service层接口(该接口的作用是定义producer模块的service规范)
package com.itbaizhan.service;
import com.itbaizhan.pojo.User;
import java.util.List;
public interface UserService {
//新增用户
public void add(User user);
//根据id删除用户
public void delete(Long userId);
//根据id修改用户
public void update(User user);
//查询所有用户
public List<User> selectAll();
//根据id查询用户
public User selectById();
}
producer模块引入相关依赖
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<!-- 引入第二个父项目 -->
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>Dubbo_father</artifactId>
<type>pom</type>
<version>1.0-SNAPSHOT</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modelVersion>4.0.0</modelVersion>
<artifactId>producer</artifactId>
<dependencies>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>mapper</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<!-- 引入dubbo_api模块 -->
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>Dubbo_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
编写producer模块service层代码(类的上方需要添加Dubbo的@Service注解,将该类注册到注册中心)
package com.itbaizhan.producer.service;
import com.itbaizhan.mapper.UserMapper;
import com.itbaizhan.pojo.User;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Service
public class UserService implements com.itbaizhan.service.UserService {
@Autowired
private UserMapper userMapper;
/**
* 添加用户
* @param user
*/
@Override
public void add(User user) {
userMapper.insert(user);
}
/**
* 根据id删除用户
* @param userId
*/
@Override
public void delete(Long userId) {
userMapper.deleteById(userId);
}
/**
* 根据id修改用户
* @param user
*/
@Override
public void update(User user) {
userMapper.updateById(user);
}
/**
* 查询所有用户
* @return
*/
@Override
public List<User> selectAll() {
return userMapper.selectList(null);
}
/**
* 根据id查询用户
* @param userId
* @return
*/
@Override
public User selectById(Long userId) {
return userMapper.selectById(userId);
}
}
配置dubbo和zookeeper
dubbo:
#项目名字
application:
name: myProducer
#注册中心地址
registry:
address: zookeeper://192.168.138.101:2181
timeout: 50000
#端口号和协议名
protocol:
port: 20880
name: dubbo
#扫描的包
scan:
base-packages: com.itbaizhan.producer.service
运行producer模块启动类,通过dubbo-admin查看是否注册到了注册中心
编写consumer消费者代码
引入相关依赖(dubbo、zookeeper、Dubbo_api)
<!-- 引入dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<!-- 引入dubbo_api模块 -->
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>Dubbo_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
编写service层代码(这里的@Service就是Spring的注解)
通过@Reference拉取Dubbo_api模块的UserService,那么此时会有小伙伴问了:我们注册的是producer模块的UserService,为什么拉取的确实Dubbo_api模块的UserService呢?
这是因为采用了jdk动态代理的模式,就是拉取这个接口的实现类,以接口引用的方式实现调用
package com.itbaizhan.dubbo_consumer.service;
import com.itbaizhan.pojo.User;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Reference
private com.itbaizhan.service.UserService userService;
//新增用户
public void add(User user){
userService.add(user);
}
//根据id删除用户
public void delete(Long userId){
userService.delete(userId);
}
//根据id修改用户
public void update(User user){
userService.update(user);
}
//查询所有用户
public List<User> selectAll(){
return userService.selectAll();
}
//根据id查询用户
public User selectById(Long userId){
return userService.selectById(userId);
}
}
编写PageController(该控制层的作用就是跳转到对应页面)
package com.itbaizhan.dubbo_consumer.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
@RequestMapping("/{page}")
public String page(@PathVariable String page){
return page;
}
}
编写UserController
package com.itbaizhan.dubbo_consumer.controller;
import com.itbaizhan.dubbo_consumer.service.UserService;
import com.itbaizhan.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//新增用户
@PostMapping("/add")
public String add(User user){
userService.add(user);
return "redirect:/ok";
}
//根据id删除用户
@GetMapping("/delete")
public String delete(Long userId){
userService.delete(userId);
return "redirect:/ok";
}
//根据id修改用户
@GetMapping("/preUpdate")
public String preUpdate(Long userId, Model model){
User user = userService.selectById(userId);
model.addAttribute("user",user);
return "update";
}
//根据id查询用户
@PostMapping("/update")
public String update(User user){
userService.update(user);
return "redirect:/ok";
}
//查询所有用户
@GetMapping("/selectAll")
public String selectAll(Model model){
List<User> userList = userService.selectAll();
model.addAttribute("userList",userList);
return "showuser";
}
}
编写index.html主页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<a href="/adduser">添加用户</a>
<a href="/user/selectAll">查询用户</a>
</body>
</html>
配置dubbo和zookeeper
dubbo:
#项目名字
application:
name: myConsumer
#注册中心地址
registry:
address: zookeeper://192.168.138.101:2181
timeout: 50000
#端口号和协议名
protocol:
port: 20881
name: dubbo
#扫描的包
scan:
base-packages: com.itbaizhan.dubbo_consumer.service
运行producer模块和consumer模块,访问localhost:8080/index
此时发现整个项目可以正常运行,那么我们就可以继续编写页面了
新增用户业务实现
编写adduser.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<form action="/user/add" method="post">
用户名:<input type="text" name="name">
年龄:<input type="text" name="age"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
编写ok.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>操作成功</title>
</head>
<body>
操作成功,点击<a href="/index.html">返回首页</a>
</body>
</html>
添加用户测试:
点击添加用户
点击提交
查看是否添加用户成功
测试成功之后编写查询所有用户(需要用到thymeleaf)
查询用户业务实现
编写showuser.html对用户有两个操作,修改和删除
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<table border="1px solid black" align="center">
<tr>
<th>id</th>
<th>用户姓名</th>
<th>用户年龄</th>
<th>操作</th>
</tr>
<tr th:each="user:${userList}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td>
<a th:href="@{/user/preUpdate(userId=${user.id})}">修改</a>
<a th:href="@{/user/delete(userId=${user.id})}">删除</a>
</td>
</tr>
</table>
</body>
</html>
查询用户测试
点击查询用户
修改用户业务实现
编写update.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">
<!-- userId的隐藏域 -->
<input type="hidden" name="id" th:value="${user.id}">
用户名:<input type="text" name="name" th:value="${user.name}">
年龄:<input type="text" name="age" th:value="${user.age}"><br/>
<input type="submit" value="修改">
</form>
</body>
</html>
修改用户测试
点击修改用户
修改用户数据,点击修改按钮过后跳转到操作成功页面,回到首页再次查询用户会发现,用户数据已经更改
删除用户业务实现
删除用户的业务在实现查询用户业务的时候已经写好了,只要点击删除即可删除用户