有时,在我们部署应用程序并且正在卸载和安装bundle之后,我们得到了类转换异常,其中A类不能转换为A类。问题是已经在内存中的实例的类加载器虽然不同于类本身的类加载器。部署不会影响存储实例的捆绑包(在此示例中为捆绑Y)。
这是伪代码:
捆绑X.
public class A extends B {
/* ... */
}
...
/* ... */
@Reference
private InMemoryUserTokenStore inMemoryUserTokenStore;
/* ... */
protected UsersTokenStore getTokenStore() {
return inMemoryUserTokenStore; // <- reference to service from another bundle where tokens are stored
}
/* The token is created and obtained in the same bundle */
A token = new A(...);
getTokenStore().addToken(token)
/* ... */
B token = getTokenStore().getToken(id)
((A) token).doSomething(); // <- this is when class cast exception is thrown*/
使用调试器我发现这里token
的类名是A
,两者的类加载器返回相同的包名和id(包X,相同的ID)但是它们不相等。
捆绑和
public class InMemoryUserTokenStore implements UsersTokenStore {
/* ... */
private ConcurrentMap<String, B> tokens = new ConcurrentHashMap();
/* ... */
public B getToken(String id) {
/* ... */
return tokens.get(id); // <- instance returned here sometimes has different class loader than class A from bundle **X**
}
/* ... */
}
我不确定这是OSGI的问题还是我们的设计错误?
如果有问题的两个类实例具有不同的类加载器,则抛出ClassCastException。
当更新或卸载软件包并且它正在导出软件包时,这些软件包不会被删除,并且针对旧的已卸载软件包解析的软件包仍将使用旧的/过时的代码,直到软件包被刷新为止。这是OSGi规范定义的正确行为。
Sling(它是AEM的底层框架)捆绑安装程序的实现在安装,更新或删除后调用刷新,因此这个问题根本不会出现。您应该调查在某些情况下刷新失败的原因 - 一个起点是为以下类启用跟踪日志记录并查看发生的事情 -
org/apache/sling/installer/core/impl/tasks/BundleUpdateTask
org/apache/sling/installer/core/impl/tasks/RefreshBundlesTask
您正在存储由旧版本的捆绑包创建的类型的对象,并期望它们可以转换为由捆绑包的较新版本创建的类型。当您的捆绑包停止时,您的缓存(令牌存储)应该使捆绑包存储的任何对象无效。