我们工作的大部分时间都在写业务代码,如何写好业务代码必然是我们追求的一大目标,在编程方面,简单、易懂、可扩展性是衡量代码质量的通用标准,所以在工作中,我们能用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;
}
}
可能绝大多数工程师能够熟练并且无误地完成以上的代码编写工作,而这也是我们最常用的设计模式(MVC设计模式),当然也是最简单且和最高效的编码方式。那么到现在停下来想一想,这样写的代码有什么问题?
我能想到以下问题:
if ... else
。OrderService
就成了一个极其臃肿的类,很快代码就超过五六百行(大家可以想想自己平时见到一个类代码有五百行时的感受)。userService
查了当前用户信息,校验完成之后走到后面,记录日志的时候又要用当前用户信息,如果不缓存就又要查一遍,缓存的话就增加了系统的复杂度。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;
}
这段代码做了什么事呢?
invokeAware
方法,比如正在创建 UserService
,而 UserService
实现了 BeanFactoryAware
接口。那么在此处,spring 就会主动调用UserService
里面的 setBeanFactory()
方法。Bean