flex4 + spring + blazeds , 使用anonation(注解)机制,利用push技术的实现例子和过程。
实现目标:java做后台service,每隔300毫秒,生成一个uuid,以 testJob做为订阅关键词,发布给所有订阅此关键词的flex客户端。
配置过程和源码:
1.修改blazeds自动生成的WEB-INF/flex/services-config.xml文件。由于原来没有polling-amf的定义,所以需要加入。代码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <services-config>
- <services>
- <service-include file-path="remoting-config.xml" />
- <service-include file-path="proxy-config.xml" />
- <service-include file-path="messaging-config.xml" />
- </services>
- <security>
- <login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat"/>
- <!-- Uncomment the correct app server
- <login-command class="flex.messaging.security.TomcatLoginCommand" server="JBoss">
- <login-command class="flex.messaging.security.JRunLoginCommand" server="JRun"/>
- <login-command class="flex.messaging.security.WeblogicLoginCommand" server="Weblogic"/>
- <login-command class="flex.messaging.security.WebSphereLoginCommand" server="WebSphere"/>
- -->
- <!--
- <security-constraint id="basic-read-access">
- <auth-method>Basic</auth-method>
- <roles>
- <role>guests</role>
- <role>accountants</role>
- <role>employees</role>
- <role>managers</role>
- </roles>
- </security-constraint>
- -->
- </security>
- <channels>
- <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
- <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
- </channel-definition>
- <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
- <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
- <properties>
- <add-no-cache-headers>false</add-no-cache-headers>
- </properties>
- </channel-definition>
- <channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
- <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
- <properties>
- <polling-enabled>true</polling-enabled>
- <polling-interval-seconds>4</polling-interval-seconds>
- </properties>
- </channel-definition>
- <channel-definition id="my-longpolling-amf" class="mx.messaging.channels.AMFChannel">
- <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amflongpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
- <properties>
- <polling-enabled>true</polling-enabled>
- <polling-interval-seconds>5</polling-interval-seconds>
- <wait-interval-millis>60000</wait-interval-millis>
- <client-wait-interval-millis>1</client-wait-interval-millis>
- <max-waiting-poll-requests>200</max-waiting-poll-requests>
- <user-agent-settings>
- <!-- MSIE 5, 6, 7 default max number of permanent HTTP connections is 2. -->
- <user-agent match-on="MSIE" max-streaming-connections-per-session="1"/>
- <!-- MSIE 8 max number is 6. -->
- <user-agent match-on="MSIE 8" max-streaming-connections-per-session="5"/>
- <!-- Firefox 1, 2 max number is 2. -->
- <user-agent match-on="Firefox" max-streaming-connections-per-session="1"/>
- <!-- Firefox 3 max number is 6. -->
- <user-agent match-on="Firefox/3" max-streaming-connections-per-session="5"/>
- <!-- Safari 3, 4 max number is 4. -->
- <user-agent match-on="Safari" max-streaming-connections-per-session="3"/>
- <!-- Chrome 0, 1, 2 max number is 6. -->
- <user-agent match-on="Chrome" max-streaming-connections-per-session="5"/>
- <!-- Opera 7, 9 max number is 4.-->
- <user-agent match-on="Opera" max-streaming-connections-per-session="3"/>
- <!-- Opera 8 max number is 8. -->
- <user-agent match-on="Opera 8" max-streaming-connections-per-session="7"/>
- <!-- Opera 10 max number is 8. -->
- <user-agent match-on="Opera 10" max-streaming-connections-per-session="7"/>
- </user-agent-settings>
- </properties>
- </channel-definition>
- <channel-definition id="my-streaming-amf" class="mx.messaging.channels.StreamingAMFChannel">
- <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/streamingamf" class="flex.messaging.endpoints.StreamingAMFEndpoint"/>
- </channel-definition>
- <!--
- <channel-definition id="my-http" class="mx.messaging.channels.HTTPChannel">
- <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/http" class="flex.messaging.endpoints.HTTPEndpoint"/>
- </channel-definition>
- <channel-definition id="my-secure-http" class="mx.messaging.channels.SecureHTTPChannel">
- <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/httpsecure" class="flex.messaging.endpoints.SecureHTTPEndpoint"/>
- <properties>
- <add-no-cache-headers>false</add-no-cache-headers>
- </properties>
- </channel-definition>
- -->
- </channels>
- <logging>
- <target class="flex.messaging.log.ConsoleTarget" level="Error">
- <properties>
- <prefix>[BlazeDS] </prefix>
- <includeDate>false</includeDate>
- <includeTime>false</includeTime>
- <includeLevel>false</includeLevel>
- <includeCategory>false</includeCategory>
- </properties>
- <filters>
- <pattern>Endpoint.*</pattern>
- <pattern>Service.*</pattern>
- <pattern>Configuration</pattern>
- </filters>
- </target>
- </logging>
- <system>
- <redeploy>
- <enabled>false</enabled>
- <!--
- <watch-interval>20</watch-interval>
- <watch-file>{context.root}/WEB-INF/flex/services-config.xml</watch-file>
- <watch-file>{context.root}/WEB-INF/flex/proxy-config.xml</watch-file>
- <watch-file>{context.root}/WEB-INF/flex/remoting-config.xml</watch-file>
- <watch-file>{context.root}/WEB-INF/flex/messaging-config.xml</watch-file>
- <watch-file>{context.root}/WEB-INF/flex/data-management-config.xml</watch-file>
- <touch-file>{context.root}/WEB-INF/web.xml</touch-file>
- -->
- </redeploy>
- </system>
- </services-config>
2.在spring的配置文件中定义 defaultMessageTemplate class="org.springframework.flex.messaging.MessageTemplate"
- <bean id="defaultMessageTemplate" class="org.springframework.flex.messaging.MessageTemplate" />
之所以要定义这个bean,是为了在做service的时候。可以用注解@Autowired来引入这个MessageTemplate的实例。
3.在web.xml中定义spring和flex的集成。这个配置是使用spring和flex集成时候的配置,和push技术无关。
- <!-- 定义 spring flex 集成的配置,使用单独的配置文件。
- 不使用在一个applicatonContext.xml中import springFlex.xml的方法
- spring flex 一定要用DispatcherServlet 方法。不能用ContextLoaderListener -->
- <servlet>
- <servlet-name>MessagebrokerServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/META-INF/springFlex.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <!-- Map /spring/* requests to the DispatcherServlet -->
- <servlet-mapping>
- <servlet-name>MessagebrokerServlet</servlet-name>
- <url-pattern>/messagebroker/*</url-pattern>
- </servlet-mapping>
- <!-- 定义 spring flex 集成的配置 结束-->
- <!-- 访问RDS 定义开始-->
- <servlet>
- <servlet-name>RDSDispatchServlet</servlet-name>
- <servlet-class>flex.rds.server.servlet.FrontEndServlet</servlet-class>
- <init-param>
- <param-name>useAppserverSecurity</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>messageBrokerId</param-name>
- <param-value>_messageBroker</param-value>
- </init-param>
- <load-on-startup>10</load-on-startup>
- </servlet>
- <servlet-mapping id="RDS_DISPATCH_MAPPING">
- <servlet-name>RDSDispatchServlet</servlet-name>
- <url-pattern>/CFIDE/main/ide.cfm</url-pattern>
- </servlet-mapping>
- <!-- 访问RDS 定义结束 -->
4.由于web.xml配置中引入了WEB-INF/classes/META-INF/springFlex.xml这个文件,所以在springFlex.xml文件中进行详细配置
5.springFlex.xml文件如下,主要是定义flex:message-destination ,既是监听的数据的关键词
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:flex="http://www.springframework.org/schema/flex"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/flex
- http://www.springframework.org/schema/flex/spring-flex-1.0.xsd">
- <flex:message-broker>
- <flex:message-service
- default-channels="my-polling-amf" />
- </flex:message-broker>
- <flex:message-destination id="simple-feed" />
- <flex:message-destination id="testJob-feed"/>
- </beans>
6.编写后台推送的java service bean:
- /***********************************************************************
- *
- * TestJob.java
- *
- * ****所有,
- * 受到法律的保护,任何公司或个人,未经授权不得擅自拷贝。
- * @copyright Copyright: 2000-2011
- * @creator 徐泽宇 <br/>
- * @create-time 2011-6-27 下午11:51:16
- * @revision $Id: *
- ***********************************************************************/
- package com.alcor.test.service.quartz;
- import java.util.Date;
- import java.util.UUID;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.flex.messaging.MessageTemplate;
- import org.springframework.flex.remoting.RemotingDestination;
- import org.springframework.flex.remoting.RemotingInclude;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Service;
- import flex.messaging.messages.AsyncMessage;
- @Service
- @RemotingDestination
- public class TestJob {
- /**
- * Logger for this class
- */
- private static final Logger logger = LoggerFactory.getLogger(TestJob.class);
- @Autowired
- MessageTemplate template;
- private static FeedThread thread;
- @RemotingInclude
- public void work3 (){
- if (thread == null) {
- thread = new FeedThread(template);
- thread.start();
- }
- }
- public static class FeedThread extends Thread {
- public boolean running = false;
- private final MessageTemplate template;
- public FeedThread(MessageTemplate template) {
- this.template = template;
- run();
- }
- @Override
- public void run() {
- this.running = true;
- while (this.running) {
- logger.debug("testJob-feed send");
- this.template.send("testJob-feed", UUID.randomUUID().toString());
- // this.template.send("testJob-feed", UUID.randomUUID().toString());
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- e.printStackTrace(System.out);
- }
- }
- }
- }
- }
注意:这里使用了@Service,@@RemotingDestination 注解和 @Autowired MessageTemplate template; 这样就避免了在spring中利用配置文件进行定义bean。
如果要使用spring 配置文件来定义。可以参考下面代码(来自blazeds的官方例子中的spring配置文件)
- <!-- MessageTemplate makes it easy to publish messages -->
- <bean id="defaultMessageTemplate" class="org.springframework.flex.messaging.MessageTemplate" />
- <!-- Pojo used to start and stop the data feed that pushes data in the 'simple-feed' destination -->
- <bean id="simpleFeedStarter" class="org.springframework.flex.samples.simplefeed.SimpleFeed">
- <constructor-arg ref="defaultMessageTemplate" />
- <flex:remoting-destination />
- </bean>
具体的业务实现是在 内联线程类FeedThread 里面的run()中实现。
这要做的目标是为了让这个线程启动,是一定要通过flex的一个客户端连上来,才能启动。而不能随着spring context启动而启动。否则会报告messageBroke没有设置在flexContext之类的错误。
那么:如果我们想后台在没有任何一个客户端连上来的情况下,也要启动这个发送服务应该如何做?
典型场景: 后台web应用启动,并且定义了一个定时器。每隔 5s 进行一个逻辑处理,然后把处理结果发送给前台 flex(无论是否有felx客户端连上)
这就需要用下面代码:
- public String work2(){
- if (logger.isDebugEnabled()) {
- logger.debug("work2() - start"); //$NON-NLS-1$
- }
- String m_rtn ="Spring 的TestJob任务work2被调用!" ;
- String returnString =new Date().toLocaleString()+ m_rtn;
- //------------------------------------------
- MessageBroker msgBroker = MessageBroker.getMessageBroker("_messageBroker");
- AsyncMessage am = new AsyncMessage();
- am.setDestination("testJob-feed");
- //am.setHeader("DSSubtopic", "tick");
- am.setClientId(UUID.randomUUID().toString());
- String msgID =UUID.randomUUID().toString();
- //System.out.println("MESSAGE ID:" + msgID);
- am.setMessageId(msgID);
- am.setTimestamp(System.currentTimeMillis());
- am.setBody(returnString);
- if (msgBroker != null)
- {
- msgBroker.routeMessageToService(am, null);
- }
- //------------------------------------------
- if (logger.isDebugEnabled()) {
- logger.debug("work2() - end"); //$NON-NLS-1$
- }
- return returnString;
- }
这个work2 方法可以通过 quartz或者spring 的@Scheduled注解来进行定时器调用。
主要的关键代码是
MessageBroker msgBroker = MessageBroker.getMessageBroker("_messageBroker");
这个就是获得一个 _messageBroker的实例。"_messageBroker"字符串是由 <flex:message-broker > 标签中的id来指定。
如果没有id 指定,则缺省使用 “"_messageBroker"”这个字符串
7.前台的订阅flex程序:
- <?xml version="1.0" encoding="utf-8"?>
- <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
- xmlns:s="library://ns.adobe.com/flex/spark"
- xmlns:mx="library://ns.adobe.com/flex/mx">
- <fx:Script>
- <!--[CDATA[
- import mx.controls.Alert;
- import mx.messaging.messages.IMessage;
- private function messageHandler(message:IMessage):void
- {
- pushedValue.text = ""+ message.body;
- }
- private function messageHandler1(message:IMessage):void
- {
- pushedValue1.text = ""+ message.body;
- }
- ]]-->
- </fx:Script>
- <fx:Declarations>
- <s:ChannelSet id="cs">
- <s:StreamingAMFChannel uri="/Aerie/messagebroker/streamingamf"/>
- <s:AMFChannel uri="/Aerie/messagebroker/amflongpolling"/>
- <s:AMFChannel uri="/Aerie/messagebroker/amfpolling"/>
- </s:ChannelSet>
- <mx:Consumer id="consumer" destination="simple-feed" channelSet="{cs}"
- message="messageHandler(event.message)"/>
- <mx:Consumer id="consumer1" destination="testJob-feed" channelSet="{cs}"
- message="messageHandler1(event.message)"/>
- </fx:Declarations>
- <s:HGroup>
- <s:Group>
- <s:TextInput id="pushedValue" width="250" verticalCenter="0" horizontalCenter="0"/>
- <s:Button label="spimle-feed 订阅" click="consumer.subscribe()" enabled="{!consumer.subscribed}" verticalCenter="30" horizontalCenter="-50"/>
- <s:Button label="Unsubscribe" click="consumer.unsubscribe()" enabled="{consumer.subscribed}" verticalCenter="30" horizontalCenter="50"/>
- </s:Group>
- <s:Group>
- <s:TextInput id="pushedValue1" width="250" verticalCenter="0" horizontalCenter="0"/>
- <s:Button label="testJob 订阅" click="consumer1.subscribe()" enabled="{!consumer1.subscribed}" verticalCenter="30" horizontalCenter="-50"/>
- <s:Button label="Unsubscribe" click="consumer1.unsubscribe()" enabled="{consumer1.subscribed}" verticalCenter="30" horizontalCenter="50"/>
- </s:Group>
- </s:HGroup>
- </s:Application>
其中关键代码是:
- <fx:Declarations>
- <s:ChannelSet id="cs">
- <s:StreamingAMFChannel uri="/Aerie/messagebroker/streamingamf"/>
- <s:AMFChannel uri="/Aerie/messagebroker/amflongpolling"/>
- <s:AMFChannel uri="/Aerie/messagebroker/amfpolling"/>
- </s:ChannelSet>
- <mx:Consumer id="consumer" destination="simple-feed" channelSet="{cs}"
- message="messageHandler(event.message)"/>
- <mx:Consumer id="consumer1" destination="testJob-feed" channelSet="{cs}"
- message="messageHandler1(event.message)"/>
- </fx:Declarations>
注意:
<s:StreamingAMFChannel uri="/Aerie/messagebroker/streamingamf"/>
<s:AMFChannel uri="/Aerie/messagebroker/amflongpolling"/>
<s:AMFChannel uri="/Aerie/messagebroker/amfpolling"/>
这三个通道的设置中用到的
Aerie是代表context名字。
messagebroker 是代码 web.xml中的配置的MessagebrokerServlet中的url-pattern
amfpolling 等是指 在services-config.xml文件中设置的 channel-definition 下的endpoint 名字