音乐网站怎么做,2018十大网络营销案例,优秀购物网站,毕业网站建设开题报告ginkgo spi 错误您的大多数代码都是私有的#xff0c;内部的#xff0c;专有的#xff0c;并且永远不会公开。 在这种情况下#xff0c;您可以放轻松–您可以重构所有错误#xff0c;包括那些可能导致API更改中断的错误。 但是#xff0c;如果要维护公共API#xff0c;… 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 isnt 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 dont 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.htmlginkgo spi 错误