??xml version="1.0" encoding="utf-8" standalone="yes"?>日韩一级大片在线,2020国产精品视频,国产一区二区视频在线http://www.aygfsteel.com/liujw/articles/39151.html刘军?/dc:creator>刘军?/author>Tue, 04 Apr 2006 05:50:00 GMThttp://www.aygfsteel.com/liujw/articles/39151.htmlhttp://www.aygfsteel.com/liujw/comments/39151.htmlhttp://www.aygfsteel.com/liujw/articles/39151.html#Feedback0http://www.aygfsteel.com/liujw/comments/commentRss/39151.htmlhttp://www.aygfsteel.com/liujw/services/trackbacks/39151.html1997 q_IBM 和 Sun Microsystems 启动了一Ҏ在促q Java 作ؓ企业开发技术的发展的合作计划。两家公司特别着力于如何 Java 用作服务器端语言Q生成可以结合进现有体系l构的企业代码。所需要的是一U远E传输技术,它兼有 Java 的 RMIQRemote Method InvocationQ远E方法调用)较少的资源占用量和更成熟的 CORBAQCommon Object Request Broker ArchitectureQ公共对象请求代理体pȝ构)技术的健壮性。出于这一需要,RMI-IIOP 问世了,它帮助将 Java 语言推向了目前服务器端企业开发的L语言的领先地位?br />
在本文中Q我简要介l RMI-IIOPQ目标是使您能开始在企业开发解x案中使用q一技术。要解释 RMI-IIOP I竟是什么,我认为提供一些关于 CORBA 和 RMI 的信息是重要的,q些信息您在各个技术的典型介绍中可能找不到。如果您对 CORBA 或 RMI 的基知识不熟悉,我徏议您在往下读之前先阅M些介l性信息。请参阅 参考资料,那里挑选了一些文章和教程。?br />
在我具体讨论 RMI-IIOP 之前Q我们将先看一下 CORBA 和 RMI 用来对请求进行数据编入的机制。CORBA 是我们的主要示例,因ؓ RMI-IIOP 数据~入是徏立在 CORBA 传输协议QIIOPQ的基础上的。我们将回顾一下该传输协议和 ORBQobject request brokerQ对象请求代理)在网l上发送请求、定位远E对象和传输对象斚w的基本功能?br />
q程对象传输

对 CORBA hq行数据~入是通过使用 IIOP 协议做到的。简a之,IIOP 以标准化格式构造的M IDLQInterface Definition LanguageQ接口定义语aQ的元素表示Zpd字节。那假设有一个 Java 客户机正在将一个 CORBA h分派刊WC++ 服务器吧。客h应用E序以 Java 接口的Ş式拥有远E对象的引用Qƈ调用该接口的一个操作。本质上是,接口调用它对该操作的相应实现Q这个实现将位于存根QstubQ(存根是您已l用 idlj 从 IDL 生成了的Q。?br />
存根把方法调用分zֈ ORB 中,ORB ׃部分l成Q客h ORB 和服务器 ORB。客h ORB 的职责是对请求进行数据编入,攑ֈ|络上,传往特定位置。服务器 ORB 的职责是侦听从网l上传下来的hQƈ这些请求{换成语言实现能够理解的方法调用。要了解对 CORBA ORB 的角色的更深入讨论,请参阅 参考资料部分。?br />
存根分派了方法调用之后,客户机 ORB 请求和所有参数{换成标准化字节格式,在这U情况中是 IIOP。接着Q请求通过导线被发送到服务器 ORBQ服务器 ORB 应该正在侦听传入h。服务器端 ORB 读q数据的字节q将h转换成对 C++ 服务器实现有意义的东ѝC++ 服务器方法将执行它的功能Q即调用所h的方法)q用相同的机制通过 IIOP 结果返回给客户机?br />
RMI 以类似的方式处理hQ但是它使用 JRMPQJava Remote Messaging ProtocolQJava q程消息传递协议)作ؓ其传输协议。当ӞRMI 传输q涉及 Java 对象的序列化?br />
 CORBA 和 RMI 的差?br />

CORBA q行在 IIOP 协议之上QRMI 使用 JRMP。?br />CORBA 是独立于语言的;RMI 是纯_ Java 刊WJava 的。?br />RMI 使用 JNDI 定位q程对象QCORBA 使用 CosNaming。?br />RMI 会将对象序列化;CORBA 则不然。?br />
 q程对象定位

CORBA 使用 CosNaming 命名服务定位q程对象。CosNaming 为名U服务器保存对 CORBA 服务器进E的l定Q或引用Q提供了一个框架。当 CORBA 客户机向名称服务发送 CosNaming hQ请求给定名U的服务器进E时Q名U服务返回该q程的 可互操作对象引用(interoperable object referenceQIORQ)。接着Q客h使用该 IOR 直接与服务器q程通信。?br />
IOR 包含关于服务器进E的信息Q例如服务器q程的位|。CosNaming 服务的缺点之一是,IOR 对hc而言是难以看懂的 — 至对我们q些没有电子大脑的h来说是这栗相反地QRMI 对用户则要友好一些。它使用q行在 JNDI 之上的 注册中心(与命名服务极为相|来定位远E对象。RMI 注册中心使用 Java Reference 对象Q它pq个 RefAddr 对象l成Q来识别和定位远E对象。这些 Java 对象比 IOR 对用h加友好。?br />
不久前,COBRA 可互操作命名服务(Interoperable Naming ServiceQINSQ)l合q了它的对象-定位Qobject-locationQ模式。INS 在 CosNaming 上运行,使用人类可以阅读的 URL 作它的对象位|。INS 不用命名服务;相反圎ͼ它将调用直接发送到指定的 URL。请参阅 参考资料了解关于 INS 的更多信息。?br />



RMI 对 CORBA

那么Q哪一个更好呢Q是 CORBA q是 RMIQ答案取决于您想做什么。CORBA 是一个运行在业界标准的第三或W四代协议上的、经q试验和试的大体系l构。如果考虑刊WCORBA 提供的所有附Ӟ例如Q事务处理、安全拦截器、事仉道Q还有更多)的话Q则 CORBA 看来是企业应用程序的解决Ҏ。CORBA 的最大缺Ҏ它很复杂。要熟练使用 CORBAQ开发者通常要经历陡峭的培训曲线?br />
相反圎ͼRMI 相当Ҏ学习。创Z个客hQ服务器实现Q绑定到注册中心和远E对象,使用 RMI 调用和/或接收请求都相当单。RMI 的资源占用量也比 CORBA 得多,因ؓ JRMP 是开销比 IIOP 得多的协议。但是,RMI ~Z CORBA 的工业的附Ӟ而且是纯Z Java 的机制。那么,我们真正需要的是 RMI 的灵zL和易用性以及 CORBA 的企业就l性,对吗Q那开始讨论 RMI-IIOP 吧?br />
 Z么是 RMI-IIOPQ?br />

RMI-IIOP 兼有 CORBA 的强度和 RMI 的灵zL。?br />开发者很Ҏ可以用 RMI-IIOPQRMI-IIOP 也易于集成到多数企业基础架构中。?br />
 



RMI-IIOP 概览

RMI-IIOP 让您仅需极少修改可以在 IIOP 上运行 RMI 调用。借助于 RMI-IIOPQ您可以~写单易懂的 Java 代码Q同时用 CORBA 提供的丰富的企业功能套g。而且Q代码的灉|性够大Q可以运行在 RMI 或IIOP 上。这意味着Q您的代码可以在U Java 环境中运行(当小的资源占用量和灵zL很关键ӞQ或者对代码作少量修改后集成到现有的 CORBA 基础架构中。?br />
RMI-IIOP 很强大的功能之一是,它让您编写纯 Java 客户机/服务器实现而不丧失 RMI cd列化的灵zL。RMI-IIOP 通过覆盖 Java 序列化ƈ在导U上 Java c{换成 IIOP 做到q一炏V在另一端,Java c被作ؓ IIOP 从导U上M来,接着创徏q个cȝ一个新实例Q用反)Q类的所有成员的值都完整无缺 — 瞧Q这是 IIOP 上的 Java 序列化! 

Z让 RMI-IIOP 实现透明的对象定位,ORB 供应商历史上曄使用 Java CosNaming 服务提供者(或用外行人的话说Q是 插gQ。该插g在 JNDI API 之下工作Q访问 CORBA 命名服务。尽我没有在这里花幅来说明原因,但这U命名解x案ƈ不理惟뀂其l果是,许多供应商 — 尤其是应用服务器供应商 — ؓ RMI-IIOP 开发了专门的对象定位机制。?br />
RMI-IIOP 也支持作为 Java CosNaming 服务的一个扩展的 INS。因为我怿 INS 确定对象定位的未来方向Q所以我们在本文讨论的代码CZ使用 INS?br />
注:因ؓ Sun 未完全遵@ OMG INS 标准Q也未公开 org.omg.CORBA.ORB 接口的 register_initial_reference Q所以本文提供的源代码将不能与 Sun JDK 一起工作。您需要 IBM Developer Kit for Java technologyQ版本?.3.1 或更高版本。不q,我已l创Z一个用命名服务的与 Sun 兼容的示例,您可以从 参考资料部分下载它 



自己动手构徏 RMI-IIOP

说得够多了,让我们来~写代码吧!在以下几部分中,我们构Z个简单的、基于 Java 的客hQ服务器 RMI-IIOP 应用E序。这个应用程序由三个部分l成QRMI 接口、服务器应用E序和客h应用E序。示例以在 IIOP 之上的 Java 序列化ؓ特色Q所以您可以看到 Java cd何被客户机实例化Q如何传递到服务器,由服务器更改Q然后将所有修改完整地回传到客h?br />
W? 部分Q定义接?br />
在 RMI-IIOP 下,我们可以选择使用 RMI 或 IDL 来定义接口。因为我们想看看 RMI 如何q行在 IIOP 上,所以我们将使用 RMI 定义CZ接口。清单? 是我们的单示例的 RMI 接口Q?br />

清单 1. RMIInterface.java
/*
 * Remote interface
 */
public interface RMIInterface extends java.rmi.Remote {
    public String hello() throws java.rmi.RemoteException;
    public SerClass alterClass(SerClass classObject) 
       throws java.rmi.RemoteException;
}
 


RMIInterface 定义一个 hello() Ҏ和一个 alterClass(SerClass) Ҏ。后一个方法用 SerClass 作参敎ͼ SerClass 是一个实玊WSerializable 的 Java c, alterClass(SerClass) Ҏq回一个类型与其参数的cd相同的类。 SerClass 是一个有几个成员的简单的c,每个成员有相应的 getter Ҏ。这些方法如清单 2 所C: 


清单 2. SerClass.java
/**
 *  This class is intended to be serialized over RMI-IIOP.
 */
public class SerClass implements java.io.Serializable {
    // members
    private int x;
    private String myString;

    // constructor
    public SerClass(int x, String myString) 
       throws java.rmi.RemoteException {
        this.x=x;
        this.myString=myString;
    } 
    
    // some accessor methods
    public int getX() {  return x;}
    public void setX(int x) { this.x=x; }
    public String getString() {  return myString;  }
    public void setString(String str) { myString=str; }
}
 


q就是我们简单的接口的全部。现在我们来研究一下服务器cR?br />

W? 部分Q构建服务器

我们用一个既充当 RMIInterface 实现cd包含 main ҎQ以启动我们的服务)的服务器c( Server.java Q。 Server.java l承 javax.rmi.PortableRemoteObject 。这P它就包含了将自己作ؓ Remote 接口l定刊WORB 和开始侦听请求所需要的全部功能。清单? 是该服务器的代码Q?br />

清单 3. Server.java
/*
 * Simple server
 */
import java.util.*;
import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.rmi.PortableRemoteObject;
import javax.rmi.CORBA.Tie;
import javax.rmi.CORBA.Util;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.*;
import org.omg.PortableServer.Servant;
import org.omg.CORBA.ORB;

public class Server extends PortableRemoteObject 
    implements RMIInterface {
    // must explicitly create default constructor 
    // to throw RemoteException
    public Server() throws RemoteException {
    }

    // implementation of RMIInterface methods
    public String hello() throws RemoteException {
        return "Hello there!";
    }

    public SerClass alterClass(SerClass classObject) 
        throws RemoteException {
        // change the values of SerClass and return it.
    // add 5 to X
        classObject.setX( 
           classObject.getX() + 5 ); 
    // alter the string
        classObject.setString( 
           classObject.getString() + " : I've altered you" ); 
        return classObject;
    }   

    public static void main(String[] args) {
        try {
            // create the ORB passing in the port to listen on
            Properties props = new Properties();
            props.put("com.ibm.CORBA.ListenerPort","8080");
            ORB orb = ORB.init(args, props);
    
            // instantiate the Server
            // this will automatically call exportObject(this)
            Server s = new Server();
            
            // now get the Stub for our server object - 
         // this will be both
            // a remote interface and an org.omg.CORBA.Object
            Remote r=PortableRemoteObject.toStub(s);                
    
            // register the process under the name 
         // by which it can be found    
            ((com.ibm.CORBA.iiop.ORB)orb).
            register_initial_reference("OurLittleClient",
            (org.omg.CORBA.Object)r);
    
            System.out.println("Hello Server waiting...");
            // it's that easy - 
        // we're registered and listening for incoming requests
            orb.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 


呃,q里发生着什么呢Q?br />
服务器应用程序的代码很长Q那我们分开来讲吧。首先,如前面提到过的, Server cd玊WRMIInterface qؓ它的所有方法提供实现。您可以在代码的前面部分看到 RMIInterface 的 hello() Ҏ和 alterClass(SerClass) Ҏ的实现。 hello() Ҏ只是q回字符东yHello there!”。 alterClass(SerClass) Ҏ用 SerClass 对象作参敎ͼ修改成员的|然后q回新的对象 — 全都通过 RMI-IIOP。?br />
Server.java 的 main Ҏ初始化一个 ORB。这个 ORB 设|ؓ 8080 的 com.ibm.CORBA.ListenerPort 属性作为参C入。这得 ORB 在端口?080 上侦听传入请求。请注意Q com.ibm.CORBA.ListenerPort 是一个专有的 IBM 属性。如果您惛_另一供应商的 ORB 上运行这些代码,那您应该参阅该供应商的文档,扑ֈ适当的属性。(Sun 使用 com.sun.CORBA.POA.ORBPersistentServerPort Q但它只在您使用 POAQportable object adapterQ可UL对象适配器)伺服器(servantQ时才能够工作。) 

初始化 ORB 后,main Ҏ接着对 Server 对象q行实例化。因个 server 对象也是一个 PortableRemoteObject Q所以缺省构造函C自动调用 exportObject(this) 。这个对象现在已l就l于接收q程调用。?br />
接着Q我们需要通过调用 ORB.register_initial_reference(String,orb.omg.CORBA.Object) 注册q个对象。ؓ此,我们需要把我们的 server 对象作ؓ org.omg.CORBA.Object 的引用。调用 PortableRemoteObject.toStub(s) 实现了这一点,因ؓ所q回的对象都实现了 java.rmi.Remote 和 org.omg.CORBA.Object 。?br />
然后Q返回的 org.omg.CORBA.Object 对象向服务器端 ORB 注册为“OurLittleClient”。ؓ了确保 INS h能够定位对象Q我们用注册调用 register_initial_reference 。当 INS 调用q入 ORB ӞORB 查扑ַl以正在被请求的名称注册的对象。由于我们将对象注册为“OurLittleClient”,所以,当一个 INS 调用q入我们的服务器 ORB 要求“OurLittleClient”时Q我们将知道客户机正在查扄是哪个对象。?br />
最后,我确信您已经注意到我们将 ORB 强制转型成 com.ibm.CORBA.iiop.ORB 。因为 Sun 未公开 org.omg.CORBA.ORB 接口的 register_initial_reference Q所以 IBM SDK 也不能将它公开。因此,我们必须我们的 ORB 强制转型成 IBM ORB。随着 Sun 来遵循 OMGQJDK 的未来版本(1.4.0 后)可能不需要这U强制{型。?br />
是q样Q 很单吧— 嗯Q是有点。我们的服务器现在正在等待传入客h INS h。但客户机怎么样呢Q?br />

W? 部分Q构建客h

客户机应用程序的代码如清单? 所C:


清单 4. Client.java
/*
 * Client application
 */
import javax.rmi.PortableRemoteObject;
import org.omg.CORBA.ORB;

public class Client {
  public static void main(String[] args) {
    try {
      ORB orb = ORB.init(args, null);
    
         // here's the URL for the local host
         String INSUrl = 
        "corbaloc:iiop:1.2@localhost:8080/OurLittleClient";  
       
         // get the reference to the remote process
         org.omg.CORBA.Object objRef=orb.string_to_object(INSUrl);
         // narrow it into our RMIInterface
         RMIInterface ri = 
  (RMIInterface)PortableRemoteObject.narrow(objRef, RMIInterface.class);
        
      // call the hello method
         System.out.println("received from server: "+ri.hello()+"\n");  
    
      // try RMI serialization
         SerClass se = new SerClass(5, "Client string! ");
         // pass the class to be altered on the server
         // of course behind the scenes this class is being 
      // serialized over IIOP
         se = ri.alterClass(se);
         // now let's see the result
         System.out.println("Serialization results :\n"+
            "Integer was 5 now is "+se.getX()+"\n"+
            "String was \"Client String! \" 
         now is \""+se.getString()+"\"");   
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 


如何分解客户Z?br />
客户Z码比服务器代码要单一些。我们初始化一个 ORBQ然后调用 string_to_object(String) Q其中的 string 是我们的 INS URL。构造 INS URL 相当单:首先Q我们指定我们用 corbaloc URLQ请参阅 参考资料)和 IIOP 协议版本 1.2。接着Q我们将L名(www.whatever.comQ和要连接的端口dq去。最后,我们指定我们要查扄服务的名U。结果 INS URL 是 corbaloc:iiop:1.2@localhost:8080/OurLittleClient。?br />
当我们将q个 URL 传递到 ORB.string_to_object(String) ӞORB 分z一个请求到所指定的服务器Q以h所h的服务。假设一切运转正常,则 ORB 接收回该服务的一个对象引用(实际上是一个 IORQ。然后,我们该对象引用强制转型QnarrowQ成我们能够使用的东西,ꐠRMIInterface Q这P我们׃ؓ开始调用方法做好了准备。?br />
在调用了单的 hello ҎQ它应该不需要Q何解释吧Q之后,我们可以开始探讨 RMI-IIOP 的序列化功能了。首先,我们创徏一个 SerClass Q一个可序列化的 Java c,q初始化它的成员变量。接着Q我们将q个cM入到我们的方法,Ҏ通过 IIOP 类写出到服务器。服务器dcdƈ它重创Zؓ服务器端 Java 对象Q修改它的成员|然后q回它(使用 IIOPQ作为方法的q回倹{当接收到在q程Ҏ调用之后重创建的对象Ӟ我们看到它的成员实已被服务器修改了。就是这么简单:在 IIOP 上进行 Java 序列化。?br />

 

W? 部分Q运行示?br />
h意,我们q里所创徏的示例必d IBM Developer Kit for Java technologyQ版本?.3.1 或更高版本中q行。如果您宁愿使用 Sun JDKQ请下蝲 特定于 Sun 的源代码Q您应该在 Sun 1.4.0 JDK 或更高版本中q行它。这个源代码包括一个解释 IBM SDK 版本和 Sun JDK 版本之间的差异的 readme.txt 文g。如果您没有 IBM Developer Kit for Java technologyQ而您又想要一个)Q请 现在׃载一个;它们是免费的。?br />
q里是运行示例的步骤Q?br />
下蝲 源文件。?br />
输入 javac *.java Qjavac 所有文件。?br />
对 server c运行 rmic Q带 IIOP 标志Q: rmic -iiop Server 。?br />
启动服务器:在 Windows 中,误入 start java Server 。?br />
启动客户机:在 Windows 中,误入 start java Client ?br /> 


关于 RMI-IIOP 和 EJB lg的一Ҏ?br />
EJB 2.0 规范指出QEJB lg必须能在 RMI 和 RMI-IIOP 上运行。添加 RMI-IIOP 作ؓ针对 EJB lg的在U协议,已经l将 J2EE 环境集成到现有的企业基础设施Q多数是 CORBA 相当密集的)带来了很大帮助。但它也引v了一些问题?br />
单地_是定制构建的lg和 EJB lg集成h要求您(开发者)处理道QplumbingQ,否则在 EJB 体系l构中它们对您来说将很抽象。到目前为止Q还没有解决q个问题的简单方案,可能永远也不会有。随着诸如 Web 服务q样的技术的发展Q或怼出现解决ҎQ但目前未可知?br />

 



l束语:此后该做什?br />
我希望本文已l向您展CZ构徏和运行 RMI-IIOP 客户机/服务器应用程序是多么Ҏ。您可以修改一下我们用的CZQ用U CORBA 替代客户机或服务器,不过q样做将除去您应用程序中的 Java 序列化?br />
如果您想在 CORBA 环境中用 RMI-IIOPQ那么看看 IDL 如何映射成 Java 以及 Java 如何映射成 IDL 是值得的。如果您惛_不安全的环境Q即不是您自q PCQ中部v RMI-IIOPQ那么研I一下 CORBA 安全功能Q如拦截器和 CORBA 安全模型Q以及其它 CORBA 企业功能Q如事务处理Q是个不错的L。CORBA 所有的丰富功能在您q行 RMI-IIOP 旉可以使用?br />
您可以随意提出对本文的意见 — 或者直接提l 我Q或者提刊W讨坛。我Ҏ的想法很感兴。?br />

 
 



参考资料?br />
您可以参阅本文在 developerWorks 全球站点上的 英文原文. 


请单L文顶部或底部的 讨论参加本文的 讨论论坛。?br />

下蝲 本文与 IBM 兼容的源代码。?br />


下蝲 本文与 Sun 兼容的源代码。?br />


IIOP 协议是由 对象理l织QObject Management GroupQOMGQ)制定的,q个l织q制定ƈl护着 CORBA 规范。?br />


要更多了解 CORBAQ请讉K OMG 的 CORBA Web 站点。?br />


要更多了解 RMIQ请讉K RMI 主页。?br />


“The Java Developer Connection”提供了一 INS 教程Q它也对命名服务、CosNaming 服务和 corbaloc URL 格式做了一般介l。?br />


x索一下您有哪些 Java 技术选择吗?请参阅 IBM Developer Kit for Java technology的完整清单。?br />


要更多了解 IBM Developer Kit for Java technologyQ版本?.3 和 RMI-IIOPQ请参阅 javax.rmi.CORBA cdơ结构。?br />


要获得对用 RMI 和 CORBA ~程的更q一步介l,请学习教E“ RMI, CORBA, and Distributed Objects”( developerWorksQ?000 qb?0 月)。?br />


如果您是用 EJB lg~程的新手,您可能需要学习教E“ EnterpriseJavaBeans fundamentals”( developerWorksQ?001 qb? 月)。?br />


要更多了解 EJB 技术和 CORBA 之间的关p,请参阅 Ken Nordby 的“ Deployingand using Enterprise JavaBeans components”— 对 EJB 技术的分ؓ三个部分的介l的W? 部分Q developerWorksQ?000 qb? 月)。?br />


RMI-IIOP 的主要设计师在 JavaWorld上的文章“ RMIover IIOP”讲qC他对此项技术的看法Q JavaWorldQ?999 qb?2 月)。?br />


要全方位了解 RMI-IIOPQ请参阅 theServerSide.com 上的文章“ RMI/IIOP, nice idea but the reality is turning out to be different”,它着重讲qWRMI-IIOP 不提供的东西QTheServerside.comQ。?br />
关于作?br /> Damian Hagge 在 IBM 的 Hursley 开发实验室工作。虽然他目前住在英国Q但他的出生地比他目前的居住地晚五个时区Q四十九U度U偏上一点(寚w些地理不达标的hQ就是加拿大Q。除了作为开发者在 IBM Java ORB 组工作和写一些关于不易理解的主题的长大Z外,Damian q是一个知晓各U鸡毛蒜皮小事的人;比如_您知道冰岛支持自立的不断发展的香蕉业吗QDamian q做一些常规运动,例如NQpower liftingQ(如果您认ZD重是常规q动的话Q)。您可以通过 Hagge@uk.ibm.com与他联系?img src ="http://www.aygfsteel.com/liujw/aggbug/39151.html" width = "1" height = "1" />

]]>
վ֩ģ壺 ¡| ʯׯ| ٰ| Ű| | | | | ٰ| | | Ȫ| | Ļ| ˳ƽ| բ| ɰ| | | | | | | | | | | | ƽ| | | ׸| | | ̴| | ʡ| | | ¹| |