SpringBoot数据聚合(spring-boot-data-aggregator-starter)
背景
接口开发是后端开发中最常见的场景, 可能是RESTFul接口, 也可能是RPC接口. 接口开发往往是从各处捞出数据, 然后组装成结果, 特别是那些偏业务的接口.
例如, 我现在需要实现一个接口, 拉取 【用户基础信息】+【用户的博客列表】+【用户的粉丝数据】的整合数据, 假设已经有如下三个接口可以使用, 分别用来获取 用户基础信息 ,用户博客列表, 用户的粉丝数据.
- 用户基础信息
@Service
public class UserServiceImpl implements UserService {
@Override
public User get(Long id) {
try {Thread.sleep(1000L);} catch (InterruptedException e) {}
/* mock a user*/
User user = new User();
user.setId(id);
user.setEmail("lvyahui8@gmail.com");
user.setUsername("lvyahui8");
return user;
}
} 用户博客列表
@Service
public class PostServiceImpl implements PostService {
@Override
public List<Post> getPosts(Long userId) {
try { Thread.sleep(1000L); } catch (InterruptedException e) {}
Post post = new Post();
post.setTitle("spring data aggregate example");
post.setContent("No active profile set, falling back to default profiles");
return Collections.singletonList(post);
}
}用户的粉丝数据
@Service
public class FollowServiceImpl implements FollowService {
@Override
public List<User> getFollowers(Long userId) {
try { Thread.sleep(1000L); } catch (InterruptedException e) {}
int size = 10;
List<User> users = new ArrayList<>(size);
for(int i = 0 ; i < size; i++) {
User user = new User();
user.setUsername("name"+i);
user.setEmail("email"+i+"@fox.com");
user.setId((long) i);
users.add(user);
};
return users;
}
}
注意, 每一个方法都sleep了1s以模拟业务耗时.
我们需要再封装一个接口, 来拼装以上三个接口的数据.
串行实现
编写性能优良的接口不仅是每一位后端程序员的技术追求, 也是业务的基本诉求. 一般情况下, 为了保证更好的性能, 往往需要编写更复杂的代码实现.
但凡人皆有惰性, 因此, 往往我们会像下面这样编写串行调用的代码
@Component
public class UserQueryFacade {
@Autowired
private FollowService followService;
@Autowired
private PostService postService;
@Autowired
private UserService userService;
public User getUserData(Long userId) {
User user = userService.get(userId);
user.setPosts(postService.getPosts(userId));
user.setFollowers(followService.getFollowers(userId));
return user;
}
}
很明显, 上面的代码, 效率低下, 起码要3s才能拿到结果, 且一旦用到某个接口的数据, 便需要注入相应的service, 复用麻烦.
并行实现
有追求的程序员可能立马会考虑到, 这几项数据之间并无强依赖性, 完全可以并行获取嘛, 通过异步线程+CountDownLatch+Future实现, 就像下面这样.
@Component
public class UserQueryFacade {
@Autowired
private FollowService followService;
@Autowired
private PostService postService;
@Autowired
private UserService userService;
public User getUserDataByParallel(Long userId) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CountDownLatch countDownLatch = new CountDownLatch(3);
Future<User> userFuture = executorService.submit(() -> {
try{
return userService.get(userId);
}finally {
countDownLatch.countDown();
}
});
Future<List<Post>> postsFuture = executorService.submit(() -> {
try{
return postService.getPosts(userId);
}finally {
countDownLatch.countDown();
}
});
Future<List<User>> followersFuture = executorService.submit(() -> {
try{
return followService.getFollowers(userId);
}finally {
countDownLatch.countDown();
}
});
countDownLatch.await();
User user = userFuture.get();
user.setFollowers(followersFuture.get());
user.setPosts(postsFuture.get());
return user;
}
}
上面的代码将串行改为并行执行,在有限的并发下可以提高性能,但是过于复杂。
优雅的注解实现
首先, 我们先定义一个聚合接口
@Component
public class UserAggregate {
@DataProvider(id="userFullData")
public User userFullData(@DataConsumer(id = "user") User user,
@DataConsumer(id = "posts") List<Post> posts,
@DataConsumer(id = "followers") List<User> followers) {
user.setFollowers(followers);
user.setPosts(posts);
return user;
}
}
其中
@DataProvider表示这个方法是一个数据提供者, 数据Id为userFullData@DataConsumer表示这个方法的参数, 需要消费数据, 数据Id为user,posts,followers.
当然, 原来的3个原子服务 【用户基础信息】 ,【用户博客列表】, 【用户的粉丝数据】, 也分别需要添加一些注解
@Service
public class UserServiceImpl implements UserService { @DataProvider(id = "user")
@Override
public User get(@InvokeParameter("userId") Long id) {
//...
}
}
@Service
public class PostServiceImpl implements PostService { @DataProvider(id = "posts")
@Override
public List<Post> getPosts(@InvokeParameter("userId") Long userId) {
//...
}
}
@Service
public class FollowServiceImpl implements FollowService { @DataProvider(id = "followers")
@Override
public List<User> getFollowers(@InvokeParameter("userId") Long userId) {
//...
}
}
其中
@DataProvider与前面的含义相同, 表示这个方法是一个数据提供者@InvokeParameter表示方法执行时, 需要手动传入的参数
这里注意 @InvokeParameter 和 @DataConsumer的区别, 前者需要用户在最上层调用时手动传参; 而后者, 是由框架自动分析依赖, 并异步调用取得结果之后注入的.
最后, 仅仅只需要调用一个统一的门面(Facade)接口, 传递数据Id, Invoke Parameters,以及返回值类型. 剩下的并行处理, 依赖分析和注入, 完全由框架自动处理.
@Component
public class UserQueryFacade {
@Autowired
private DataBeanAggregateQueryFacade dataBeanAggregateQueryFacade;
public User getUserFinal(Long userId) throws InterruptedException,
IllegalAccessException, InvocationTargetException {
return dataBeanAggregateQueryFacade.get("userFullData",
Collections.singletonMap("userId", userId), User.class);
}
}
如何用在你的项目中
只需在你的项目引入依赖.
<dependency>
<groupId>io.github.lvyahui8</groupId>
<artifactId>spring-boot-data-aggregator-starter</artifactId>
<version>1.0.1</version>
</dependency>
并在 application.properties 文件中声明注解的扫描路径.
# 替换成你需要扫描注解的包
io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example
之后, 就可以使用如下注解和 Spring Bean 实现聚合查询
@DataProvider@DataConsumer@InvokeParameter- Spring Bean
DataBeanAggregateQueryFacade
注意, @DataConsumer 和 @InvokeParameter 可以混合使用, 可以用在同一个方法的不同参数上. 且方法的所有参数必须有其中一个注解, 不能有没有注解的参数.
注:此文转载自http://springcloud.cn/view/639,代码:https://github.com/lvyahui8/spring-boot-data-aggregator
SpringBoot数据聚合(spring-boot-data-aggregator-starter)的更多相关文章
- Spring Boot的启动器Starter详解
Spring Boot的启动器Starter详解 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs Spring Boot ...
- 图书-技术-SpringBoot:《Spring Boot 企业级应用开发实战》
ylbtech-图书-技术-SpringBoot:<Spring Boot 企业级应用开发实战> Spring Boot 企业级应用开发实战,全书围绕如何整合以 Spring Boot 为 ...
- Spring boot data JPA数据库映射关系 : @OneToOne,@OneToMany,@ManyToMany
问题描述 在利用Spring boot data JPA进行表设计的时候,表对象之间经常存在各种映射关系,如何正确将理解的映射关系转化为代码中的映射关系是关键之处. 解决办法 概念理解 举例:在公司的 ...
- 小代学Spring Boot之自定义Starter
想要获取更多文章可以访问我的博客 - 代码无止境. 上一篇小代同学在Spring Boot项目中配置了数据源,但是通常来讲我们访问数据库都会通过一个ORM框架,很少会直接使用JDBC来执行数据库操作的 ...
- 【Java面试】如何理解Spring Boot中的Starter?
一个工作了3年的Java程序员,遇到一个Spring Boot的问题. 他对这个问题有一些了解,但是回答得不是很好,希望参考我的高手回答. 这个问题是:"如何理解Spring Boot中的S ...
- Spring Boot(五):Spring Boot的启动器Starter大全及自定义Starter
现有启动器Starter目录 Spring Boot应用启动器基本的一共有44种,具体如下: 1)spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置.日志和 ...
- SpringBoot(十一):Spring boot 中 mongodb 的使用
原文出处: 纯洁的微笑 mongodb是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多.由于很多公司使用了云服务,服务器默认都开放了外网地址,导致前一阵子大批 ...
- springboot(十一):Spring boot中mongodb的使用
mongodb是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多.由于很多公司使用了云服务,服务器默认都开放了外网地址,导致前一阵子大批 MongoDB 因配置 ...
- springboot(三):Spring boot中Redis的使用
spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化. redis介绍 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结 ...
- 【Spring Boot && Spring Cloud系列】Spring Boot的启动器Starter
Spring Boot的内置Servlet Container: Name Servlet Version Java Version Tomcat8 3.1 Java 7+ Tomcat7 3.0 J ...
随机推荐
- Leetcode之深度优先搜索(DFS)专题-1020. 飞地的数量(Number of Enclaves)
Leetcode之深度优先搜索(DFS)专题-1020. 飞地的数量(Number of Enclaves) 深度优先搜索的解题详细介绍,点击 给出一个二维数组 A,每个单元格为 0(代表海)或 1( ...
- Leetcode之回溯法专题-47. 全排列 II(Permutations II)
Leetcode之回溯法专题-47. 全排列 II(Permutations II) 给定一个可包含重复数字的序列,返回所有不重复的全排列. 示例: 输入: [1,1,2] 输出: [ [1,1,2] ...
- [SQL] 外卖系统数据库设计
注意: 1.项目需求:小程序外卖系统,以美团,饿了么为参考. 2.表设计没有外键约束,设计是在程序中进行外键约束. 3.希望通过分享该数据库设计,获取大家的建议和讨论. SQL: CREATE DAT ...
- Delphi - cxGrid字段类型设定为ComboBox 并实现动态加载Item
cxGrid设定字段类型为ComboBox 在cxGrid中选中需要设定的字段: 单击F11调出属性控制面板,在Properties下拉选项中选中ComboBox,完成字段类型的设定. cxGrid ...
- Drop Table对MySQL的性能影响分析
[问题描述] 最近碰到有台MySQL实例出现了MySQL服务短暂hang死,表现为瞬间的并发线程上升,连接数暴增. 排查Error Log文件中有page_cleaner超时的信息,引起我们的关注: ...
- 为什么有了Compose和Swarm,还会有Kubernetes的出现?
一.k8s设计思想更先进 k8s的主要设置思想,是从更宏观的角度,以统一的方式来定义任务之间的各种关系 1.k8s的核心功能图 2.k8s的全局架构图 kube-apiserver:API服务 Kub ...
- HDU-4857-逃生-反向拓扑排序+优先队列
HDU-4857 题意就是做一个符合条件的排序,用到拓扑序列. 我一开始wa了多发,才发现有几个样例过不了,发现1->2->3...的顺序无法保证. 后来就想用并查集强连,还是wa: 后来 ...
- 基于SpringBoot从零构建博客网站 - 开发文章详情页面
文章详情页面是博客系统中最为重要的页面,登录用户与游客都可以浏览文章详情页面,只不过只有登录用户才能进行其它的一些操作,比如评论.点赞和收藏等等. 本次的开发任务只是将文章详情页面展示出来,至于一些收 ...
- Spring中常用的23中设计模式
1.spring 中常用的设计模式有23中 分类 设计模式 创建型 工厂方法模式(FactoryMethod).抽象工厂模式(AbstractFactory).建造者模式(Builder).原型 ...
- 反序列化JSON
本人编程生涯刚刚起步,以下是个人理解,如果有些不对的地方,请各位在评论区指出,如果有更详细的博客也可以推荐给我. 首先要根据JSON创建一个实体类,并且要实现Serializable接口,再创建一个J ...