1

我有一个应用程序,它由战争、一个核心 ejb 和一个 jar 中的许多服务 bean 和另一个 jar 中的远程接口组成。一切都打包在一个耳朵里,并在 Glassfish 4.1 上运行。

现在我想为核心 ejb 添加扩展点或插件支持。

目标是拥有不同的热插拔数据导入服务,它们都共享相同的接口,因为它们从路透社和彭博等供应商那里获取和规范化财务数据。

这些插件应该由核心 ejb jar 中的“插件管理器”bean 检测和管理。插件应支持在运行时加载、卸载和替换。

理想情况下,插件接口位于单独的包中,这样其他人就可以针对它们进行开发,而无需我的应用程序或 Glassfish,即使没有 Java EE 堆栈也可以完美地进行开发。我还想按需部署插件,而不是总是部署整个应用程序。

目前我尝试使用 CDI 实例迭代器,只要它们在核心 ejb 中,它就可以很好地与两个导入服务实现一起工作。如果我将一个实现放入单独的 ejb jar 中,那么 CDI 根本找不到它。我猜问题是 Glassfish 将每个 ejb jar 作为应用程序加载到单独的类加载器中。

现在是我当前的简化代码!

单独的jar包中的插件接口:

package com.photon.extensions;

import java.io.Serializable;

public interface ImportServiceExtension extends Serializable {
    String getImportServiceName();
}

未找到的单独 ejb jar 包中的插件实现:

package com.photon.services.extensions.vitrex.services;

import com.photon.extensions.ImportServiceExtension;
import javax.ejb.Remote;
import javax.ejb.Stateless;

@Remote(ImportServiceExtension.class)
@Stateless
public class ReutersImportService implements ImportServiceExtension {
    @Override
    public String getImportServiceName() {
        return "Reuters";
    }
}

找到的核心ejb jar包中的插件实现:

package com.photon.services.extensions;

import com.photon.extensions.ImportServiceExtension;
import javax.ejb.Stateless;

@Stateless
public class BloombergImportService implements ImportServiceExtension {
    @Override
    public String getImportServiceName() {
        return "Bloomberg";
    }
}

远程接口 jar 中“插件管理器”的远程接口:

package com.photon.services.extensions;

import java.util.List;
import javax.ejb.Remote;

@Remote
public interface ImportServiceExtensionsRemote {
    List<String> getImportServiceNames();
}

核心 ejb jar 中的“插件管理器”bean 实现:

package com.photon.services.extensions;

import com.photon.extensions.ImportServiceExtension;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@Stateless
public class ImportersService implements ImportServiceExtensionsRemote {

    @Inject private Instance<ImportServiceExtension> importServiceExtensions;

    @Override
    public List<String> getImportServiceNames() {
        Iterator<ImportServiceExtension> iter = importServiceExtensions.iterator();
        List<String> names = new ArrayList<>();
        while ( iter.hasNext() ) {
            ImportServiceExtension extension = iter.next();
            names.add(extension.getImportServiceName());
        }
        return names;
    }
}

最后是在战争中将名称呈现给网站的控制器:

package com.photon.website;

import com.photon.services.extensions.ImportServiceExtensionsRemote;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@RequestScoped
@Named
public class ImportController implements Serializable {

    @EJB private ImportServiceExtensionsRemote importServiceExtensions;

    public String getImportServiceNames() {
        String names = "";
        for ( String name : importServiceExtensions.getImportServiceNames() ) {
            names += name;
        }
        return names;
    }
}

最后只渲染了“Bloomberg”。

现在我的问题:

  1. 我在正确的轨道上吗?

  2. 如果是这样,我在代码中缺少什么?

  3. 这个问题是否有更好的解决方案(OSGI、自定义 clazz.forName、...)?

4

1 回答 1

1

我不能给你完整的答案,但这里有一些值得思考的东西......

我猜问题是 Glassfish 将每个 ejb jar 作为应用程序加载到单独的类加载器中。

你搞定了。可悲的是,这是每个 JEE 规范,所以这是预期的行为。

我不了解 Glassfish,但可能有一些功能允许您在部署之间共享类加载器(Wildfly 中有一些 - 称为部署隔离)。这可能会解决您的问题。

我从 Wildfly 了解到的另一件事是,您可以将一些应用程序部署为服务器模块,然后可以访问所有其他部署(及其类加载器)。如果 Glassfish 中有类似的东西,你可以试试。如果您愿意试一试 Wildfly,这里是讨论此问题的问题的链接。

现在,从 CDI 的角度来看,这种行为也是正确的,恐怕您无法更改它,因为您无法从其他部署访问类加载器(如果有,您可能会加载BeanManager给定的部署并搜索相关的bean)。

我希望这至少能给你一些见解。

于 2016-09-05T06:53:07.420 回答