PHP开发者的BlazeDS和JMS指南,第一部分

BlazeDS是来自Adobe公司的一个开源项目,它可以让您的Flex应用程序与数据服务进行连接。JMS(Java消息服务)是用Java编写的与服务相互通信的一种方法。本文有助于您体会使用JMS的优点,以及在Flex应用程序中如何使用BlazeDS通过JMS与Java服务进行通信。

\

JMS概述

\

JMS是一个API(应用编程接口)标准,它允许您使用Java EE技术发送和接收消息。在Java社区很多软件商都提供了JMS的商业和开源实现,您可以根据自己的需求自由选择软件商。

\

使用JMS的优点

\

使用JMS有几个优点,包括抽象、可靠的传递、异步消息,以及故障转移和高可用性。其中,抽象是非常重要的优点,因为JMS的消费者和生产者不必彼此相互依赖。无论是消费者端还是生产者端的代码都可以改变,只要JMS消息保持一致,两者间的连接就不会中断。

\

JMS通常支持两种形式的消息——持久化的消息和非持久化的消息。非持久化的消息是在内存中进行处理的,速度很快,但可能会因系统故障而丢失消息。持久化的消息则会把消息写入磁盘,即便系统发生故障也可找回消息,但相应的处理速度较慢。不管是否使用事务处理,JMS消息提供者都会确保所有持久化消息只被可靠传递一次。异步消息传递则提供了发送消息而无需等待响应的能力,其在Web应用中非常有用,当发送请求后应用程序无需阻塞或挂起等待响应完成。这种消息传递类型对于需要长时间处理的应用(如酒店预订或构建动态文档等应用)来说是一个理想选择。

\

只要配置正确,很多JMS的实现都提供了故障转移和高可用性能力。因此,如果某个正在运行JMS的服务器突然失效了,相同JMS实现的另一台服务器能够处理其负载。对于需要提供业务连续性的应用程序来说,故障转移是一个理想选择。

\

使用JMS和BlazeDS的优点

\

在使用BlazeDS连接JMS时,您的Flex应用程序可以获得使用JMS的所有好处,而过去这些好处往往只有完全在J2EE平台上实现的Web应用才能享受。

\

使用BlazeDS和JMS向服务发送消息,可以让您的Flex应用程序开发更加独立于服务。服务的JMS实现甚至可以在更换软件供应商,而您的Flex应用程序不用做任何修改。

\

使用BlazeDS和JMS的另一个主要优点是,如果您在使用JMS之前正在使用超文本传输协议(HTTP),那么您的Flex应用程序的代码基本不需要做什么改变,BlazeDS会为您处理的所有细节。

\

理解JMS消息

\

JMS提供了两种类型的消息传递模型:点对点(P2P)和发布/订阅(pub/sub)模型。简单地说,发布/订阅是一对多的方式广播消息,而点对点则是一对一的方式传递消息。

\

在JMS中的消息客户端被称为JMS客户端;而消息系统则被称为面向消息的中间件(MOM——Messaging Orientated Middleware),它也是JMS提供者。另外,产生消息的JMS客户端被称为是生产者,而接收消息的JMS客户端被称为消费者。

\

P2P模型

\

P2P消息模型允许JMS客户端通过名为队列(Queue)的虚拟信道发送和接收消息。JMS队列可视为消息的容器,就像一个邮箱。JMS发送者推送一条消息到JMS消息队列就如同寄件人把一封信件放到了邮箱。而后,接收者从邮箱中获得消息并对之采取某种动作。就像邮箱中的信件,使用JMS队列从发件人发送的消息由单个接收者读取。尽管JMS对于P2P也支持类似于发布/订阅模型所使用的“推”的方式,传统的P2P模型却是一个基于“拉”或基于“轮询”的模型,是从队列中请求消息,而不是把消息推送到客户端。

\

可能用到JMS消息和Flex客户端的一个实例是:从Flex客户端初始化一个进程,该进程需要服务层花费一些时间才能完成,比如创建一个按需定制的PDF文件。

\

发布/订阅模式

\

在发布/订阅模式中,生产者可以通过名为主题(Topic)的虚拟信道发送一个信息给多个消费者。主题与印刷杂志类似:订阅者向出版商注册订阅,接收指定的杂志。出版商定期向不同的订阅者发送同一杂志的副本。发布/订阅模型总的来说是基于推的模型,消息被广播到消费者,而不是通过请求或拉的方式。

\

使用JMS主题,您可以从服务层一次发送信息到多个Flex客户端。在Flex客户端使用JMS主题的一个例子是用户上线。在这个例子中,当用户登录到该系统,服务层会经由JMS主题发送一个消息,告诉其他所有的Flex客户端该用户已上线。

\

JMS消息

\

JMS提供一种方式来构建信息,使得主题和队列使用起来更加容易。当编写Java代码时,JMS API提供的方法能以一致的方式来正确构建消息。而使用BlazeDS的一个好处是,您不必担心怎样创建正确的信息,BlazeDS会辅助实现这一点的。

\

JMS的实现

\

JMS的实现有若干,既有开源项目也有商业项目。本文使用的JMS实现是ActiveMQ,它是Apache软件基金会的一个开放源码的项目,以Apache许可证发布。ActiveMQ除了提供一个JMS的实现外,还提供使用其它编程语言发送消息的功能,包括PHP。其它的JMS实现还包括IBM的WebSphere MQ,JBoss的HornetQ(注:前身是JBoss Messaging,开放的消息队列),以及OpenJMS。

\

配置BlazeDS使用JMS

\

若要BlazeDS使用JMS,其配置包括:建立正确的端点(endpoint)并在终点(destination)配置中设置JMS属性。本文的示例用到了一个简单的Flex界面、BlazeDS库,以及ActiveMQ。

\

JMSAdapter

\

当消息到达MessageBroker Servlet后,BlazeDS使用JMSAdapter来完成Flex应用程序和JMS的实现之间的消息转换。JMSAdapter在messages-config.xml文件中配置:

\
\\u0026lt;adapters\u0026gt;\\t\u0026lt;adapter-definition id=\"actionscript\" class=\"flex.messaging.services.messaging.adapters.ActionScriptAdapter\" default=\"true\" /\u0026gt;\\t\u0026lt;adapter-definition id=\"jms\" class=\"flex.messaging.services.messaging.adapters.JMSAdapter\" /\u0026gt;\\u0026lt;/adapters\u0026gt;\
\

端点的不同

\

一定要记住,BlazeDS的Java库和MessageBroker servlet需要进行配置并运行于Web容器。由于MessageBroker必须处于运行状态,使用JMS适配器的终点(destination)仍要使用AMF(Adobe Action Message Format)通道。在本例中使用的端点是:

\
\\u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\"?\u0026gt;\\u0026lt;services-config\u0026gt;\   \u0026lt;services\u0026gt;\       \u0026lt;service-include file-path=\"messaging-config.xml\" /\u0026gt;\   \u0026lt;/services\u0026gt;\\   \u0026lt;security/\u0026gt;\\   \u0026lt;channels\u0026gt;\      \u0026lt;channel-definition id=\"my-amf\" class=\"mx.messaging.channels.AMFChannel\"\u0026gt;\         \u0026lt;endpoint url=\"http://{server.name}:8161/blazeds/messagebroker/amf\" class=\"flex.messaging.endpoints.AMFEndpoint\"/\u0026gt;\      \u0026lt;/channel-definition\u0026gt;\\      \u0026lt;channel-definition id=\"my-streaming-amf\" class=\"mx.messaging.channels.StreamingAMFChannel\"\u0026gt;\         \u0026lt;endpoint url=\"http://{server.name}:8161/blazeds/messagebroker/streamingamf\" class=\"flex.messaging.endpoints.StreamingAMFEndpoint\"/\u0026gt;\      \u0026lt;/channel-definition\u0026gt;\\      \u0026lt;channel-definition id=\"my-polling-amf\" class=\"mx.messaging.channels.AMFChannel\"\u0026gt;\         \u0026lt;endpoint url=\"http://{server.name}:8161/blazeds/messagebroker/amfpolling\" class=\"flex.messaging.endpoints.AMFEndpoint\"/\u0026gt;\         \u0026lt;properties\u0026gt;\            \u0026lt;polling-enabled\u0026gt;true\u0026lt;/polling-enabled\u0026gt;\            \u0026lt;polling-interval-seconds\u0026gt;1\u0026lt;/polling-interval-seconds\u0026gt;\         \u0026lt;/properties\u0026gt;\      \u0026lt;/channel-definition\u0026gt;\   \u0026lt;/channels\u0026gt;\\u0026lt;/services-config\u0026gt;\
\

JMS属性

\

JMS需要一些自己的配置,比如连接工厂用于建立JMS连接的JNDI(Java命名和目录接口)。队列名或主题名也是使用JNDI进行解析的。JNDI是一个Java API,允许把资源的物理位置抽象成名字。JNDI的常见用法是数据库命名,应用程序使用此JNDI名获得数据库连接。

\

除了连接工厂、主题或队列的JNDI名,JMS变量还允许您定义用户证书、消息类型、终点类型,等等。下面展示的是在本例中使用的JMS属性:

\
\\u0026lt;destination id=\"my-jms-destination\"\u0026gt;\   \u0026lt;properties\u0026gt;\      \u0026lt;jms\u0026gt;\         \u0026lt;destination-type\u0026gt;Topic\u0026lt;/destination-type\u0026gt;\         \u0026lt;message-type\u0026gt;javax.jms.TextMessage\u0026lt;/message-type\u0026gt;\         \u0026lt;connection-factory\u0026gt;ConnectionFactory\u0026lt;/connection-factory\u0026gt;\         \u0026lt;destination-jndi-name\u0026gt;dynamicTopics/MyTopic\u0026lt;/destination-jndi-name\u0026gt;\         \u0026lt;delivery-mode\u0026gt;NON_PERSISTENT\u0026lt;/delivery-mode\u0026gt;\         \u0026lt;message-priority\u0026gt;DEFAULT_PRIORITY\u0026lt;/message-priority\u0026gt;\         \u0026lt;acknowledge-mode\u0026gt;AUTO_ACKNOWLEDGE\u0026lt;/acknowledge-mode\u0026gt;\         \u0026lt;initial-context-environment\u0026gt;\            \u0026lt;property\u0026gt;\               \u0026lt;name\u0026gt;Context.INITIAL_CONTEXT_FACTORY\u0026lt;/name\u0026gt;\               \u0026lt;value\u0026gt;org.apache.activemq.jndi.ActiveMQInitialContextFactory\u0026lt;/value\u0026gt;\            \u0026lt;/property\u0026gt;\            \u0026lt;property\u0026gt;\               \u0026lt;name\u0026gt;Context.PROVIDER_URL\u0026lt;/name\u0026gt;\               \u0026lt;value\u0026gt;tcp://localhost:61616\u0026lt;/value\u0026gt;\            \u0026lt;/property\u0026gt;\         \u0026lt;/initial-context-environment\u0026gt;\      \u0026lt;/jms\u0026gt;\   \u0026lt;/properties\u0026gt;\   \u0026lt;channels\u0026gt;\      \u0026lt;channel ref=\"my-amf\" /\u0026gt;\   \u0026lt;/channels\u0026gt;\   \u0026lt;adapter ref=\"jms\" /\u0026gt;\\u0026lt;/destination\u0026gt;\
\

您可以查看BlazeDS文档以获得进一步的属性解释。

\

从BlazeDS发送消息

\

要尝试本文的例子,请在创建您的测试Flex项目之前,按照下面的步骤行事。下载ActiveMQ二进制发布包并解压,您可以启动ActiveMQ并完成ActiveMQ文档中的验证步骤,但在继续下一步之前请先关闭ActiveMQ。

\

下载BlazeDS二进制发布包

\

首先要下载BlazeDS二进制发布包

\
\

将BlazeDS发布包blazeds.war解压到ActiveMQ的webapps目录下。这么做可保证您运行的例子中只使用了ActiveMQ。

\

在Jetty服务器中建立Web应用上下文,Jetty是一个Web容器,ActiveMQ二进制发布包自带了Jetty。如要增加上下文,需修改jetty.xml文件,它位于ActiveMQ目录下的conf目录:

\
\\u0026lt;webAppContext contextPath=\"/blazeds\" resourceBase=\"${activemq.base}/webapps/blazeds\" logUrlOnStart=\"true\"/\u0026gt; 
\

启动ActiveMQ,在浏览器地址栏输入http://localhost:8161/blazeds。应该可以看到BlazeDS的目录列表。这一步是验证你已经把BlazeDS WAR放在正确的位置,并配置正确。

\

创建一个Flex项目,它包含两个配置文件,配置文件位于src/WEB-INF/flex目录,分别为:messaging-config.xml和services-config.xml。使用文章前述的例子来设置JMS的终点,并添加JMSAdapter。

\

把Flex应用项目中的services-config.xml和messaging-config.xml文件复制到ActiveMQ的webapps/blazeds/WEB-INF/flex目录。

\
\

发送消息(示例)

\

为测试从Flex发送的消息,本文使用了ActiveMQ自带的命令行例子。为从Flex应用程序中发送一条消息到命令行客户端,您可以在Flex代码中建立一个生产者(producer),然后用不同的参数运行命令行例子,并查看结果。

\

您需要在Flex应用程序中创建一个生产者来发送消息。作为一个终点,这个新生产者将使用配置在messaging-config.xml文件中的my-jms-destination。下面这个例子是一个非常简单的带有消息生产者(producer)的Flex表单:

\
\\u0026lt;?xml version=\"1.0\" encoding=\"utf-8\"?\u0026gt; \\u0026lt;mx:Application xmlns:mx=\"http://www.adobe.com/2006/mxml\" layout=\"absolute\" initialize=\"consumer.subscribe()\"\u0026gt; \   \u0026lt;mx:Script\u0026gt; \      \u0026lt;![CDATA[ \         import mx.messaging.messages.IMessage; \         import mx.messaging.messages.AsyncMessage; \         import mx.messaging.events.MessageEvent; \         private function sendMessage(value : String) : void \         { \            var message : IMessage = new AsyncMessage(); \            message.body = value; \            producer.send(message); \          } \      ]]\u0026gt; \   \u0026lt;/mx:Script\u0026gt; \   \u0026lt;mx:Producer id=\"producer\" \      channelConnect=\"trace('producer connected')\" \        destination=\"my-jms-destination\" \        fault=\"trace(event.faultDetail)\" \   /\u0026gt; \   \u0026lt;mx:VBox\u0026gt; \      \u0026lt;mx:Form\u0026gt; \         \u0026lt;mx:FormItem label=\"Message:\"\u0026gt; \         \u0026lt;mx:TextInput id=\"textInput\" /\u0026gt; \      \u0026lt;/mx:FormItem\u0026gt; \      \u0026lt;/mx:Form\u0026gt; \      \u0026lt;mx:Button id=\"sendButton\" label=\"Send Message\" click=\"sendMessage(textInput.text)\" /\u0026gt; \   \u0026lt;/mx:VBox\u0026gt; \\u0026lt;/mx:Application\u0026gt; \
\

界面上有一个按钮sendButton,点击此按钮会调用sendMessage()函数。sendMessage()函数先创建一个AsyncMessage,其是IMessage接口的一个实现,然后使用producer的send()方法发送此消息。赋值给fault属性的trace()方法会把给定的信息打印到控制台,从而帮助您调试任何可能发生的问题。

\

验证消息:运行Flex应用程序;切换到命令行,运行ActiveMQ自带的消费者(consumer)实例,脚本如下:

\
\$ ant consumer -Dtopic=true -Dsubject=MyTopic -Dmax=2 
\

如果你输入框中输入消息,然后点击发送消息按钮,该消息会出现在消费者端的命令行上。在收到两条消息后,消费者端的命令行例子会自动退出。

\

接收消息(示例)

\

要接收JMS消息,您需要构建一个使用JMS终点的消费者(consumer)。在例子中为简单起见,消费者重用了生产者用来发送消息的终点。创建消费者的代码大致如下:

\
\\u0026lt;mx:Consumer id=\"consumer\" channelConnect=\"trace('consumer connected')\" \   channelFault=\"trace(event.faultDetail)\" \   fault=\"trace(event.faultDetail)\" \   destination=\"my-jms-destination\" \   message=\"messageHandler(event)\" /\u0026gt; \... \\u0026lt;mx:TextArea id=\"results\" width=\"100%\" /\u0026gt; \
\

在\u0026lt;mx:Script\u0026gt;代码块中处理消息的函数大致如下:

\
\private function messageHandler(event : MessageEvent) : void { \   results.text += event.message.body + \"\\"; \} \
\

添加了代码后,启动Flex应用程序。当Flex应用启动后,在命令行运行生产者的例子,脚本如下:

\
\ant producer -Dtopic=true -Dsubject=MyTopic -Dmax=5 
\

消息将出现在Flex的文本框中。

\

通过REST服务与PHP集成

\

Web服务是集成PHP Web应用和Java的方法之一。REST Web服务在Java中作为Servlets以及在PHP中作为脚本实现都比较容易。

\

REST概述

\

REST Web服务比SOAP Web服务更为简单,因为REST Web服务没有一个标准的消息格式(例如,单个的网页可以是一个REST Web服务)。URL地址就是服务端点(endpoint),而返回的Web页面则是该服务的响应。

\

编写REST Web服务的一个优点是可被多种类型的客户端使用。REST Web服务示例如下,它作为一个Java Servlet,能被其它语言如PHP调用,而无需修改服务。

\

Java Servlet示例

\

本例使用了一个简单的Java Servlet来作为REST Web服务。此Servlet处理PUT请求,从PUT请求的内容和各种参数中得到消息,例如:得到目标对象(JMS终点的名字),此消息是否是一个主题, JMS连接使用的URL等。

\
\package com.example.servlets;\\import java.io.BufferedReader;\import java.io.IOException;\import java.io.InputStreamReader;\import java.net.URLDecoder;\\import javax.jms.Connection;\import javax.jms.DeliveryMode;\import javax.jms.Destination;\import javax.jms.MessageProducer;\import javax.jms.Session;\import javax.jms.TextMessage;\import javax.servlet.ServletException;\import javax.servlet.http.HttpServlet;\import javax.servlet.http.HttpServletRequest;\import javax.servlet.http.HttpServletResponse;\\import org.apache.activemq.ActiveMQConnection;\import org.apache.activemq.ActiveMQConnectionFactory;\import org.apache.activemq.util.IndentPrinter;\\public class MessageService extends HttpServlet {\    private static final long serialVersionUID = -8129137144911681674L;\\    private Destination destination;\    private String user = ActiveMQConnection.DEFAULT_USER;\    private String password = ActiveMQConnection.DEFAULT_PASSWORD;\\    @Override\    protected void doPut(HttpServletRequest request, HttpServletResponse response)\            throws ServletException, IOException {\\        Connection connection = null;            \\        try {\            boolean isTopic = \"true\".equals(request.getParameter(\"topic\"));\            boolean isPersistent = \"true\".equals(request.getParameter(\"persistent\"));\            String subject = request.getParameter(\"subject\");\            String url = URLDecoder.decode(request.getParameter(\"url\"), \"UTF-8\");\            String messageText = \"\";\\            BufferedReader buffer = new BufferedReader(new InputStreamReader(request.getInputStream()));\\            messageText = buffer.readLine();\\            System.out.println(\"Using URL:  \u0026lt;\" + url + \"\u0026gt;\");\            System.out.println(\"Using Subject:  \u0026lt;\" + subject + \"\u0026gt;\");\            System.out.println(\"Sending Message Text:  \u0026lt;\" + messageText + \"\u0026gt;\");\\            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);\            connection = connectionFactory.createConnection();\            connection.start();\\            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);\\            if (isTopic) {\                destination = session.createTopic(subject);\            } else {\                destination = session.createQueue(subject);\            }\\            // Create the producer.\            MessageProducer producer = session.createProducer(destination);\            if (isPersistent) {\                producer.setDeliveryMode(DeliveryMode.PERSISTENT);\            } else {\                producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);\            }\\            TextMessage message = session.createTextMessage(messageText);\\            producer.send(message);\\            System.out.println(\"Done.\");\\            // Use the ActiveMQConnection interface to dump the connection\            // stats.\            ActiveMQConnection c = (ActiveMQConnection)connection;\            c.getConnectionStats().dump(new IndentPrinter());\\        } catch (Exception e) {\            System.out.println(\"Caught: \" + e);\            e.printStackTrace();\        } finally {\            try {\                connection.close();\            } catch (Throwable ignore) {\            }\        }\\    }\\}\
\

除了用ActiveMQ特定类,你还可以使用JNDI来写代码。对于想了解使用JNDI来获得连接的更多信息,请参阅ActiveMQ文档

\

编译后的Servlet类文件(比如前面的MessageService类)必须放在ActiveMQ的webapps目录下。要添加新目录,则需要在conf目录的jetty.xml配置文件中添加入口(与前面的步骤4相似)。

\

PHP示例

\

PHP脚本使用CURL库(译者注:原文的CURL库的链接有错)调用REST服务,将数据发送到REST服务的URL(由Java Servlet处理)。如果您的PHP版本没有包含CURL库,您可以使用Ajax来调用REST Web服务。在Web应用的实际产品中,Ajax可能会提供更好的用户体验。  

\

PHP脚本包含一个表单来提交自身。当提交表单时,该脚本从文本框取得消息,并把消息作为数据发送到REST服务的URL。请看下面的例子:

\
\\u0026lt;?php \   if (isset($_POST['submit'])) { \      $url = 'http://localhost:8161/messageservice/?url=tcp://localhost:61616\u0026amp;subject=MyTopic\u0026amp;topic=true'; \      function doPut($ch, $a_data) { \         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); \         curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Length: '.strlen($a_data))); \         curl_setopt($ch, CURLOPT_POSTFIELDS, $a_data); \         return curl_exec($ch); \      } \      $data = $_POST['message']; \      $ch = curl_init(); \      curl_setopt($ch, CURLOPT_URL, $url); \      $response = doPut($ch, $data); \      echo 'Message sent'; \   } else { \      ?\u0026gt; \         \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Post a message\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \         \u0026lt;body\u0026gt; \         \u0026lt;form action=\"\u0026lt;?php echo $_SERVER['PHP_SELF'];?\u0026gt;\" method=\"post\"\u0026gt; \         \u0026lt;p\u0026gt;\            \u0026lt;label for=\"message\"\u0026gt;Message: \               \u0026lt;input type=\"text\" id=\"message\" name=\"message\" /\u0026gt; \            \u0026lt;/label\u0026gt;\         \u0026lt;/p\u0026gt; \         \u0026lt;input type=\"submit\" value=\"submit\" name=\"submit\" id=\"submit\" /\u0026gt; \         \u0026lt;/form\u0026gt; \         \u0026lt;/body\u0026gt; \      \u0026lt;/html\u0026gt; \      \u0026lt;?php \   } \?\u0026gt; \
\

在URL上传递的JMS服务器的topic、subject和URL信息都赋值给了$url。

\

用BlazeDS发送/接收的示例

\

要想看BlazeDS发送/接收的实例,启动Flex应用程序。当应用程序打开,在不同的浏览器上打开PHP脚本,给消息框添加一个值,然后单击提交。消息框的值将会出现在Flex应用的文本框中。

\

小结

\

JMS是一个消息传递服务,支持主题和队列两种模式以及其它的很多特征,是可靠消息传递的不错选择。 BlazeDS可以让您只需做少量工作即可完成从Flex客户端向JMS发送消息。

\

PHP应用程序可以以多种方式连接到JMS。使用REST Web服务是把PHP和Java应用程序整合在一起的一种方式——REST Web服务提供了一种相对简单的接口供客户端使用。使用Java实现REST服务,您可以利用现有的Java代码来发送JMS消息。

\

本系列的第2部分主要讲通过JMS整合PHP和Flex应用的其它方法:PHP / Java桥和STOMP。

\

译者注:STOMP,Streaming Text Orientated Message Protocol,是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。

\

查看英文原文:BlazeDS and JMS for PHP Developers, Part 1

\

译者简介:李强,计算机硕士,毕业于电子科技大学,目前在四川长虹电器股份有限公司技术中心从事研发工作,主要研究领域是SOA、ESB、Web服务以及分布式应用等。

\

感谢宋玮对本文的审校。

\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值