说明
整个项目分为三个模块,分别是Service、Provider和Consumer。Service中包括服务接口的声明、实体类的创建等;Provider负责Service中接口的具体实现;Consumer则通过Service来实现具体的业务逻辑,整体结构与Dubbo相同。
实际开发中由于不同的模块可能是不同的团队开发,因此这里创建多个不同的项目以确保各个模块的协同开发。整个项目采用Eclipse/MyEclipse开发。
0. 创建数据库和表(MySQL数据库)
create database db_springboot;
use db_springboot;
create table tbl_user
(
id int primary key auto_increment,
name varchar(256) not null,
age int not null
);
1. 创建Service部分
Service部分主要包括实体类和服务接口的创建,具体操作如下。
1) 创建maven project
具体操作:略,项目名称:dubbo-springboot-service
项目结构如下图
2) 编写实体类
注意点:实体类必须要实现java.io.Serializable 接口,否则会报错。
package study.pojo;
import java.io.Serializable;
/**
* 实体类:必须要实现 {@link java.io.Serializable} 接口
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer age;
public User() {
super();
}
public User(Integer id, String name, Integer age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
3) 编写服务接口
package study.service;
import java.util.List;
import study.pojo.User;
public interface IUserService {
List<User> getAllUsers();
void addUser(User user);
User getUserById(Integer id);
void updateUser(User user);
void deleteUserById(Integer id);
}
4) pom.xml
为了项目完整性,这里仍然贴上pom.xml的内容。
<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>study.dubbo-springboot</groupId>
<artifactId>dubbo-springboot-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
</project>
创建完成之后maven install这个项目,将生成的jar包打包到本地仓库,准备用于Provider和Consumer项目。
2. 创建Provider部分
Provider部分是整个项目的核心,需要实现服务接口,并且将服务注册到zookeeper,为Consumer提供服务调用。同时在这里借助SpringBoot实现与持久层的交互,避免繁琐的配置,具体操作如下。
1) 创建maven project
具体操作:略,项目名称:dubbo-springboot-provider
项目结构如下图
2) pom.xml
Provider项目中的pom.xml需要同时引入Dubbo的相关依赖和SpringBoot的相关依赖。单纯两者的依赖都是非常成熟的,但是如果要想将两者整合,需要额外引入新的依赖,即 dubbo-spring-boot-starter 依赖,通过这个依赖实现Dubbo与SpringBoot的整合。在查找资料的过程中发现了这个依赖存在多个版本,这里给一张从maven官网查找到的结果图。从下图可以看出,alibaba和apache都给出了这个依赖,在引入这个依赖之后还需要引入Dubbo的依赖,而alibaba和apache也都给出了Dubbo的依赖。经过测试之后发现alibaba给出的依赖与其余依赖之间的契合度是最好的,直接就可以使用;使用apache给的Dubbo相关依赖的时候使用zkclient连接zookeeper会报异常。
除了Dubbo依赖,SpringBoot的版本同样有限制,最好使用2.0.x版本,在测试2.1.4.RELEASE版本中报异常LoggerFactory is not a Logback LoggerContext but Logback,改为2.0.5版本之后就没有这个问题了。这里给出一个版本匹配图,第一列的版本代表 dubbo-spring-boot-starter 版本。
pom.xml具体内容如下:
<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>study.dubbo-springboot</groupId>
<artifactId>dubbo-springboot-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<dependencies>
<!-- 自建的 service 依赖 -->
<dependency>
<groupId>study.dubbo-springboot</groupId>
<artifactId>dubbo-springboot-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- dubbo启动器 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
</dependency>
<!-- 使用 dubbo 注解的时候必须引入该依赖,否则报错 -->
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.2</version>
</dependency>
<!-- 引入netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- zkclient依赖 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
<!-- MyBatis启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version><!--$NO-MVN-MAN-VER$ -->
<scope>runtime</scope>
</dependency>
<!-- 数据库连接池坐标 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- 添加 junit 测试环境的 jar 包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<!-- 修改JDK版本 -->
<java.version>1.8</java.version>
<!-- 没有下面这句话在旧版本的Eclipse中pom.xml会报错 -->
<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
</properties>
</project>
3) 编写Dao接口
与持久层的交互采用的是MyBatis的框架,SpringBoot也已经集成了MyBatis,pom.xml中已经添加了相关的依赖,这里直接使用即可,具体代码如下。
package study.provider.dao;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import study.pojo.User;
public interface IUserDao {
@Insert(value = "insert into tbl_user (name, age) values (#{name}, #{age})")
int insert(User user);
@Select("select * from tbl_user")
List<User> selectAll();
@Select("select * from tbl_user where id=#{id}")
User selectById(Integer id);
@Update("update tbl_user set name=#{name}, age=#{age} where id=#{id}")
int update(User user);
@Delete("delete from tbl_user where id=#{id}")
int deleteById(Integer id);
}
MyBatis的Dao有两种实现方式,一种是通过上述这种注解的方式,还有一种是自己编写mapper.xml的方式。个人更倾向于第二种,因为这种拓展性更好,这里为了省略完全采用注解的方式,这也比较符合SpringBoot的开发风格,注解优于配置。关于Spring Boot整合MyBatis开发的更多信息,参考资料:
4) 编写服务接口实现类
package study.provider.service.impl;
import java.util.List
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.dubbo.config.annotation.Service;
import study.pojo.User;
import study.provider.dao.IUserDao;
import study.service.IUserService;
@Transactional
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public void addUser(User user) {
userDao.insert(user);
}
@Override
public void deleteUserById(Integer id) {
userDao.deleteById(id);
}
@Override
public List<User> getAllUsers() {
return userDao.selectAll();
}
@Override
public User getUserById(Integer id) {
return userDao.selectById(id);
}
@Override
public void updateUser(User user) {
userDao.update(user);
}
}
5) 编写配置文件与启动类
在Dubbo项目中使用的是xml配置文件,而在SpringBoot项目中使用的是application.properties全局配置文件,在他们的整合中两种形式都可以存在,或者只采用application.properties文件配置形式。
- application.propertis + provider.xml 的配置形式
这种配置形式保留了Dubbo的原生配置形式,provider.xml中包含Dubbo相关配置,包括服务名、协议、注册地址、暴露的服务等信息,application.properties中则包含MyBatis整合的相关配置,具体配置代码如下。
provider.xml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 1. 给当前的 provider 起名 -->
<dubbo:application name="dubbo_springboot_provider">
<dubbo:parameter key="qos.enable" value="true" />
<dubbo:parameter key="qos.accept.foreign.ip" value="false" />
<dubbo:parameter key="qos.port" value="22222" />
</dubbo:application>
<!-- 2. 配置注册中心,这里是集群模式 -->
<dubbo:registry protocol="zookeeper"
address="node1:2181,node2:2181,node3:2181" group="dubbo_springboot" client="zkclient" />
<!-- 3. 配置dubbo协议 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 如果有多个服务需要对外提供,只需要重复 4和5 即可 -->
<!-- 4. 声明需要暴露的服务接口 -->
<!-- <dubbo:service interface="study.menu.service.MenuServiceInter" ref="menuServiceImpl"
/> -->
<!-- 5. 具体的实现类 -->
<!-- <bean id="menuServiceImpl" class="study.menu.provider.service.impl.MenuServiceImpl"
/> -->
<!-- 6. 采用注解的方式,需要在实现类中添加注解 @Service(com.alibaba.dubbo.config.annotation.Service),
此时4.5两步不需要,但是此时会报异常,需要在引入额外的依赖,参考pom.xml文件 -->
<dubbo:annotation package="study.provider.service.impl" />
</beans>
application.properties代码如下:
# 数据库和连接池的配置信息,用于springboot 整合 mybatis
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_springboot
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
由于此时采用了双配置文件,其中application.properties会自动加载,而provider.xml则需要在启动类中通过 @ImportResource 注解手动引入,启动类代码如下:
package study.provider;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@MapperScan(basePackages = { "study.provider.dao" }) // 扫描dao接口
@ImportResource(locations = { "classpath:provider.xml" })
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class, args);
}
}
- application.propertis单文件配置形式(推荐方式)
Dubbo的配置信息即provider.xml中的内容也可以写到application.properties文件中,实现Dubbo与SpringBoot更好的整合,这里直接给出代码如下:
# 数据库和连接池的配置信息,用于springboot 整合 mybatis
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_springboot
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# dubbo配置信息,用于整合springboot和dubbo,与xml配置文件是一一对应的
# 1. 等价于 <dubbo:application> 中的配置
dubbo.application.name=dubbo_springboot_provider
dubbo.application.qosEnable=true
dubbo.application.qosPort= 22222
dubbo.application.qosAcceptForeignIp=false
# 2. 等价于 <dubbo:registry> 中的配置
dubbo.registry.group=dubbo_springboot
dubbo.registry.address=zookeeper://node1:2181?backup=node2:2181,node3:2181
# 3. 等价于 <dubbo:protocol> 中的配置
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
# 4. 等价于 <dubbo:annotation> 中的配置
dubbo.scan.basePackages=study.provider
application.properties中将provider.xml中的配置信息一一对应过来了。此时启动类无需引入provider.xml,启动类的具体代码如下:
package study.provider;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@MapperScan(basePackages = { "study.provider.dao" }) // 扫描dao接口
//@ImportResource(locations = { "classpath:provider.xml" })
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class, args);
}
}
6) 项目启动和测试
上述代码编写完成之后,开启zookeeper,就可以将provider服务发布了。在发布之前,为了确保代码没有问题添加一个单元测试,代码如下:
package study.provider.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import study.provider.ProviderApp;
import study.service.IUserService;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { ProviderApp.class })
public class UserServiceTest {
@Autowired
private IUserService userService;
@Test
public void test() {
System.out.println(userService + " - " + userService.getClass());
userService.getAllUsers().forEach(u -> System.out.println(u));
}
}
这里有一个疑问:在服务接口实现类中使用的注解 @Service 是Dubbo中的注解,不是Spring中的注解,因此也就应该不会将该实现类注入到SpringIOC容器中,但是这里通过 @Autowired 仍然获取到了该对象,有可能是Dubbo与SpringBoot整合的依赖中将这个工作完成了,具体原因不明。
3. 创建Consumer部分
Consumer部分相比于Provider部分简单很多,更多的就是一个web项目开发,且无需与持久层交互,这里直接给出代码,不做过多说明。
1) 创建maven project
具体操作:略,项目名称:dubbo-springboot-consumer
项目结构如下图
2) pom.xml
相比于Provider的pom.xml文件,Consumer的pom.xml文件不需要整合MyBatis,只需要整合web和thymeleaf即可。具体代码如下:
<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>study.dubbo-springboot</groupId>
<artifactId>dubbo-springboot-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<dependencies>
<!-- 自建的 service 依赖 -->
<dependency>
<groupId>study.dubbo-springboot</groupId>
<artifactId>dubbo-springboot-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- dubbo启动器 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
</dependency>
<!-- 使用 dubbo 注解的时候必须引入该依赖,否则报错 -->
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.2</version>
</dependency>
<!-- 引入netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- zkclient依赖 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
<!-- web启动器坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- thymeleaf 启动器的坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<!-- 修改JDK版本 -->
<java.version>1.8</java.version>
<!-- 没有下面这句话旧版本的Eclipse中pom.xml会报错 -->
<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
</properties>
</project>
3) 编写Controller
package study.consumer.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.dubbo.config.annotation.Reference;
import study.pojo.User;
import study.service.IUserService;
@Controller
@RequestMapping("/user")
public class UserController {
@Reference
private IUserService userService;
@RequestMapping("/")
public String index(Model model) {
model.addAttribute("allUsers", userService.getAllUsers());
return "all";
}
@RequestMapping("/toAddUI")
public String toAddUI() {
return "add";
}
@RequestMapping("/add")
public String add(User user) {
userService.addUser(user);
// 后台重定向
return "redirect:/user/";
}
@RequestMapping("/toUpdateUI/{id}")
public String toUpdateUI(Model model, @PathVariable("id")Integer id) {
model.addAttribute("user", userService.getUserById(id));
return "update";
}
@RequestMapping("/update")
public String update(User user) {
userService.updateUser(user);
return "redirect:/user/";
}
@RequestMapping("/del/{id}")
public String deleteUser(@PathVariable("id")Integer id) {
userService.deleteUserById(id);
return "redirect:/user/";
}
}
4) 编写页面
所有的页面都直接放在src/main/resources/templates文件夹下,具体参考SpringBoot与thymeleaf的整合相关内容以及前面的项目结构。
- all.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>所有用户信息</title>
</head>
<body>
<h3>所有用户信息如下</h3>
<table border="1">
<tr>
<th>ID</th>
<th>NAME</th>
<th>AGE</th>
<th>修改</th>
<th>删除</th>
</tr>
<tr th:each="user : ${allUsers}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td><a th:href="@{'/user/toUpdateUI/' + ${user.id}}">修改</a></td>
<td><a onclick="return confirm('确认删除?')" th:href="@{'/user/del/' + ${user.id}}">删除</a></td>
</tr>
</table>
<div>
<a th:href="@{/user/toAddUI}">添加用户</a>
</div>
</body>
</html>
- add.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加用户页面</title>
</head>
<body>
<h3>添加用户基本信息</h3>
<form action="/user/add" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>年龄</td>
<td><input type="text" name="age"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="提交"></td>
</tr>
<tr>
<td colspan="2"><a th:href="@{/user/}">返回</a></td>
</tr>
</table>
</form>
</body>
</html>
- update.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>更新用户页面</title>
</head>
<body>
<h3>用户基本信息</h3>
<form action="/user/update" method="post">
<input type="hidden" name="id" th:value="${user.id}">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="name" th:value="${user.name}"></td>
</tr>
<tr>
<td>年龄</td>
<td><input type="text" name="age" th:value="${user.age}"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="确认修改"></td>
</tr>
<tr>
<td colspan="2"><a th:href="@{/user/}">返回</a></td>
</tr>
</table>
</form>
</body>
</html>
5) 编写配置文件和启动类
与Provider部分相同,这里的配置文件也存在两个形式,这里给出只用application.properties的配置方式,具体代码如下:
server.port=10000
# dubbo配置信息,用于整合springboot和dubbo
# 1. 等价于 <dubbo:application> 中的配置
dubbo.application.name=dubbo_springboot_consumer
dubbo.application.qosEnable=true
dubbo.application.qosPort=33333
dubbo.application.qosAcceptForeignIp=false
# 2. 等价于 <dubbo:registry> 中的配置
dubbo.registry.group=dubbo_springboot
dubbo.registry.address=zookeeper://node1:2181?backup=node2:2181,node3:2181
# 3. 等价于 <dubbo:annotation> 中的配置
dubbo.scan.basePackages=study.consumer
启动类代码如下:
package study.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
//@ImportResource(locations = { "classpath:consumer.xml" })
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class, args);
}
}
6) 项目启动
访问:http://localhost:10000/user/
如果不是在本地启动,将localhost换成对应的主机地址即可,10000端口号是在Consumer的application.properties中配置的。
4. 参考资料
https://blog.csdn.net/ThirdFloor/article/details/80926327
https://blog.csdn.net/qq_33404395/article/details/85046493
https://blog.csdn.net/u012988901/article/details/84963766
https://blog.csdn.net/gebitan505/article/details/54929287