【项目学习】谷粒商城学习记录6 - 异步+商品详情搭建+异步编排
【项目学习】谷粒商城学习记录6 - 异步
一、异步知识点复习
1. 四种java实现异步方法
(1) 继承Thread类,重写run()方法
- 测试
public class ThreadTest { public static void main(String[] args) { System.out.println("main...start..."); Thread01 thread01 = new Thread01(); thread01.start(); System.out.println("main...end..."); } public static class Thread01 extends Thread { @Override public void run() { System.out.println("当前线程:"+Thread.currentThread().getName()); Integer i = 10 / 2; System.out.println("运行结果:"+i); } } }
- 测试结果:
(2) 实现Runnable接口,重写run()方法
- 优点:Runnable实现方式克服了java使用Thread时单继承的问题,Runnable方法可以被多个线程共有,适合多线程处理同一任务或者资源的情况。
- 测试
public static void main(String[] args) { System.out.println("main...start..."); Thread02 thread02 = new Thread02(); new Thread(thread02).start(); System.out.println("main...end..."); } public static class Thread02 implements Runnable { @Override public void run() { System.out.println("当前线程:"+Thread.currentThread().getName()); Integer i = 12 / 2; System.out.println("运行结果:"+i); } }
- 结果:
(3) 实现Callable接口,FutureTask(可以获得返回值,可以处理异常)
- 优点:可以获得返回结果
- 测试:
public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main...start..."); //可以获得返回结果 FutureTask futureTask = new FutureTask<>(new Thread03()); new Thread(futureTask).start(); Integer i = (Integer) futureTask.get(); System.out.println("main...end..." + i); } public static class Thread03 implements Callable { @Override public Object call() throws Exception { System.out.println("当前线程:"+Thread.currentThread().getName()); Integer i = 14 / 2; System.out.println("运行结果:"+i); return i; } }
- 测试结果:
(4) 【推荐】创建线程池,直接提交任务
- 优点:上面方法都不能控制资源,可能导致系统一直在创建线程资源。但线程池方法会固定线程数量,当线程都在工作时就让任务等待。
- 测试
public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main...start..."); ExecutorService exceutorService = Executors.newFixedThreadPool(10); exceutorService.execute(new Thread02()); System.out.println("main...end..."); }
- 测试结果
二、线程池介绍
1. 使用执行器工具类 Executors 创建线程池
(1) 介绍
- Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
- 四种可以创建的线程池:
newCachedThreadPool
: 缓存线程池。 核心线程数为0,线程数量随任务增加而增加,当线程任务执行完毕且空闲一段时间后会被回收newFixedThreadPool
: 固定大小的线程池。 核心线程数=最大线程数【不回收】。当线程异常结束后,线程池会补充一个新线程。newScheduledThreadPool
: 定时任务线程池。可以设定多久之后执行,也可以定期执行任务newSingleThreadPool
: 单线程化的进程池。核心和最大线程数都是1【不回收】。该线程池只会用唯一的工作线程执行任务,保证所有任务按照指定舒徐(FIFO, LIFO, 优先级)执行。
(2) 示例
- 如一中的线程池使用。
2.【推荐】使用ThreadPoolExecutor创建自定义线程池
(1) 介绍
- 自定义命令:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit,workQueue, threadFactory, handler);
- 包含7个参数:
corePoolSize
: 核心线程数 创建线程池时就存在,一直存在到线程池销毁,空闲时也不销毁maximumPoolSize
: 最大线程数量 当任务队列满了后,线程池会在核心线程的基础上继续创建新进程来执行任务,最大线程数量规定了能创建多少个线程(包括核心进程数)keepAliveTime
: 存活时间 指当一个线程(非核心)执行完任务后,它会等待多长时间再销毁TimeUnitunit
: 时间单位 时间的单位,如秒,分,小时,天...workQueue
: 任务队列 当核心线程都在执行任务时,会将其他任务存入任务队列,核心进程空闲时会从任务队列取出任务执行threadFactory
: 线程的创建工厂【可以自定义】 默认的线程创建工厂为Executors.defaultThreadFactory()RejectedExecutionHandler handler
: 拒绝策略 当任务队列等待满了,且当前线程已达到最大线程数量,且都在执行任务时,对于新任务将按照拒绝策略执行任务,包括:Rejected 丢弃最老任务,Cakker 调用者同步调用,直接调用run方法,不创建线程了。Abort(默认) 直接丢弃新任务, Discard 丢弃新任务,并且抛出异常。
(2) 线程池执行任务流程(线程池原理)
- 推荐博客: Java线程池初步解读
- 任务加入时判断的顺序: 核心线程数,任务队列,最大线程数,拒绝策略
- 线程池执行原理:
- 新加入任务,判断核心线程数corePoolSize是否到达最大值,没有就创建核心线程。(注意:核心线程并不是一开始就存在,毕竟要节省资源。但是当核心进程创建后就不会被关闭,除非设置了自动关闭
allowCoreThreadTimeOut=true
) - 如果有空闲核心线程,就调用空闲核心线程执行新任务。如果没有空闲的核心线程,就将任务加入任务队列
- 如果任务队列已满,则判断当前线程数是否大于线程池允许的最大线程数,如果没有就创建新线程执行任务,如果达到最大线程数了,就执行拒绝策略。
- 新加入任务,判断核心线程数corePoolSize是否到达最大值,没有就创建核心线程。(注意:核心线程并不是一开始就存在,毕竟要节省资源。但是当核心进程创建后就不会被关闭,除非设置了自动关闭
- 注意: 线程本身没有核心与非核心概念。都是靠比较corePoolSize和当前线程数量判断一个线程能不能看作核心线程。
(3) 使用线程的好处
- 降低了资源的消耗【创建和销毁线程会有一定的开销】
- 通过线程复用减少反复创建和销毁线程带来的开销
- 提高响应速度
- 当线程池中线程数没有超过线程最大上限时,存在线程处于待分配任务状态,当任务来时无需创建新的线程就能执行
- 提高线程的可管理性
- 线程池可以不断根据当前任务和系统的特点对线程池内线程进行优化处理。
(4) 实践
- 直接new一个ThreadPoolExecutor就可以创建一个线程池
public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main...start..."); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, //核心线程数 10, //最大线程数 10, //存活时间 TimeUnit.SECONDS, //时间单位 new LinkedBlockingDeque<>(100), //任务队列 Executors.defaultThreadFactory(), //线程的创建工厂 new ThreadPoolExecutor.AbortPolicy() //拒绝策略 ); //任务1 executor.execute(() -> { try { Thread.sleep(3 * 1000); System.out.println("--helloWorld_001" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println("main...end..."); }
- 结果:
三、异步编排 CompletableFuture
1. 介绍
- CompletableFuture提供了非常强大的Future接口的扩展功能, 可以简化异步编程
- 提供了函数式编程的能力, 可以通过回调的方式处理计算结果, 并且提供了转换和组合 CompletableFuture 的方法。
- CompletableFuture 和 FutureTask ( 构造参数为Callable实现类)同属于 Future 接口的实现类, 都可以获取线程的执行结果。
2. 启动异步任务
- 测试代码:
public static ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main...start..."); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { System.out.println("当前线程:" + Thread.currentThread().getName()); Integer i = 10 / 2; System.out.println("运行结果:" + i); return i; }, executor); Integer integer = future.get(); System.out.println("main...end..." + integer); }
- 测试结果
3. 线程结果感知和处理
(1) 使用whenCompleteAsync与exceptionally
- 简介:
- whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况。
- whenComplete 和 whenCompleteAsync 的区别:
- whenComplete: 是执行当前任务的线程执行继续执行 whenComplete 的任务。
- whenCompleteAsync: 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
(2) 【推荐】 使用haddle方法
- 简介:和 complete 一样, 可对结果做最后的处理(可处理异常),可改变返回值
- 一般用handle,因为whencomplete如果异常不能给定默认返回结果,需要再调用exceptionally,而handle可以
- 该方法作用:获得前一任务的返回值【自己也可以是异步执行的】,也可以处理上一任务的异常,调用exceptionally修改前一任务的返回值【例如异常情况时给一个默认返回值】而handle方法可以简化操作
4.线程串行化
(1) 简介:
- 所谓串行化,就是将一个一个任务能按照一定的顺序依次执行
- 方法介绍:
-
theRun: 继续执行,不接受上一个任务的返回结果,且自己执行完后也没有返回结果
-
theAccept: 继续执行,接收 上一个任务的返回结果,自己执行完没有返回结果
-
theApply: 继续执行,接收上一个任务的返回结果,且自己的返回结果也被下一个任务所感知
-
Async表示任务将异步执行
-
5.两任务组合
(1) 两任务都要完成
-
介绍
(2) 两个任务只要一个完成
- runAfterEither: 两个任务有一个执行完成, 不需要获取 future 的结果, 处理任务, 也没有返回值。
- acceptEither: 两个任务有一个执行完成, 获取它的返回值, 处理任务, 没有新的返回值。
- applyToEither: 两个任务有一个执行完成, 获取它的返回值, 处理任务并有新的返回值。
6.多任务组合
-
allof
//allOf: 等待所有任务完成 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { return andTree(cfs, 0, cfs.length - 1); }
-
anyof
//anyOf: 只要有一个任务完成 public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { return orTree(cfs, 0, cfs.length - 1); }
二、商品业务-商品详情
1. 环境搭建
- 添加host:
item.gulimall.com
- 修改网关getway模块:
- 详情页放在product模块模板目录下并改名为
item.html
,静态资源放在nginx下html/statics/item目录下 (我是windows环境nginx) - 修改item.index下静态资源的路径
href="
修改为href="/static/item/
src="
修改为src="/static/item/
- 修改search模块的list.html文件:
修改前:
修改后:
2. 配置页面跳转controller类
- 新建com.atguigu.gulimall.product.web.ItemController:
@Controller public class ItemController { /** * 展示当前sku的详情 * @param skuId * @return */ @GetMapping("/{skuId}.html") public String skuItem(@PathVariable("skuId") Long skuId) { System.out.println("准备查询:" + skuId + "的详情"); return "item.html"; } }
- 效果:
3.模型类抽取和controller
-
页预期展示信息抽取:
-
首页模型类vo
注意:这里内部类后面也会拿出来,且SpuBaseAttrVo会直接使用attr类,可以先按这样写,后面视频中再拿出来。同时SkuItemSaleAttrVo内部类的attrValues类型后面也会改为String
@ToString @Data public class SkuItemVo { //1、sku基本信息的获取 pms_sku_info public SkuInfoEntity info; public boolean hasStock = true; //2、sku的图片信息 pms_sku_images public List<SkuImagesEntity> images; //3、获取spu的销售属性组合 public List<SkuItemSaleAttrVo> saleAttr; //4、获取spu的介绍 public SpuInfoDescEntity desc; //5、获取spu的规格参数信息 public List<SpuItemAttrGroupVo> groupAttrs; @Data public static class SkuItemSaleAttrVo { private Long attrId; private String attrName; private List<String> attrValues; } @Data public static class SpuItemAttrGroupVo { private String groupName; private List<SpuBaseAttrVo> attrs; } @Data public static class SpuBaseAttrVo { private String attrName; private String attrValue; } }
4.获得详情页相关数据
(1) item方法
- 在ItemController的skuItem方法中调用item()方法。
@GetMapping("/{skuId}.html") public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException { System.out.println("准备查询" + skuId + "详情"); SkuItemVo vos = skuInfoService.item(skuId); model.addAttribute("item",vos); return "item"; }
(2) sku基本信息 pms_sku_info
- 第一部分获取sku基本信息
SkuItemVo skuItemVo = new SkuItemVo(); //1、sku基本信息 pms_sku_info SkuInfoEntity info = getById(skuId); Long spuId = info.getSpuId(); skuItemVo.setInfo(info);
- 在SkuImagesService中创建getImagesBySkuId()方法
item()中代码如下@Override public List<SkuImagesEntity> getImagesBySkuId(Long skuId) { SkuImagesDao imagesDao = this.baseMapper; List<SkuImagesEntity> imagesEntities = imagesDao.selectList(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId)); return imagesEntities; }
//2、sku的图片信息 pms_sku_images List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId); skuItemVo.setImages(images);
(3) 获取spu的介绍
@Autowired
private SpuInfoDescService spuInfoDescService;
//4、获取spu的介绍
Long spuId = info.getSpuId();
SpuInfoDescEntity spuInfoDescServiceById = spuInfoDescService.getById(spuId);
skuItemVo.setDesc(spuInfoDescServiceById);
(4) 获取 spu 的规格参数信息
@Autowired
private AttrGroupService attrGroupService;
//5、获取spu的规格参数信息
Long catalogId = info.getCatalogId();
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId, catalogId);
skuItemVo.setGroupAttrs(attrGroupVos);
-
实现getAttrGroupWithAttrsBySpuId方法
/** * 查处当前spuId对应的所有属性分组信息 以及 当前分组下的所有属性对应的值 * @param spuId * @param catalogId * @return */ @Override public List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(Long spuId, Long catalogId) { AttrGroupDao baseMapper = this.getBaseMapper(); List<SpuItemAttrGroupVo> vos = baseMapper.getAttrGroupWithAttrsBySpuId(spuId,catalogId); return vos; }
-
Dao层
List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(@Param("spuId") Long spuId, @Param("catalogId") Long catalogId);
-
xml文件
<!--resultType 返回集合里面元素的类型,只要有嵌套属性就要自定义结果集--> <resultMap id="spuItemAttrGroupVo" type="com.atguigu.gulimall.product.vo.SpuItemAttrGroupVo"> <result property="groupName" column="attr_group_name"></result> <collection property="attrs" ofType="com.atguigu.gulimall.product.vo.Attr"> <result property="attrName" column="attr_name"></result> <result property="attrValue" column="attr_value"></result> </collection> </resultMap> <select id="getAttrGroupWithAttrsBySpuId" resultMap="spuItemAttrGroupVo"> SELECT pav.spu_id, ag.attr_group_name, ag.attr_group_id, aar.attr_id, attr.attr_name, pav.attr_value FROM pms_attr_group ag LEFT JOIN pms_attr_attrgroup_relation aar ON ag.attr_group_id = aar.attr_group_id LEFT JOIN pms_attr attr ON attr.attr_id = aar.attr_id LEFT JOIN pms_product_attr_value pav on pav.attr_id = attr.attr_id WHERE ag.catelog_id=#{catalogId} AND pav.spu_id = #{spuId}; </select>
-
测试代码:
@Test public void test() { List<SpuItemAttrGroupVo> attrGroupWithAttrsBySpuId = attrGroupDao.getAttrGroupWithAttrsBySpuId(13L, 225L); System.out.println(attrGroupWithAttrsBySpuId); }
-
测试结果:
[SpuItemAttrGroupVo(groupName=主体, attrs=[com.atguigu.gulimall.product.vo.Attr@114b2414, com.atguigu.gulimall.product.vo.Attr@57920d6c]), SpuItemAttrGroupVo(groupName=基本信息, attrs=[com.atguigu.gulimall.product.vo.Attr@465d1345, com.atguigu.gulimall.product.vo.Attr@62cf86d6]), SpuItemAttrGroupVo(groupName=主芯片, attrs=[com.atguigu.gulimall.product.vo.Attr@2a6c751f, com.atguigu.gulimall.product.vo.Attr@6dd2e453])]
(5) 获取spu的销售属性组合
- impl
@Autowired
private SkuSaleAttrValueService skuSaleAttrValueService;
// 3、获取 spu 的销售属性组合
List<SkuItemSaleAttrsVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);
skuItemVo.setSaleAttr(saleAttrVos);
@Override
public List<SkuItemSaleAttrsVo> getSaleAttrsBySpuId(Long spuId) {
SkuSaleAttrValueDao dao = this.baseMapper;
List<SkuItemSaleAttrsVo> saleAttrVos = dao.getSaleAttrsBySpuId(spuId);
return saleAttrVos;
}
- Dao
List<SkuItemSaleAttrsVo> getSaleAttrsBySpuId(@Param("spuId") Long spuId);
- xml文件
<select id="getSaleAttrsBySpuId" resultType="com.atguigu.gulimall.product.vo.SkuItemSaleAttrsVo">
SELECT
ssav.attr_id attr_id,
ssav.attr_name attr_name,
GROUP_CONCAT(DISTINCT ssav.attr_value) attr_values
FROM pms_sku_info info
LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id
WHERE spu_id = #{spuId}
GROUP BY ssav.attr_id,ssav.attr_name;
</select>
- 测试一下
SkuItemSaleAttrVo类的attrValues类型后面也会改为String
@Autowired
private SkuSaleAttrValueDao skuSaleAttrValueDao;
@Test
public void test() {
List<SkuItemSaleAttrsVo> saleAttrsBySpuId = skuSaleAttrValueDao.getSaleAttrsBySpuId(13L);
System.out.println(saleAttrsBySpuId);
}
(6) 完整的item()代码
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
//1、sku基本信息 pms_sku_info
SkuInfoEntity info = getById(skuId);
Long spuId=info.getSpuId();
skuItemVo.setInfo(info);
//2、sku的图片信息 pms_sku_images
List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
//3、获取spu的销售属性组合
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);
skuItemVo.setSaleAttr(saleAttrVos);
//4、获取spu的介绍
SpuInfoDescEntity spuInfoDescServiceById = spuInfoDescService.getById(spuId);
skuItemVo.setDesc(spuInfoDescServiceById);
//5、获取spu的规格参数信息
Long catalogId = info.getCatalogId();
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId, catalogId);
skuItemVo.setGroupAttrs(attrGroupVos);
return skuItemVo;
}
5.页面渲染
-
添加thymeleaf名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
-
修改标题显示
-
修改副标题显示
-
优惠信息链接跳转(删除)
-
大图片
-
放大图片
-
价格信息(格式化显示)
-
有货/无货
- 在SkuItemVo类中添加有货/无货boolean属性 hasStock (默认设为true)。
- 在SkuItemVo类中添加有货/无货boolean属性 hasStock (默认设为true)。
-
所有图片展示
-
选择信息展示
-
暂时阶段效果:
-
商品介绍
-
商品规格:
-记得删除这些a标签的跳转
-
实现规格多个选择后显示正确的sku信息
- 新建vo:
@Data public class AttrValueWithSkuIdVo { private String attrValue; private String skuIds; }
- 修改SkuItemSaleAttrVo类中attrValues属性的类型为
List<AttrValueWithSkuIdVo>
- 修改后的SkuSaleAttrValueDao.xml代码:
<resultMap id="SkuItemSaleAttrVo" type="com.atguigu.gulimall.product.vo.SkuItemSaleAttrVo"> <result column="attr_id" property="attrId"></result> <result column="attr_name" property="attrName"></result> <collection property="attrValues" ofType="com.atguigu.gulimall.product.vo.AttrValueWithSkuIdVo"> <result column="attr_value" property="attrValue"></result> <result column="sku_ids" property="skuIds"></result> </collection> </resultMap> <select id="getSaleAttrsBySpuId" resultMap="SkuItemSaleAttrVo"> SELECT ssav.attr_id attr_id, ssav.attr_name attr_name, ssav.attr_value, GROUP_CONCAT(DISTINCT info.sku_id) sku_ids FROM pms_sku_info info LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id WHERE info.spu_id = #{spuId} GROUP BY ssav.attr_id,ssav.attr_name,ssav.attr_value; </select>
- 修改item.html页面
- 使用js实现点击按钮后sku信息变化
- 不过先修改选择这块代码,加上checked
- js代码:
$(".sku_attr_value").click(function () { // 1、点击的元素添加自定义的属性。为了识别我们是刚才被点击 var skus = new Array(); $(this).addClass("ckicked"); // 寻找本列属性中class属性中有 ckicked var curr = $(this).attr("skus").split(","); // 将当前被点击的所有sku组合数组放进去 skus.push(curr); // 去掉同一行中所有的 checked $(this).parent().parent().find(".sku_attr_value").removeClass("checked"); // 寻找其他属性中class属性有 checked $("a[class='sku_attr_value checked']").each(function () { skus.push($(this).attr("skus").split(",")); }); console.log(skus); // 2、取出他们的交集,得到skuId var filterEle = skus[0]; for (var i = 1; i<skus.length; i++) { filterEle = $(filterEle).filter(skus[i]); } console.log(filterEle[0]); // 3、跳转 location.href = "http://item.gulimall.com/"+ filterEle[0] +".html"; }); $(function () { $(".sku_attr_value").parent().css({"border":"solid 1px #CCC"}); $("a[class='sku_attr_value checked']").parent().css({"border":"solid 1px red"}); })
- 不过先修改选择这块代码,加上checked
- 新建vo:
6.异步查询
- 之前在item()方法中查询各结果时,各查询都是同步的,但是我们可以利用前面学习的异步知识,将他们异步执行、
- 首先添加自己的线程池配置类
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
- 写配置信息类:ThreadPoolConfigProperties.class
@ConfigurationProperties(prefix = "gulimall.thread") @Component @Data public class ThreadPoolConfigProperties { private Integer coreSize; private Integer maxSize; private Integer keepAliveTime; }
- 在application.properties中添加配置信息
gulimall.thread.core-size=20 gulimall.thread.max-size=200 gulimall.thread.keep-alive-time=10
因为有spring-boot-configuration-processor依赖,所以写配置时会有提示,springboot配置原理差不多也是如此
- 实现自己的线程池类
@Configuration public class MyThreadConfig { @Bean public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) { return new ThreadPoolExecutor(pool.getCoreSize(), pool.getMaxSize(), pool.getKeepAliveTime(), TimeUnit.SECONDS, new LinkedBlockingDeque<>(100000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); } }
- 修改item()方法,实现异步编排
@Autowired ThreadPoolExecutor executor;
@Override public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException { SkuItemVo skuItemVo = new SkuItemVo(); //创建异步对象用supplyAsync而不是runAsync, 以便获取线程返回结果 CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> { //1、sku基本信息 pms_sku_info SkuInfoEntity info = getById(skuId); skuItemVo.setInfo(info); return info; }, executor); //线程串行化用thenAcceptAsync接收第一步的结果,自己执行没有返回结果 CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> { //3、获取spu的销售属性组合 List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId()); skuItemVo.setSaleAttr(saleAttrVos); }, executor); //需要第一步的sku实体类,所以还是使用第一部分thenAcceptAsync CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> { //4、获取spu的介绍 SpuInfoDescEntity spuInfoDescServiceById = spuInfoDescService.getById(res.getSpuId()); skuItemVo.setDesc(spuInfoDescServiceById); }, executor); CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> { //5、获取spu的规格参数信息 List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId()); skuItemVo.setGroupAttrs(attrGroupVos); }, executor); // 这个任务不用等第一个任务,也没有返回值,可以用runAsync CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> { //2、获取sku图片信息 List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId); skuItemVo.setImages(images); }, executor); //等待上面任务都完成,才返回 CompletableFuture.allOf(saleAttrFuture, descFuture, baseAttrFuture, imageFuture).get(); return skuItemVo; }
- 测试效果:所有信息获取正常