分享一个金融、电信领域的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的方法”来实现二者间的互通。
php和java的互通
php和java是两种不同的语言,在LAJP架构中二者之间的互通有两种机制。
- 一、消息队列
以操作系统的消息队列为沟通媒介,在通讯过程中php作为客户端调用java端服务。消息队列属于IPC技术(进程间通讯),php语言中内置了一组函数(msg_send、msg_receive等)可以和System V消息队列通讯,而java中没有相似的方法,因此通过调用底层JNI接口使用C函数来实现。 使用消息队列有以下好处:
- 使php和java保持独立性
- 有极高的传输速度,大于socket
- 相对于socket方式,Java服务端只向本机提供服务(没有对外侦听端口),相对安全,易于管理。
- 二、Socket
消息队列技术只能适用于Unix/Linux/BSD系统,因此LAJP提供基于TCP/IP的通讯机制,从而适应各种平台。
数据类型转换
PHP和Java各有其语言内部定义的数据类型,当PHP数据传送到Java,或Java数据传送到PHP时,LAJP在内部自动地、准确地对他们进行转换,程序员无需进行任何的解码工作。
详细参看《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有两个核心能力:
- PHP优雅、高效地调用Java方法的能力
- 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安装配置
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
- 编译 : 执行 ./make.sh
- 部署编译好的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、缓存等需要开发者自己管理。
- 消息队列配置
对于消息队列,有三个系统配置影响其性能:
- MSGMNI 指定系统中消息队列最大数目
- MSGMAX 指定一个消息的最大长度
- MSGMNB 指定在一个消息队列中最大的字节数
一般性的,Linux系统的默认消息队列配置非常可怜,通过查看下面三个文件获得系统配置信息:
- /proc/sys/kernel/msgmni 缺省设置:16
- /proc/sys/kernel/msgmax 缺省设置:8192
- /proc/sys/kernel/msgmnb 缺省设置:16384
# /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链接数据库需要配置数据源,这里提供一个简单的配置示例,以供参考。
项目:
- 一个简单的Web应用
- 数据库:Mysql
- 数据源: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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod1", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod2", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod3", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod4", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass1::myMethod5", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod1", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod3", $a); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $b = lajp_call("aaa.bbb.ccc.MyClass2::myMethod4", 10); echo "b--->" . serialize($b) . "<---<br/>"; echo "b--->" . $b . "<---<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--->" . serialize($a) . "<---<br/>"; $ret = lajp_call("aaa.bbb.ccc.MyClass3::myMethod1", $a, $b); echo "ret--->" . serialize($ret) . "<---<br/>"; echo "ret--->" . $ret . "<---<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--->" . serialize($ret) . "<---<br/>"; echo "ret--->" . ($ret == NULL) . "<---<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--->" . serialize($ret) . "<---<br/>"; echo "ret--->" . ($ret == NULL ? "NULL" : $ret) . "<---<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--->" . serialize($a) . "<---<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["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--->" . serialize($a) . "<---<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--->" . serialize($ret) . "<---<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--->" . serialize($a) . "<---<br/>"; $ret = lajp_call("aaa.bbb.ccc.MyClass5::myMethod1", $a); echo "ret--->" . serialize($ret) . "<---<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