ginkgo 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
ginkgo spi 错误