【项目实践】实现InitializingBean接口在服务启动时加载初始数据

一、InitializingBean的介绍

       我们知道,SpringBean的生命周期中,bean对象创建出来后会继续执行一系列操作进行属性注入和初始化,若bean对象所在的类实现了InitializingBean接口,那么该bean在初始化的时候,spring会调用其重写后的afterPropertiesSet()方法。通常,我们重写这个方法可以用来加载一些初始数据或初始化操作,如加载菜单树形列表、创建第三方系统的客户端连接对象等等,这样可以使得项目启动完成后,客户在请求数据时,后端可以立即执行业务逻辑,而不是先加载一些系统资源,导致请求响应缓慢或者超时,提高客户体验。

二、实现InitializingBean接口提前加载资源的项目实践

        1、加载菜单树形列表并放入缓存中间件

        通常情况下,菜单信息、商品分类信息以及母公司子公司的单位层级这种包含关系的数据,在数据表中是通过id和parent_id两个字段进行关联的,而我们要查询出这些信息并且将这种树形层级关系展示出来,需要执行大量的遍历和递归,时间复杂度和空间复杂度都比较高。如果我们每次访问这些数据都去执行数据库查询和递归数据封装,不仅响应时间很长,而且内存的资源消耗也会很高,我们希望对这类操作做一些优化。

        事实上,我们可以发现,像这种层级数据,一旦保存到数据库后,一般就会比较少地去修改它,大多情况下我们都是查询操作。所以,我们可以联想到将这类层级数据放入到缓存中,提高查询速度。同时,我们可以通过服务启动的初始化操作,预先去加载数据,使用户在首次查询时也可以快速响应结果,提升用户体验。

        因此,我的优化思路是:service实现类实现InitializingBean接口,重写afterPropertiesSet方法,在方法中执行数据库查询和层级结构封装,然后将封装后的数据保存到redis中,后续针对该数据的请求都会先从redis中获取

        1.1、缓存优化的具体代码实现及测试结果对比

        以下是两种方案的代码以及jmeter压测的数据:封装树形结构的具体实现

        不使用缓存:

@Override
public List<CategoryVo> findCategoryTreeNoCatch() {
    // 从数据库中查询所有数据
    List<CategoryVo> list = categoryMapper.selectAll();
    // 对数据进行层级结构封装
    return CollUtil.isNotEmpty(list) ? BaseTreeUtils.listTreeNodes(list) : CollUtil.newArrayList();
}

        使用缓存进行优化:

@Service
public class CategoryServiceImpl implements CategoryService, InitializingBean {

    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public List<CategoryVo> findCategoryTree() {
        String jsonVos = redisTemplate.opsForValue().get(ProductConstant.CATEGORY_TREE_INFO);
        List<CategoryVo> resultVos = null;
        if (StrUtil.isNotBlank(jsonVos)) {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                resultVos = objectMapper.readValue(jsonVos, new TypeReference<List<CategoryVo>>() {
                });
                return resultVos;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        List<CategoryVo> list = categoryMapper.selectAll();
        resultVos = CollUtil.isNotEmpty(list) ? BaseTreeUtils.listTreeNodes(list) : CollUtil.newArrayList();
        redisTemplate.opsForValue().set(
                ProductConstant.CATEGORY_TREE_INFO,
                JSON.toJSONString(resultVos),
                30l,
                TimeUnit.MINUTES
        );
        return resultVos;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // todo: 实现InitializingBean接口,重写afterPropertiesSet方法在启动时加载商品分类信息
        List<CategoryVo> list = categoryMapper.selectAll();
        List<CategoryVo> resultVos = CollUtil.isNotEmpty(list) ? BaseTreeUtils.listTreeNodes(list) : CollUtil.newArrayList();
        String jsonVos = JSON.toJSONString(resultVos);
        redisTemplate.opsForValue().set(
                ProductConstant.CATEGORY_TREE_INFO,
                jsonVos,
                30l,
                TimeUnit.MINUTES
        );
    }
}

        1.2、结果分析

        通过对比我们不难发现,在不使用缓存的情况下,接口的吞吐量为51.3/sec,响应速度的平均值为2636ms;而使用缓存进行优化的情况下,接口的吞吐量为332.6/sec,响应速度的平均值为13ms。优化的效果是很明显的。因此,我们可以实现Spring框架的InitializingBean接口提前加载多读少写的数据,并使用缓存中间件来提升数据读取速度,实现查询优化。

        在此基础上,还可以引入SpringCatch对逻辑代码进行简化:

<!--引入Spring Catch-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
@Service
public class CategoryServiceImpl implements ICategoryService, InitializingBean {

    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    @Cacheable(value = ProductConstant.PRODUCT_CATEGORY_KEY, key = "'level1'")
    public List<Category> getLevel1List() {
        return categoryMapper.getLevel1List();
    }

    @Override
    @Cacheable(value = ProductConstant.PRODUCT_CATEGORY_KEY, key = "'tree'")
    public List<CategoryVo> findCategoryTree() {
        List<CategoryVo> list = categoryMapper.selectAll();
        return CollUtil.isNotEmpty(list) ? BaseTreeUtils.listTreeNodes(list) : CollUtil.newArrayList();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        initProductTree();
    }

    @CachePut(value = ProductConstant.PRODUCT_CATEGORY_KEY, key = "'tree'")
    public List<CategoryVo> initProductTree() {
        List<CategoryVo> list = categoryMapper.selectAll();
        return CollUtil.isNotEmpty(list) ? BaseTreeUtils.listTreeNodes(list) : CollUtil.newArrayList();
    }
}

        2、预加载第三方系统的客户端连接

        在项目中,我们会用到很多其他的工具或依赖来实现具体的功能。例如使用minio来存储用户上传的文件,使用ElasticSearch来实现全文检索等等。我们使用这些工具的大致步骤通常为:创建网络连接,进行数据交互,关闭连接通道,释放资源。如果我们每次访问这些第三方工具都要执行一整个完整的逻辑,很明显可以看出网络连接的频繁创建和释放会提高请求的响应时间,同时也会加剧系统资源的消耗。

        在spring框架中,我们的service实现类对象通常都是单例模式的。因此,我们可以通过实现InitializingBean接口在项目启动时预加载第三方工具的客户端连接,并作为成员变量存放在系统中,接收请求后可以立即执行数据交互,多次请求复用已有的连接,提高响应速度。以文件上传功能使用minio进行存储为例,具体代码实现为:

@Service
public class FileUploadServiceImpl implements FileUploadService, InitializingBean {

    @Autowired
    private MinioProperties minioProperties;

    private MinioClient minioClient;

    @Override
    public String fileUpload(MultipartFile multipartFile) {
        ......
    }

    @Override
    public void afterPropertiesSet() {
        // 创建一个Minio的客户端对象
        minioClient = MinioClient.builder()
                .endpoint(minioProperties.getEndpointUrl())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecreKey())
                .build();
    }
}

三、总结

        在后端项目中,我们可以通过实现SpringBean的InitializingBean接口在服务启动时预加载初始数据或其他客户端连接,优化程序响应速度。

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值