背景

接口开发是后端开发中最常见的场景, 可能是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 ,postsfollowers.

当然, 原来的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 实现聚合查询

注意, @DataConsumer 和 @InvokeParameter 可以混合使用, 可以用在同一个方法的不同参数上. 且方法的所有参数必须有其中一个注解, 不能有没有注解的参数.

注:此文转载自http://springcloud.cn/view/639,代码:https://github.com/lvyahui8/spring-boot-data-aggregator

												

最新文章

  1. GNU Readline 库及编程简介
  2. websocket 实现聊天功能
  3. 遇到 Line 21: StartTag: invalid element name ios
  4. 豆瓣FM 歌词跟随插件
  5. python 代码片段26
  6. 数据库测试DbUnit
  7. Socket Programming in C#--Multiple Sockets
  8. 九、Java基础---------面向对象封装、继承、多态
  9. Java 7 命令/工具 jcmd 使用详细解释
  10. php函数 date() 详细资料
  11. mingw fbx sdk /浮点数精度
  12. SMARTFORM报表程序设计(2)
  13. java学习多线程之创建多线程一
  14. 【转】JAVA处理线程超时
  15. Java并发之乐观锁悲观锁
  16. 玩转PHP(二)--PHP强大的时间函数:date()
  17. leetcode — reverse-linked-list-ii
  18. maven项目-修复Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:build-helper-maven-plugin:1.8:add-resource (execution: add-resource, phase: generate-resources) pom.xml报错
  19. Django学习手册 - ORM 单表数据获取
  20. 1705: 小明在工作(zzuli)

热门文章

  1. 前端通过Blob实现文件下载
  2. 欧几里得(Euclid)与拓展的欧几里得算法
  3. python中的全局变量
  4. codeforces 389 D. Fox and Minimal path(构造+思维)
  5. CF - 652F Ants on a Circle
  6. POJ 1182 食物链(经典并查集) (多组输入有时乱加也会错!)
  7. codeforces 755D. PolandBall and Polygon(线段树+思维)
  8. CF940B Our Tanya is Crying Out Loud
  9. 树莓派4B NAS系统搭建
  10. Python---列表的学习(一)