———问题提出
 EJB3提出了4中接口类型包括:远程接口(remote interface)、本地接口(local interface)端点接口(endpoint interface)和消息接口(message interface),本文主要议论前两种接口。
 〇远程接口定义了session bean(具体接口的实现类)的业务方法,这些方法可以被来自EJB容器以外的应用访问到。它是个普通的Java接口。被标注了@java.ejb.Remoter注解;
 〇本地接口同样定义了session bean得业务方法,这些方法可以被处于同一EJB容器的其他bean使用:也就是bean提供给运行于同一JVM中其他bean的业务方法。被标注了@javax.ejb.Local注解;
 session bean的客户端端从来不于session bean class直接打交道,相应的,客户端必须始终使用session bean的组件接口所提供的方法来完成工作。尽管本地接口不涉及分布式对象协议,但他们人就为bean class提供了一个代理或存根。
 在实际工作中我们的业务为了支持多种情况(远程或本地访问),我们是否可以让接口同时标注@java.ejb.Remoter和@javax.ejb.Local让其支持两种访问方式,使之在服务器端透明。在Client端我们定义一个全局常量(targetServicerLocation)来标注目前client和Server是否在同一jvm中。然后再到Clent端通过targetServicerLocation的值进行分支处理。不就可以完成在服务器端或者在Client端都透明了吗?
———服务器端透明的处理
以《EnterPrise JavaBean 3.0》中的Tiain系统为假想业务,来探索我们的处理。
Tiain系统是一个船运系统。既然是个船运系统,必然有实体Cabin(船舱),我们就这一实体的CRUD展开议论。
先定义Cabin实体 import javax.persistence.* ;@Entity
@Table(name="CABIN")
public class Cabin {
    private int id;
    private String name;
    private int deckLevel;    @Id
    @GeneratedValue
    @Column(name="CABIN_ID")
    public int getId( ) { return id; }    public void setId(int pk) { this.id = pk; }    @Column(name="CABIN_NAME")
    public String getName( ) { return name; }    public void setName(String str) { this.name = str; }    @Column(name="CABIN_DECK_LEVEL")
    public int getDeckLevel( ) { return deckLevel; }    public void setDeckLevel(int level) { this.deckLevel = level; }
}业务逻辑的定义public interface TravelAgent {
/**创建船舱*/
    public void createCabin(Cabin cabin);
    /**获得船舱*/
    public Cabin findCabin(int id);
}为了让其支持远程和本地调用我们为之标注@java.ejb.Remoter和@javax.ejb.Local变成这样;@Remote
@Local
public interface TravelAgent {
/**创建船舱*/
    public void createCabin(Cabin cabin);
    /**获得船舱*/
    public Cabin findCabin(int id);
}接口实现类package com.titan.travelagent;import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;import com.titan.domain.Cabin;@Stateless
public class TravelAgentBean implements TravelAgent{
    @PersistenceContext 
(unitName="titan")
private EntityManager manager;    public void createCabin(Cabin cabin) {
        manager.persist(cabin);
    }    public Cabin findCabin(int pKey) {
       return manager.find(Cabin.class, pKey);
    }
}ok,到目前为止开来一切都非常顺利。我们定义了一个接口,并且标注@Remote和@Local。这样我们就可以通过远程和本地方法调用了。
部署测试!
结果大大的异常
java.lang.RuntimeException: An exception occurred initialising interceptors for class com.titan.travelagent.TravelAgentBean.equals
想想就明白了,如果可以这么搞EJB3规范早就这么搞了,还要我在这费心思!但是要同时实现远程接口和本地接口的需求实在很迫切。难道写两个接口仅仅是名字不同和标注的注解不同吗?我们可不可以将公用的代码提出来。
改造如下
1、将TravelAgent的@Remote和@Local去掉,加入如下两个接口package com.titan.travelagent;
import javax.ejb.Remote;
@Remote
public interface TravelAgentRemote extends TravelAgent{}
 package com.titan.travelagent;
import javax.ejb.Local;
@Local
public interface TravelAgentLocal extends TravelAgent{}改造TravelAgentBean时之实现这两个接口package com.titan.travelagent;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.titan.domain.Cabin;
@Stateless
public class TravelAgentBean implements TravelAgentLocal,TravelAgentRemote{
    @PersistenceContext 
(unitName="titan")
private EntityManager manager;    public void createCabin(Cabin cabin) {
        manager.persist(cabin);
    }    public Cabin findCabin(int pKey) {
       return manager.find(Cabin.class, pKey);
    }
}至此服务器端的透明处理就完成了。写来开一下Client端的透明处理
———Client端的透明处理
  文章开头简单提了一下思路,就是定义一个常量argetServicerLocation来标注目前client和Server是否在同一jvm中,然后更具argetServicerLocation值得不同来进行分支逻辑。
我们已java web开发为例,这个常量的首选位置当然是<context-param>了<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <context-param>
  <param-name>targetServicerLocation</param-name>
  <param-value>remote</param-value>
  <description>标志是远程访问还是本地访问 local/remote</description>
  </context-param>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>写个jsp页面读取targetServicerLocation值进行分支处理
首选看一下远程调用和本地调用有什么不同
1、获取JndiContext的方式不同,本地调用直接使用系统中的上下文,而远程调用需要制定相应的Properties;
2、session bean 自动绑定到jndi的名字不同 本地:TravelAgentBean/local 远程:TravelAgentBean/remote;
3、获取的对象类型不同 本地:TravelAgentLocal 远程TravelAgentRemote
对于第1、2个问题,都不算问题,一判断一个if就可以搞定,第3个问题也不难办。我们可以使用TravelAgent来保存TravelAgentLocal或者TravelAgentRemote,让它多态去吧!
代码实现(示例就直接写到jsp了)<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="javax.naming.*,com.titan.travelagent.*,com.titan.domain.*" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'testLocal.jsp' starting page</title>
    
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">    
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->  </head>
  
  <body>
  <p>目标服务提供商位置:<%=getServletContext().getInitParameter("targetServicerLocation")%></p>
   <%
    String targetServicerLocation = getServletContext().getInitParameter("targetServicerLocation");
    InitialContext jndiContext = null;
     if(targetServicerLocation.equals("local")){
      jndiContext = new javax.naming.InitialContext();
     }else{
       Properties p = new Properties();
      p.put(Context.INITIAL_CONTEXT_FACTORY,
          "org.jnp.interfaces.NamingContextFactory");
      p.put(Context.URL_PKG_PREFIXES,
          " org.jboss.naming:org.jnp.interfaces");
      p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
       jndiContext = new javax.naming.InitialContext(p);
  }
    jndiContext = new InitialContext();
     String jndiName = "TravelAgent"+"Bean/"+(targetServicerLocation.equals("local")?"local":"remote");
    Object ref = jndiContext.lookup(jndiName);
   
    /使用TravelAgentTravelAgentLocal或者TravelAgentRemote
    TravelAgent dao = (TravelAgent)ref;
   
    Cabin cabin_2 = dao.findCabin(1);
    out.println(cabin_2.getName( ));
    out.println(cabin_2.getDeckLevel( ));
    out.println(cabin_2.getShipId( ));
    out.println(cabin_2.getBedCount( ));
              
    %>
  </body>
</html> 部署测试,
首先部署到不同的jvm中,我这里将ejb部署到jboss中,web部署到tomcat中。测试远程访问。
结果没问题一切工作完美。
然后将targetServicerLocation的值修改为local全部部署到jboss中。问题出来了!
类型转换失败 $.proxy72不能转换为TravelAgent!
————问题分析
文章开头提到了,通过jndi拿到的永远只是代理对象而不是实际对象。可是为什么部署到不同的jvm中可以正常工作而部署在同一jvm中就罢工了呢?
其实代理对象的生成有两种方式
第一种是基于接口的,他会使用java的动态代理技术生成代理对象。从而为原始对象添加新的特性或者方法。缺点是,不能将代理对象通过类型转换转换为原始对象。
 知识链接:Spring中大量使用这种技术实现代理对象的生成。这是Spring生成代理对象的首选方式。
第二种是基于字节码,或者类加载器的。他可以通过cglib、或者classLoder等等。直接将逻辑插入到已经编译好的类。可以通过类型转换转换为原始对象。
  知识链接:这是Spring的备选方案,若果你的类没有实现任何接口,却要织入逻辑的话便采用这种方式。
JBoss显然对远程和本地接口生成代理时使用了这两种不同的策略。其中远程接口是使用第二种策略,而本地接口则使用了第一种策略。所以造成了失败。由于对JBoss的代理生成没有进行过升入研究。目前无法解决透明的Client端访问。欢迎批评、指正和赐教。