模块化/可插拔的Java Web应用程序

时间:2011-07-22 20:43:56

标签: java web-applications osgi vaadin

我一直在尝试构建模块化Web应用程序。

我的要求是动态生成UI,但是ui的组件是可插拔的。例如,我可能有一组开箱即用的核心UI小部件,但如果客户想要创建自己的小部件,则会有一个定义的接口供他们实现自己的组件。

我正在使用vaadin作为我的ui框架。并且只想让最终用户提供包含ui的jar或war文件。我不想在我的war文件中捆绑jar,但是,无论最终用户提供什么,都应该按原样部署。

我已经看过使用osgi,并且已经能够获得一个框架,允许使用vaadin从bundle中获取动态ui,但是我正在通过其他req进行依赖地狱。还有其他我未考虑的替代方案吗?

3 个答案:

答案 0 :(得分:4)

我按照我想要的方式使用osgi和vaadin。我使用此tutorial作为参考。这让我得到了我需要的一半。

答案 1 :(得分:3)

我似乎在做一件非常相似的事情。虽然最终组件框架(如OSGi和NetBeans平台(也可以在服务器端使用))是一个可行的解决方案,我已经使用并且我用于其他项目,但是当您使用更多功能时,它们会付出复杂的代价他们提供的,除了搜索已注册的组件(例如,强制执行依赖项检查,版本检查,模块隔离等)。

但是对于扫描捆绑类,有一个更简单的解决方案,基于注释扫描。在我与Vaadin的项目中,我正在创建一个引用“抽象”组件名称的用户界面,它必须与用户可能提供的实际Java类相匹配。

实现组件的Java类标有自定义注释:例如

@ViewMetadata(typeUri="component/HtmlTextWithTitle", controlledBy=DefaultHtmlTextWithTitleViewController.class)
public class VaadinHtmlTextWithTitleView extends Label implements HtmlTextWithTitleView

然后我使用ClassScanner在类路径中搜索带注释的类:

    final ClassScanner classScanner = new ClassScanner();
    classScanner.addIncludeFilter(new AnnotationTypeFilter(ViewMetadata.class));

    for (final Class<?> viewClass : classScanner.findClasses())
      {
        final ViewMetadata viewMetadata = viewClass.getAnnotation(ViewMetadata.class);
        final String typeUri = viewMetadata.typeUri();
        // etc...
      }

这是我在ClassScanner上的完整实现,在Spring上实现:

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

public class ClassScanner 
  {
    private final String basePackage = "it"; // FIXME

    private final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);

    @Nonnull
    public final Collection<Class<?>> findClasses() 
      {
        final List<Class<?>> classes = new ArrayList<Class<?>>();

        for (final BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) 
          {
            classes.add(ClassUtils.resolveClassName(candidate.getBeanClassName(), ClassUtils.getDefaultClassLoader()));
          }

        return classes;
      }

    public void addIncludeFilter (final @Nonnull TypeFilter filter)
      {
        scanner.addIncludeFilter(filter);
      }
  }

这很简单,但很有效。请注意,由于Java ClassLoaders的工作方式,您必须至少指定一个要搜索的包。在我的例子中,我硬连线顶级软件包“it”(我的东西是“it.tidalwave。*”),很容易将这些信息放在可配置的属性中,最终指定多个软件包。


只需使用 NetBeans平台中的两个库即可使用另一种解决方案。我强调这个概念,即不会将整个平台导入到您的项目中,包括类加载器设施等,而只是使用两个jar文件。因此它不是侵入性的。这些库是org-openide-util.jar和org-openide-util-lookup.jar(我再次强调,你可以使用普通的.jar文件而不是NetBeans平台特有的.nbm文件)。

基本上,您使用@ServiceProvider annotation。它在编译期间(使用Java 6)被触发,并生成将放置在类路径中的META-INF / services / description文件。此文件是Java的标准功能(我相信自1.3以来),可以使用标准类ServiceLoader进行查询。在这种情况下,您只在编译期间使用NetBeans平台库 ,因为它们仅用于生成META-INF /服务。最后,通过Lookup class,这些库也可以用于更好的方式来查询已注册的服务。

两种解决方案之间存在设计差异。使用我的自定义注释,我发现了类:然后我使用它们反射来实例化对象。使用@ServiceProvider,系统会自动从类中实例化“单例”对象。因此,在前一种情况下,我注册了我想要创建的对象的类,在第二种情况下,我注册了一个工厂来创建它们。在这种情况下,似乎前一个解决方案需要少一个通道,这就是我使用它的原因(通常,我经常使用@ServiceProvider)。


总结一下,列举了三个解决方案:

  1. 在Spring中使用我提供的ClassScanner。运行时需要Spring。
  2. 在代码中使用@ServiceProvider并使用ServiceLoader进行扫描。编译时需要两个NetBeans平台库,运行时只需要Java Runtime。
  3. 在代码中使用@ServiceProvider并使用Lookup进行扫描。运行时需要两个NetBeans平台库。
  4. 您还可以查看this question的答案。

答案 2 :(得分:1)

好吧,之前我已经将OSGi用于大型模块化UI。我们使用shindig内部运行的opensocial小工具。 OSGi很不错,因为您可以将其他小工具作为捆绑包放入框架中,并让监听器选择它们并将它们添加到用户的小工具选项中。这个模型很好地延伸到主题等其他东西。您对OSGi和其他依赖项有什么问题?