如何强制转换从Module返回的对象



我尝试使用JDK 11的ModuleLayer,我创建了两个模块ImplementationModel。考虑Implementation模块提供了一个返回类型为Foo的对象的方法,Foo类在Model模块中定义。

模块Implementationrequires Model;和模块Model导出其所有的命名空间。

我是一个单独的应用程序,我正在创建Configuration来解决这两个模块并创建ModuleLayer。通过使用layer.findLoader("Implementation").loadClass(),我从Implementation模块加载了一个类,并使用method.invoke(null, "test");调用静态方法返回Object.

现在我不明白了,我该如何将这个对象转换为Foo?我不能使用(Foo) obj,因为Model模块的jar不在类路径上。即使我把它放进去,也会得到exception

Caused by: java.lang.ClassCastException: class com.test.model.Foo cannot be cast to class com.test.model.Foo (com.test.model.Foo is in module Model of loader jdk.internal.loader.Loader @e9b78b0; com.test.model.Foo is in unnamed module of loader com.company.container.internal.Interpreters$ClassLoaders$$anon$1 @45815ffc)

Object provider=null;
try
{
var path = Path.of(Core.getConfiguration().getBasePath().getPath() ,"\model\lib\EC");
ModuleFinder finder = ModuleFinder.of(path);
var parent = ModuleLayer.boot();
Configuration cf = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of("Implementation"));
ClassLoader scl = this.getClass().getClassLoader(); // intentionally using this class's loader  
ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl);
var cl = layer.findLoader("Implementation");
Class<?> classTest = cl.loadClass("com.test.AutoConfiguration");
var method = classSendMailFromX.getMethod("getProvider", String.class);
provider = method.invoke(null, "test");
}
catch (Exception e) {
throw new CoreException(e);
}
if(provider == null)
{
throw new Exception("provider cannot be empty");
}
var myProvider = (Foo) provider; // This line compiles but calls fails at runtime if I put `Model` module jar on classpath

我理解的原因,任何jar(即使它是一个模块)在类路径将被视为未命名模块。但是,为什么我不能投射它呢?有什么办法可以做到吗?

@Holger的评论指出了问题:Foo不是Foo,因为它们都是由不同的类/模块加载器加载的。

让我简述两种不同的方法:

方案1:模块方式

这里的目标是使Foo类只加载一次。要实现这一点,上面代码的应用程序也应该在一个模块中,并且需要Model:

module App {
requires Model;
}

要使此工作,您还必须避免通过所提供的代码第二次加载Model。这可以通过(a)而不是将其放入"\model\lib\EC"中来实现。或者通过(b)改变Configuration.resolve()ModuleFinders的顺序:

Configuration cf = parent.configuration().resolve(ModuleFinder.of(), finder. Set.of("Implementation"));

这样,Implementation将使用在调用代码的模块路径中存在的Model版本,而不是通过finder加载它。

解决方案2:使用代理这要求Foo是一个接口。

你可以有一个方法proxyOf,看起来像这样:

private Foo proxyOf(Object result) {
InvocationHandler handler = (proxy, method, args) -> {
Method delegate = result.getClass().getMethod(method.getName(), method.getParameterTypes());
return delegate.invoke(result, args);
};
return (Foo) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{ Foo.class }, handler);
}

这样你可以有两个版本的Foo。您只需要确定,应用程序代码实际上被允许访问方法delegate。因此,它(以及包含它的实现类)必须是public,或者在调用它之前需要一个额外的delegate.setAccessible(true);。如果应用程序位于模块内(如解决方案1),则必须通过opens子句打开delegate包:

module Implementation {
requires transitive Model;
exports org.example.impl;
opens org.example.impl;
}

请不要认为存在主要缺陷代理解决方案中的:只要您传递JRE的类型,它就能很好地工作,但是一旦您传递自己的类型,method.getParameterTypes()就会返回错误的类型,导致NoSuchMethodException。即使您设法获得了正确的类型,您也需要传递该类型的代理。然后,所有这些将变得非常复杂,你将以自己的"来回代理"告终。框架。

最后指出

不幸的是,你没有描述你实际上想要实现什么。所以我们不知道你想用它解决什么问题,为什么你需要上面的代码。老实说,让别人来维护你的代码对我来说是相当困难的。也许你可以通过使用ServiceLoader来实现同样的事情?

最新更新