日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

开发SPI时不要犯这个错误

發布時間:2023/12/3 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 开发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

總結

以上是生活随笔為你收集整理的开发SPI时不要犯这个错误的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。