2017-07-10 24 views
2

我試圖提供一個通用javax.cache兼容適配器類javax.cache.configuration.FactoryBuilder檢索工廠,然後由ignite實例化緩存。所描述的問題可能會使用Apache Ignite,但是,我相信它不一定與Ignite相關,但更多的是泛型和閉包在Java中的工作方式。工廠從泛型高速緩存適配器類指針使用FactoryBuilder

Ignite CacheStoreAdapter接口繼承自javax.cache.CacheLoaderjavax.cache.CacheWriter,我提供了一個適配器實現。該實現需要緩存鍵和值的兩個(通用)類型以及值類引用,以便能夠實例化適配器中的值。請參閱下面的部分課程MyCacheAdapter

public class MyCacheAdapter<K,V extends StorableModel> extends CacheStoreAdapter<K,V> implements LifecycleAware { 
    private final Class<V> valueClazz; 
    public MyCacheAdapter(Class<V> valueClass) { 
     this.valueClazz = valueClass; 
    } 
    @Override 
    public V load(K key) throws CacheLoaderException { 
     // load from database 
     return valueClazz.newInstance(); // dummy instantiation 
    } 
    @Override 
    public void write(Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException { 
     // write to database 
    } 
} 

現在,當我明確聲明的適配器,並將其提供給FactoryBuilder一切正常......

public class MyPersonAdapter extends MyCacheAdapter<String,Person> { 
    public MyPersonAdapter() { 
     super(Person.class); 
    } 
} 

...當緩存我的服務啓動被實例化。

// run init cache (on 1st node only) 
public <K,V extends StorableModel> void init() { 
    CacheConfiguration cacheConfiguration = new CacheConfiguration<K,V>(); 
    cacheConfiguration.setCacheStoreFactory(FactoryBuilder.factoryOf(MyPersonAdapter.class)); 
    // add node filter to prevent other nodes from failing on cache distribution 
    cacheConfiguration.setNodeFilter((node) -> { // exclude 2nd node }); 
    ignite.getOrCreateCache(cacheConfiguration); 
} 

到目前爲止的工作實例!現在我不想明確聲明MyPersonAdapter(還有幾十個),而是讓我的初學者根據提供的類型和鍵/值類來處理適配器細節。所以,我可以提供我自己的工廠......

public static class AdapterFactory<K,V extends StorableModel> implements Factory<CacheStore<? super K, ? super V>> { 
    private final Class<V> valueClass; 
    public AdapterFactory(Class<V> valueClass) { 
     this.valueClass = valueClass; 
    } 
    @Override public CacheStore<? super K, ? super V> create() { 
     return new MyCacheAdapter<K,V>(valueClass); 
    } 
} 

...,然後在高速緩存初始化像這樣使用:

// run init cache (on 1st node only) 
public <K,V extends StorableModel> void init(Class<V> valueClass) { 
    CacheConfiguration cacheConfiguration = new CacheConfiguration<K,V>(); 
    cacheConfiguration.setCacheStoreFactory(new AdapterFactory<K,V>(valueClass)); 
    // add node filter to prevent other nodes from failing on cache distribution 
    cacheConfiguration.setNodeFilter((node) -> { // exclude 2nd node }); 
    ignite.getOrCreateCache(cacheConfiguration); 
} 

但是,這將引發我java.lang.ClassNotFoundException對我的第二個值類因爲該類不在第二個節點的類路徑中,所以點燃節點。我絕對不想提供那個類,我在問自己,第一個實現有什麼不同。我知道工廠在需要的時候確實創建了一個實例(當緩存分發到另一個節點時),它必須知道它沒有的值類。所以我嘗試了另一個實現來接近第一個(工作)的實現。相反,提供我自己的工廠,我想我有明確的適配器初始化嵌套類前右聲明:

public <K,V extends StorableModel> void init(Class<V> valueClass) { 
    class DynamicAdapter extends MyCacheAdapter<K,V> { 
     public DynamicAdapter() { 
      super(valueClass); 
     } 
    } 
    CacheConfiguration cacheConfiguration = new CacheConfiguration<K,V>(); 
    cacheConfiguration.setCacheStoreFactory(FactoryBuilder.factoryOf(DynamicAdapter.class)); 
    // add node filter to prevent other nodes from failing on cache distribution 
    cacheConfiguration.setNodeFilter((node) -> { // exclude 2nd node }); 
    ignite.getOrCreateCache(cacheConfiguration); 
} 

這再次引發了我的InstationException因爲類是超出範圍的工廠,我猜。

java.lang.RuntimeException: Failed to create an instance of DynamicAdapter 
    Caused by: java.lang.InstantiationException: DynamicAdapter 
    Caused by: java.lang.NoSuchMethodException: DynamicAdapter.<init>() 

所以,我想知道如果有一種方法來實現我的目標沒有一個自定義的工廠和類分佈跨越服務(這是沒辦法),但仍然有一些動態適配器聲明。

UPDATE

堆棧跟蹤的InstantiationException上課時由靜態方法返回

private static <X,Y extends StorableModel> Class getAdapterClass(Class<Y> valueClass) { 
    class MyClass extends MongoIgniteCacheAdapter<X,Y> { 
     MyClass() { 
      super(valueClass); 
     } 
    } 
    return MyClass.class; 
} 

// run init cache (on 1st node only) 
public <K,V extends StorableModel> void init() { 
    CacheConfiguration cacheConfiguration = new CacheConfiguration<K,V>(); 
    cacheConfiguration.setCacheStoreFactory(FactoryBuilder.factoryOf(getAdapterClass(valueClass))); 
    // add node filter to prevent other nodes from failing on cache distribution 
    cacheConfiguration.setNodeFilter((node) -> { // exclude 2nd node }); 
    ignite.getOrCreateCache(cacheConfiguration); 
} 

// output 

class org.apache.ignite.IgniteCheckedException: Failed to create an instance of IgniteServiceStarter$1MyClass 
at org.apache.ignite.internal.util.IgniteUtils.cast(IgniteUtils.java:7242) 
at org.apache.ignite.internal.util.future.GridFutureAdapter.resolve(GridFutureAdapter.java:258) 
at org.apache.ignite.internal.util.future.GridFutureAdapter.get0(GridFutureAdapter.java:206) 
at org.apache.ignite.internal.util.future.GridFutureAdapter.get(GridFutureAdapter.java:158) 
at org.apache.ignite.internal.processors.cache.GridCachePartitionExchangeManager$ExchangeWorker.body(GridCachePartitionExchangeManager.java:1812) 
at org.apache.ignite.internal.util.worker.GridWorker.run(GridWorker.java:110) 
at java.lang.Thread.run(Thread.java:745) 
Caused by: java.lang.RuntimeException: Failed to create an instance of IgniteServiceStarter$1MyClass 
at javax.cache.configuration.FactoryBuilder$ClassFactory.create(FactoryBuilder.java:134) 
at org.apache.ignite.internal.processors.cache.GridCacheProcessor.createCache(GridCacheProcessor.java:1458) 
at org.apache.ignite.internal.processors.cache.GridCacheProcessor.prepareCacheStart(GridCacheProcessor.java:1931) 
at org.apache.ignite.internal.processors.cache.GridCacheProcessor.prepareCacheStart(GridCacheProcessor.java:1833) 
at org.apache.ignite.internal.processors.cache.CacheAffinitySharedManager.onCacheChangeRequest(CacheAffinitySharedManager.java:379) 
at org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture.onCacheChangeRequest(GridDhtPartitionsExchangeFuture.java:688) 
at org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture.init(GridDhtPartitionsExchangeFuture.java:529) 
at org.apache.ignite.internal.processors.cache.GridCachePartitionExchangeManager$ExchangeWorker.body(GridCachePartitionExchangeManager.java:1806) 
... 2 more 
Caused by: java.lang.InstantiationException: IgniteServiceStarter$1MyClass 
at java.lang.Class.newInstance(Class.java:427) 
at javax.cache.configuration.FactoryBuilder$ClassFactory.create(FactoryBuilder.java:132) 
... 9 more 
Caused by: java.lang.NoSuchMethodException: IgniteServiceStarter$1MyClass.<init>() 
at java.lang.Class.getConstructor0(Class.java:3082) 
at java.lang.Class.newInstance(Class.java:412) 
... 10 more 
+0

您應該發佈'ClassNotFoundException'的堆棧跟蹤。 – Radiodef

回答

0

這可能是部分答案,但我認爲第二個例子(與本地類)失敗的原因本地班級是一個內部班級。內部類隱式地將外部類的實例作爲構造函數的第0個參數,因此它們沒有無參數構造函數。

class NewInstanceExample { 
    public static void main(String[] args) throws Exception { 
     try { 
      staticMethod(); 
      new NewInstanceExample().instanceMethod(); 
     } catch (Exception x) { 
      System.out.println("4. " + x); 
      System.out.println(" caused by " + x.getCause()); 
     } 
    } 
    static void staticMethod() throws Exception { 
     class StaticInner {} 
     System.out.println("1. " + StaticInner.class.newInstance()); 
    } 
    void instanceMethod() throws Exception { 
     class InstanceInner {} 
     System.out.println("2. " + java.util.Arrays.toString(InstanceInner.class.getDeclaredConstructors())); 
     System.out.println("3. " + InstanceInner.class.newInstance()); 
    } 
} 

的輸出如下:

1. [email protected] 
2. [mcve.NewInstanceExample$1InstanceInner(mcve.NewInstanceExample)] 
4. java.lang.InstantiationException: mcve.NewInstanceExample$1InstanceInner 
    caused by java.lang.NoSuchMethodException: mcve.NewInstanceExample$1InstanceInner.<init>()

注意的粗線,其示出了對於InstanceInner構造函數接受的NewInstanceExample一個實例,因此,爲什麼Class.newInstance()拋出InstantiationException

InstantiationException - 如果此Class表示抽象類,接口,數組類,基元類型或void;或如果該類沒有空的構造函數;或者由於其他原因實例化失敗。

的解決辦法是,以確保嵌套類沒有外圍實例,或者通過使用static修改(如在你的例子其中工程)或以其他方式聲明類的static背景下(如在我的例子與方法static)。

我不認爲我可以在沒有更多信息的情況下對ClassNotFoundException進行大量猜測。

+0

有趣的一點,我現在用靜態方法試了一下。不幸的是,我不得不返回這個類本身,而不是將它傳遞給工廠。 – Ben

+0

請參閱我在「InstantiationException」的完整堆棧跟蹤的初始文章中的更新。請注意,這是安靜點燃具體。 – Ben

+0

那麼,你可以在完整的堆棧跟蹤中看到它仍然是一個問題。 'Class.newInstance'找不到0參數的構造函數。所有其他的東西只是Ignite重新拋出該異常。你可以嘗試使構造函數爲'public',但在測試了一些東西之後,似乎本地類在這裏就不起作用了。一旦你將構造函數改爲'public',你將會得到一個'IllegalAccessException',因爲這個類本身並不是公有的,在寫這個答案的時候我昨天沒有發現它。 – Radiodef

0

不知道爲什麼你的第一個例子工作,它實際上也失敗了。

但是無論如何,整個方法都沒有意義。如果你沒有部署類,你將無法使用這些類的實例,最終你的商店將失敗。例如,load方法將在newInstance()調用失敗。

爲避免部署類,您需要在商店中使用BinaryObject s,並將CacheConfiguration#keepBinaryInStore標誌設置爲true,以便Ignite在調用CacheStore時不會序列化/反序列化對象。有關更多詳細信息,請參閱此頁面:https://apacheignite.readme.io/docs/binary-marshaller#binaryobject-and-cachestore

+0

關鍵是緩存僅在特定節點(由緩存節點過濾器選擇)上使用/實例化(cacheConfiguration.setNodeFilter((node) - > {...});'這就是爲什麼我的第一個例子工作我猜。我知道這個問題沒有提供。我不希望將服務內部類分發到集羣的所有節點,因爲這會破壞CI/CD爲首要任務的面向服務方法的目的。 – Ben

+0

我也試過'keepBinaryOnStore(true)',但它沒有任何區別。不支持指定緩存的節點仍然在classpath中請求該類。 – Ben

+0

'keepBinaryInStore'當然不會改變任何東西。您還需要更改商店的實現,以便它可以與「BinaryObject」API而不是您的類一起使用。一旦從存儲和存儲工廠實現中刪除對類的所有引用,就可以解決問題。 –