LAJP(Linux Apache Java Php) -- PHP结合JAVA的开发技术

时间:2022-03-27 22:09:04

分享一个金融、电信领域的web环境:


   LAJP名称含义

LAJP名称来源于著名的LAMP(Linux,Apache,Mysql,Php),LAMP是轻量级的开发Web程序的环境,在Internet上有广泛的应用,但对于企业开发,如金融、电信领域,LAMP略显能力不足,而这些领域通常是Java(J2EE)的*范围。LAJP是将LAMP的简便性和Java高端能力结合起来的一项技术,LAJP中的J指的是Java,由于数据库厂商对Java的广泛支持,数据库也不再特别限制为Mysql。
LAJP可以理解为PHP和Java相结合的技术,也可称为PHP和Java混合编程技术,或者PHP调用Java服务的技术,也有人习惯称之为前台PHP后台Java的技术框架。

特点

  • 优势互补: PHP是非常流行的WEB编程脚本语言,有易学、易用、开发部署效率高的特点,非常适合网页编程;JAVA适合编写具有复杂的业务功能和数据的程序,二者结合可发挥各自优势。
  • 高效稳定:Apache+PHP组合可带来优异的WEB服务稳定性,而JAVA可补充如连接池、事物管理、分布式、对象模型等高端特性。
  • 创新的通信机制 PHP和Java间的通讯方式采用系统消息队列和Socket两种机制,兼顾通讯效率和平台兼容性。
  • 数据类型自动转换机制 PHP数据和Java数据可准确地自动匹配和转换,无须程序员编写解析代码。
  • 易用:LAJP安装配置简单,PHP端和JAVA端编程符合各自的编程习惯。
  • 轻量级:LAJP架构非常轻量级,除了最基本的PHP和Java环境,不需要任何扩充的、第三方的组件、容器。

LAMP和LAJP的简要对比 LAMP从传统技术架构上看属于2层结构,虽然在php5以后增强了面向对象的能力,有了形成业务逻辑层的语言基础,但对于复杂的企业级WEB应用,php语言能力仍显不足。LAJP继承了LAMP在WEB领域编程的活力,并用java构建业务逻辑层,通过“PHP调用Java的方法”来实现二者间的互通。

LAJP(Linux Apache Java Php) -- PHP结合JAVA的开发技术

php和java的互通

php和java是两种不同的语言,在LAJP架构中二者之间的互通有两种机制。

  • 一、消息队列

以操作系统的消息队列为沟通媒介,在通讯过程中php作为客户端调用java端服务。消息队列属于IPC技术(进程间通讯),php语言中内置了一组函数(msg_send、msg_receive等)可以和System V消息队列通讯,而java中没有相似的方法,因此通过调用底层JNI接口使用C函数来实现。 使用消息队列有以下好处:

  1. 使php和java保持独立性
  2. 有极高的传输速度,大于socket
  3. 相对于socket方式,Java服务端只向本机提供服务(没有对外侦听端口),相对安全,易于管理。
  • 二、Socket

消息队列技术只能适用于Unix/Linux/BSD系统,因此LAJP提供基于TCP/IP的通讯机制,从而适应各种平台。

LAJP(Linux Apache Java Php) -- PHP结合JAVA的开发技术

数据类型转换

PHP和Java各有其语言内部定义的数据类型,当PHP数据传送到Java,或Java数据传送到PHP时,LAJP在内部自动地、准确地对他们进行转换,程序员无需进行任何的解码工作。

LAJP(Linux Apache Java Php) -- PHP结合JAVA的开发技术

详细参看《lajp数据转换示例》 http://code.google.com/p/lajp/wiki/Example

示例

示例程序表现了一个简单的PHP调用Java的程序片段,PHP在调用过程中向Java传递了3个参数,参数类型分别是字符串、数组、对象,Java服务方法返回字符串应答。

  • php端程序
  require_once("php_java.php"); //LAJP提供的程序脚本

  //php类,映射到JavaBean类:cn.com.ail.test.Bean
  class cn_com_ail_test_Bean
  {
    var $a = "v1";
    var $b = "v2";
  }

  $p1 = "a";     //字符串,传给Java方法的第一个参数

  $p2 = array(); //数组,传给Java方法的第二个参数
  $p2[] = 10;
  $p2[] = 20;

  $p3 = new cn_com_ail_test_Bean; //php对象,传给Java方法的第三个参数

  //"lajp_call"是LAJP提供的函数,用来调用java端服务
  //"cn.com.ail.test.Objtest::method1"表示调用java的cn.com.ail.test.Objtest类中的method1方法
  //"$p1,$p2,$p3"是向method1方法传递的3个参数。
  $ret = lajp_call("cn.com.ail.test.Objtest::method1", $p1, $p2, $p3);

  echo "返回信息:".$ret;    //打印"OK,收到并返回字符串应答"
  • java端程序
  //对应php中$p3的JavaBean(普通的JavaBean)
  package cn.com.ail.test;
  public class Bean
  {
    private String a;
    private String b;
        
    public String getA()
    {
      return a;
    }
    public void setA(String a)
    {
      this.a = a;
    }
    public String getB()
    {
      return b;
    }
    public void setB(String b)
    {
      this.b = b;
    } 
  }
  //java端服务
  
  package cn.com.ail.test;
  public class Objtest
  {
    //PHP调用的Java方法(普通的Java方法,LAJP仅要求声明为public static final)
    //php传来的三个参数自动转换为相应的Java数据类型
    public static final String method1(String param1, java.util.List param2, Bean param3)
    {
      System.out.println("$p1=" + param1);
      for (int i = 0; i < param2.size(); i++)
      {
        System.out.printf("$p2[%i]=%i\n", i, (Integer)param2.get(i));
      }
      System.out.println("$p3->a=" + param3.getA());
      System.out.println("$p3->b=" + param3.getB());

      //返回给PHP的应答字符串
      return "OK,收到并返回字符串应答";
    }
  }

LAJP帮助文档

LAJP是用来解决PHP和Java通讯的一项技术,在PHP中可以通过"正常"的PHP函数来调用Java的一个方法,如同下面的一个例子:

java(service):

package aaa.bbb.ccc;
public class MyClass
{
  public static final int addMethod(int a, int b)
  {
    return a + b;
  }
}

php(client):

$ret = lajp_call("aaa.bbb.ccc.MyClass::addMethod", 10, 20);
echo $ret;  //30

LAJP有两个核心能力:

  1. PHP优雅、高效地调用Java方法的能力
  2. PHP数据和Java数据合理、自动地转换的能力

在LAJP的当前版本中,使用两种技术进行PHP和Java间的通信,我对它们分别命名为: 消息队列模式 和 socket模式 。它们各自有优缺点,在使用中应根据程序所在环境特点加以选择:

  • 消息队列 以System V的消息队列作为PHP和Java间的通信媒介,优点是理论速度快,占用资源较小;缺点是只能使用在支持System V的系统中,可运用于大多数的Unix/Linux/BSD系统,但不能用于windows。
  • socket 以TCP/IP作为PHP和Java间的通信媒介,优点是基本无系统限制;缺点是理论速度慢,占用资源较大。

一、LAJP运行环境要求

"消息队列模式"和"socket模式"对运行环境的要求是不同的,下面分别加以阐述:

消息队列模式

环境需要满足System V消息队列的运行:

  • 系统 目前常见的Unix/Linux系统都可满足php(Apache)、java的运行,其中大部分默认支持System V消息队列。
  • php php需要通过消息队列和java进程通信,按php的说明,php在4.3.0版本以后支持System V消息队列。
  • apache 无特殊要求,满足php要求即可。
  • java java版本在1.5以后。

  • 在Unix/Linux环境中,推荐使用消息队列模式。

socket模式

  • 系统 没有限制,很难找到不支持TCP/IP的系统。
  • php 按php的说明,php版本>=4.1.0支持socket
  • apache 无特殊要求,满足php要求即可。
  • java java版本在1.5以后。
  • Windows系统只能使用socket模式

在开发过程中可以同时使用这两种模式,比如一般开发者使用Windows环境,而程序部署在Linux系统中,LAJP在模式的配置上和编码无关。

二、LAJP安装与运行

Windows下的LAJP安装配置

请阅读 《图解LAJP在Windows系统上的安装配置》

Unix/Linux下的LAJP安装配置

  • 下载 下载Lajp的安装文件,解压后目录结构如下:

    lajp安装包
      |
      |--jin                      //消息队列模式必要的JNI源程序
      |   |
      |   |--lajp_MsgQ.h
      |   |--lajp_MsgQ.c
      |   |--make.sh
      |   
      |--php                       //PHP端脚本
      |   |
      |   |--php_java.php.msgq
      |   |--php_java.php.socket
      |   
      |--test_service/             //Hello World 示例服务程序
      |   
      |--lajp-10.05.jar            //LAJP主程序
      |--run_msgq.sh               //Unix/Linux使用消息队列模式启动脚本
      |--run-socket.bat            //windows使用启动脚本
      |--run-socket.sh             //Unix/Linux使用socket模式启动脚本

Unix/Linux中运行LAJP依赖以下前提设置

  • Apache+php环境 部分发行版本的php默认安装不支持消息队列(System V messages)、信号量(System V semaphore)、共享内存(System V shared memory), 如使用消息队列模式需在编译php时附带编译选项 --enable-sysvsem,--enable-sysvshm和--enable-sysvmsg;如使用socket模式则要检查sockets是否激活,这些可以通过phpinfo()函数来观察。
  • java环境 要求Java5.0以上。

Unix/Linux中socket模式的配置运行

Socket模式使用run-socket.sh脚本,运行前确保run-socket.sh有执行权限,在脚本内部可以配置Java服务端口(默认21230),PHP和Java传输字符集(默认UTF-8),classpath等。

Unix/Linux中消息队列模式的配置运行

  • 首先配置好c语言编译环境
  • 编译JNI 将下载的lajp安装包中的3个源代码文件:lajp_MsgQ.c,lajp_MsgQ.h,make.sh复制到某个目录,确保make.sh有执行权限,按注释要求编辑make.sh
    #!/bin/sh
    
    # -----------------------------------------------------------
    #  LAJP-JNI 编译脚本 (2009-09 http://code.google.com/p/lajp/)
    #  
    #  编译环境: Unix/Linux
    #  
    #  源文件: lajp_MsgQ.c lajp_MsgQ.h
    #  目标文件: liblajpmsgq.so
    #  编译参数:
    #    --share  : 编译为动态库
    #    -I       : 搜索编译JNI需要的.h文件, 注意"/usr/lib/jvm/java-6-sun/"要换成编译环境中
    #               的JAVA_HOME路径
    #
    #  liblajpmsgq.so发布 : 
    #    复制到<java.library.path>中,可通过java程序
    #    System.out.println(System.getProperties().getProperty("java.library.path")); 
    #    获得本机的<java.library.path>
    # -----------------------------------------------------------
    
    
    gcc lajp_MsgQ.c --share -I. -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -o liblajpmsgq.so
    1. 编译 : 执行 ./make.sh
    2. 部署编译好的liblajpmsgq.so库文件: 如果编译成功,会生成liblajpmsgq.so文件,将它复制到任一个"java.library.path"路径中,"java.library.path"路径可以通过java程序侦测: System.out.println(System.getProperties().getProperty("java.library.path"))

消息队列模式使用run_msgq.sh脚本,运行前确保run_msgq.sh有执行权限,在脚本内部可以配置PHP和Java传输字符集(默认UTF-8),classpath等。

三、LAJP使用注意事项

  • Java Java方法如果要做为LAJP的服务方法,必须声明为 public static final
  • 数据类型 PHP和Java通过LAJP传输的数据,包括PHP调用时向Java传递的参数,和Java方法的返回值,都需要遵循LAJP数据类型要求。

php语言规范定义了8中数据类型:boolean、int、float、string、array、object、resource、NULL;java语言的数据类型分为2类:基本数据类型和对象类型,基本数据类型有byte、short、int、long、 char、boolean、float、double, 对象类型包括数组、集合、javaBean等。在LAJP架构中,php数据以参数形式传递给Java方法,Java方法的返回值再回传给php调用程序,在调用过程中,php数据“自动”转换为Java数据,反之亦然。

并不是所有数据类型都可以转换,在LAJP中建立了以下转换规则:

表1

  php java 说明
布尔 boolean boolean  
整形 int int  
浮点 float double 在php中float和double含义相同
字符串 string java.lang.String  
顺序集合 array(key:int) java.util.List php中array的每个元素的key类型必须是int
key-value集合 array(key:string) java.util.Map php中array的每个元素的key类型必须是string
对象 object JavaBean  
NULL null  

详细的数据转换规则请查阅 《LAJP数据转换示例》

四、LAJP基本配置

LAJP只是单纯的PHP和Java传输的中间机制,像Web系统中常见的数据库连接池、JNDI、缓存等需要开发者自己管理。

  • 消息队列配置

对于消息队列,有三个系统配置影响其性能:

  1. MSGMNI 指定系统中消息队列最大数目
  2. MSGMAX 指定一个消息的最大长度
  3. MSGMNB 指定在一个消息队列中最大的字节数

一般性的,Linux系统的默认消息队列配置非常可怜,通过查看下面三个文件获得系统配置信息:

  • /proc/sys/kernel/msgmni 缺省设置:16
  • /proc/sys/kernel/msgmax 缺省设置:8192
  • /proc/sys/kernel/msgmnb 缺省设置:16384
为了更好的性能,可编辑 /etc/sysctl.conf 文件,修改缺省配置:

# /etc/sysctl.conf

# set message queue 20M
kernel.msgmnb = 20971520
kernel.msgmni = 20480
  • socket侦听端口配置

在socket模式中,Java端默认的侦听端口是21230,如要修改,有两处:

  • php_java.php : 在下载包中命名为php_java.php.socket
    define("LAJP_IP", "127.0.0.1");                 //Java端IP
    define("LAJP_PORT", 新的端口);                      //Java端侦听端口
  • run-socket启动脚本 : windows中使用run-socket.bat,Unix/Linux中使用run-socket.sh

windows中在run-socket.bat中添加下面两行:

rem 设置服务侦听端口
set SERVICE_PORT=新的端口

Unix/linux中在run-socket.sh中修改:

# 设置服务侦听端口
export SERVICE_PORT=新的端口
  • 传输字符集

LAJP中默认的PHP和Java交互字符集是UTF-8,可通过修改启动脚本环境变量变更。

run-socket.bat

rem 字符集设置  GBK | UTF-8
set CHARSET=字符集

run-socket.sh或run-msgq.sh

# 字符集设置  GBK|UTF-8
export CHARSET=字符集

配置示例

一般性的使用Java链接数据库需要配置数据源,这里提供一个简单的配置示例,以供参考。

项目:

  1. 一个简单的Web应用
  2. 数据库:Mysql
  3. 数据源:DBCP

run-msgq.sh

#!/bin/sh

# -----------------------------------------------------------
#  LAJP-Java Service 启动脚本 
#               
#               (2009-10 http://code.google.com/p/lajp/)
#  
# -----------------------------------------------------------

#-----------------------
#DBCP配置
export DBCP_url="jdbc:mysql://127.0.0.1:3306/paper?characterEncoding=utf8&zeroDateTimeBehavior=round"
export DBCP_username=root
export DBCP_password=root
export DBCP_maxActive=30
export DBCP_maxIdle=10
export DBCP_maxWait=1000
export DBCP_removeAbandoned=false
export DBCP_removeAbandonedTimeout=120
export DBCP_testOnBorrow=true
export DBCP_validationQuery="select 1"
export DBCP_logAbandoned=true
#-----------------------


# java服务中需要的jar文件或classpath路径,如业务程序、第三方jar文件(如log4j等)
export classpath=lib/commons-beanutils-1.8.2.jar:lib/commons-logging-1.1.1.jar:lib/log4j-1.2.8.jar:lib/commons-dbcp-1.2.2.jar:lib/commons-collections-3.2.1.jar:lib/commons-pool-1.5.4.jar:lib/mysql-connector-java-5.1.7-bin.jar:lib/lajp_10.04.jar:bin/

# 自动启动类和方法,LAJP服务启动时会自动加载并执行
export AUTORUN_CLASS=cn.programmerdigest.Init
export AUTORUN_METHOD=init

# 字符集设置  GBK|UTF-8
# export CHARSET=GBK

# LAJP服务启动指令(前台)
java -classpath .:$classpath lajp.PhpJava

# LAJP服务启动指令(后台)
# nohup java -classpath .:$classpath lajp.PhpJava &

Java自动启动方法cn.programmerdigest.Init类中的init方法:

        /**
         * 初始化DBCP数据源
         */
        public static void init()
        {

                try
                {
                        //从环境变量中获取DBCP配置信息
                        Properties p = new Properties();
                        p.setProperty("driverClassName", "com.mysql.jdbc.Driver");
                        p.setProperty("url", System.getenv("DBCP_url"));
                        p.setProperty("username", System.getenv("DBCP_username"));
                        p.setProperty("password", System.getenv("DBCP_password"));
                        p.setProperty("maxActive", System.getenv("DBCP_maxActive"));
                        p.setProperty("maxIdle", System.getenv("DBCP_maxIdle"));
                        p.setProperty("maxWait", System.getenv("DBCP_maxWait"));
                        p.setProperty("removeAbandoned", System.getenv("DBCP_removeAbandoned"));
                        p.setProperty("removeAbandonedTimeout", System.getenv("DBCP_removeAbandonedTimeout"));
                        p.setProperty("testOnBorrow", System.getenv("DBCP_testOnBorrow"));
                        p.setProperty("validationQuery", System.getenv("DBCP_validationQuery"));
                        p.setProperty("logAbandoned", System.getenv("DBCP_logAbandoned"));

                        //创建数据源
                        dataSource = (BasicDataSource) BasicDataSourceFactory
                                        .createDataSource(p);

                }
                catch (Exception e)
                {
                        //--
                        throw new RuntimeException(e.getMessage());
                }
        }

五、其他的文档

LAJP的blog http://programmerdigest.cn/category/lajp

LAJP数据转换示例

文章简介

本文通过程序样例来介绍LAJP中PHP和Java之间的的数据转换。

PHP序列化数据简介

在PHP语言中,数据类型是隐匿的,并且是根据上下文而自动变化的,比如:

$a = 10;
$a = "a is " . $a;

在第一行中,$a是int类型,在第二行中$a变化为string类型。通常“弱”类型语言,像Javascript,VB,PHP等都是这样。PHP中提供了一些函数(is_array()、is_bool()、is_float()、is_integer()等)来获得变量的类型,更直接的方式是观察变量序列化后的排列规则:


$a = 10;
echo serialize($a);

输出:

i:10;

i表示int类型,10是其值。


$a = "abcd";
echo serialize($a);

输出:

s:4:"abcd";

s表示string类型,4表示长度,"abcd"是其值。


$a = TRUE;
echo serialize($a);

输出:

b:1;

b表示boolean类型,1表示TRUE,0表示FALSE。


$a = 10.24;
echo serialize($a);

输出:

d:10.2400000000000002131628207280300557613372802734375;

d表示double类型,10.2400000000000002131628207280300557613372802734375是其值。


数组、对象等复杂类型也可以序列化:

$a = array();
$a[] = 20;
$a[] = "abcde";
$a[] = TRUE;

echo serialize($a);

输出:

a:3:{i:0;i:20;i:1;s:5:"abcde";i:2;b:1;}

开始的a表示array,紧跟着的3表示数组长度,{}内部是数组元素:

  • i:0;i:20;是第一个元素,i:0;是KEY(表示下标是int类型的0),i:20;是VALUE。
  • i:1;s:5:"abcde";是第二个元素,i:1;是KEY(表示下标是int类型的1),s:5:"abcde";是VALUE。
  • i:2;b:1;是第三个元素,i:2;是KEY(表示下标是int类型的2),b:1;是VALUE。

$a = array();
$a["a"] = 20;
$a["b"] = "abcde";
$a["c"] = TRUE;

echo serialize($a);

输出:

a:3:{s:1:"a";i:20;s:1:"b";s:5:"abcde";s:1:"c";b:1;}

这里数组下标是字符串,数据结构可以看作是其他语言的Hashtable类型。


在LAJP中,PHP和Java之间传输的数据封装形式,即是上面这种PHP序列化数据形式。

Example 1 基本


Java:

package aaa.bbb.ccc;
public class MyClass1
{
        public static final int myMethod1(int i)
        {
                return ++i;
        }
}

PHP:

$a = 10;
echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod1", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---
b--->i:11;<---
b--->11<---

在LAJP中,当PHP将整形10传给Java服务时,传送的数据即是字符串i:10;,Java服务返回整形11也包装为字符串i:11;


Java:

package aaa.bbb.ccc;
public class MyClass1
{
        public static final int myMethod2(long i)
        {
                return (int)++i;
        }
}

PHP:

$a = 10;
echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod2", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---

Fatal error: Uncaught exception 'Exception' with message '[LAJP Error] Response receive Java exception: MethodNotFoundException: Can't match method: aaa.bbb.ccc.MyClass1.myMethod2(long)' in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php:215 Stack trace: #0 /media/sda3/prog/eclipse_php/workspace/LAJP_test/test1_02.php(8): lajp_call('aaa.bbb.ccc.MyC...', 10) #1 {main} thrown in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php on line 215

myMethod2方法参数声明为long,在PHP中没有与之对应的数据类型,因此抛出异常。

也有朋友认为应该允许这种情况,因为在Java中int可以自动转换为long;我的意见还是不允许,因为有带来二义性的可能。


Java:

package aaa.bbb.ccc;
public class MyClass1
{
        public static final int myMethod3(Integer i)
        {
                return (int)++i;
        }
}

PHP:

$a = 10;
echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod3", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---

Fatal error: Uncaught exception 'Exception' with message '[LAJP Error] Response receive Java exception: MethodNotFoundException: Can't match method: aaa.bbb.ccc.MyClass1.myMethod3(java.lang.Integer)' in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php:215 Stack trace: #0 /media/sda3/prog/eclipse_php/workspace/LAJP_test/test1_03.php(8): lajp_call('aaa.bbb.ccc.MyC...', 10) #1 {main} thrown in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php on line 215

myMethod3方法参数声明为Integer,在Java语言中有装箱、拆箱,int和Integer通常可互换,但在LAJP中此种情况不被允许。所以,如果PHP传送int,Java方法必声明为基本类型int;传double,必声明为基本类型double;传boolean,必声明为基本类型boolean。


Java:

package aaa.bbb.ccc;
public class MyClass1
{
        public static final long myMethod4(int i)
        {
                return ++i;
        }
}

PHP:

$a = 10;
echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod4", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---
b--->O:14:"java_lang_Long":0:{}<---

Catchable fatal error: Object of class __PHP_Incomplete_Class could not be converted to string in /media/sda3/prog/eclipse_php/workspace/LAJP_test/test1_04.php on line 11

这里myMethod4方法返回类型为long,在PHP中并没有与之对应的数据类型,但PHP端仍然接收到了:

O:14:"java_lang_Long":0:{}

其中起始的"O"表示这是一个对象。

在Java端,LAJP是这样来转换返回数据的:

  • 如果方法返回类型是int或包装类Integer,封装为PHP的int序列化数据;
  • 如果返回是double或包装类Double,封装为PHP的float序列化数据;
  • 如果返回是boolean或包装类Boolean,封装为PHP的boolean序列化数据;
  • 如果返回是java.lang.String,封装为PHP的String序列化数据;
  • 如果返回是java.util.List或其子类,封装为PHP的array序列化数据,array下标为递增整数;
  • 如果返回是java.util.Map或其子类,封装为PHP的array序列化数据,array下标为字符串;
  • 如果以上都不是,视为JavaBean,封装为PHP4的对象序列化数据。

本例中,返回类型long被视为最后一种情况。


Java:

package aaa.bbb.ccc;
public class MyClass1
{
        public static final Integer myMethod5(int i)
        {
                return ++i;
        }
}

PHP:

$a = 10;
echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod5", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---
b--->i:11;<---
b--->11<---

在LAJP中,Java方法的返回类型,当声明为int,boolean,double或它们的包装类型是等价的。我的建议是不要声明为包装类型,将来的版本很可能不再支持。


Example 2 参数


Java:

package aaa.bbb.ccc;
public class MyClass2
{
        public static final int myMethod1(int a, int b)
        {
                return a + b;
        }
}

PHP:

$a = 10;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod1", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->i:10;<---

Fatal error: Uncaught exception 'Exception' with message '[LAJP Error] Response receive Java exception: MethodNotFoundException: Can't match method: aaa.bbb.ccc.MyClass2.myMethod1(1 parameters)' in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php:215 Stack trace: #0 /media/sda3/prog/eclipse_php/workspace/LAJP_test/test2_01.php(8): lajp_call('aaa.bbb.ccc.MyC...', 10) #1 {main} thrown in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php on line 215

在PHP语言中,方法参数是可变长的,但在LAJP中,不允许。


Java:

package aaa.bbb.ccc;
public class MyClass2
{
        public static final String myMethod3(boolean a)
        {
                if (a)
                {
                        return "input TRUE";
                }
                else
                {
                        return "input FALSE";
                }
        }
        //同名方法
        public static final String myMethod3(boolean a, int b)
        {
                if (a)
                {
                        return "input TRUE, " + b;
                }
                else
                {
                        return "input FALSE, " + b;
                }
        }
}

PHP:

$a = TRUE;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod3", $a);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->b:1;<---
b--->s:10:"input TRUE";<---
b--->input TRUE<---

Java的方法重载(overload),允许。下面是另一个例子(同名,参数数量也相同):

Java:

package aaa.bbb.ccc;
public class MyClass2
{
        public static final String myMethod4(boolean a)
        {
                if (a)
                {
                        return "input TRUE";
                }
                else
                {
                        return "input FALSE";
                }
        }

        public static final String myMethod4(int a)
        {
                        return "input " + a;
        }
}

PHP:

$a = TRUE;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod4", 10);

echo "b---&gt;" . serialize($b) . "&lt;---<br/>";
echo "b---&gt;" . $b . "&lt;---<br/>";

输出:

a--->b:1;<---
b--->s:8:"input 10";<---
b--->input 10<---

Example 3 空和异常


Java:

package aaa.bbb.ccc;
public class MyClass3
{
        public static final int myMethod1(int a, int b)
        {
                return a / b;
        }
}

PHP:

$a = 10;
$b = 0;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$ret = lajp_call("aaa.bbb.ccc.MyClass3::myMethod1", $a, $b);

echo "ret---&gt;" . serialize($ret) . "&lt;---<br/>";
echo "ret---&gt;" . $ret . "&lt;---<br/>";

输出:

a--->i:10;<---

Fatal error: Uncaught exception 'Exception' with message '[LAJP Error] Response receive Java exception: InvocationTargetException for call method aaa.bbb.ccc.MyClass3.myMethod1' in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php:215 Stack trace: #0 /media/sda3/prog/eclipse_php/workspace/LAJP_test/test3_01.php(9): lajp_call('aaa.bbb.ccc.MyC...', 10, 0) #1 {main} thrown in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php on line 215

这是Java端除0异常的例子。Java方法抛出的异常,如果是Exception或其子类,都可以“抛”给PHP端。


Java:

package aaa.bbb.ccc;
public class MyClass3
{
        public static final String myMethod2()
        {
                return null;
        }
}

PHP:

$ret = lajp_call("aaa.bbb.ccc.MyClass3::myMethod2");

echo "ret---&gt;" . serialize($ret) . "&lt;---<br/>";
echo "ret---&gt;" . ($ret == NULL) . "&lt;---<br/>";

输出:

ret--->N;<---
ret--->1<---

这是Java端返回null的例子。

Java:

package aaa.bbb.ccc;
public class MyClass3
{
        public static final void myMethod3()
        {
                
        }
}

PHP:

$ret = lajp_call("aaa.bbb.ccc.MyClass3::myMethod3");

echo "ret---&gt;" . serialize($ret) . "&lt;---<br/>";
echo "ret---&gt;" . ($ret == NULL ? "NULL" : $ret) . "&lt;---<br/>";

输出:

ret--->N;<---
ret--->NULL<---

这是Java端返回void的例子。

在LAJP的9.10版本中,null和void返回类型在PHP端被解释为FALSE,10.04版本中已改正。


Example 4 集合


Java:

package aaa.bbb.ccc;
public class MyClass4
{
        //方法参数声明为List
        public static final void myMethod1(java.util.List list)
        {
                if (list == null)
                {
                        System.out.println("list集合为空");
                        return;
                }
                
                System.out.println("list集合长度: " + list.size());
                for (int i = 0; i < list.size(); i++)
                {
                        System.out.printf("----list[%d]:%s\n", i, (String)list.get(i));
                }
        }
        //方法参数声明为Map
        public static final void myMethod1(java.util.Map<String, String> map)
        {
                if (map == null)
                {
                        System.out.println("map集合为空");
                        return;
                }
                
                System.out.println("map集合长度: " + map.size());
                
                java.util.Set<String> keySet = map.keySet();
                for (String key : keySet)
                {
                        System.out.printf("----map[%s=>%s]\n", key, (String)map.get(key));
                }
        }
}

PHP:

$a = array(); //定义一个数组
$a[0] = "aaa";//第一个元素"aaa"
$a[1] = "bbb";//第二个元素"bbb"
$a[2] = "ccc";//第三个元素"ccc"

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

lajp_call("aaa.bbb.ccc.MyClass4::myMethod1", $a);

PHP输出:

a--->a:3:{i:0;s:3:"aaa";i:1;s:3:"bbb";i:2;s:3:"ccc";}<---

Java输出:

list集合长度: 3
----list[0]:aaa
----list[1]:bbb
----list[2]:ccc

在PHP中,array可以模拟多种数据类型,如队列、哈西、栈等,对应到Java,有以下规定:

  • 如果array的第一个元素的KEY是int类型,对应为Java的java.util.List
  • 如果array的第一个元素的KEY是string类型,对应到Java的java.util.Map
上面的例子中, $a[0] = "aaa";  第一个元素KEY是整形,因此调用的是myMethod1(java.util.List list)方法。下面将PHP代码稍加改动:

$a["a"] = "aaa";//第一个元素"aaa"
$a["b"] = "bbb";//第二个元素"bbb"
$a["c"] = "ccc";//第三个元素"ccc"

array的KEY数据类型被改动为字符串,再来看Java端的输出:

map集合长度: 3
----map[b=>bbb]
----map[c=>ccc]
----map[a=>aaa]

myMethod1(java.util.Map<String, String> map)方法使用了泛型,在Java中运行态是去泛型化的,因此对于LAJP,泛型无作用。但我建议仍然多用泛型,至少在代码review中清晰很多。


Java:

package aaa.bbb.ccc;
public class MyClass4
{
        public static final void myMethod2(java.util.ArrayList list)
        {
                if (list == null)
                {
                        System.out.println("list集合为空");
                        return;
                }
                
                System.out.println("list集合长度: " + list.size());
                for (int i = 0; i < list.size(); i++)
                {
                        System.out.printf("----list[%d]:%s\n", i, (String)list.get(i));
                }
        }
}

PHP:

$a = array();
$a[] = 10;
$a[] = 20;
$a[] = 30;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

lajp_call("aaa.bbb.ccc.MyClass4::myMethod2", $a);

PHP输出:

a--->a:3:{i:0;i:10;i:1;i:20;i:2;i:30;}<---

Fatal error: Uncaught exception 'Exception' with message '[LAJP Error] Response receive Java exception: MethodNotFoundException: Can't match method: aaa.bbb.ccc.MyClass4.myMethod2(java.util.ArrayList)' in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php:215 Stack trace: #0 /media/sda3/prog/eclipse_php/workspace/LAJP_test/test4_03.php(11): lajp_call('aaa.bbb.ccc.MyC...', Array) #1 {main} thrown in /media/sda3/prog/eclipse_php/workspace/LAJP_test/php_java.php on line 215

集合入参类型声明必须是java.util.List或java.util.Map。这里不能声明为ArrayList是因为PHP传入的参数被视为List,而从面向对象角度理解ArrayList继承自List,可以说ArrayList是List,而List不是ArrayList,因此这里类型不匹配。


Java:

package aaa.bbb.ccc;
public class MyClass4
{
        public static final java.util.HashMap<String, Integer> myMethod4()
        {
                java.util.HashMap map = new java.util.HashMap();
                map.put("aaa", 10);
                map.put("bbb", 20);
                map.put("ccc", 30);
                
                return map; 
        }
}

PHP:

$ret = lajp_call("aaa.bbb.ccc.MyClass4::myMethod4");
echo "ret---&gt;" . serialize($ret) . "&lt;---<br/>";

PHP输出:

ret--->a:3:{s:3:"aaa";i:10;s:3:"ccc";i:30;s:3:"bbb";i:20;}<---

这里返回类型声明为java.util.Map的子类形是可以的。从面向对象角度理解:HashMap即是Map。


Example 5 对象


Java:

package aaa.bbb.ccc;
//JavaBean
public class MyBean
{
        private int i;
        private boolean b;
        private String c;
        private java.util.List<Double> list;
        
        public int getI()
        {
                return i;
        }
        public void setI(int i)
        {
                this.i = i;
        }
        public boolean isB()
        {
                return b;
        }
        public void setB(boolean b)
        {
                this.b = b;
        }
        public String getC()
        {
                return c;
        }
        public void setC(String c)
        {
                this.c = c;
        }
        public java.util.List<Double> getList()
        {
                return list;
        }
        public void setList(java.util.List<Double> list)
        {
                this.list = list;
        }
}
package aaa.bbb.ccc;
public class MyClass5
{
        public static final MyBean myMethod1(MyBean bean)
        {
                if (bean == null)
                {
                        return null;
                }
                
                bean.setI(bean.getI() + 1);
                bean.setB(!bean.isB());
                bean.setC(bean.getC() + " OK!");
                
                java.util.ArrayList<Double> retList = new java.util.ArrayList<Double>();
                for (double d : bean.getList())
                {
                        retList.add(d + 1);
                }
                bean.setList(retList);
                
                return bean;
        }
}

PHP:

class aaa_bbb_ccc_MyBean
{
        var $i;
        var $b;
        var $c;
        var $list;
}

$a = new aaa_bbb_ccc_MyBean; //实例化对象
$a->i = 10;
$a->b = TRUE;
$a->c = "zhangsan";
$a->list = array();
$a->list[] = 10.2;
$a->list[] = 20.4;

echo "a---&gt;" . serialize($a) . "&lt;---<br/>";

$ret = lajp_call("aaa.bbb.ccc.MyClass5::myMethod1", $a);
echo "ret---&gt;" . serialize($ret) . "&lt;---<br/>";

PHP输出:

a--->O:18:"aaa_bbb_ccc_MyBean":4:{s:1:"i";i:10;s:1:"b";b:1;s:1:"c";s:8:"zhangsan";s:4:"list";a:2:{i:0;d:10.199999999999999289457264239899814128875732421875;i:1;d:20.39999999999999857891452847979962825775146484375;}}<---
ret--->O:18:"aaa_bbb_ccc_MyBean":4:{s:1:"i";i:11;s:1:"b";b:0;s:1:"c";s:12:"zhangsan OK!";s:4:"list";a:2:{i:0;d:11.199999999999999289457264239899814128875732421875;i:1;d:21.39999999999999857891452847979962825775146484375;}}<---

在LAJP中,对象传输有下面的规则:

  • Java端,对象必须符合JavaBean规则
  • PHP端,必须是PHP4对象(PHP5对象序列化数据不完备,目前不支持)
  • Java和PHP对象映射,属性名称一致(有过Struts编程经历的人很容易理解这点)

LAJP中提供了一个自动生成PHP4对象类的工具,比如可以通过Java的aaa.bbb.ccc.MyBean类,来生成与之对应的PHP4对象类代码:

php:

$ret = lajp_call("lajp.ReflectUtil::javaBean2Php", "aaa.bbb.ccc.MyBean");
//$ret = lajp_call("lajpsocket.ReflectUtil::javaBean2Php", "aaa.bbb.ccc.MyBean"); //LAJP的socket版本用这一行
echo $ret;

PHP输出:

class aaa_bbb_ccc_MyBean
{
    var $b;
    var $c;
    var $i;
    var $list;
}

对象、数组(Java映射为Map和List)属于容器型数据类型,可以嵌套其他类型数据包括对象和数组,LAJP对嵌套的层级没有限制,但嵌套的子类型(对象内部的属性、数组内部的元素)必须是以下几种:

  • int
  • boolean
  • php(float或double) | Java(double)
  • php(string) | Java(java.lang.String)
  • php4对象 | Java(JavaBean)
  • array | java.util.Map或java.util.List
以上6种数据,也就是LAJP所能支持的可以传输的所有数据类型。