较多业步骤场景通用框架

news/2024/5/6 1:10:45/文章来源:https://blog.csdn.net/jisuanji12306/article/details/127135714

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

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

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

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

@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic 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设计模式),当然也是最简单且和最高效的编码方式。那么到现在停下来想一想,这样写的代码有什么问题?

我能想到以下问题:

  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. 调用所有的 invokeAware方法,比如正在创建 UserService,而 UserService 实现了 BeanFactoryAware 接口。那么在此处,spring 就会主动调用UserService里面的 setBeanFactory()方法。
  2. 找到容器中所有的 BeanPostProcessors,并执行postProcessBeforeInitialization()
  3. 执行bean的 init 方法,也就是在方法上加了 @PostConstract注解,或实现了InitializingBean重写的 afterPropertiesSet()方法。
  4. 找到容器中所有的 BeanPostProcessors,并执行postProcessAfterInitialization()方法。

主要看以下两个方法:

applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessBeforeInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}

spring实现AOP功能,实际上就是在启动的过程中往容器里面加了一个 BeanPostProcessor,并在applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)这个方法中,执行了 aop的这个 BeanPostProcessor的方法,得到的动态代理。而spring aop并没有改变spring ioc的代码。


按照以上的方式,我们可以将 UserService改造一版:

  1. 定义一个 OrderPostProcessor 接口,将方法加上default是因为有些类不不需要都实现这两个方法
    public interface OrderPostProcessor {// 调用save()方法前执行default void postProcessBeforeSave(OrderCreateRequest request){}// 调用save()方法后执行,因为save之后就有一个新的 Order了。default void postProcessAfterSave(Order order, OrderCreateRequest request){}}
    
  2. 将校验的业务逻辑分成不同的类,并实现 OrderPostProcessor接口。举两个例子,一个是save之前执行,一个是save之后执行
    //这个类用于查询product并校验商品库存是否足够,商品的状态是否符合要求等
    @Component
    public class ProductValidatorPostProcessor implements OrderPostProcessor{@Autowiredprivate ProductClient productClient;@Overridepublic void postProcessBeforeSave(OrderCreateRequest request) {// productClient.getProduct()}
    }// 这个类用于订单入库之后发送消息通知商户或者买家
    @Component
    public class SendMessagePostProcessor implements OrderPostProcessor{@Overridepublic void postProcessAfterSave(Order order, OrderCreateRequest request) {// 发各种消息。。。}
    }
    

  1. OrderService 中完成各个处理类的组装。
    
    @Service
    public class OrderService implements ApplicationContextAware {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic OrderDTO createOrder(OrderCreateRequest request) {applyOrderPostProcessorsBeforeSave(request);final Order save = orderRepository.save();applyOrderPostProcessorsAfterSave(save, request);return convert(save);}private void applyOrderPostProcessorsBeforeSave(OrderCreateRequest request) {// 从spring容器中取出所有实现了 OrderPostProcessor 接口的类,循环调用它的 postProcessBeforeSave() 方法final Map<String, OrderPostProcessor> beansOfType = applicationContext.getBeansOfType(OrderPostProcessor.class);for (OrderPostProcessor value : beansOfType.values()) {value.postProcessBeforeSave(request);}}private void applyOrderPostProcessorsAfterSave(Order order, OrderCreateRequest request) {final Map<String, OrderPostProcessor> beansOfType = applicationContext.getBeansOfType(OrderPostProcessor.class);for (OrderPostProcessor value : beansOfType.values()) {value.postProcessAfterSave(order, request);}}private OrderDTO convert(Order save) {OrderDTO dto = new OrderDTO();BeanUtils.copyProperties(save, dto);return dto;}ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
    }
    

代码是简洁了一些,我们将本应放在 OrderService 中的代码转移到各个其它的类中去了,OrderService 变小了。另我大家也发现这个OrderService有明显的问题: applyOrderPostProcessorsBeforeSaveapplyOrderPostProcessorsAfterSave 这两个方法与业务无关太突兀了,不应该放在 OrderService 里,并且每次调用都需要从容器中获所有的 OrderPostProcessor实现类,不太合适,应该在启动的时候就创建好。

修改后如下:

定义了一个 OrderServiceBisProvider 类,将与业务无关的操作放分开

public abstract class OrderServiceBisProvider implements SmartInitializingSingleton, ApplicationContextAware {List<OrderPostProcessor> orderPostProcessors = new ArrayList<>();protected void applyOrderPostProcessorsBeforeSave(OrderCreateRequest request) {for (OrderPostProcessor value : orderPostProcessors) {value.postProcessBeforeSave(request);}}protected void applyOrderPostProcessorsAfterSave(Order order, OrderCreateRequest request) {for (OrderPostProcessor value : orderPostProcessors) {value.postProcessAfterSave(order, request);}}@Overridepublic void afterSingletonsInstantiated() {final Map<String, OrderPostProcessor> beansOfType = applicationContext.getBeansOfType(OrderPostProcessor.class);final Collection<OrderPostProcessor> values = beansOfType.values();// 排序AnnotationAwareOrderComparator.sort(Collections.singletonList(values));this.orderPostProcessors.addAll(values);}ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

OrderServiceBisProvider 只是一个辅助类,目的是将 OrderService 中的部分代码抽出来,此处并无重用代码的想法,但是后面可以改进。

实现 SmartInitializingSingleton 这个接口,那么spring将会在所有的单实例非懒加载的bean创建之后来调用(参考spring bean创建的生命周期)。这时我们可以取出容器中所有实现了OrderPostProcessor接口的bean。AnnotationAwareOrderComparator.sort(Collections.singletonList(values));这段代码起排序作用。只要bean使用了 @Order注解或者实现了 order 接口都可以进行排序。


@Service
public class OrderService extends OrderServiceBisProvider {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic OrderDTO createOrder(OrderCreateRequest request) {applyOrderPostProcessorsBeforeSave(request);final Order save = orderRepository.save();applyOrderPostProcessorsAfterSave(save, request);return convert(save);}private OrderDTO convert(Order save) {OrderDTO dto = new OrderDTO();BeanUtils.copyProperties(save, dto);return dto;}}

这时候再来看 OrderService的代码确实简洁了许多,但是也增加了不少的类。尤其是增加了很多处理器。由于业务的需要,可能有些校验必须先执行,我们可以使用 @Order 注解对它们进行编排,这样就可以保证 OrderServiceBisProvider 中的List<OrderPostProcessor> orderPostProcessors是按我们的要求排好序的。

//这个类用于查询product并校验商品库存是否足够,商品的状态是否符合要求等
@Order(1)
@Component
public class ProductValidatorPostProcessor implements OrderPostProcessor{@Autowiredprivate ProductClient productClient;@Overridepublic void postProcessBeforeSave(OrderCreateRequest request) {// productClient.getProduct()}
}// 这个类用于订单入库之后发送消息通知商户或者买家
@Order(10)
@Component
public class SendMessagePostProcessor implements OrderPostProcessor{@Overridepublic void postProcessAfterSave(Order order, OrderCreateRequest request) {// 发各种消息。。。}
}

现在的业务流程图:
在这里插入图片描述

总结:

通过以上重构,我们就实现了部分的开闭原则和单一职责原则,如果我们要增加一个创建订单前的校验,只需要增加OrderPostProcessor接口的实现类即可,不需要改OrderService的代码,理论上我们只要保证新加的代码进行了充分的测试即可,原先的代码就可以不用回归。另外,由于将各个校验或处理的业务逻辑分为不同的单元分散到各个类中,这也符合类的单一职责原则。


二、将前置处理过程中产生的数据保存到线程中,给后面的处理过程使用,实现性能的提升

假设现在有这么一个业务,在保存订单前校验用户的身份权限,保存订单后要给当前操作人发邮件。那么基于以上的结构,我们可以很快实现这个业务逻辑,增加一个类如下:

@Order(5)
@Component
public class OperatorPostProcessor implements OrderPostProcessor{@Autowiredprivate UserClient userClient;@Overridepublic void postProcessBeforeSave(OrderCreateRequest request) {
//        userClient.getUser(request.getOperatorUuid());// 1. 调用 userClient 服务,查询当前操作人的信息// 2. 取出用户权限进行校验}@Overridepublic void postProcessAfterSave(Order order, OrderCreateRequest request) {
//        userClient.getUser(request.getOperatorUuid());// 1. 调用 userClient 服务,查询当前操作人的信息// 2. 取出用户的邮箱,发邮件}
}

理论上,加了上面的类,系统是能正常运行的(实际也能正常运行),可是代码有瑕疵:调用了两次查询用户的方法 userClient.getUser(request.getOperatorUuid());,由于这个对象是单例bean,所以不能用一个成员变量去维护用户的数据(会出现线程安全问题),只能将数据缓存到当前的线程中,这个时候很容易想到使用 ThreadLocal 来保存。

使用 ThreadLocal 是没问题的,但是不能将它放在这个类中,因为不仅仅只有这一个场景需要保存数据,其它的类也有同样的需求。因此将它放置到 OrderServiceBisProvider 中,命名为:ORDER_PROCESSOR_CONTEXT

public abstract class OrderServiceBisProvider implements SmartInitializingSingleton, ApplicationContextAware {List<OrderPostProcessor> orderPostProcessors = new ArrayList<>();public static final ThreadLocal<Map<String, Object>> ORDER_PROCESSOR_CONTEXT = new ThreadLocal<>();protected void applyOrderPostProcessorsBeforeSave(OrderCreateRequest request) {ORDER_PROCESSOR_CONTEXT.remove();ORDER_PROCESSOR_CONTEXT.set(new HashMap<>());for (OrderPostProcessor value : orderPostProcessors) {value.postProcessBeforeSave(request);}}protected void applyOrderPostProcessorsAfterSave(Order order, OrderCreateRequest request) {for (OrderPostProcessor value : orderPostProcessors) {value.postProcessAfterSave(order, request);}}@Overridepublic void afterSingletonsInstantiated() {final Map<String, OrderPostProcessor> beansOfType = applicationContext.getBeansOfType(OrderPostProcessor.class);final Collection<OrderPostProcessor> values = beansOfType.values();// 排序AnnotationAwareOrderComparator.sort(Collections.singletonList(values));this.orderPostProcessors.addAll(values);}ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}}

注意:在 applyOrderPostProcessorsBeforeSave 方法开始前加了两行代码:

ORDER_PROCESSOR_CONTEXT.remove();
ORDER_PROCESSOR_CONTEXT.set(new HashMap<>());

因为TomeCat的线程是基于线程池的,某个新请求的线程可能复用了之前的某个线程,就会带上ThreadLocal里面的数据,为了防止数据错乱,必需在线程开始前它清空重置。


OperatorPostProcessor 改造如下:

@Order(5)
@Component
public class OperatorPostProcessor implements OrderPostProcessor{@Autowiredprivate UserClient userClient;@Overridepublic void postProcessBeforeSave(OrderCreateRequest request) {User operator = userClient.getUser(request.getOperatorUuid());ORDER_PROCESSOR_CONTEXT.get().put("operator", operator); // 将信息存入 context 中// 1. 调用 userClient 服务,查询当前操作人的信息// 2. 取出用户权限进行校验}@Overridepublic void postProcessAfterSave(Order order, OrderCreateRequest request) {
//        直接从 context 中拿数据final User operator = (User) ORDER_PROCESSOR_CONTEXT.get().get("operator");// 1. 调用 userClient 服务,查询当前操作人的信息// 2. 取出用户的邮箱,发邮件}
}

同样的操作适用于其它的处理器,当然为了代码的可读性,可以自定义一个类,用于替换 ThreadLocal中的map,提供一些 getter和 setter方法。



三、使用多态,使增加处理类更易于扩展

按照上述的业务框架,可以对工作中部分业务代码进行重构,只能是部分,为什么呢?经常写业务代码的同学就会发现,业务需求会经常变化,偶尔加一个种类什么的很常见,就以下单为例,一个正常的下单业务流程,可能过一段时间加了一个内购活动,员工自己下单有额外的优惠,也可能订单又分为虚拟物品的订单(例如充值)等等,大家会发现,越往后,代码中加入的 if - else 分支越来越多,有的是根据下单用户的角色进行分类,有的是根据商品的种类进行分类,导致代码中有大量的分支,于是后来的人就不想看这些代码了,而且大家也不敢修改,害怕某一个改动影响了很早之前的一个隐藏的逻辑。

这个时候多态就要上场了,不同的类型用不同的类去实现,这样也可以实现对扩展开放,对修改封闭,不动以前的逻辑,保证代码的可维护性更强。那么怎么将多态整合到我们这个业务框架中呢?

现在的这个业务框架已经将各种业务分为一个一个的单元了,所以我们可以将多态运用到各个单元上,而不必动 OrderSerivce 。以一个具体的场景为例:如果是一个内购订单,就校验用户是否是本公司的员工,否则不校验。这时要对 OperatorPostProcessor 类进行重构。

  1. 引入一个抽象类,作为主框架,子类通过重写模版方法实现多态
    public abstract class AbstractOperatorPostProcessor implements OrderPostProcessor{@Overridepublic void postProcessBeforeSave(OrderCreateRequest request) {User operator = userClient.getUser(request.getOperatorUuid());ORDER_PROCESSOR_CONTEXT.get().put("operator", operator);// 1. 调用 userClient 服务,查询当前操作人的信息if (support(request)) {// 2. 查询当前用户是否是员工,不是则抛出异常...}}@Overridepublic void postProcessAfterSave(Order order, OrderCreateRequest request) {final User operator = (User) ORDER_PROCESSOR_CONTEXT.get().get("operator");// 1. 调用 userClient 服务,查询当前操作人的信息// 2. 取出用户的邮箱,发邮件if (support(request)) {doExtraOperate(request);}}protected void doExtraOperate(OrderCreateRequest request) {// 如果是内部员工,做一些额外的操作,由具体的子类重写}abstract boolean support(OrderCreateRequest request);

  1. 子类继承 AbstractOperatorPostProcessor
    // 普通的订单
    @Order(5)
    @Component
    public class OperatorPostProcessor extends AbstractOperatorPostProcessor{// 是否是内购的订单@Overrideboolean support(OrderCreateRequest request) {return false;}
    }// 内购的订单
    @Order(5)
    @Component
    public class InnerOperatorPostProcessor extends AbstractOperatorPostProcessor{// 是否是内购的订单@Overrideboolean support(OrderCreateRequest request) {return true;}@Overrideprotected void doExtraOperate(OrderCreateRequest request) {// 做一些额外的操作System.out.println("内部员工正在购买商品");}
    }
    

以上只是简单地举了一个例子,虽然有点迁强,但是足以体现了将多态整合到这个业务框架中的方法,当然实际工作中要视具体的情况来看到底是定义抽象类还是只定义接口。


4、小结

一提到设计模式,给大家的第一映象就是在框架,中间件中使用,而在我看来恰恰相反,设计模式的初衷就是使系统能有效地应对未来的变化,业务代码的变化是很频繁的,所以我觉得在业务代码中运用一些设计模式,或设计模式的思想是很有必要的。在实现一个功能之前应好好思考业务中不变的部分,只要找到了这些不变的部分,就将它封装到抽象类或接口中,而将变化的部分定义为抽象方法由不同的子类去实现。

本文提供的一个业务框架,仅仅适用于有较多的业务校验场景,如果是简单的业务,直接将它写在 OrderService里面会更好,等后续业务变复杂了,再将它们抽出来不迟。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_17206.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OSCP-Vulnhub靶机记录-LordoftheRoot-walkthrough

靶机地址 https://www.vulnhub.com/entry/lord-of-the-root-101,129/ 交流学习联系&#xff1a;webMsec 靶机安装 主机发现 靶机ip 192.168.160.131 使用nmap扫描后发现只开放了22 ssh 尝试连接ssh 这里需要端口碰撞 再次nmap扫描 1337端口开放apache Dirsearch扫一下 404…

IS-IS 路由选择协议入门

为了理解中间系统一中间系统(IntermediateSystem-to-Intermediate System, IS-IS) 路由选择协议的本质和内在的工作原理&#xff0c;把它放在整个网际协议和相关技术的框架中学习是十分重要的。本章深入IS-IS协议的本质&#xff0c;并且探讨了国际标准化组织(Intemational Orga…

Understanding the Users and Videos by Mining a Novel Danmu Dataset

题目&#xff1a;Understanding the Users and Videos by Mining a Novel Danmu Dataset 作者&#xff1a;Guangyi Lv, Kun Zhang, Le Wu, Enhong Chen, Tong Xu, Qi Liu, and Weidong He 发表&#xff1a;IEEE TRANSACTIONS ON BIG DATA, 2022 切入点&#xff1a;弹幕交流…

C++实现二分法求零点

​目录前言 题目: 一、零点是什么? 二、二分法求零点 1.二分法 2.完整代码 总结 前言 首先,我们要清楚我们是干嘛的;其次,知道原理;最后,才能明白自己要怎么办。明确:用二分法求函数。 题目: 二分法求函数的零点: 有函数: f(x) = x5 - 15 * x4+ 85 * x3- 225 * x2…

十一、动态规划题目相关

学习来源&#xff1a; 代码随香炉&#xff1a;https://www.programmercarl.com/ labuladong算法&#xff1a;https://labuladong.github.io/algo/ 动态规划 动态规划五部曲 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历…

炫酷的花式滑块滑动无缝切换特效

&#x1f482; 个人网站:【 海拥】【小霸王游戏机】【大转盘】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习群】【学习文档】&#x1f4ac; 免费且实用的计…

【ML05】Feature Scaling 特征缩放

Feature ScalingFeature Scaling 特征缩放的目的是什么Feature Scaling Method #3Dividing by maximumMean NormalizationZ-Score normalizationFeature Scaling 特征缩放的目的是什么 考虑前两个组图&#xff1a; 组图1&#xff1a;同一辆大货车拉货&#xff0c;同一个函数在…

Flink学习笔记(2)——Flink快速上手

目录 一、Flink快速上手 1.1、环境准备 1.2 创建项目 1.3 编写代码 1.3.1 批处理 1.3.2 流处理 1.4 本章总结 一、Flink快速上手 对 Flink 有了基本的了解后&#xff0c;接下来就要理论联系实际&#xff0c;真正上手写代码了。Flink 底层是 以 Java 编写的&#xff0c;…

计算机网络—物理层

计算机网络—物理层 物理层的基本概念 物理层的作用是要尽可能地屏蔽掉传输媒体和通信手段的差异&#xff0c;使物理层上面的数据链路层感觉不到这些差异&#xff0c;这样就可以使数据链路层只需要考虑如何完成本次的协议和服务&#xff0c;而不必考虑网络具体的传输媒体和通…

切记:Python迭代器只可以读取一次,忽略会有意想不到的麻烦。

Python 官网&#xff1a; https://www.python.org/- ###### Free&#xff1a;大咖免费“ 圣经”教程 《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单……My CSDN主页、My HOT博、My Python 学习个人备忘录好文力荐、老齐教室自学并不是什么神秘的东西 &#xff0c…

Java学习笔记:高级数据过滤

通配符过滤 1、名字以T开头的 SELECT * FROM T_Persons WHERE Name LIKE ‘T%’ 2、名字以ke结尾的 SELECT * FROM T_Persons WHERE Name LIKE ‘%ke’ 3、名字中包含“中”的 SELECT * FROM T_Persons WHERE Name LIKE ‘%中%’ 多值检测 SELECT Age,Name FROM T_…

Java的输入 Scanner in=new Scanner(System.in);

java和c还是有好多不同的地方&#xff0c;需要从头开始认认真真地学 文章目录输入数字输入double输入整型输入字符串判断2个字符串是否相等Java的字符串要用""双引号引起来&#xff0c;而不是单引号输入一维数组输入二维数组输入数字 输入double import java.util.…

算法分析与设计:10 大排序算法大汇总(Java)

冒泡排序 相邻比较并交换位置&#xff0c;将大的数冒泡交换到最后。 /******************************************************************************** 冒泡排序&#xff08;Bubble Sort&#xff09;它重复地走访过要排序的元素&#xff0c;依次比较相邻两个元素&#xf…

E2成都电路板设计_启动保持停止电路的原理

电气技术分享之2 本文介绍电气工程里常见的启动、保持、停止电路的原理。 1、起保停电路的功能 起保停电路实现的功能&#xff1a;按启动按键&#xff0c;电路的负载得电并保持&#xff0c;按停止按键&#xff0c;负载断电。 2、起保停电路所需的元件 起保停电路所需的元件…

matplotlib绘制直方图,饼图,散点图,气泡图,箱型图,雷达图

matplotlib绘制直方图&#xff0c;饼图&#xff0c;散点图&#xff0c;气泡图&#xff0c;箱型图&#xff0c;雷达图一.直方图用10000个正态分布随机数画直方图二.绘制饼图或者圆环图圆环图根据消费支出画圆环图三.绘制散点图或气泡图使用scatter()函数绘制一个散点图&#xff…

【进制计算】 2 ~ N 进制计算

目录 规则 图解十、二、八、十六进制之间的转换 举例 除法计算出3进制&#xff1a; 乘法次方逆向计算原数&#xff1a; 图解二进制加减乘除计算 规则 十进制 除以 进制数 取余法&#xff1a;&#xff08;1&#xff09;被除数 除以 除数 等于 商 并取得余数&#xff0c;&am…

SSM进阶-Duubo入门demo整合MyBatis

搭建入门demo 搭建SpringSpringMVCDubbo入门demo 准备数据 数据库创建demo表 create table demo (id bigint auto_increment primary key,name varchar(255) null,description text null ); 插入数据 INSERT INTO demo(id, name, description) VAL…

数据库基础,看完这篇就够了!

转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/747a74ea.html你好,我是测试蔡坨坨。 对于测试同学来说,除了知道测试基础知识外,还需要掌握一些测试基本技能,主要有Linux、数据库、计算机网络等,在此之前我们已经讨论过Linux基础知识以及在实际工作中的应…

神经网络模型训练简记(一)

神经网络模型训练简记&#xff08;一&#xff09;一、概念介绍1.1人工智能、机器学习、神经网络与深度学习1.2backbone与pretrain_model1.3batch_size、learning_rate、epoch与iteration1.4模型评价指标二、官方数据集简介2.1ImageNet数据集2.2 ILSVRC竞赛2.3 MS COCO数据集2.4…

【专栏】RPC系列(实战)-低配版NameServer

公众号【离心计划】,一起离开地球表面 【RPC系列合集】 【专栏】RPC系列&#xff08;理论&#xff09;-夜的第一章 【专栏】RPC系列&#xff08;理论&#xff09;-协议与序列化 【专栏】RPC系列&#xff08;理论&#xff09;-动态代理 【专栏】RPC系列&#xff08;实战&am…