• 较多业步骤场景通用框架


    我们工作的大部分时间都在写业务代码,如何写好业务代码必然是我们追求的一大目标,在编程方面,简单、易懂、可扩展性是衡量代码质量的通用标准,所以在工作中,我们能用java将产品经理的想法表达出来还不够,我们产出的内容最好还能让其它的工程师一目了然。

    本文以创建订单这个业务场景着手,提供一种通用完成创建和编辑业务对象的模式。此业务发起是从前端的一个post请求,就假设为 /api/v1/order,后端通过 OrderController控制层接口,然后由 OrderService处理相关的业务逻辑,再通过OrderRepository持久层入库,请求处理完之后返回给前端一个orderUuid,这是一种很常见的业务:
    在这里插入图片描述

    对于Controller层,大多数情况下只用于构建参数和VO,以及及少量的参数校验(例如非空校验,格式校验等),多数的校验包含业务性的,有时需要与其它的微服务进行交互,我们通常会将这些校验移到service层,repository层只用于数据持久化,这里不做过多讨论,变化后的流程图如下:
    在这里插入图片描述

    流程图一完成,业务代码轻车熟路,仅看service层的代码实现,省略其它

    @Service
    public class OrderService {
       
        
        @Autowired
        private OrderRepository orderRepository;
        
        @Transactional
        public OrderDTO createOrder(OrderCreateRequest request) {
       
            // 校验1:用户是否有权限
            checkoutUserAuthorization(request);
            // 校验2:产品是否还有库存, 产品状态等
            checkoutProduct(request);
            
            final Order save = orderRepository.save();
            
            // 发mq消息给其它系统
            sendMqMessage(save);
            // 发邮件
            // sendEmail();
            
            // 记录操作日志
            // logService.saveLog()...
            
            return convert(save);
        }
    
        private void checkoutUserAuthorization(OrderCreateRequest request) {
       
            // ....查 userService等等
        }
    
        private void checkoutProduct(OrderCreateRequest request) {
       
            // ...查 productService,处理业务
        }
        
        private void sendMqMessage(Order order) {
       
            // 调mqService发消息
        }
    
        private OrderDTO convert(Order save) {
       
            OrderDTO dto = new OrderDTO();
            BeanUtils.copyProperties(save, dto);
            return dto;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    可能绝大多数工程师能够熟练并且无误地完成以上的代码编写工作,而这也是我们最常用的设计模式(MVC设计模式),当然也是最简单且和最高效的编码方式。那么到现在停下来想一想,这样写的代码有什么问题?

    我能想到以下问题:

    1. 如果是简单的业务,这段代码没有什么问题,也值得推荐。但订单业务是很复杂的,一是校验可能很多很严格;二与其交互的微服务或第三方系统也不少;三是可能根据订单的种类,有不同的校验方法,那么代码中就会有很多if ... else
    2. 如果业务非常复杂,这项工作要由多人共同完成,那么大家都会到这个类中编写代码,OrderService就成了一个极其臃肿的类,很快代码就超过五六百行(大家可以想想自己平时见到一个类代码有五百行时的感受)。
    3. 性能问题,假设在校验的时候通过调用userService查了当前用户信息,校验完成之后走到后面,记录日志的时候又要用当前用户信息,如果不缓存就又要查一遍,缓存的话就增加了系统的复杂度。
    4. 一旦代码结构这样设计了,那么使用面向对象的思想就终结了,这也是我觉得最重要的一点。

    spring框架给我们提供了很多便利,但是要警惕这些便利的诱惑,我们可以很容易将一个类定义成一个单例bean,并将它交给spring容器进行管理,但是一旦这么做了,在容器启动过程中就创建了这个类的单实例,那么这个对象就必定是一种无状态的,那它本身就成为了一个工具类,处理业务逻辑的工具,它能对外提供一系列方法来处理业务逻辑,我们只需要使用Autowired注解注入这个对象,就能在很多地方随意的使用它提供的方法,仅此而已,这时我们已经坠入了面向过程编辑的陷井。


    那么如何解决以上问题呢?

    一、校验逻辑又多又杂,需要设计成可插拔的组件。每个组件一个类,这样可以由多人独立开发,并且代码不会冗余到OrderService类中。

    参考spring在生产bean的过程中有很多生命周期,而在每个生命周期的处理逻辑上,都使用了不同的 BeanPostProcessor,每个postProcessor都是可扩展的,对它的扩展不会影响spring创建 bean的主流程。简单点来说,增加或减少一个校验逻辑,OrderService的代码不会有任何改动。

    下面是spring初始化bean的核心逻辑:

    protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
       
            if (System.getSecurityManager() != null) {
       
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
       
                    invokeAwareMethods(beanName, bean);
                    return null;
                }, getAccessControlContext());
            }
            else {
       
                invokeAwareMethods(beanName, bean);
            }
    
            Object wrappedBean = bean;
            if (mbd == null || !mbd.isSynthetic()) {
       
                wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
            }
    
            try {
       
                invokeInitMethods(beanName, wrappedBean, mbd);
            }
            catch (Throwable ex) {
       
                throw new BeanCreationException(
                        (mbd != null ? mbd.getResourceDescription() : null),
                        beanName, "Invocation of init method failed", ex);
            }
            if (mbd == null || !mbd.isSynthetic()) {
       
                wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
            }
    
            return wrappedBean;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这段代码做了什么事呢?

    1. 调用所有的 invokeAware方法,比如正在创建 UserService,而 UserService 实现了 BeanFactoryAware 接口。那么在此处,spring 就会主动调用UserService里面的 setBeanFactory()方法。
    2. 找到容器中所有的 Bean
  • 相关阅读:
    小节9:Python之numpy
    [附源码]JAVA毕业设计桔子酒店客房管理系统(系统+LW)
    Java学习Day027(Java常见面试题21-25)
    BOS EDI & Excel 方案简介
    移远通信携手MIKROE推出搭载LC29H系列模组的Click boards开发板,为物联网应用带来高精定位服务
    生产级Stable Diffusion AI服务部署指南【BentoML】
    nginx的安装(一)
    金九银十Go面试题进阶知识点:select和channel
    算法——二分查找
    关于自动化测试工具selenium
  • 原文地址:https://blog.csdn.net/jisuanji12306/article/details/127135714