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

发布于 2024-11-26 10:24:52 字数 331 浏览 0 评论 0原文

我一直在尝试构建一个模块化的网络应用程序。

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

我正在使用 vaadin 作为我的 ui 框架。并且只想让最终用户提供一个包含 ui 的 jar 或 war 文件。我不想将 jar 捆绑在我的 war 文件中,但是,最终用户提供的任何内容都应该可以按原样部署。

我已经研究过使用 osgi,并且已经能够获得一个允许使用 vaadin 从捆绑包中动态 ui 的框架,但是我正在经历其他需求的依赖地狱。还有其他我没有考虑过的选择吗?

I have been trying to build a modular web application.

My requirements are to generate UI dynamically, but have the components of the ui be pluggable. For instance I may have a core set of UI widgets that come out of the box, but if a client wanted to create there own there would be a defined interface for them to implement their own component.

I am using vaadin for my ui framework. And would just like to have the end users provide a jar or war file that contains there ui. I don't want to have to bundle the jar inside my war file however, whatever the end user provides should be deployable as is.

I have looked at using osgi, and have been able to get a framework that allows for dynamic ui from bundles using vaadin, however I am going through dependency hell with other req. Are there other alternatives I haven't considered?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ι不睡觉的鱼゛ 2024-12-03 10:24:52

我使用 osgi 和 vaadin 按照我想要的方式工作。我使用这个教程作为一个参考。这让我达到了我所需要的一半。

I got this working the way I wanted using osgi and vaadin. I used this tutorial as a reference. That got me half way to what I needed.

一世旳自豪 2024-12-03 10:24:52

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

但对于扫描捆绑类,有一个更简单的解决方案,基于注释扫描。在我的 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 类加载器的工作方式,您必须至少指定一个要搜索的包。在我的示例中,我硬连线了顶级包“it”(我的东西是“it.tidalwave.*”),很容易将此信息放入可配置的属性中,最终指定多个包。


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

基本上,您可以使用 @ServiceProvider 注解。它在编译期间被触发(使用 Java 6)并生成一个 META-INF/services/ 描述文件,该文件将放置在类路径中。该文件是 Java 的标准功能(我相信是从 1.3 开始),可以使用标准类 ServiceLoader。在这种情况下,您只能在编译期间使用 NetBeans 平台库,因为它们仅用于生成 META-INF/服务。最终,通过 Lookup 类。

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


总结起来,列举了三种解决方案:

  1. 将我提供的 ClassScanner 与 Spring 结合使用。运行时需要 Spring。
  2. 在代码中使用@ServiceProvider并使用ServiceLoader进行扫描。在编译时需要两个 NetBeans 平台库,在运行时仅需要 Java 运行时。
  3. 在代码中使用@ServiceProvider并使用Lookup进行扫描。运行时需要两个 NetBeans 平台库。

您还可以查看此问题的答案。

It seems I'm doing a very similar thing. While in the end component frameworks such as OSGi and the NetBeans Platform (that can be uses also server-side) are a workable solution that I've used and I'm using for other projects, they pay their complexity when you use more features that they offer, beyond searching for registered components (e.g. enforcing dependencies checks, version checks, module isolation, etc...).

But for scanning bundled classes there's a simpler solution, based on annotation scanning. In my project with Vaadin I'm creating a user interface referring to "abstract" component names, that have to be matched by actual Java classes that might be provided by a user.

Java classes implementing a component are marked with a custom-made annotation: e.g.

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

Then I search for annotated classes in the classpath with a 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...
      }

This is my complete implementation for the ClassScanner, implemented on top of 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);
      }
  }

It's very simple, but effective. Note that, because of how Java ClassLoaders work, you have to specify at least one package to search into. In my example I hardwired the top package "it" (my stuff is "it.tidalwave.*"), it's easy to put this information in a property that can be configured, eventually specifying more than one package.


Another solution could be used by just using two libraries from the NetBeans Platform. I stress the concept that this wouldn't import the whole platform into your project, including the classloader facilities etc., but just using two jar files. Thus it's not invasive. The libraries are org-openide-util.jar and org-openide-util-lookup.jar (I stress again, you can use the plain .jar files instead of the .nbm files that are specific of the NetBeans Platform).

Basically, you'd use the @ServiceProvider annotation. It gets triggered during compilation (with Java 6) and generates a META-INF/services/ description file that will be placed in the classpath. This file is a standard feature of Java (since 1.3, I believe) and can be queried with the standard class ServiceLoader. In this case, you'd use the NetBeans Platform libraries only during compilation, because they are only used for generating META-INF/services. Eventually, the libraries could be used also for better ways to query the registered services, by means of the Lookup class.

There's a design difference between the two solutions. With my custom annotation, I'm discovering classes: then I use them with reflection to instantiate objects. With @ServiceProvider the system automatically instantiates a 'singleton' object from the class. Thus in the former case I register the classes for the objects I want to create, in the second cases I register a factory for creating them. In this case, it seems that the former solution requires one less passage, and that's why I'm using it (normally, I use @ServiceProvider a lot).


Summing up, three solutions have been enumerated:

  1. Use my provided ClassScanner with Spring. Requires Spring in the runtime.
  2. Use @ServiceProvider in code and scan with ServiceLoader. Requires two NetBeans Platform libraries at compile-time, and just the Java Runtime at runtime.
  3. Use @ServiceProvider in code and scan with Lookup. Requires two NetBeans Platform libraries at runtime.

You might also look at answers to this question.

轻许诺言 2024-12-03 10:24:52

嗯,我之前曾将 OSGi 用于大型模块化 UI。我们使用在 Shindig 内运行的 opensocial 小工具。 OSGi 很好,因为您可以将其他小工具作为捆绑包放入框架中,然后让侦听器拾取它们并将它们添加到用户的小工具选择中。该模型可以很好地扩展到主题等其他事物。您在使用 OSGi 和其他依赖项时遇到哪些问题?

Well, I've used OSGi for a large modular UI before. We used opensocial gadgets running inside of shindig. OSGi is nice because you can just drop additional gadgets into the framework as bundles and have a listener pick them up and add them to the user's gadget choices. This model extends well to other things like themes. What issues are you having with OSGi and other dependencies?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文