使用GWT.create()和字符串字面量而不是类字面量进行GWT动态加载

15
9个回答

13

虽然有些复杂,但是确实可以实现。以下是详细内容:

如果你只把GWT看作是Java转JS的工具,那么它是无法胜任这个任务的。但是,如果你考虑到生成器——一种特殊的类,在编译期间由GWT编译器编译和执行,就可以实现这个功能。因此,即使在编译过程中,你也可以生成Java源代码。

我今天有这个需求——我们的系统处理服务端的动态资源,最终输出一个字符串和需要的类。下面是我想出的解决方案——顺便说一句,它可以在hosted、IE和Firefox上运行。

  • 创建一个GWT模块,声明以下内容:
    • 源路径
    • 一个生成器(应该放在GWT Module源路径的外部位置)
    • 一个接口替换(它将使用生成的类代替接口)
  • 在该包内创建一个Marker接口(我称之为Constructable)。生成器将查找该Marker。
  • 创建一种基础抽象类来容纳该工厂。我这样做是为了简化生成的源代码。
  • 在Application.gwt.xml中声明该模块的继承关系

一些说明:

  • 理解生成器的概念是关键;
  • 为了方便起见,抽象基础类非常实用。
  • 另外,请注意,在生成的.js源代码和生成的Java源代码中都有命名处理。
  • 请记住,生成器输出Java文件。
  • GWT.create需要对.class文件进行某些引用。只要你的生成器输出被应用程序从某个地方引用即可(检查Application.gwt.xml是否继承了你的模块,并且该模块替换了一个接口为你的生成器)。
  • 将GWT.create调用包装在工厂方法/单例内,并放置在GWT.isClient()下面。
  • 在GWT.runAsync周围包装你的代码-类加载调用也是一个非常好的主意,因为它可能需要触发一个模块加载。这一点非常重要

希望很快能够发布源代码。祝好运 :)


5

Brian,

问题在于GWT.create不知道如何为你的抽象类选择正确的实现。

我曾经遇到过与新的GWT MVP编码风格相似的问题(请参见GWT MVP文档)。

当我调用:

ClientFactory clientFactory = GWT.create(ClientFactory.class);

我也得到了同样的错误:

Deferred binding result type 'com.test.mywebapp.client.ClientFactory' should not be abstract

我所要做的就是在MyWebapp.gwt.xml文件中添加以下行:

<!-- Use ClientFactoryImpl by default -->
    <replace-with class="com.test.mywebapp.client.ClientFactoryImpl">
    <when-type-is class="com.test.mywebapp.client.ClientFactory"/>
    </replace-with>

那么它就像一个魔法一样起作用。

1
但是这仍然需要在编译时存在ClientFactoryImpl,对吧? - system PAUSE

5

我今天遇到了这个问题,并找到了解决方案。提问者基本上想编写一个方法,例如:

public <T extends MyInterface> T create(Class<T> clz) {
    return (T)GWT.create(clz);
}

这里的MyInterface只是一个标记接口,用于定义我想要动态生成的类的范围。如果您尝试编写上述代码,将会出现错误。技巧在于定义一个“实例化器”,例如:

public interface Instantiator {
    public <T extends MyInterface> T create(Class<T> clz);
}

现在定义一个GWT延迟绑定生成器,返回上述实例。在生成器中,查询TypeOracle以获取所有MyInterface类型,并为它们生成实现,就像生成任何其他类型一样。例如:
public class InstantiatorGenerator extends Generator {

public String generate(...) {
   TypeOracle typeOracle = context.getTypeOracle();
   JClassType myTYpe= typeOracle.findType(MyInterface.class.getName());

    JClassType[] types = typeOracle.getTypes();
    List<JClassType> myInterfaceTypes = Collections.createArrayList();

    // Collect all my interface types.
    for (JClassType type : types) {
        if (type.isInterface() != null && type.isAssignableTo(myType)
                && type.equals(myType) == false) {
            myInterfaceTypes.add(type);
        }
        for (JClassType nestedType : type.getNestedTypes()) {
            if (nestedType.isInterface() != null && nestedType.isAssignableTo(myType)
                    && nestedType.equals(myTYpe) == false) {
                myInterfaceTypes.add(nestedType);
            }
        }
    }

    for (JClassType jClassType : myInterfaceTypes) {
        MyInterfaceGenerator generator = new MyInterfaceGenerator();
        generator.generate(logger, context, jClassType.getQualifiedSourceName());
    }
 }

 // Other instantiator generation code for if () else if () .. constructs as 
 // explained below.
}

MyIntefaceGenerator类与任何其他延迟绑定生成器一样。唯一的区别是,您在上面的生成器中直接调用它,而不是通过GWT.create。在生成MyInterface子类型时(在生成器中生成MyInterface的子类型时,请确保使类名具有唯一模式,例如MyInterface.class.getName()+"_MySpecialImpl"),完成所有已知子类型的生成后,只需再次遍历所有已知MyInterface子类型并创建一堆 实例化程序即可

if (clz.getName().equals(MySpecialDerivativeOfMyInterface)) { return (T) new MySpecialDerivativeOfMyInterface_MySpecialImpl();}

代码风格。最后抛出异常,以便在所有情况下都能返回值。

现在,在您调用GWT.create(clz);的位置执行以下操作:

private static final Instantiator instantiator = GWT.create(Instantiator.class);
...
return instantiator.create(clz);

此外请注意,在您的GWT模块xml中,您只需要为Instantiator生成器定义一个生成器,而不是MyInterface生成器。
<generate-with class="package.rebind.InstantiatorGenerator">
    <when-type-assignable   class="package.impl.Instantiator" />
</generate-with>

万岁!


可以发布完整的实现吗?如何定义MyInterfaceGenerator? - Sam
我已经分享了代码,我想在上面执行此操作。 如果在服务器端运行,GWT.create(Instantiator.class).create(clz)是否仍然有效? - Michael

4

您的问题是什么 - 我猜您希望在向生成器传递类字面量的同时传递参数。

正如您可能已经知道的那样,传递给 GWT.create() 的类字面量大多是一个选择器,以便 GWT 可以选择并执行生成器,最终输出一个类。向生成器传递参数的最简单方法是在接口中使用注释,并将该接口.class传递给 GWT.create()。当然要注意,接口/类必须扩展传递到 GWT.create() 中的类字面量。

class Selector{
}

@Annotation("string parameter...")
class WithParameter extends Selector{}

Selector instance = GWT.create( WithParameter.class )

2

一切皆有可能,尽管可能会很困难甚至毫无用处。正如Jan所提到的,您应该使用生成器来完成这项工作。基本上,您可以通过生成器代码创建您的接口,该代码在创建时编译该接口并将其返回给您的实例。一个例子可能是:

//A marker interface
public interface Instantiable {
}
//What you will put in GWT.create
public interface ReflectionService {
 public Instantiable newInstance(String className);
}
//gwt.xml, basically when GWT.create finds reflectionservice, use reflection generator
<generate-with class="...ReflectionGenerator" >
<when-type-assignable class="...ReflectionService" />
</generate-with>  
//In not a client package
public class ReflectionGenerator extends Generator{
...
}
//A class you may instantiate
public class foo implements Instantiable{
}
//And in this way
ReflectionService service = GWT.create(ReflectionService.class);
service.newInstance("foo");

你所需要知道的就是如何使用生成器。我可以告诉你,在生成器中所做的事情就是以这种方式创建Java代码:

if ("clase1".equals(className)) return new clase1();
else if ("clase2".equals(className)) return new clase2();
...

最后我想,嘿,我可以手动实现一个InstanceFactory...

祝好!

2

1

如果你想找出他们是如何做到的,那么你应该查看rocket/gwittir的代码(毕竟它是开源的)。我猜测他们采用延迟绑定的方式,在编译时计算所有反射调用,并静态生成实现这些调用所需的所有代码。因此在运行时,你不能进行不同的调用。


1

1
有可能 - GXT 的 BeanModel 实际上就是这样做的 - 在编译时将生成的代码包装在代码周围。 - aldrinleal

1

你也许可以通过在服务器端完成它来避免整个问题。例如使用一个接受字符串并返回某种可序列化超类型的服务。在服务器端,你可以这样做:

return (MySerializableType)Class.forName("className").newInstance();

根据你的情况,这可能不会成为一个很大的性能瓶颈。

有趣的想法 - 不过,最终这将通过由RPC生成的序列化机制在客户端上创建 - 这是一种客户端反射(如果您让它们处理任何类型,可能会失控),因此您可以完全在客户端上构建它。可能需要制作一个生成器来完成这个任务... - Colin Alworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接