架构师技能7:循环依赖引发的架构设计思考

Posted hguisu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了架构师技能7:循环依赖引发的架构设计思考相关的知识,希望对你有一定的参考价值。

本文单纯是个人的思考和总结。

一、背景


1、问题

最近团队项目一个服务出现循环依赖的问题,导致无法启动。

循环依赖即:bean A依赖于另一个bean B,而bean B又依赖于bean A,这个时候就很容易形成一个闭环甚至死循环下去。

官网:Core Technologies

spring造成循环依赖也是有前提条件:Autowired构造器注入和prototype类型的field注入。

而单例的属性注入是可以成功的。

Autowired构造注入:

@Component
public class BServiceImpl implements IBService 


    private AService aService;

    @Autowired
    public BServiceImpl(AService aService) 
        this.aService = aService;
    


@Component
public class AServiceImpl implements IAService 


    private BService bService;

    @Autowired
    public AServiceImpl(BService bService) 
        this.bService = bService;
    

prototype类型的field注入:

@Scope(“prototype”)

spring中bean的scope属性,有如下5种类型:

singleton 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例
prototype表示每次获得bean都会生成一个新的对象
request表示在一次http请求内有效(只适用于web应用)
session表示在一个用户会话内有效(只适用于web应用)
globalSession表示在全局会话内有效(只适用于web应用)
在多数情况,我们只会使用singleton和prototype两种scope,如果在spring配置文件内未指定scope属性,默认为singleton。

 

2、直接的曲线解决办法:

1)、使用@Lazy延迟加载bean

打破循环的一个简单方法是让Spring延迟地初始化其中一个bean。那就是:它不是完全初始化bean,而是创建一个代理将它注入另一个bean。注入的bean只有在第一次需要时才会完全创建。

2)使用Setter注入

最流行的解决方法之一,也是Spring文档提出的,是使用setter注入。

3)、使用@PostConstruct来

在其中一个bean上使用@Autowired注入依赖项,然后使用@PostConstruct注释的方法来设置其他依赖项。

4)实现ApplicationContextAwareInitializingBean

实现ApplicationContextAwareInitializingBean,然后通过BeanUtil类获取另外的bean:

BeanUtil.getBean(xxxService.class)

3、透过现象看本质

我习惯由表及里去分析每个问题,带着问题去思考,复盘总结经验,并把总结内容记录下来。

本文不想去追根究底去深入循环依赖的底层原理,而是从另外架构的角度去思考问题产生的原因。

如果想追根究底了解循环依赖底层原理,可以参考之前的博文:

Spring Boot(6) 原理和启动流程_hguisu的博客-CSDN博客

Spring学习笔记(2)一DI依赖注入和Spring Bean配置、注解原理、动态注入


 

而是把一件事情或者问题从现象上升到逻辑,再上升到方法,再到一整套方法体系,最后形成自己个人一套方法论,让自己的工作经验稳定地输出价值。


What:能够“洞察异常现象”,说明你是个很用心的人;

Why:能够“透过现象看到逻辑”,说明你是个爱动脑子的人;

How:能够“从逻辑中总结出一些方法”,说明你就能利用专业知识赚钱;

Know how:能够“把一些方法转化为一整套方法论”。

二、问题的思考1:工程代码架构规范


如果出现循环依赖的问题,虽然可以通过上面的直接曲线方法来解决,可以肯定的是应用程序设计以下问题之一:

1、类职责设计问题。

2、接口依赖设计原则问题。

3、顶层结构设计的问题。

我们遇到的问题,xxxService依赖xxxUtils, 按理说xxxUtils应该是静态方法类,结果是spring bean,这是我们历史遗留的问题:

1、类职责不清:xxxUtils职责应该都是静态方法类,不应该是spring bean。

2、接口依赖设计原则:xxxService和xxxUtils直接注入具体实现对象,违反依赖倒置设计原则,针对接口编程。

3、顶层结构设计的问题:如果xxxUtils确实要做成spring bean,可以下沉到通用基础层如manager层,但是我们这个应用没有任何通用基础层。

1、顶层结构设计:

在之前的博文已经详细说明

架构师技能1:Java工程规范、浅析领域模型VO、DTO、DO、PO、优秀命名

 如果应用程序层次设计规范,层次调用清晰,就不会出现这个循环依赖的问题。

2、接口依赖设计原则:

面向对象设计三大核心思想原则:封装变化点,对接口进行编程,多使用组合而不是继承。

面向对象设计常用的7个原则也基本从上面三大核心原则衍生出来,这些原则也并不是孤立存在的,它们相互依赖,相互补充前5个原则组合称为:SOLID 固定原则

设计原则:面向对象设计原则详解_hguisu的博客-CSDN博客_面向对象设计原则

三、问题的思考2:架构设计问题


个人理解:面对大工程,仍然需要有一定的架构设计方法论,需要架构师通过理解业务,全局把控,权衡业务需求和技术实现,选择合适技术,解决关键问题、指导研发落地实施,促进业务发展,提高效率。因此架构师是即掌控整体又需要洞悉局部问题的领导型人物

若是业务在野蛮生长阶段,业务边界和场景很难把握,业务产品需要快速迭代和变现,需求频繁更新,这个时候可以快速实现,那么这个阶段欠下的技术债和埋下的坑是可以理解。

若业务模式和应用场景边界都已经比较清晰,这个时候最需要架构师能对线上业务进行模块划分,对系统进行有序化重构,使系统不断进化和适应扩展。

架构设计的本质是管理复杂性,因此做好架构设计就避免一些不应该出现的问题,如上面提到的循环依赖问题。若大项目工程没有沉淀下相关架构设计文档之类,那该工程后续的扩展大概率朝着技术体系失控的方向发展。

我个人比较习惯写架构设计文档,最后写下项目总结。善于总结、不断反思做更好的自己_hguisu的博客-CSDN博客_善于总结反思

架构设计设计文档的目的:

架构设计(1)-谈谈架构_hguisu的博客-CSDN博客_架构

架构设计(7)—如何设计架构和画架构图_

总结的目的是复盘当前项目是否和当初的架构设计相符。

例如针对我们最近项目做了个总结:

总结的主要内容:设计哪些方面?在此过程中应用哪些原则,服务化如何做,组件化如何实施,计划是否顺利实施?在此过程中遇到哪些问题?

1、总体架构

2、指导原则:

主要总结项目应用来哪些原则:架构设计不像数据公式或者定律,很难一概而就。很多时候是设计者(架构师)的各种设想,各种权衡折中而符合系统需求的智慧输出。一些好的架构设计原则可以确保设计决策在一定程度上能够满足需求。

比如典型的分布式事务场景,上层应用服务A,完整的事物逻辑如下

事务开启
  任务1:改数据A // sql 存储
  任务2:调用系统底层服务接口C // -- 被调服务可能是类似情况
  任务3:调用外部服务接口D
提交事务

由于外部服务接口D不可控,可能会导致事务提交失败。因此上层服务A需要有幂等设计,那么底层服务接口C也有有幂等能力,确保上层服务A可以幂等调用。这就是在做设计的时候要求服务符合业务原则的幂等设计原则和应用设计的无状态原则。

具体可以查看:架构设计(2)-架构设计原则

 3、组件化设计

总结项目使用哪些组件,这些组件的层次结构如何设计:

组件化就是基于可重用的目的,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,已较少耦合。把重复的代码提取出来合并成为一个个组件,组件最重要的就是重用(复用),位于框架最底层,其他功能都依赖于组件,可供不同功能使用,独立性强。
大部分来说,组件主要分三层:业务组件,基础业务组件以及基础组件,组件之间只能通过接口耦合,也就是依赖倒置原则,每个组件都提供对外的接口文档以描述该组件提供的功能。
 

架构师技能2:组件化思想之框架、脚手架、基础应用框架。

以上是关于架构师技能7:循环依赖引发的架构设计思考的主要内容,如果未能解决你的问题,请参考以下文章

架构师技能7:循环依赖引发的架构设计思考

架构师技能7:循环依赖引发的架构设计思考

从MLSQL性能设计到对架构师的重新思考

从MLSQL性能设计到对架构师的重新思考

从MLSQL性能设计到对架构师的重新思考

[思维模式-6]:《如何系统思考》-2- 认识篇 - 为什么要系统思考?系统思考是系统架构师系统设计师的基本技能