1.前言
最近在公司里面做项目,用的是SpringMVC+EJB+JBOSS+Jpa。在整个框架中,唯一难于理解的是JBOSS的相关配置,尤其是JBOSS从5以后,在版本上做了一个很大的改变,例如加入了一些安全认证,域模式等,于是想写几篇博客来学习一下JBOSS6的有关应用。整个过程用的的JBOSS EAP6.2的版本。本篇博客就先从一个例子来讲一下,有关通过JBOSS,远程调用EJB本地Bean。所有的知识内容翻译自官网。JBOSS官网
2.从一个Demo讲起
先通过一个Demo,运行起来看一下效果,后面再来详细的解释一下,整个Demo中的配置。
2.1 建立远程Bean(限于篇幅限制,只把Bean的实现放在这里,接口就不在放置了)
有状态远程CounterBean
<span style="font-family:SimSun;font-size:18px;"> package com.tgb; import javax.ejb.Remote; import javax.ejb.Stateful; /** * @author LUCKY */ @Stateful @Remote(RemoteCounter.class) public class CounterBean implements RemoteCounter { private int count = 0; @Override public void increment() { this.count++; } @Override public void decrement() { this.count--; } @Override public int getCount() { return this.count; } } </span>
无状态远程CalculatorBean
<span style="font-family:SimSun;font-size:18px;"> package com.tgb; import javax.ejb.Remote; import javax.ejb.Stateless; /** * @author LUCKY */ @Stateless @Remote(RemoteCalculator.class) public class CalculatorBean implements RemoteCalculator { @Override public int add(int a, int b) { return a + b; } @Override public int subtract(int a, int b) { return a - b; } } </span>
2.2 部署远程服务端把上面的远程的Bean,打成jar包,放置到jboss中,命名为ejb-service,并且启动jboss,出现下面,即为成功部署。另外JBOSS6.2的部署目录结构为JBOSS_HOME\standalone\deployments
2.3 客户端远程调用
废话不多说了,直接上Demo吧
package main; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import com.tgb.RemoteCalculator; import com.tgb.RemoteCounter; /** * A sample program which acts a remote client for a EJB deployed on AS7 server. This program shows how to lookup stateful and * stateless beans via JNDI and then invoke on them */ public class RemoteEJBClient { public static void main(String[] args) throws Exception { // Invoke a stateless bean invokeStatelessBean(); // Invoke a stateful bean invokeStatefulBean(); } /** * Looks up a stateless bean and invokes on it * * @throws NamingException */ private static void invokeStatelessBean() throws NamingException { // Let's lookup the remote stateless calculator final RemoteCalculator statelessRemoteCalculator = lookupRemoteStatelessCalculator(); System.out.println("Obtained a remote stateless calculator for invocation"); // invoke on the remote calculator int a = 204; int b = 340; System.out.println("Adding " + a + " and " + b + " via the remote stateless calculator deployed on the server"); int sum = statelessRemoteCalculator.add(a, b); System.out.println("Remote calculator returned sum = " + sum); if (sum != a + b) { throw new RuntimeException("Remote stateless calculator returned an incorrect sum " + sum + " ,expected sum was " + (a + b)); } // try one more invocation, this time for subtraction int num1 = 3434; int num2 = 2332; System.out.println("Subtracting " + num2 + " from " + num1 + " via the remote stateless calculator deployed on the server"); int difference = statelessRemoteCalculator.subtract(num1, num2); System.out.println("Remote calculator returned difference = " + difference); if (difference != num1 - num2) { throw new RuntimeException("Remote stateless calculator returned an incorrect difference " + difference + " ,expected difference was " + (num1 - num2)); } } /** * Looks up a stateful bean and invokes on it * * @throws NamingException */ private static void invokeStatefulBean() throws NamingException { // Let's lookup the remote stateful counter final RemoteCounter statefulRemoteCounter = lookupRemoteStatefulCounter(); System.out.println("Obtained a remote stateful counter for invocation"); // invoke on the remote counter bean final int NUM_TIMES = 5; System.out.println("Counter will now be incremented " + NUM_TIMES + " times"); for (int i = 0; i < NUM_TIMES; i++) { System.out.println("Incrementing counter"); statefulRemoteCounter.increment(); System.out.println("Count after increment is " + statefulRemoteCounter.getCount()); } // now decrementing System.out.println("Counter will now be decremented " + NUM_TIMES + " times"); for (int i = NUM_TIMES; i > 0; i--) { System.out.println("Decrementing counter"); statefulRemoteCounter.decrement(); System.out.println("Count after decrement is " + statefulRemoteCounter.getCount()); } } /** * Looks up and returns the proxy to remote stateless calculator bean * * @return * @throws NamingException */ private static RemoteCalculator lookupRemoteStatelessCalculator() throws NamingException { final Hashtable jndiProperties = new Hashtable(); jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); final Context context = new InitialContext(jndiProperties); // The JNDI lookup name for a stateless session bean has the syntax of: // ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName> // // <appName> The application name is the name of the EAR that the EJB is deployed in // (without the .ear). If the EJB JAR is not deployed in an EAR then this is // blank. The app name can also be specified in the EAR's application.xml // // <moduleName> By the default the module name is the name of the EJB JAR file (without the // .jar suffix). The module name might be overridden in the ejb-jar.xml // // <distinctName> : AS7 allows each deployment to have an (optional) distinct name. // This example does not use this so leave it blank. // // <beanName> : The name of the session been to be invoked. // // <viewClassName>: The fully qualified classname of the remote interface. Must include // the whole package name. // let's do the lookup return (RemoteCalculator) context.lookup("ejb:/ejb-service/CalculatorBean!" + RemoteCalculator.class.getName()); } /** * Looks up and returns the proxy to remote stateful counter bean * * @return * @throws NamingException */ private static RemoteCounter lookupRemoteStatefulCounter() throws NamingException { final Hashtable jndiProperties = new Hashtable(); jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); final Context context = new InitialContext(jndiProperties); // The JNDI lookup name for a stateful session bean has the syntax of: // ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName>?stateful // // <appName> The application name is the name of the EAR that the EJB is deployed in // (without the .ear). If the EJB JAR is not deployed in an EAR then this is // blank. The app name can also be specified in the EAR's application.xml // // <moduleName> By the default the module name is the name of the EJB JAR file (without the // .jar suffix). The module name might be overridden in the ejb-jar.xml // // <distinctName> : AS7 allows each deployment to have an (optional) distinct name. // This example does not use this so leave it blank. // // <beanName> : The name of the session been to be invoked. // // <viewClassName>: The fully qualified classname of the remote interface. Must include // the whole package name. // let's do the lookup return (RemoteCounter) context.lookup("ejb:/ejb-service/CounterBean!" + RemoteCounter.class.getName() + "?stateful"); } }
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false remote.connections=default remote.connection.default.host=localhost remote.connection.default.port = 4447 remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
3 Demo详解
上面的Demo来自于官方网站,并且每一行都有详细的注释,下面的就带大家来分析一下,整个Demo中的关键点。
3.1 JNDI命名规则
For stateless beans
ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName>
For stateful beans
ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName>?stateful
首先ejb:命名空间,表明了,这是一个有关于EJB的远程调用的实例,其余的部分的解释如下
app-name:这是一个有关于.ear的前缀名称,如果我们部署在jboss中的是.ear结尾的文件的话,那么这个app-name就代表的是除去前缀的名称,例如如果我部署的是ejb-server.ear的话,那么app-name就代表的是ejb-server,如果没有的话,则为空表示,需要注意的是,前面没有斜线
module-name:这是有关于.jar包的前缀,正如我们这个例子中,我们把服务端最后打成了一个ejb-server.jar包,那么前缀就是ejb-server,这个模块的名称也可以为空,也可以重写
distinct-name:这是一个可选的名称,可以为空
bean-name:这是我们需要远程调用的bean的名称,记住这里是实现的bean的名称,不是接口的名称
viewClassName:这是我们所要调用bean的接口的全名,包括包的名称例如一个远程调用的bean的接口是RemoteCalculator,则这里必须是包名+类名,即为com.tgb.RemoteCalculator。再次需要注意的是,如果是有状态的bean的话,需要加上?stateful。下面再看我们上面例子的话,有状态的bean的全称为
ejb:/ejb-service/CounterBean!com.tgb.RemoteCounter?stateful
因为我们没有打ear包,所以名称可以省略。
3.2 jboss-ejb-client.properties详解
接下来我们来分析一下,有关JNDI properties的配置。
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
上面的Context.URL_PKG_PREFIXES是为了我们让JDNI API知道,我们目前带调用的是EJB命名空间的bean