EJB3 - 自远程注入/EJB 插件

EJB3 - Self Remote Injection / EJB Plugins

我想使用 EJB3 实现一种 "plugin mechanism"。我有两个以上 war,每个都包含自己的 bean 类型,使用在单独项目中定义的远程接口。基本上,我希望始终部署我的主 Bean(产品)并为其他 Bean(n 个不同的项目 Bean)提供注册机制。重要的是任意 Bean 都可以注册,只要它们知道产品的远程接口。示例代码:

产品 Bean

@Singleton
@ConcurrencyManagement(BEAN)
public class ProductBean implements ProductRemote, Serializable {
    private static final long serialVersionUID = 4686943363874502919L;

    private ProjectRemote project;

    public void registerProject(ProjectRemote project) {
        this.project = project;
    }

    public void something() {
        if(project != null)
            project.doSomething();
    }

}

Bean 项目

@ConcurrencyManagement(BEAN)
@Singleton
@Startup
public class ProjectBean implements ProjectRemote, Serializable {
    private static final long serialVersionUID = -2034521486195622039L;

    @EJB(lookup="java:global/product/ProductBean")
    private ProductRemote product;

    @PostConstruct
    private void registerSelf() {
        product.registerProject(this);
    }

    public void doSomething() {
        System.err.println("FOOBAR");
    }
}

产品界面

@Remote
public interface ProductRemote {
    public void registerProject(ProjectRemote project);
}

项目界面

@Remote
public interface ProjectRemote {
    public void doSomething();
}

不幸的是,当我尝试部署项目时,出现了 ClassNotFoundException:

22:56:33,146 ERROR [org.jboss.as.controller.management-operation] (XNIO-1 task-7) JBAS014613: Operation ("add") failed - address: ([{"deployment" => "project-0.1-SNAPSHOT.war"}]) - failure description: {"JBAS014671: Failed services" => {"jboss.deployment.unit.\"project-0.1-SNAPSHOT.war\".component.ProjectBean.START" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"project-0.1-SNAPSHOT.war\".component.ProjectBean.START: java.lang.IllegalStateException: JBAS011048: Failed to construct component instance
Caused by: java.lang.IllegalStateException: JBAS011048: Failed to construct component instance
Caused by: javax.ejb.EJBException: java.lang.RuntimeException: JBAS014154: Failed to marshal EJB parameters
Caused by: java.lang.RuntimeException: JBAS014154: Failed to marshal EJB parameters
Caused by: java.lang.ClassNotFoundException: com.xxx.test.project.ProjectBean from [Module \"deployment.product-0.1-SNAPSHOT.war:main\" from Service Module Loader]"}}

所以我的问题是:有什么办法可以实现这样的功能吗?还是因为每个 war 的类路径不同而完全不可能?

我认为第一个问题是,即使您正在共享您的界面,客户端实现 class 仍然无法用于其他项目,并且它的实例加载了不同的 classloader(即另一个 war 容器)。

您可以使用消息 bean (JMS) 设计来实现,这将允许关注点分离、准注册(到 queue/topic),并允许您部署您想要的任何实现。查看我如何使用 .

另一种解决方案是使用委托模式,例如:

假设您从两个单独的 war 部署转到单个 EAR 部署,您的项目将具有以下模块。

我的图书馆

EJB 接口:

@Local
public interface MyBeanLocal
    public void doSomething(String something);

MyClient 注释:

@Documented
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface MyClient {

}

我的默认注释:

@Documented
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface MyDefault {

}

myClientEJB

@Stateless
@MyClient
public class MyBean implements MyBeanLocal {
@Inject
@MyDefault
private MyBeanLocal myDefault;

@Override
public void doSomething(String something) {
    myDefault.doSomething(something);
}
}

myDefaultEJB

@Stateless
@MyDefault
public class MyBean implements MyBeanLocal {

@Override
public void doSomething(String something) {
    // do something
}
}

我的网站

@Named
@Request
public class MyRequestBean {

@Inject
@MyClient
private MyBeanLocal myClient;

public void something() {
    myClient.doSomething("...");
}
}

最后,您可能需要考虑 OSGi。我没有对此做任何事情,但就注册资源而言,这可能是您正在寻找的东西。当然,您始终可以考虑 资源适配器,具体取决于您的问题域。

我的一位同事给了我正确的提示,告诉我我在代码中做错了什么 - 我写代码的方式是试图让我的 实现 注入。显然,由于 class 路径(和 JVM)不同,这不起作用。相反,我不得不将 对为远程接口创建的代理对象 的引用传递到注册方法中。

因此,为了使上述代码正常工作,对 Project.java 进行了两项更改:

  1. 为自己添加一个 EJB:

    @EJB
    private ProjectRemote self;
    
  2. 更改@PostConstruct 方法以引用 EJB

    @PostConstruct
    private void registerSelf() {
        product.registerProject(self);
    }
    

这样,class 的实际实现不需要被使用它的 class 知道。容器根据为 EJB 创建的代理对象知道将信息传递到哪里。