我想实现一种"插件机制"使用EJB3。我有两个+ war,每个都使用在单独项目中定义的远程接口包含自己的bean类型。基本上,我希望我的主Bean(产品)总是被部署,并为其他Beans(n个不同的Project Beans)提供注册机制。任意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();
}
不幸的是,当我尝试部署Project时,我得到一个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]"}}
所以我的问题是:有没有办法实现这样的功能?或者因为每次战争的不同阶级路径完全不可能?
答案 0 :(得分:0)
我认为第一个问题是,即使您正在共享您的界面,客户端实现类仍然无法与其他项目一起使用,并且它的实例已加载了不同的类加载器(即,另一个战争集装箱)。
您可以使用消息bean(JMS)设计来实现,这将允许分离关注点,准注册(到队列/主题),并允许您部署所需的任何实现。看看我如何使用JMS and ACtiveMQ。
另一种解决方案是使用委托模式,如下:
假设您从两个单独的战争部署转到单个EAR部署,您的项目将具有以下模块。
<强>在MyLibrary 强>
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 {
}
MyDefault注释:
@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
}
}
<强> MYWEB 强>
@Named
@Request
public class MyRequestBean {
@Inject
@MyClient
private MyBeanLocal myClient;
public void something() {
myClient.doSomething("...");
}
}
最后,您可能需要考虑 OSGi 。我还没有做过任何事情,但它可能是你在注册资源方面寻找的东西。当然,您可以随时考虑资源适配器,具体取决于您的问题域。
答案 1 :(得分:0)
我的同事给了我正确的提示,告诉我在我的代码中我做错了什么 - 我编写它的方式我试图让我的实现注入。显然,由于不同的类路径(和JVM),这不起作用。相反,我必须将为远程接口创建的代理对象的引用传递给注册方法。
因此,为了使上述代码正常工作,对Project.java进行了两处更改:
为自己添加EJB:
@EJB
private ProjectRemote self;
更改@PostConstruct方法以引用EJB
@PostConstruct
private void registerSelf() {
product.registerProject(self);
}
这样,类的实际实现不需要被使用它的类知道。容器根据为EJB创建的代理对象知道传递信息的位置。