SpringPlugin-Core在业务中的应用
前言
一直负责部门的订单模块,从php转到Java也是如此,换了一种语言来实现订单相关功能。那么Spring里有很多已经搭建好基础模块的设计模式来帮助我们解耦实际业务中的逻辑,用起来非常的方便!就比如我们的订单操作模块。生成订单后,有很多操作。比如:取消、支付、关闭....等等。那么用设计模式的思想去处理这些不同的操作,最好用的就是策略模式来解决它们!把不同的操作分配到不同的实现类里去。这不,我发现了一个不错的东西Spring plugin
!
Spring Plugin
Spring plugin
这个小东西我也是在看一些开源项目才看到的,感觉还不错。就立马拿来用了下,把它带到我们业务场景里去。这不,带大家体验下。
下面用Spring plugin
来重构下订单的相关操作实现。这里我们只模拟,支付和关闭的操作。最后再来简单分析下Spring plugin
的原理
实战
首先呢、定义一个操作类型的枚举类,来边界下当前我们系统支持的操作类型!
public enum OrderOperatorType {
/**
* 关闭
*/
CLOSED,
/**
* 支付
*/
PAY
;
}
第二步、定义操作接口,实现Spring plugin
的Plugin<S>
接口,和配置插件
public interface OrderOperatorPlugin extends Plugin<OrderOperatorDTO> {
/**
* 定义操作动作
* @param operator
* @return
*/
public Optional<?> apply(OrderOperatorDTO operator);
}
//配置插件,插件写好了,我们要让插件生效!
@Configuration
@EnablePluginRegistries({OrderOperatorPlugin.class})
public class OrderPluginConfig {
}
第三步 、定义具体的实现类(支付操作、关闭操作)
@Component
public class PayOperator implements OrderOperatorPlugin {
@Override
public Optional<?> apply(OrderOperatorDTO operator) {
//支付操作
//doPay()
return Optional.of("支付成功");
}
@Override
public boolean supports(OrderOperatorDTO operatorDTO) {
return operatorDTO.getOperatorType() == OrderOperatorType.PAY;
}
}
@Component
public class ClosedOperator implements OrderOperatorPlugin {
@Override
public Optional<?> apply(OrderOperatorDTO operator) {
//关闭操作
//doClosed()
return Optional.of("关闭订单成功");
}
@Override
public boolean supports(OrderOperatorDTO operatorDTO) {
return operatorDTO.getOperatorType() == OrderOperatorType.CLOSED;
}
}
这里要注意的是实现 supports
方法,此方法返回的是一个boolean
值,直观的看起来就是一个选择器的条件,这里可直接认为,当Support
返回True
的时候,就找到了当前操作的实现类!
两个不同的实现类定义好,那么我们怎么找到具体的实现类呢?
最后、定义业务接口,和业务实现类
public interface OrderService {
/**
* 操作订单接口
* @param operator
* @return
*/
Optional<?> operationOrder(OrderOperatorDTO operator);
}
@Service
public class OrderServiceImpl implements OrderService {
@Resource
PluginRegistry<OrderOperatorPlugin, OrderOperatorDTO> orderOperatorPluginRegistry;
@Override
public Optional<?> operationOrder(OrderOperatorDTO operator) {
OrderOperatorPlugin pluginFor = orderOperatorPluginRegistry.getPluginFor(operator);
return pluginFor.apply(operator);
}
}
在业务接口实现类里我们注入了
@Resource
PluginRegistry<OrderOperatorPlugin, OrderOperatorDTO> orderOperatorPluginRegistry;
名字一定是 接口名 + Registry,我这里是orderOperatorPluginRegistry
至于为什么要这样写,等回我们分析源码的时候看一下。目前这样写就对了。
接下来我们测试下
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderOperatorPluginTest {
@Resource
OrderService orderService;
@Resource
ApplicationContext applicationContext;
@Test
public void test_operation_closed() {
final OrderOperatorDTO operator = new OrderOperatorDTO();
operator.setOperatorType(OrderOperatorType.CLOSED);
Optional<?> optionalO = orderService.operationOrder(operator);
Assertions.assertEquals("关闭订单成功", optionalO.get());
}
@Test
public void test_operation_pay() {
final OrderOperatorDTO operator = new OrderOperatorDTO();
operator.setOperatorType(OrderOperatorType.PAY);
Optional<?> optionalO = orderService.operationOrder(operator);
Assertions.assertEquals("支付成功", optionalO.get());
}
}
这个运行结果是没有问题的,可以自己把代码下载下来,跑一下~~
感受
如果我把整个订单的流程都当作不同的插件来开发的话...创建订单是一个流程、在这个流程的过程中,我们分别在不同的位置插入不同的插件。比如下图!
最后,这只把所以Plugin
组织起来,是不是就可以搞定一套完整的订单流程了,而我们做的只是面对插件开发,把注意力集中到某个插件中就可以了呢?或许下次订单重构的时候,我可以会这样的去尝试下!
源码重点分析
首先看下注册插件的注释EnablePluginRegistries
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({PluginRegistriesBeanDefinitionRegistrar.class})
public @interface EnablePluginRegistries {
Class<? extends Plugin<?>>[] value();
}
value
属性是一个数组,必须实现Plugin
接口,这个是定义插件的基本条件~。
再看Import
注释,PluginRegistriesBeanDefinitionRegistrar
实现了ImportBeanDefinitionRegistrar
接口,这个有点味道了,
public class PluginRegistriesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
}
之前我有一个文章是分析相关加载类到容器的一篇文章,请看下面文章的介绍。
ImportBeanDefinitionRegistrar的作用
直接看核心代码
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//当前我只注册了一个 插件 OrderOperatorPlugin
Class<?>[] types = (Class[])((Class[])importingClassMetadata.getAnnotationAttributes(EnablePluginRegistries.class.getName()).get("value"));
Class[] var4 = types;
int var5 = types.length;
//长度也就为1
for(int var6 = 0; var6 < var5; ++var6) {
Class<?> type = var4[var6];
//是FactoryBean 见名思义。PluginRegistryFactoryBean#getObject 的方法最终返回的是 OrderAwarePluginRegistry 看名字是支持排序的功能。
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PluginRegistryFactoryBean.class);
builder.addPropertyValue("type", type);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
Qualifier annotation = (Qualifier)type.getAnnotation(Qualifier.class);
if (annotation != null) {
AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
beanDefinition.addQualifier(qualifierMetadata);
}
//插件上没有添加 Qualifier 所以为null, nulll的话就给拼接上 Registry! 这就是为啥注入的时候用 插件名 + Registry、另外 PluginRegistryFactoryBean实现了PluginRegistry。
String beanName = annotation == null ? StringUtils.uncapitalize(type.getSimpleName() + "Registry") : annotation.value();
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
那么注入容器后,调用getPluginFor
找到当前策略的实现类。
// OrderAwarePluginRegistry 类
public T getPluginFor(S delimiter) {
Iterator var2 = super.getPlugins().iterator();
Plugin plugin;
do {
if (!var2.hasNext()) {
return null;
}
plugin = (Plugin)var2.next();
//这里判断 supports的方法 返回true时即跳出Loop
} while(plugin == null || !plugin.supports(delimiter));
return plugin;
}
//另外 super.getPlugins里 会调用 initializa的方法,即插件是支持排序功能的,只要在插件上加入Order()注释即可。
protected List<T> initialize(List<T> plugins) {
List<T> result = super.initialize(plugins);
Collections.sort(result, this.comparator);
return result;
}
SpringPlugin-Core在业务中的应用的更多相关文章
- Core 1.0中的管道-中间件模式
ASP.NET Core 1.0中的管道-中间件模式 SP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middlewar ...
- .NET Core 2.1中的HttpClientFactory最佳实践
ASP.NET Core 2.1中出现一个新的HttpClientFactory功能, 它有助于解决开发人员在使用HttpClient实例从其应用程序发出外部Web请求时可能遇到的一些常见问题. 介绍 ...
- C# 动态创建SQL数据库(二) 在.net core web项目中生成二维码 后台Post/Get 请求接口 方式 WebForm 页面ajax 请求后台页面 方法 实现输入框小数多 自动进位展示,编辑时实际值不变 快速掌握Gif动态图实现代码 C#处理和对接HTTP接口请求
C# 动态创建SQL数据库(二) 使用Entity Framework 创建数据库与表 前面文章有说到使用SQL语句动态创建数据库与数据表,这次直接使用Entriy Framwork 的ORM对象关 ...
- 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制
你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...
- HBase在大搜车金融业务中的应用实践
摘要: 2017云栖大会HBase专场,大搜车高级数据架构师申玉宝带来HBase在大搜车金融业务中的应用实践.本文主要从数据大屏开始谈起,进而分享了GPS风控实践,包括架构.聚集分析等,最后还分享了流 ...
- ASP.NET Core Web API中使用Swagger
本节导航 Swagger介绍 在ASP.NET CORE 中的使用swagger 在软件开发中,管理和测试API是一件重要而富有挑战性的工作.在我之前的文章<研发团队,请管好你的API文档& ...
- ASP.NET Core - 在ActionFilter中使用依赖注入
上次ActionFilter引发的一个EF异常,本质上是对Core版本的ActionFilter的知识掌握不够牢固造成的,所以花了点时间仔细阅读了微软的官方文档.发现除了IActionFilter.I ...
- 更优雅的配置:docker/运维/业务中的环境变量
目录 docker-compose 环境变量 .env 文件 env_file docker stack 不支持基于文件的环境变量 envsubst envsubst.py 1. 使用行内键值对 2. ...
- Java线程池实现原理及其在美团业务中的实践
本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...
- Spring事件,ApplicationEvent在业务中的应用
前言 关于事件驱动模型,百度百科在有明确的解释.在JDK的Util包里抽象了事件驱动,有兴趣的朋友可以自行去看下相关类的定义.Spring事件模型ApplicationEvent是基于JDK里的事件模 ...
随机推荐
- Oracle数据库 —— DML完结
时间:2016-8-18 01:17 ----------------------------------------------------------------------------停下休息的 ...
- Go语言 判断key是否在map里 if _, ok := map[key]; ok
if val, ok := map[key]; ok { //do something here } 如果key在map里 val 被赋值map[key] ok 是true 否则val得到相应类型的零 ...
- 分享几个下载豆瓣资源的chrome插件
最近chrome终于以4.69%的市场占有率击败firefox成为中国第二大浏览器.(第一当然是争霸宇宙的IE了) 虽然chrome官方应用程序商店有不少豆瓣的辅助插件,但大多没什么用.属于蛋疼插件. ...
- 基于Linux系统的网络服务——高速缓存DNS及企业级域名解析服务
1.DNS域名系统 DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数 ...
- You have mail in /var/mail/xxx
因为配置 DDNS, 我添加了个 crontab 定时任务,每隔 1 分钟执行一段 python 脚本 然后就发现 terminal 经常提示 'You have mail in /var/mail/ ...
- JUC原子操作类与乐观锁CAS
JUC原子操作类与乐观锁CAS 硬件中存在并发操作的原语,从而在硬件层面提升效率.在intel的CPU中,使用cmpxchg指令.在Java发展初期,java语言是不能够利用硬件提供的这些便利来提 ...
- Spring Cloud Eureka 实践(二)
接上一篇的内容,Eureka服务已经启动成功后,可以尝试开发服务的提供者与消费者,并注册到Eureka来实现服务的发现与调用. 首先,在父工程中继续创建服务提供者的Module,最新的目录结构如下图所 ...
- 解决CSDN文章下载后,依然请求跳转至首页出错的问题
1. 搜索关键字:"onerror" 然后找到如下所示代码: <div style="display:none;"> <img ...
- 关于golang结束了编程风格中对于左大括号要不要换行之争.
golang规定了左大括号必须紧跟在语句后面,这样一下子就结束了各种代码风格之争. 其实golang是继承了早期的C语言,为了节省空间,才将左括号放到代码后面. 哪种编码风格是你的"菜&qu ...
- Windows Phone 页面之间参数传递方法
目前对WP7开发正在研究,对页面之间参数传递进行了一个小总结,有不正确的地方,欢迎大家指正.. WP7编程采用的技术是Silverlight,页面之间参数传递的方式主要有 通过NavigationCo ...