第 10 章:Jython 和 Java 集成

Java 集成是 Jython 应用程序开发的核心。大多数 Jython 开发人员要么是希望利用 JVM 提供的庞大库的 Python 开发人员,要么是希望利用 Python 语言语义而无需迁移到完全不同平台的 Java 开发人员。事实是,大多数 Jython 开发人员使用它是为了利用 Java 世界中可用的庞大库,为此,应用程序中需要一定程度的 Java 集成。无论您计划在应用程序中使用一些现有的 Java 库,还是对将一些很棒的 Python 代码混合到 Java 应用程序中感兴趣,本章都旨在帮助您进行集成。

本章将重点介绍 Java 和 Python 的集成,但它将从几个不同的角度来探讨这个主题。您将学习几种在 Java 应用程序中使用 Jython 代码的技术。也许您想简化代码,本章将向您展示如何在 Java 中编写代码的某些部分,而在其他部分中编写 Jython 代码,以便您可以使代码尽可能简单。您将学习如何在 Jython 应用程序中使用 Pythonic 语法使用许多 Java 库!忘记用 Java 编写这些程序,为什么不使用 Jython,这样 Java 库中的 Java 实现就在幕后,本章将展示如何用 Python 编写并直接使用这些库。

在 Java 应用程序中使用 Jython

通常,能够在 Java 应用程序中使用 Jython 非常方便。也许有一个类在 Python 语法中实现起来会更好,例如 Javabean。或者也许有一段方便的 Jython 代码在一些 Java 逻辑中很有用。无论情况如何,都有几种方法可以用来实现这些技术的组合。在本节中,我们将介绍一些在 Java 中使用 Jython 的旧技术,然后介绍当前和未来的最佳实践。最后,您应该对如何在 Java 应用程序中使用模块、脚本,甚至只是一些 Jython 代码有一个很好的理解。您还将对 Jython 在这个领域的发展方式有一个总体了解。

历史回顾

在 Jython 2.5 之前,Jython 的标准发行版包含一个名为 jythonc 的实用程序。它的主要目的是提供将 Python 模块转换为 Java 类的方法,以便 Java 应用程序可以无缝地使用 Python 代码,尽管方式比较迂回。jythonc 实际上将 Jython 代码编译成 Java .class 文件,然后在 Java 应用程序中使用这些类。此实用程序还可以用于冻结代码模块、创建 jar 文件以及根据使用的选项执行其他任务。这种技术不再是将 Jython 用于 Java 应用程序的推荐方法,但我将在接下来的几段中简要介绍它,以供历史参考。事实上,从 2.5 版本开始,jythonc 不再包含在 Jython 发行版中。对于那些对旧方法不感兴趣的人,请随时跳到下一节,该节介绍了目前执行此类任务的首选方法之一。

为了让 jythonc 将 Jython 类转换为相应的 Java 类,它必须遵守一些标准。首先,Jython 类必须是 Java 对象的子类,可以是类或接口。它还必须执行以下操作之一:覆盖 Java 方法、实现 Java 方法或使用签名创建新方法。我们将使用 Jython 2.2.1 通过一个简短的示例来演示这种技术。由于这不再是首选或可接受的方法,因此我们不会深入探讨这个主题。

Fish.py
class Fish(object):
    """ Fish Object """
    def __init__(self, type, salt_or_fresh):
        self.type = type
        self.salt_or_fresh = salt_or_fresh
    def getType(self):
        "@sig public String getType()"
        return self.type
    def getSaltOrFresh(self):
        "@sig public String getSaltOrFresh()"
        return self.salt_or_fresh

虽然这种方法运行良好,并完成了它应该做的事情,但它导致了 Jython 代码和 Java 代码之间的分离。使用 jythonc 将 Jython 编译成 Java 的步骤很干净,但它在开发过程中造成了隔阂。代码应该能够无缝地协同工作,而无需单独的编译过程。应该能够仅通过引用,而无需使用中间的特殊编译器,从 Java 应用程序中无缝地使用 Jython 类和模块。在这一领域取得了一些重大进展,我将在接下来的几节中讨论其中的一些技术。在看到接下来的几个概念的实际应用后,您将明白为什么不再需要 jythonc。

对象工厂

如今,将 Jython 代码集成到 Java 应用程序中最广泛使用的技术可能是对象工厂设计模式。这个想法基本上通过使用对象工厂来实现 Java 和 Jython 之间的无缝集成。逻辑的实现方式多种多样,但最终的结果都是一样的。对象工厂范式的实现允许在 Java 应用程序中包含 Jython 模块,而无需额外的编译步骤。此外,这种技术允许通过使用 Java 接口来干净地集成 Jython 和 Java 代码。在本节中,我将解释对象工厂技术的核心概念,然后向您展示各种实现。

这种设计模式的关键是创建一个工厂方法,该方法使用 PythonInterpreter 加载所需的 Jython 模块。工厂通过 PythonInterpreter 加载模块后,它会创建一个模块的 PyObject 实例。最后,工厂使用 PyObject 的 __tojava__ 方法将 PyObject 强制转换为 Java 代码。总的来说,这个想法并不难实现,而且相对简单。但是,不同的实现方式在传递 Jython 模块的引用和相应的 Java 接口时发挥作用。需要注意的是,工厂负责实例化 Jython 对象并将其转换为 Java。对生成的 Java 对象执行的所有操作都针对相应的 Java 接口进行编码。这是一个很棒的设计,因为它允许我们根据需要更改 Jython 代码实现,而无需更改接口中包含的定义。Java 代码可以编译一次,我们可以随意更改 Jython 代码,而不会破坏应用程序。

让我们从高层次概述整个过程。假设您想在 Java 应用程序中将现有的 Jython 模块之一用作对象容器。首先,编码一个 Java 接口,该接口包含您想要公开给 Java 应用程序的模块中包含的方法的定义。接下来,您需要修改 Jython 模块以实现新编码的 Java 接口。之后,编码一个 Java 工厂类,该类将执行将模块从 PyObject 转换为 Java 对象的必要转换。最后,获取新创建的 Java 对象并根据需要使用它。为了执行一个简单的任务,这听起来可能需要很多步骤,但我认为一旦您看到它的实际应用,您就会同意它并不难。

在接下来的几节中,我将带您了解实现的不同示例。第一个示例是一种简单而优雅的方法,它涉及一对一的 Jython 对象和工厂映射。在第二个示例中,我们将研究一种非常松散耦合的方法来使用对象工厂,该方法基本上允许一个工厂用于所有 Jython 对象。每种方法都有自己的优势,您可以使用最适合您的方法。

一对一的 Jython 对象工厂

首先,我们将讨论为每个想要使用的 Jython 对象创建单独的对象工厂的概念。这种一对一的技术可能会产生大量样板代码,但它也有一些优点,我们将在后面详细介绍。为了使用这种技术利用 Jython 模块,您必须确保 .py 模块包含在您的 sys.path 中,或者在您的 Java 代码中硬编码模块的路径。让我们看一个使用这种技术的示例,该示例使用一个表示建筑物的 Jython 类来使用 Java 应用程序。

from org.jython.book.interfaces import BuildingType

class Building(BuildingType):
   def __init__(self, name, address, id):
      self.name = name
      self.address  =  address
      self.id = id

   def getBuildingName(self):
      return self.name

   def getBuildingAddress(self):
      return self.address

   def getBuldingId(self):
      return self.id


package org.jython.book.interfaces;

public interface BuildingType {

    public String getBuildingName();
    public String getBuildingAddress();
    public String getBuildingId();

}


package org.jython.book.util;

import org.jython.book.interfaces.BuildingType;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.util.PythonInterpreter;

public class BuildingFactory {

    private PyObject buildingClass;

    public BuildingFactory() {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("from Building import Building");
        buildingClass = interpreter.get("Building");
    }

    public BuildingType create(String name, String location, String id) {
        PyObject buildingObject = buildingClass.__call__(new PyString(name),
new PyString(location),
new PyString(id));
        return (BuildingType)buildingObject.__tojava__(BuildingType.class);
    }

}

package org.jython.book;

import org.jython.book.util.BuildingFactory;
import org.jython.book.interfaces.BuildingType;

public class Main {

    private static void print(BuildingType building) {
        System.out.println("Building Info: " +
                building.getBuildingId() + " " +
                building.getBuildingName() + " " +
                building.getBuildingAddress());

    }

    public static void main(String[] args) {
        BuildingFactory factory = new BuildingFactory();
        print(factory.create("BUILDING-A", "100 WEST MAIN", "1"));
        print(factory.create("BUILDING-B", "110 WEST MAIN", "2"));
        print(factory.create("BUILDING-C", "120 WEST MAIN", "3"));
    }
}

让我们对这段代码进行逐行分析。我们从一个名为 Building.py 的 Jython 模块开始,该模块放置在我们的 sys.path 上的某个位置。现在,我们必须首先确保在这样做之前没有名称冲突,否则我们可能会看到一些非常意外的结果。通常,将此文件放在应用程序的源代码根目录是一个安全的选择,除非您明确地将文件放在您的 sys.path 中的别处。您可以看到我们的 Building.py 对象是一个简单的容器,用于保存建筑信息。我们必须在我们的 Jython 类中显式地实现一个 Java 接口。这将允许 PythonInterpreter 稍后强制转换我们的对象。我们的第二段代码是我们在 Building.py 中实现的 Java 接口。从代码中可以看出,返回的 Jython 类型将被强制转换为 Java 类型,因此我们使用最终的 Java 类型定义我们的接口方法。

上面的示例中的第三段代码在游戏中扮演着最重要的角色,这是将我们的 Jython 代码强制转换为结果 Java 类的对象工厂。在构造函数中,创建了一个新的 PythonInterpreter 实例,然后我们利用解释器获取对我们 Jython 对象的引用,并将其存储到我们的 PyObject 中。接下来,有一个名为 create 的静态方法,它将被调用以将我们的 Jython 对象强制转换为 Java 并返回结果类。它通过在 PyObject 包装器本身执行 __call__ 来做到这一点,并且如您所见,如果我们愿意,我们可以向它传递参数。参数也必须由 PyObjects 包装。当在 PyObject 包装器上调用 __tojava__ 方法时,强制转换就会发生。为了使对象实现我们的 Java 接口,我们必须将接口 EmployeeType.class 传递给 __tojava__ 调用。

最后一段提供的代码 Main.java 展示了如何使用我们的工厂。您可以看到工厂负责所有繁重的工作,而我们在 Main.java 中的实现非常小。只需调用 factory.create() 方法来实例化一个新的 PyObject 并将其强制转换为 Java。

这种使用对象工厂设计的过程具有从 Java 代码中完全了解 Jython 对象的优点。换句话说,为每个 Jython 对象创建单独的工厂允许将参数传递到 Jython 对象的构造函数中。由于工厂是为特定 Jython 对象设计的,因此我们可以使用特定参数对 PyObject 上的 __call__ 进行编码,这些参数将传递到强制转换的 Jython 对象的新构造函数中。这不仅允许将参数传递到构造函数中,而且还增加了对对象进行良好文档的可能性,因为 Java 开发人员将确切地知道新构造函数的外观。尽管在大多数情况下,使用这种技术的开发人员也将是编写 Jython 对象的人。本小节中执行的过程可能是整个 Jython 社区中最常用的过程。在下一节中,我们将研究应用于通用对象工厂的相同技术,该工厂可以被任何 Jython 对象使用。

使用松散耦合的对象工厂

对象工厂设计不必使用上面示例中描述的这种一对一策略来实现。可以以一种通用的方式设计工厂,使其能够用于任何 Jython 对象。这种技术允许减少样板代码,因为您只需要一个可以用于所有 Jython 对象的 Singleton 工厂,并且它还允许易于使用,因为您可以将对象工厂逻辑分离到它自己的项目中,然后在您想要的地方应用它。例如,我在我的环境中创建了一个项目,该项目基本上包含一个 Jython 对象工厂,可以在任何 Java 应用程序中使用,以便从 Java 创建 Jython 对象,而无需担心工厂。您可以创建类似的项目,或者使用我创建并链接到本书源代码的项目。在本节中,我们将研究该项目背后的设计及其工作原理。

让我们看一下上面相同的例子,并应用松耦合对象工厂设计。你会注意到,这种技术迫使 Java 开发人员在从工厂创建对象时做更多工作,但它具有节省为每个 Jython 对象创建单独工厂所花费的时间的优势。你还可以看到,现在我们需要在我们的 Jython 对象中编写 setter 方法,并通过 Java 接口公开它们,因为我们不能再使用构造函数将参数传递到对象中,因为松耦合工厂对 PyObject 执行了通用的 __call__ 操作。

from org.jython.book.interfaces import BuildingType

class Building(BuildingType):
    def __init__(self):
       self.name = None
       self.address  =  None
       self.id = -1

    def getBuildingName(self):
        return self.name

    def setBuildingName(self, name):
        self.name = name;

    def getBuildingAddress(self):
        return self.address

    def setBuildingAddress(self, address):
        self.address = address

    def getBuildingId(self):
        return self.id

    def setBuildingId(self, id):
        self.id = id
package org.jython.book.interfaces;

public interface BuildingType {

    public String getBuildingName();
    public String getBuildingAddress();
    public int getBuildingId();
    public void setBuildingName(String name);
    public void setBuildingAddress(String address);
    public void setBuildingId(int id);

}



import java.util.logging.Level;
import java.util.logging.Logger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;

public class JythonObjectFactory {
   private static JythonObjectFactory instance = null;
   private static PyObject pyObject = null;

   protected JythonObjectFactory() {

   }

   public static JythonObjectFactory getInstance(){
        if(instance == null){
            instance = new JythonObjectFactory();
        }

        return instance;

    }


   public Object createObject(Object interfaceType, String moduleName){
       Object javaInt = null;
       PythonInterpreter interpreter = new PythonInterpreter();
       interpreter.exec("from " + moduleName + " import " + moduleName);

       pyObject = interpreter.get(moduleName);

        try {

            PyObject newObj = pyObject.__call__();

            javaInt = newObj.__tojava__(Class.forName(interfaceType.toString().substring(
                    interfaceType.toString().indexOf(" ")+1, interfaceType.toString().length())));
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(JythonObjectFactory.class.getName()).log(Level.SEVERE, null, ex);
        }

        return javaInt;
   }

}

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jythonbook.interfaces.BuildingType;
import org.jybhonbook.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

        JythonObjectFactory factory = JythonObjectFactory.getInstance();

        BuildingType building = (BuildingType) factory.createObject(
                BuildingType.class, "Building");
        building.setBuildingName("BUIDING-A");
        building.setBuildingAddress("100 MAIN ST.");
        building.setBuildingId(1);
        System.out.println(building.getBuildingId() + " " + building.getBuildingName() + " " +
                building.getBuildingAddress());

    }

}

如果我们遵循这种范式,那么你可以看到我们的 Jython 模块必须以与我们的一对一示例中不同的方式进行编码,但区别不大。主要区别在于初始化器,因为它不再接受任何参数,而且我们现在还在我们的对象中编写了 setter 方法。其余的概念仍然成立,即我们必须实现一个 Java 接口,该接口将公开我们希望在 Java 应用程序中调用的那些方法。在本例中,我们编写了 BuildingType.java 接口,并包含了必要的 setter 定义,以便我们有一种方法可以使用值加载我们的类。

我们的下一步是编写一个松耦合对象。如果你看一下 JythonObjectFactory.java 类中的代码,你会发现它是一个单例;也就是说,它只能被实例化一次。要查看的重要方法是 createObject(),因为它完成了所有工作。正如你从代码中看到的,PythonInterpreter 负责获取对我们作为字符串值传递到方法中的 Jython 对象名称的引用。一旦 PythonInterpreter 获取了对象并将其存储到 PyObject 中,它的 __call__() 方法就会在没有任何参数的情况下被调用。这将检索一个空对象,然后将其存储到另一个由 newObj 引用的 PyObject 中。最后,我们新获得的对象通过调用 __tojava__() 方法被强制转换为 Java 代码,该方法接受我们使用 Jython 对象实现的 Java 接口的完全限定名称。然后返回新的 Java 对象。

看一下 Main.java 代码,你会看到工厂是通过使用 JythonObjectFactory.getInstance() 实例化或引用的。一旦我们有了工厂的实例,就会调用 createObject(Interface, String),传递接口和我们想要使用的模块名称的字符串表示。代码还必须使用接口强制转换强制转换后的对象。此示例假设对象位于你的 sys.path 上的某个位置,否则你可以使用 createObjectFromPath(Interface, String),它接受我们想要强制转换的模块路径的字符串表示。当然,这不是首选技术,因为它现在将包含硬编码的路径,但它对于测试目的很有用。假设你编写了两个 Jython 模块,其中一个包含用于测试目的的不同对象实现,这种技术将允许你指向测试模块。

另一个类似但更精炼的实现省略了 PythonInterpreter 的使用,而是使用 PySystemState。为什么我们要使用另一种产生相同结果的实现?嗯,有几个值得注意的原因。我在本节开头描述的松耦合对象工厂设计实例化了 PythonInterpreter,然后对其进行调用。这会导致性能下降,因为使用解释器相当昂贵。另一方面,我们可以使用 PySystemState,并避免因对解释器进行额外开销调用而带来的麻烦。下一个示例不仅展示了如何使用这种技术,还展示了如何在强制转换后的对象上进行调用,并同时传递参数。这是我们之前展示的对象工厂的局限性。

JythonObjectFactory.java

package org.jython.book.util;

import org.python.core.Py;

import org.python.core.PyObject;
import org.python.core.PySystemState;

public class JythonObjectFactory {

    private final Class interfaceType;
    private final PyObject klass;

    // likely want to reuse PySystemState in some clever fashion since expensive to setup...
    public JythonObjectFactory(PySystemState state, Class interfaceType, String moduleName, String className) {
        this.interfaceType = interfaceType;
        PyObject importer = state.getBuiltins().__getitem__(Py.newString("__import__"));
        PyObject module = importer.__call__(Py.newString(moduleName));
        klass = module.__getattr__(className);
        System.err.println("module=" + module + ",class=" + klass);
    }

    public JythonObjectFactory(Class interfaceType, String moduleName, String className) {
        this(new PySystemState(), interfaceType, moduleName, className);
    }

    public Object createObject() {
        return klass.__call__().__tojava__(interfaceType);
    }

    public Object createObject(Object arg1) {
         return klass.__call__(Py.java2py(arg1)).__tojava__(interfaceType);
    }

    public Object createObject(Object arg1, Object arg2) {
         return klass.__call__(Py.java2py(arg1), Py.java2py(arg2)).__tojava__(interfaceType);
    }

    public Object createObject(Object arg1, Object arg2, Object arg3) {
         return klass.__call__(Py.java2py(arg1), Py.java2py(arg2), Py.java2py(arg3)).__tojava__(interfaceType);
    }

    public Object createObject(Object args[], String keywords[]) {
        PyObject convertedArgs[] = new PyObject[args.length];
        for (int i = 0; i < args.length; i++) {
            convertedArgs[i] = Py.java2py(args[i]);
        }
        return klass.__call__(convertedArgs, keywords).__tojava__(interfaceType);
    }

    public Object createObject(Object... args) {
        return createObject(args, Py.NoKeywords);
    }

}

import org.jython.book.interfaces.BuildingType;
import org.jython.book.util.JythonObjectFactory;

public class Main{

 public static void main(String args[]) {
        // what other control options should we provide to the factory?
        // jsr223 might have some good ideas, but also let's keep some simple code usage
        // for now, let's just try out and refine
        JythonObjectFactory factory = new JythonObjectFactory(
                BuildingType.class, "building", "Building");

        BuildingType building = (BuildingType) factory.createObject();

        building.setBuildingName("BUIDING-A");
        building.setBuildingAddress("100 MAIN ST.");
        building.setBuildingId(1);

        System.out.println(building.getBuildingId() + " " + building.getBuildingName() + " " +
                building.getBuildingAddress());
    }

}

正如你从上面的代码中看到的,它与之前展示的对象工厂实现有很大的不同。首先,你可以看到对象工厂的实例化需要一些不同的参数。在本例中,我们传递了接口、模块和类名。接下来,你可以看到 PySystemState 获取对导入器 PyObject 的引用。导入器然后对我们请求的模块进行 __call__ 操作。顺便说一下,请求的模块必须包含在 sys.path 上的某个位置。最后,我们通过调用模块上的 __getattr__ 方法获取对我们类的引用。我们现在可以使用返回的类将我们的 Jython 对象强制转换为 Java。如前所述,你会注意到这种特定实现包含几个 createObject() 变体,允许在调用模块时向其传递参数。实际上,这使我们能够将参数传递到 Jython 对象的初始化器中。

哪个对象工厂最好?你的选择取决于你的应用程序遇到的情况。底线是,有几种方法可以执行对象工厂设计,它们都允许从 Java 代码中无缝使用 Jython 对象。

现在我们已经拥有一个强制转换的 Jython 对象,我们可以继续使用在 Java 接口中定义的方法。如您所见,上面的简单示例设置了一些值,然后打印出对象的值。希望您能看到创建一个可用于任何 Jython 对象而不是仅用于一个对象的单个对象工厂是多么容易。

返回 __doc__ 字符串

从任何 Jython 类中获取 __doc__ 字符串也非常容易,只需在对象本身编码一个访问器方法即可。我们将在前面示例中使用的构建对象中添加一些代码。无论您决定使用哪种类型的工厂,这个技巧都适用于两者。

from org.jython.book.interfaces import BuildingType

class Building(BuildingType):
    ''' Class to hold building objects '''

    def __init__(self):
       self.name = None
       self.address  =  None
       self.id = -1

    def getBuildingName(self):
        return self.name

    def setBuildingName(self, name):
        self.name = name;

    def getBuildingAddress(self):
        return self.address

    def setBuildingAddress(self, address):
        self.address = address

    def getBuildingId(self):
        return self.id

    def setBuildingId(self, id):
        self.id = id

    def getDoc(self):
        return self.__doc__

package org.jython.book.interfaces;

public interface BuildingType {

    public String getBuildingName();
    public String getBuildingAddress();
    public int getBuildingId();
    public void setBuildingName(String name);
    public void setBuildingAddress(String address);
    public void setBuildingId(int id);
    public String getDoc();

}


import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jython.book.interfaces.BuildingType;
import org.plyjy.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

        JythonObjectFactory factory = JythonObjectFactory.getInstance();
        BuildingType building = (BuildingType) factory.createObject(
                BuildingType.class, "Building");
        building.setBuildingName("BUIDING-A");
        building.setBuildingAddress("100 MAIN ST.");
        building.setBuildingId(1);
        System.out.println(building.getBuildingId() + " " + building.getBuildingName() + " " +
                building.getBuildingAddress());
        System.out.println(building.getDoc());

   }
}

1 BUIDING-A 100 MAIN ST.
 Class to hold building objects

将设计应用于不同的对象类型

此设计适用于所有对象类型,而不仅仅是普通的 Jython 对象。在以下示例中,Jython 模块是一个包含简单计算器方法的类。工厂强制转换的工作方式相同,结果是将 Jython 类转换为 Java。

from org.jython.book.interfaces import CostCalculatorType

class CostCalculator(CostCalculatorType, object):
    ''' Cost Calculator Utility '''

    def __init__(self):
        print 'Initializing'
        pass

    def calculateCost(self, salePrice, tax):
        return salePrice + (salePrice * tax)

package org.jython.book.interfaces;

public interface CostCalculatorType {

    public double calculateCost(double salePrice, double tax);

}

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.plyjy.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

        JythonObjectFactory factory = JythonObjectFactory.getInstance();
        CostCalculatorType costCalc = (CostCalculatorType) factory.createObject(
                CostCalculatorType.class, "CostCalculator");
        System.out.println(costCalc.calculateCost(25.96, .07));

    }
}

Initializing
27.7772

我们还可以在 Jython 对象中与 Java 一起使用 Python 属性。事实上,Java 接口的编码方式与常规访问器风格对象的编码方式相同。在此示例中,我们将填充一个包含汽车属性的 Jython 对象。虽然此示例中的汽车对象并不十分逼真,因为它出于各种法律原因不包含制造商、型号、年份等,但我认为您会明白的。

from org.jython.book.interfaces import AutomobileType

class Automobile(AutomobileType, object):
    ''' Bean to hold automobile objects '''

    def __init__(self):
        print 'Initializing Now'
        pass

    def setType(self, value):
        self.__type = value
        print 'setting object: ' + value

    def getType(self):
        print 'getting object'
        return self.__type

    type = property(fget=getType,
                    fset=setType,
                    doc="The Type of the Automobile.")

    def setColor(self, value):
        self.__color = value

    def getColor(self):
        return self.__color

    color = property(fget=getColor,
                     fset=setColor,
                     doc="Color of Automobile.")

    def getDoc(self):
        return self.__doc__

package org.jython.book.interfaces;

public interface AutomobileType {
    public String getType();
    public void setType(String value);
    public String getColor();
    public void setColor(String value);
    public String getDoc();


}

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jython.book.interfaces.AutomobileType;
import org.plyjy.factory.JythonObjectFactory;


public class Main {

    private static void print(AutomobileType auto) {
        System.out.println("Doc: " + auto.getDoc());
        System.out.println("Type: " + auto.getType());
        System.out.println("Color: " + auto.getColor());

    }

    public static void main(String[] args) {

        JythonObjectFactory factory = JythonObjectFactory.getInstance();
        AutomobileType automobile = (AutomobileType) factory.createObject(
                AutomobileType.class, "Automobile");
        automobile.setType("Sport");
        automobile.setColor("red");
        print (automobile);

    }

}
Initializing Now
setting object: Sport
Doc:  Bean to hold automobile objects
getting object
Type: Sport
Color: red

JSR-223

随着 Java SE 6 的发布,JVM 上的动态语言有了新的优势。JSR-223 使得动态语言能够以无缝的方式通过 Java 调用。虽然这种访问 Jython 代码的方法不如使用对象工厂灵活,但它对于从 Java 代码中运行简短的 Jython 脚本非常有用。脚本项目 (https://scripting.dev.java.net/) 包含许多可以在 Java 中运行不同语言的引擎。为了运行 Jython 引擎,您必须从脚本项目中获取 jython-engine.jar 并将其放入您的类路径中。您还必须将 jython.jar 放入类路径中,并且它目前尚不支持 Jython 2.5,因此必须使用 Jython 2.2.1。

下面是一个展示脚本引擎使用的小示例。

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Main {

    public static void main(String[] args) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("python");
        engine.eval("import sys");
        engine.eval("print sys");
        engine.put("a", 42);
        engine.eval("print a");
        engine.eval("x = 2 + 2");
        Object x = engine.get("x");
        System.out.println("x: " + x);
    }

}

*sys-package-mgr*: processing new jar, '/jsr223-engines/jython/build/jython-engine.jar'
*sys-package-mgr*: processing modified jar, '/System/Library/Java/Extensions/QTJava.zip'
sys module
42
x: 4

使用 PythonInterpreter

与 JSR-223 类似,嵌入 Jython 的另一种技术是直接使用 PythonInterpreter。这种嵌入代码的风格与使用脚本引擎非常相似,但它具有与 Jython 2.5 一起使用的优势。另一个优势是 PythonInterpreter 使您能够直接使用 PyObjects。为了使用 PythonInterpreter 技术,您只需要将 jython.jar 放入您的类路径中,无需涉及额外的引擎。

import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;
public class Main {
    /**
     * @param args the command line arguments
     */
     public static void main(String[] args) throws PyException {
        PythonInterpreter interp = new PythonInterpreter();
        interp.exec("import sys");
        interp.exec("print sys");
        interp.set("a", new PyInteger(42));
        interp.exec("print a");
        interp.exec("x = 2+2");
        PyObject x = interp.get("x");
        System.out.println("x: " + x);
    }
}
<module 'sys' (built-in)>
42
x: 4

在 Jython 应用程序中使用 Java

在 Jython 应用程序中使用 Java 与在 Jython 脚本中使用外部 Jython 模块一样无缝。您只需导入所需的 Java 类并直接使用它们即可。Java 类可以像 Jython 类一样调用,方法调用也是如此。您只需调用类方法并以与在 Python 中相同的方式传递参数。

类型强制转换与在 Java 中使用 Jython 时一样,以便无缝集成两种语言。在下表中,您将看到强制转换为 Python 类型的 Java 类型以及它们如何匹配。此表取自 Jython 用户指南。

Java 类型     Python 类型  
char       String(长度为 1)
boolean       Integer(true = 非零)
byte, short, int, long   Integer    
java.lang.String, byte[], char[] String      
java.lang.Class     JavaClass  
Foo[]       Array(包含 Foo 类或子类的对象)
java.lang.Object     String  
orb.python.core.PyObject 不变      
Foo       表示 Java 类 Foo 的 JavaInstance

表 10.1- Python 和 Java 类型

关于在 Jython 中使用 Java 的另一个需要注意的是,可能会出现一些命名冲突。如果 Java 对象与 Python 对象名称冲突,那么您只需完全限定 Java 对象以确保解决冲突。

在接下来的几个示例中,您将看到一些 Java 对象从 Jython 中导入和使用。

>>> from java.lang import Math
>>> Math.max(4, 7)
7L
>>> Math.pow(10,5)
100000.0
>>> Math.round(8.75)
9L
>>> Math.abs(9.765)
9.765
>>> Math.abs(-9.765)
9.765
>>> from java.lang import System as javasystem
>>> javasystem.out.println("Hello")
Hello

现在让我们创建一个 Java 对象,并在 Jython 应用程序中使用它。

public class Beach {

    private String name;
    private String city;
    private String state;
    private boolean publicBeach;

    public Beach(String name, String city, String state, boolean publicBeach){
        this.name = name;
        this.city = city;
        this.state = state;
        this.publicBeach = publicBeach;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public boolean isPublicBeach() {
        return publicBeach;
    }

    public void setPublicBeach(boolean publicBeach) {
        this.publicBeach = publicBeach;
    }

}

>>> import Beach
>>> beach = Beach("Cocoa Beach","Cocoa Beach","FL",True)
>>> beach.getName()
u'Cocoa Beach'
>>> print beach.getName()
Cocoa Beach
>>> print beach.isPublicBeach()
True

你需要确保你想要使用的 Java 类位于你的 CLASSPATH 中。在上面的例子中,我创建了一个包含 Beach 类的 JAR 文件,然后将该 JAR 文件放在 CLASSPATH 中。

也可以通过 Jython 类扩展 Java 类。这允许我们使用 Jython 对象扩展给定 Java 类的功能,这在某些情况下非常有用。下面的例子展示了一个扩展 Java 类的 Jython 类,该 Java 类包含一些计算功能。Jython 类然后添加另一个计算方法,并使用 Java 类和 Jython 类中的计算方法。

public class Calculator {

    public Calculator(){

    }

    public double calculateTip(double cost, double tipPercentage){
        return cost * tipPercentage;
    }

    public double calculateTax(double cost, double taxPercentage){
        return cost * taxPercentage;
    }

}

import Calculator
from java.lang import Math

class JythonCalc(Calculator):
    def __init__(self):
        pass

    def calculateTotal(self, cost, tip_and_tax):
        return cost + tip_and_tax




if __name__ == "__main__":
    calc = JythonCalc()
    cost = 23.75
    tip = .15
    tax = .07
    print "Starting Cost: ", cost
    print "Tip Percentage: ", tip
    print "Tax Percentage: ", tax
    print Math.round(calc.calculateTotal(cost,
               (calc.calculateTip(cost, .15) + calc.calculateTax(cost, .07))))

Starting Cost:  23.75
Tip Percentage:  0.15
Tax Percentage:  0.07
29

结论

将 Jython 和 Java 集成是这门语言的核心。在 Jython 中使用 Java 就如同添加其他 Jython 模块一样,非常无缝的集成。这样做的优点是我们现在可以从 Jython 应用程序中使用 Java 的所有库和 API。能够在 Jython 中使用 Java 还提供了用 Python 语法编写 Java 代码的优势……这是我们都喜欢的。

利用 Jython 对象工厂等设计模式,我们也可以在 Java 应用程序中使用 Jython 代码。虽然 jythonc 不再是 Jython 发行版的一部分,但我们仍然可以在 Java 中有效地使用 Jython。有很多关于对象工厂的例子,以及像 PlyJy (http://kenai.com/projects/plyjy) 这样的项目,它们允许我们通过在 Java 应用程序中包含一个 JAR 文件来使用对象工厂。

还有更多方法可以在 Java 中使用 Jython。Java 语言在 Java 6 发布时添加了 JSR-223 的脚本语言支持。使用 jython 引擎,我们可以利用 JSR-223 方言将 Jython 代码添加到我们的 Java 应用程序中。类似地,PythonInterpreter 可以从 Java 代码中使用来调用 Jython。还要关注像 Clamp (http://github.com/groves/clamp/tree/master) 这样的项目。Clamp 项目的目标是利用注解从 Jython 类创建 Java 类。看到这个项目的发展方向会很令人兴奋,一旦项目完成,它将被记录下来。

在下一章中,您将看到如何在一些集成开发环境中使用 Jython。具体来说,我们将看看用 Eclipse 和 Netbeans 开发 Jython。使用 IDE 可以极大地提高开发人员的生产力,还可以帮助解决一些细微问题,例如将模块和 JAR 文件添加到类路径中。