开发SPI时不要犯这个错误

您的大多数代码都是私有的,内部的,专有的,并且永远不会公开。 在这种情况下,您可以放轻松–您可以重构所有错误,包括那些可能导致API更改中断的错误。

但是,如果要维护公共API,则不是这种情况。 如果您要维护公共SPI( 服务提供商接口 ),那么情况就更糟了。

H2触发SPI

在最近的有关如何使用jOOQ实现H2数据库触发器的 Stack Overflow问题中,我再次遇到了org.h2.api.Trigger SPI –一种实现触发器语义的简单且易于实现的SPI。 触发器在H2数据库中的工作方式如下:

使用扳机

CREATE TRIGGER my_trigger
BEFORE UPDATE
ON my_table
FOR EACH ROW
CALL "com.example.MyTrigger"

实施触发器

public class MyTrigger implements Trigger {@Overridepublic void init(Connection conn, String schemaName,String triggerName, String tableName, boolean before, int type)throws SQLException {}@Overridepublic void fire(Connection conn, Object[] oldRow, Object[] newRow)throws SQLException {// Using jOOQ inside of the trigger, of courseDSL.using(conn).insertInto(LOG, LOG.FIELD1, LOG.FIELD2, ..).values(newRow[0], newRow[1], ..).execute();}@Overridepublic void close() throws SQLException {}@Overridepublic void remove() throws SQLException {}
}

整个H2触发器SPI实际上相当好用,通常您只需要实现fire()方法。

那么,这个SPI有什么问题呢?

这是非常微妙的错误。 考虑init()方法。 它具有一个boolean标志,指示触发器是在触发事件之前还是之后触发,即UPDATE 。 如果突然之间,H2还支持INSTEAD OF触发器怎么办? 理想情况下,此标志将被enum代替:

public enum TriggerTiming {BEFORE,AFTER,INSTEAD_OF
}

但是我们不能简单地引入这种新的enum类型,因为init()方法不应不兼容地更改,从而破坏所有实现代码! 使用Java 8,我们至少可以这样声明一个重载:

default void init(Connection conn, String schemaName,String triggerName, String tableName, TriggerTiming timing, int type)throws SQLException {// New feature isn't supported by defaultif (timing == INSTEAD_OF)throw new SQLFeatureNotSupportedException();// Call through to old feature by defaultinit(conn, schemaName, triggerName,tableName, timing == BEFORE, type);}

这将允许新的实现处理INSTEAD_OF触发器,而旧的实现仍将起作用。 但这感觉很毛,不是吗?

现在,想象一下,我们还将支持ENABLE / DISABLE子句,并且希望将这些值传递给init()方法。 或者,也许我们想处理FOR EACH ROW 。 目前尚无法使用此SPI进行此操作。 因此,我们将越来越多地实现这些重载,这些重载很难实现。 实际上,这已经发生了,因为还有org.h2.tools.TriggerAdapter ,它与Trigger冗余(但与Trigger略有不同)。

那么,哪种方法更好呢?

SPI提供者的理想方法是提供“参数对象”,如下所示:

public interface Trigger {default void init(InitArguments args)throws SQLException {}default void fire(FireArguments args)throws SQLException {}default void close(CloseArguments args)throws SQLException {}default void remove(RemoveArguments args)throws SQLException {}final class InitArguments {public Connection connection() { ... }public String schemaName() { ... }public String triggerName() { ... }public String tableName() { ... }/** use #timing() instead */@Deprecatedpublic boolean before() { ... }public TriggerTiming timing() { ... }public int type() { ... }}final class FireArguments {public Connection connection() { ... }public Object[] oldRow() { ... }public Object[] newRow() { ... }}// These currently don't have any propertiesfinal class CloseArguments {}final class RemoveArguments {}
}

如上例所示,使用适当的弃用警告已成功开发了Trigger.InitArguments 。 没有客户端代码被破坏,并且如果需要,可以使用新功能。 另外,即使我们不需要任何参数, close()remove()也为将来的发展做好了准备。

该解决方案的开销是每个方法调用最多分配一个对象,这不会造成太大的损失。

另一个示例:Hibernate的UserType

不幸的是,这个错误经常发生。 另一个著名的例子是Hibernate难以实现的org.hibernate.usertype.UserType SPI:

public interface UserType {int[] sqlTypes();Class returnedClass();boolean equals(Object x, Object y);int hashCode(Object x);Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException;void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException;Object deepCopy(Object value);boolean isMutable();Serializable disassemble(Object value);Object assemble(Serializable cached, Object owner);Object replace(Object original, Object target, Object owner);
}

SPI看起来很难实现。 也许您可以使某些工作很快完成,但是您会感到放心吗? 你会认为你做对了吗? 一些例子:

  • 从来没有在nullSafeSet()也需要owner引用的情况吗?
  • 如果您的JDBC驱动程序不支持按名称从ResultSet获取值怎么办?
  • 如果需要在存储过程的CallableStatement使用用户类型怎么办?

此类SPI的另一个重要方面是实现者可以向框架提供价值的方式。 在SPI中使用非void方法通常是一个坏主意,因为您将永远无法再更改方法的返回类型。 理想情况下,您应该具有接受“结果”的参数类型。 上面的许多方法都可以用单个configuration()方法代替,例如:

public interface UserType {default void configure(ConfigureArgs args) {}final class ConfigureArgs {public void sqlTypes(int[] types) { ... }public void returnedClass(Class<?> clazz) { ... }public void mutable(boolean mutable) { ... }}// ...
}

另一个示例,SAX ContentHandler

在这里看看这个例子:

public interface ContentHandler {void setDocumentLocator (Locator locator);void startDocument ();void endDocument();void startPrefixMapping (String prefix, String uri);void endPrefixMapping (String prefix);void startElement (String uri, String localName,String qName, Attributes atts);void endElement (String uri, String localName,String qName);void characters (char ch[], int start, int length);void ignorableWhitespace (char ch[], int start, int length);void processingInstruction (String target, String data);void skippedEntity (String name);
}

此SPI缺点的一些示例:

  • 如果在endElement()事件中需要元素的属性怎么办? 您必须自己记住它们。
  • 如果您想在endPrefixMapping()事件中知道前缀映射uri怎么办? 还是其他任何事件?

显然,SAX针对速度进行了优化,并且在JIT和GC仍然较弱的时候针对速度进行了优化。 尽管如此,实现SAX处理程序并非易事。 部分原因是由于SPI难以实现。

我们不知道未来

作为API或SPI提供程序,我们根本不知道未来。 现在,我们可能认为给定的SPI就足够了,但是我们将在下一个次要版本中将其破坏。 否则我们不会破坏它,并告诉我们的用户我们无法实现这些新功能。

通过以上技巧,我们可以继续发展我们的SPI,而不会引起任何重大变化:

  • 始终将唯一一个参数对象传递给方法。
  • 总是返回void 。 让实现者通过参数对象与SPI状态进行交互。
  • 使用Java 8的default方法,或提供“空”默认实现。

翻译自: https://www.javacodegeeks.com/2015/05/do-not-make-this-mistake-when-developing-an-spi.html

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

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

相关文章

有什么好一点的方法读jdk源码吗?

建议是: 1:找相关的书在看相关源码时应该对程序有整体了解,比如看虚拟机时至少要知道:垃圾回收算法,Java内存布局,class文件格式,加载连接的过程...很多人看源码一点头绪都没有,就是因为不了解对应的模块是干嘛的(比如Ja

给女朋友道歉的java代码_跟女朋友道歉的话,高情商的哄人句子

女朋友生气了&#xff0c;而且很严重。在这种情况下&#xff0c;不知道做什么挽回她。第一诚实的道歉可以缓和彼此紧张的气氛。重要的是&#xff0c;道歉完就需要说一些话语去哄她&#xff0c;至于怎么哄又是一个技术性的问题了&#xff0c;下面一起来看看跟女朋友道歉的话,高情…

使用Myeclipse完成Hibernate的逆向工程

Hibernate的开发流程一般有两种&#xff1a; 1、由Domain object > mapping > db 2、由db开始&#xff0c;用工具生成生成mapping 和Domain object。 在实际的开发过程中&#xff0c;涉及的表太多了&#xff0c;一个一个的写java实体类很费事费时的。Myeclipse提供了一个…

面试用涉及到的jvm常见的面试题(一)

什么是jvm? JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机…

kali安装docker(有效详细的教程)

前记&#xff1a; 博主有着多次安装docker的丰富经验&#xff0c;曾经为了在kali成功安装docker花费不少时间。在kali2016.3一直到最新的kali2019.4都通吃&#xff01;所以跟着下面的步骤走&#xff0c;绝对不会出错。&#xff08;该机子此前没装过docker&#xff0c;并且配置好…

java中 下列不合法的语句_在Java中,下列( )是不合法的赋值语句。_学小易找答案...

【多选题】一个国家一定时期的商品销售额属于?【单选题】某厂2007年完成产值200万元,2008年计划增长10%,实际完成231万元,超额完成计划?【多选题】我国GDP每增长1%,相当于人均增加收入70多元;全国将增加60—80万个就业岗位。这里用到的指标有?【单选题】轴的最小实体尺寸为(…

最大流EK和Dinic算法

最大流EK和Dinic算法 EK算法 最朴素的求最大流的算法。 做法&#xff1a;不停的寻找增广路&#xff0c;直到找不到为止 代码如下&#xff1a; Frosero #include <cstdio> #include <iostream> #include <cstring> #include <queue> #define INF 0x3f3…

某大厂JVM常见面试题(二)吐血整理

什么是JVM? JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机…

_stat64获取错误_Log4j,Stat4j,SMTPAppender集成–汇总错误日志以发送过多电子邮件...

_stat64获取错误我们的开发团队希望在生产系统出现问题时尽快得到通知&#xff0c;这是一个每天为成千上万的客户提供服务的关键Java Web应用程序。 想法是让它在出现太多错误时向我们发送电子邮件&#xff0c;这些错误通常表示数据库&#xff0c;外部Web服务有问题&#xff0c…

Kali2021修改更新源

本次演示三种环境更换更新源 Xfce4版本 KDE版本 命令行版本 更新源 #中科大 deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib

java面试中jvm执行子系统详细分析(三)

什么是jvm? JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机…

java中菜单分几级_JavaWeb三级菜单分类查询详解

废话不多说&#xff0c;直接贴代码&#xff1a;dao层代码&#xff1a;mapper&#xff1a;ListselectByParentId(Integer id);mapper.xmlselectfrom easybuy_product_categorywhere parentId #{parentId,jdbcTypeINTEGER}Test:import cn.hd.entity.ProductCategory;import cn.h…

使用Javaslang进行Java 8中的函数式编程

我们非常高兴地在jOOQ博客上宣布一个客座帖子&#xff0c;该帖子由HSH Nordbank的高级软件工程师&#xff0c;三个孩子的父亲&#xff0c; Daniel Dietrich撰写。 他目前作为项目负责人和首席开发人员为金融产品创建定价框架。 除工作外&#xff0c;他还对编程语言&#xff0c…

HFS远程命令执行漏洞复现

漏洞程序下载地址:Http File Server 这里说一下&#xff0c;在Windows server 2003中复现时&#xff0c;我直接使用Administrator用户登录&#xff0c;然后复现&#xff0c;未出现权限问题。再Windows 10中尝试复现时发现权限不足&#xff0c;需要以管理员权限执行漏洞程序。 运…

HTML标签速查表

有时候,学习的前端的标签太多,容易记不牢,这里整理一份标签速查表,便于查阅。不区分html或html5.

Silverlight 中datagrid控件-- 通过设置数据虚拟化加速显示

定义依赖属性作为datagrid的数据源 protected static readonly DependencyProperty ViewLogsProperty DependencyProperty.Register("ViewLogs", typeof(ObservableCollection<RMSReportEvent>), typeof(LogViewer), new PropertyMetadata(null)); protected …

java解析java源码_JAVA语言-Java源码解析-Stack源码分析

一、简介stack类图.png栈是数据结构中一种很重要的数据结构类型&#xff0c;因为栈的后进先出功能是实际的开发中有很多的应用场景。Java API中提供了栈(Stacck)的实现。Stack类继承了Vector类&#xff0c;而Vector类继承了AbstractList抽象类&#xff0c;实现了List类&#xf…

渗透测试神器CS(4.0)的使用

CS CS简介CS功能安装CSCS的基本使用深入标题介绍 Beacon Commands基础操作演示 CS简介 CS 是Cobalt Strike的简称&#xff0c;是一款渗透测试神器&#xff0c;常被业界人称为CS神器。Cobalt Strike已经不再使用MSF而是作为单独的平台使用&#xff0c;它分为客户端与服务端&…

UTF-8和GBK有啥区别?

粉丝求助: 如何解决: 把编辑器和浏览器的字符集统一设置成utf-8或者gbk即可。 主要区别: 1.GBK是在bai国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不du是国家标准)。zhiGBK编码dao专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。 2. UTF-8 编码是…

服务osgi_OSGi –具有服务的简单Hello World

服务osgi在本文中&#xff0c;我们将使用OSGi开发一个简单的Hello World应用程序。 我们将使用Felix作为OSGi容器 。 在下一篇文章中&#xff0c;我们将继续使用该应用程序&#xff0c;并使用Spring Dynamic Modules对其进行改进。 为了使开发有趣&#xff0c;我们将创建两个捆…