[Flex]FMS3系列(四):在线视频录制、视频回放

mikel阅读(698)

      使用Flash/Flex+FMS实现在线视频录制、视频回放的很简单的。通过阅读API文档后基本都可以实现这个功能,本文也意在抛砖引玉,希望对刚入手这块的朋友有所帮助。

      首先建立好Flash(ActionScript 3.0)文件,从组件(可使用Ctrl+F7打开)库中拖拽相应的组件到Flash舞台上,如下图:

            

      界面布局好后我们通过程序设置组见的显示文本以及为按扭添加事件监听,新建一个ActionScript类文件,编写代码如下:

 1         public function PublishPlay():void
 2         {
 3             lbName.text="请输入视频文件名:";
 4             btnPublish.label="开始录制";
 5             btnPublish.addEventListener(MouseEvent.CLICK,onPublishClick);
 6             btnStop.label="停止录制";
 7             btnStop.addEventListener(MouseEvent.CLICK,onStopHandler);
 8             btnPlay.label="视频回放";
 9             btnPlay.addEventListener(MouseEvent.CLICK,onPlayHandler);
10             
11             video=new Video();
12             cam = Camera.getCamera();
13             mic = Microphone.getMicrophone();
14             if(cam==null)
15             {
16                 trace("没检测到视频摄像头");
17             }
18             else
19             {
20                 video.attachCamera(cam);
21             }
22             addChild(video);
23         }

 

      以上代码同时实现了将视频显示到flash界面上,通过Camera的静态方法getCamrea()方法可以直接获取到视频摄像头的数据。其中用到的video,cam和mic变量为预先定义好的,如下:

1     private var nc:NetConnection;
2     private var ns:NetStream;
3     private var video:Video;
4     private var cam:Camera;
5     private var mic:Microphone;

 

      接下来就需要连接到FMS服务器实现视频录制功能了,通过NetConnection类实现与FMS服务器的连接,并通过流将视频数据发布到FMS服务器。

 1         private function onPublishClick(evt:MouseEvent):void
 2         {
 3             nc=new NetConnection();
 4             nc.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
 5             nc.connect("rtmp://localhost/PulishedStreams");
 6         }
 7         
 8         private function onPublishStatusHandler(evt:NetStatusEvent):void
 9         {
10             if(evt.info.code=="NetConnection.Connect.Success")
11             {
12                 ns=new NetStream(nc);
13                 ns.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
14                 ns.client=new CustomClient();
15                 ns.attachCamera(cam);
16                 ns.attachAudio(mic);
17                 ns.publish(tbName.text,"record");
18             }
19         }

 

      在录制视频的时候视频命名是取的文本输入框的值作为视频名,OK,现在测试Flash(Ctrl+Enter),通过点击 按扭开始录制视频。通过查看FMS服务器的文件目录可以看到,刚刚测试录制的视频存放于FMS服务器应用下的streams/_definst_目录下。 详见下图所示:

            

      录制功能完成了,通过测试也可以成功的录制视频。最后我们通过程序来播放刚刚录制是视频。关于播放视频在上一篇文章《FMS3系列(三):创建基于FMS的流媒体播放程序,看山寨帮的山寨传奇》中已介绍怎么实现,这里就直接帖出代码不做解释。

 1 private function onPlayHandler(evt:MouseEvent):void
 2         {
 3             nc=new NetConnection();
 4             nc.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
 5             nc.connect("rtmp://localhost/PulishedStreams");
 6         }
 7         
 8         private function onPlayStatusHandler(evt:NetStatusEvent):void
 9         {
10             if(evt.info.code=="NetConnection.Connect.Success")
11             {
12                 ns=new NetStream(nc);
13                 ns.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
14                 ns.client=new CustomClient();
15                 
16                 video = new Video();
17                 video.attachNetStream(ns);
18                 ns.play(tbName.text,0);
19                 addChild(video);
20             }
21         }

 

      通过本文的基础上可以很方便的扩展出在线拍照等多种应用,有兴趣的朋友可以去试验下。下面是本文完整的示例代码。


 1package
 2{
 3    import flash.net.*;
 4    import flash.events.*;
 5    import flash.media.*;
 6    import flash.display.*;
 7    import fl.controls.*;
 8    
 9    public class PublishPlay extends Sprite
10    {
11        private var nc:NetConnection;
12        private var ns:NetStream;
13        private var video:Video;
14        private var cam:Camera;
15        private var mic:Microphone;
16        
17        public function PublishPlay():void
18        {
19            lbName.text="请输入视频文件名:";
20            btnPublish.label="开始录制";
21            btnPublish.addEventListener(MouseEvent.CLICK,onPublishClick);
22            btnStop.label="停止录制";
23            btnStop.addEventListener(MouseEvent.CLICK,onStopHandler);
24            btnPlay.label="视频回放";
25            btnPlay.addEventListener(MouseEvent.CLICK,onPlayHandler);
26            
27            video=new Video();
28            cam = Camera.getCamera();
29            mic = Microphone.getMicrophone();
30            if(cam==null)
31            {
32                trace("没检测到视频摄像头");
33            }

34            else
35            {
36                video.attachCamera(cam);
37            }

38            addChild(video);
39        }

40        
41        private function onStatusHandler(evt:NetStatusEvent):void
42        {
43            trace(evt.info.code);
44            if(evt.info.code=="NetConnection.Connect.Success")
45            {
46                ns=new NetStream(nc);
47                ns.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
48                ns.client=new CustomClient();
49            }

50        }

51        
52        private function onPublishClick(evt:MouseEvent):void
53        {
54            nc=new NetConnection();
55            nc.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
56            nc.connect("rtmp://localhost/PulishedStreams");
57        }

58        
59        private function onPublishStatusHandler(evt:NetStatusEvent):void
60        {
61            if(evt.info.code=="NetConnection.Connect.Success")
62            {
63                ns=new NetStream(nc);
64                ns.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
65                ns.client=new CustomClient();
66                ns.attachCamera(cam);
67                ns.attachAudio(mic);
68                ns.publish(tbName.text,"record");
69            }

70        }

71        
72        private function onStopHandler(evt:MouseEvent):void
73        {
74            nc.close();
75        }

76        
77        private function onPlayHandler(evt:MouseEvent):void
78        {
79            nc=new NetConnection();
80            nc.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
81            nc.connect("rtmp://localhost/PulishedStreams");
82        }

83        
84        private function onPlayStatusHandler(evt:NetStatusEvent):void
85        {
86            if(evt.info.code=="NetConnection.Connect.Success")
87            {
88                ns=new NetStream(nc);
89                ns.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
90                ns.client=new CustomClient();
91                
92                video = new Video();
93                video.attachNetStream(ns);
94                ns.play(tbName.text,0);
95                addChild(video);
96            }

97        }

98    }

99}

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

[Flex]基于flex4技术从零开发flex博客系统: 4 数据存储

mikel阅读(778)

第四课:存储数据

关于”基于flex4技术从零开发flex博客系统”这个系列,我已经写过三课。今天在Google中搜索了一下标题, 发现有许多网站已经偷偷转载了。转载没有问题,但是拜托,请附上原文链接好不好,这不仅是对作者的尊重,也是对读者的负责。昨天我对前三课进行了一些相当 程度的更新,由于这些转载者的小气,他们的读者是不可能获取更新了。不过没有关系,有意继续关注的朋友,只要在Google中搜索”基于flex4技术从零开发flex博客系统“就可以找到这个系列的最新版本,相信我的博客凭借原创以及等于5的Google PR值,会一直稳居搜索排行之首。

通过前三课我艰苦卓绝的努力,客户端与服务端通讯已经没有问题了。这对于一个没有学过flex4,没有用过java的初学者,已经相当不容易了。到目前为止,开发博客系统的准备工作,已经仅剩最后一项了:数据存储。

Google App Engine没有数据库的概念,不过app engine提供了JDO存储接口,google充许开发者直接定义、存储、查询、修改实体(entity)。

一,数据定义

我在sban.flexblog package下添加一个名为Greeting的实体类,这个一个POJO(Plain Old Java Object),意即简单朴素的java对象。Greeting.java类的代码如下:

package sban.flexblog;

import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

/**
 * @author sban.li
 *
 */

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Greeting {

        @PrimaryKey
        @Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
        private Long id;
       
        @Persistent
        private String user;
       
        @Persistent
        private String greetingContent;
       
        @Persistent
        private Date date;
       
        public Greeting(String user, String content, Date date)
        {
                this.user = user;
                this.greetingContent=content;
                this.date = date;
        }
       
        public String getUser()
        {
                return this.user;
        }
       
        public Long getId()
        {
                return this.id;
        }
       
        public String getGreetingContent()
        {
                return this.greetingContent;
        }
       
        public Date getDate()
        {
                return this.date;
        }
}

关于Pojo实体定义的简要说明:
1,@PrimaryKey用于定义实体的主键
2,@Persistent用于标识该变量名称将被存储
3,valueStrategy=IdGeneratorStrategy.IDENTITY设置id为自增模式,这意味着实例化该实体类时不用给id赋值
4,identityType=IdentityType.APPLICATION是干什么用的,不清楚,暂时不用管它

二,数据存储

实体定义好了,如何存储呢?这要用到javax.jdo.PersistenceManager,在sban.flexblog.managers package下新建一个类,名为PMFactory.java,代码如下:

package sban.flexblog.managers;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

/**
 * @author sban.li
 *
 */

public class PMFactory {

        private static final PersistenceManagerFactory pmfInstance = JDOHelper
                        .getPersistenceManagerFactory("transactions-optional");

        private PMFactory() {
        }

        public static PersistenceManagerFactory getInstance() {
                return pmfInstance;
        }

}

该工厂用于返回一个PersistenceManagerFactory的单例,其中transactions-optional参数是在src/META-INF/jdoconfig.xml中定义的:

<persistence-manager-factory name="transactions-optional">

   </persistence-manager-factory>

但是PersistenceManagerFactory并不是数据的管理者,PersistenceManager才是,现在我们可以用 PMFactory.getInstance().getPersistenceManager()方法获取PersistenceManager了。

我们需要提供一个新接口,这个接口充许用户提交用户名与greeting内容参数,并存储到google app engine。修改HelloWorld.java,添加如下代码:

private PersistenceManager pm = PMFactory.getInstance()
                        .getPersistenceManager();

        public Boolean greet2(String user, String content) {
                Boolean result = true;
                Greeting greeting = new Greeting(user, content, new Date());

                try {
                        pm.makePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }

pm.makePersistent用于存储Greeting实例对象。使用之后记得要记得调用pm.close。该接口需要两个参数,一为用户名称,一为greeting内容。如果存储成功,将返回true。

三,客户端代码调用

好了,server端已经准备就绪,现在客户端可以调用了。修改Index.mxml,修改greetViaRemoting方法,代码如下:

<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="configRemoting()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                       
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                       
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                       
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                op.addEventListener(ResultEvent.RESULT,
                function(event : ResultEvent) : void
                                        {
                                                Alert.show( event.result.toString() );
                                        };
             );
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                ]]>

        </Script>
       
        <layout>
                <BasicLayout />
        </layout>
       
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
               
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet" click="greetViaRemoting()" />
                </HGroup>
               
        </VGroup>
       
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
       
</FxApplication>

其中configRemoting将在Application initialize时调用,用于初始化RemoteObject对象。BasicLayout用于声明当前Application使用absolate布局,详见new language tag Private and layout in flex4

编译,运行,点击greeting,弹出如下提示框:
alert-ok.png

四,有问题,需要调试了

貌似没有问题,貌似数据已经存进Google了。但是第二次单击提交,就有问题了,Alert了两次。为什么点击一次button,弹出了两个提示框?难道接口被调用了二次?

为了找到原因所在,我必须要采用Debug模式了。在本地采用Debug模式之前,需修改Flex Project的属性里的output folder url为http://localhost:8080/,并把RemoteObject的endpoint设为http: //localhost:8080/weborb.wo。但是在正式布署到Google app engine时,需记得移除这些设置。

通过Debug,我发现greet2接口确实仅调用了一次,但是Alert.show却执行了两次。这说明AbstractOperation是一 个引用对象,第一次调用_remotingObj.getOperation(”greet2″)返回的对象与第二次调用返回的对象是同一个对象,我们在 同一个对象上添加了两次ResultEvent事件监听。handler做为内嵌函数变量,虽然前后名称相同,但因是局部函数变量,所以内部ID并不相 同,是两个完全不同的对象。结果便是,每单击一次,Alert便会多一次。

为了验证我的想法,我查看了RemoteObject的sdk源码,在其及其父类AbstractService中,有如下相关代码:

override public function getOperation(name:String):AbstractOperation
    {
        var op:AbstractOperation = super.getOperation(name);
        if (op == null)
        {
            op = new Operation(this, name);
            _operations[name] = op;
            op.asyncRequest = asyncRequest;
        }
        return op;
    }

    public function getOperation(name:String):AbstractOperation
    {
        var o:Object = _operations[name];
        var op:AbstractOperation = (o is AbstractOperation) ? AbstractOperation(o) : null;
        return op;
    }

由上面源码可以看出,在第一次调用时,flex会实例化一个Operation,并缓存在_operations之中。第二次,便是直接取出了。
思考1:读者可以思考一下,为什么flex要在父类中取出AbstractOperation引用,而在子类中实例化Operation并存储。

签于以上原因,我修改了我的greetViaRemoting的代码,如下:

private function greetViaRemoting() : void
{
        var op : AbstractOperation = _remotingObj.getOperation("greet2");
        var handler : Function = function(event : ResultEvent) : void
                {
                        op.removeEventListener(ResultEvent.RESULT, handler);
                        Alert.show( event.result.toString() );
                };
       
        op.addEventListener(ResultEvent.RESULT, handler);
        op.send(vNameTxt.text,vContentTxt.text);
}

修改之后,便没有重复弹出的问题了。

sban:在flex开发中,使用之后的事件监听一定要记得移除,特别对于通过内嵌函数添加的事件监听。反复的移除与添加事件监听,并不会 影响程序性能,相反如果只添加而不移除,才会让程序运行愈加沉重。这就好比做人,财富、名誉、资历、爱情等等这些身外之物,多了并不是负担,但要是每一样 都放在心里,就累了。

有些学者(学习者)可能说了,貌似数据存储成功了,但是我并不知道到底有没有真正存进Google云啊,怎么查看我存储的数据呢?这些存储如何更 新,如何删除?可惜Google并没有直接给我们开放直接访问App Engine数据库的权限。在我想继续描述app engine是如何让开发者查询、修改、删除数据时,我发现这一课已经写的相当长了。我的肩膀有点酸了,我想我应该去健身了。读者们也需要体息了。

本课最终源码:见下一课源码
sban 2009/4/26 于北京
本文为sban原创,作者保留所有权利,如需转载请保留作者原文链接
flex选修课系列:基于flex4技术从零开发flex博客系统

[Flex]基于flex4技术从零开发flex博客系统 : 5 数据存储之管理Greeting

mikel阅读(644)

第五课:存储数据之管理Greeting

Google App Engine for java自今年4月7日开放申请,至今不足二十日,但关于GAE for java的博文已经出现了不少。继数据存储之后,这一课要研究如何通过客户端管理Greeting,即CURD的实现。

一,列表显示
在sban.flexblog.HelloWorld.java中添加一个获取所有Greeting的接口:

public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                } finally {
                        pm.close();
                }

                return result;
        }

在上例中,pm.newQuery(Greeting.class)负责查询所有已存的Greeting信息。List数据在Flex客户端将被映射成ArrayList。

修改Index.mxml客户端代码,修改之后最终代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="onInit()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                        import mx.events.IndexChangedEvent
                        
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        
                        [Bindable]
                        private var _greeting : Object;
                        
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                        
                        private function onInit() : void
                        {
                                configRemoting();
                                getAllGreetings();
                        }
                        
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                        
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                Alert.show( event.result.toString() );
                                        };
                                
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                        
                        private function getAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("getAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                        
                                                _greetingData = ArrayCollection(event.result);
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                ]]>

        </Script>
        
        <layout>
                <BasicLayout />
        </layout>
        
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
                
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet"click="greetViaRemoting()" />
                </HGroup>
                
                <FxList id="vList1" dataProvider="{_greetingData}" width="60%"itemRenderer="GreetingItemRenderer">
                        <layout>
                                <VerticalLayout />
                        </layout>
                </FxList>
                
        </VGroup>
        
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
        
</FxApplication>

1,用一个Bindable的、ArrayList类型的_greetingData,用于存储Greeting数据列表。
2,getAllGreetings方法用于从远程接口sban.flexblog.HelloWorld.getAllGreetings中读取数据,其在OnInit中被调用。
3,FxList用于显示列表数据,使用了VerticalLayout布局,数据纵向依次排列。如果是HorizontalLayout布局,但是横向排列。关于布局,详见new language tag Private and layout in flex4
4,FxList的itemRenderer,即GreetingItemRenderer,是一个自定义的ItemRenderer,是一个mxml格式的skin文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<ItemRenderer xmlns="http://ns.adobe.com/mxml/2009">
        
    <states>
                <State name="normal"/>
                <State name="hovered"/>
                <State name="selected"/>
        </states>

        <TextBox text="ID:{data.id},User:{data.user}, 
                Greeting:{data.greetingContent}, 
                Date:{data.date}"
 
                verticalCenter="0" left="3" right="3" top="6" bottom="4" />

</ItemRenderer>

这个文件的逻辑很简单,就是把Greeting的数据信息在一个TextBox中显示出来。有关于在flex4中编写ItemRender的skin组件件详见:use skin as dataContainer’s itemRenderer in flex4 gumbo

运行一下,没有问题,但是FxList中内容不能点选。
fxlist-no-selection.png

这是由于我们没有在GreetingItemRenderer中定义点选的状态引起的,修改GreetingItemRenderer的代码,最终代码如下:

<ItemRenderer xmlns="http://ns.adobe.com/mxml/2009">
        
    <states>
                <State name="normal"/>
                <State name="hovered"/>
                <State name="selected"/>
        </states>
        
        <Rect left="0" right="0" top="0" bottom="0">
                <fill>
                        <SolidColor color="0xffffff" 
                                color.selected="0×666666" color.hovered="0×999999" />

                </fill>
        </Rect>

        <TextBox color="0×000000" color.selected="0xffffff" 
                text="ID:{data.id},User:{data.user}, 
                Greeting:{data.greetingContent}, 
                Date:{data.date}"
 
                verticalCenter="0" left="3" right="3" top="6" bottom="4" />

</ItemRenderer>

此时,FxList便可以点选了。当点选时,背景为深灰色,文字为白色,鼠标rollOver时,背景为浅灰。这个效果是由GreetingItemRenderer中的selected与hovered状态决定的。关于flex4中的新状态(State)详见:new state in flex4。运行效果如下图所示:
fxlist-selection-enabled.png

二,可恶的2030错误
在运行时,我发现会遇到以下Error:

Error: Error #2030: 遇到文件尾。

这个Error是flex客户端抛出的。但是根源却在server端,在类HelloWorld.java的getAllGreetings方法内,我们使用了List做为返回数据类型。在这里List仅是接口,具体数据载体是一个ArrayList。而ArrayList这个鬼类型,有容量与列表长度之分,初始创建时ArrayList有10个容量,当容量不足时,会自动增长2/3。这便造成,在接口的返回结果中,有一些列表元素是null,这是造成“遇到文件尾”的罪魁祸首。真不知这个bug应该归罪于WebORB还是flex。

修改getAllGreetings方法,最终代码如下:

   @SuppressWarnings(value="unchecked")
        public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                        while (result.get(result.size()  1) == null) {
                                result.remove(result.size()  1);
                        }
                } finally {
                        pm.close();
                }

                return result;
        }

@SuppressWarnings用于告诉Eclipse不要再提示unchecked警告,不然总会有一个黄色的小灯泡在哪里挂着。

sban:储如2030、2032等等这类与IO、文件等相关的IOError,在flex客户端接口调试中是最常见,也是爆错信息最简单的一类Error。这类Error多是server端代码引起的。

二,删除与更新
在sban.flexblog.HelloWorld这个类中,添加有关删除一个Greeting,删除所有Greeting,编辑单个Greeting,以及获取单个Greeting的接口方法,该类最终代码如下:

package sban.flexblog;

import java.util.Date;
import java.util.List;
import javax.jdo.PersistenceManager;

import sban.flexblog.managers.PMFactory;

/**
 * @author sban.li
 *
 */

public class HelloWorld {
        public String greet(String name) {
                return "Hi " + name + ", this message comes from remoting. Now:"
                                + new Date().toString();
        }

        private PersistenceManager pm = PMFactory.getInstance()
                        .getPersistenceManager();

        public Boolean greet2(String user, String content) {
                Boolean result = true;
                Greeting greeting = new Greeting(user, content, new Date());

                try {
                        pm.makePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }
        
        public Greeting getGreetingById(Long id)
        {
                return (Greeting) pm.getObjectById(Greeting.class, id);
        }
        
        public Boolean editGreeting(Long id, String content)
        {
                Boolean result = true;
                
                try {
                        Greeting greeting = getGreetingById(id);
                        greeting.setGreetingContent(content);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }
                
                return result;
        }

        public Boolean deleteById(Long id) {
                Boolean result = true;

                try {
                        Greeting greeting = getGreetingById(id);
                        pm.deletePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }
        @SuppressWarnings(value="unchecked")
        public Boolean deleteAllGreetings() {
                Boolean result = true;

                try {
                        List<Greeting> greetings = (List<Greeting>) pm.newQuery(
                                        Greeting.class).execute();
                        pm.deletePersistentAll(greetings);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }

        @SuppressWarnings(value="unchecked")
        public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                        while (result.get(result.size()  1) == null) {
                                result.remove(result.size()  1);
                        }
                } finally {
                        pm.close();
                }

                return result;
        }
}

1,pm.deletePersistent用于删除单个对象
2,pm.deletePersistentAll用于删除一个集合
3,pm.getObjectById用于获取单个对象
4,更新Greeting对象时,只需设置该对象属性,在pm.close之前,数据会自动存储。需在sban.flexblog.Greeting中,添加如下setter:

public void setGreetingContent(String v)
        {
                this.greetingContent = v;
        }

修改Index.mxml客户端代码,最终如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="onInit()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                        import mx.events.IndexChangedEvent
                        
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        
                        [Bindable]
                        private var _greeting : Object;
                        
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                        
                        private function onInit() : void
                        {
                                configRemoting();
                                getAllGreetings();
                        }
                        
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                        
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                Alert.show( event.result.toString() );
                                        };
                                
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                        
                        private function getAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("getAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                        
                                                _greetingData = ArrayCollection(event.result);
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                        
                        private function deleteAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("deleteAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                //Alert.show(event.result.toString());
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                        
                        private function deleteItem() : void
                        {
                                if(vList1.selectedItem)
                                {
                                        var itemId : Number = vList1.selectedItem.id;
                                        
                                        var op : AbstractOperation = _remotingObj.getOperation("deleteById");
                                        var handler : Function = function(event : ResultEvent) : void
                                                {
                                                        op.removeEventListener(ResultEvent.RESULT, handler);
                                                        getAllGreetings();
                                                        //Alert.show(event.result.toString());
                                                };
                                                
                                        op.addEventListener(ResultEvent.RESULT, handler);
                                        op.send(itemId);
                                }
                        }
                        
                        private function selectionChangedHandler(event : IndexChangedEvent) : void
                        {
                                var itemId : Number = vList1.selectedItem.id;
                                var op : AbstractOperation = _remotingObj.getOperation("getGreetingById");
                                
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                
                                                _greeting = event.result;
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(itemId);
                        }
                        
                        private function edit() : void
                        {
                                var content : String = vContentEditTxt.text;
                                var op : AbstractOperation = _remotingObj.getOperation("editGreeting");
                                
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(_greeting.id, content);
                        }
                ]]>

        </Script>
        
        <layout>
                <BasicLayout />
        </layout>
        
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
                
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet"click="greetViaRemoting()" />
                        <FxButton id="vDelAllBtn" label="deoete all greetings"click="deleteAllGreetings()" />
                        <FxButton id="vdelItemBtn" label="delete selected item"click="deleteItem()" />
                </HGroup>
                
                <FxList id="vList1" selectionChanged="selectionChangedHandler(event)" 
                        dataProvider="{_greetingData}" width="60%"itemRenderer="GreetingItemRenderer">

                        <layout>
                                <VerticalLayout />
                        </layout>
                </FxList>
                
                <Form borderStyle="solid">
                        <FormItem label="ID:">
                                <Label text="{_greeting.id}" />
                        </FormItem>
                        <FormItem label="User:">
                                <Label text="{_greeting.user}" />
                        </FormItem>
                        <FormItem label="Content:">
                                <FxTextInput id="vContentEditTxt"text="{_greeting.greetingContent}" />
                        </FormItem>
                        <ControlBar>
                                <FxButton id="vSubmitBtn" label="submit" click="edit()" />
                        </ControlBar>
                </Form>
                
        </VGroup>
        
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
        
</FxApplication>

为了让逻辑简单,在编辑时,只充许修改Greeting的内容。这块逻辑比较简要,不再一一述说。读者们可自行下载源码运行查看。我把Index.mxml改名为Greeting.mxml,布署到了http://flex-blog.appspot.com/Greeting.html。附运行截图一张:
flex4-lesson4-5-screenshot.png

布署时要删除war/WEB-INF/appengine-generated目录下的文件,否则可能导致上传不成功。

本课最终源码:flex4-lessons4-5.zip
sban 2009/4/26 于北京
本文为sban原创,作者保留所有权利,如需转载请保留作者原文链接
flex选修课系列:基于flex4技术从零开发flex博客系统

[Flex]flex选修课之第三课:使用Remoting服务

mikel阅读(1084)

第三课:使用Remoting服务

在flex中有多种对象可用于与server交互,有URLLoader、HttpService、WebService、RemotingObject、Socket,NetConnection等:
1,URLLoader与HttpService多种于加载静态文本文件。
2,WebService基于soap协议,效率虽不及Remoting,但可移植性好,与平台、语言完全解藕。
3,Remoting采用amf通讯协议,通讯效率被认为是WebServive的10倍。目前已有多种成熟的服务端框架可供选用,.Net有fluorineFx,php有amfphp,java有blazeDSWebORB同时有支持多个语言的版本,如同pureMVC一般。python、ruby等也均实现了amf。

无论使用哪一种server端语言,Remoting均采用统一的配置。加上目前多种语言已实现amf通讯协议,移植已不成问题。因此,Remoting应该是flex目前最好的与server端交互的方式。

一,在Google App中使用WebORB
既如此,我想把我的hello world程序修改一下,用Remoting与server交互。我采用WebORB做为remoting server,配置步骤如下:

1,下载build of WebORB for Google App Engine解压缩至任何地方
2,把WEB-INF/classes目录的两个文件weborb-acl.xml, weborb-config.xml拷贝到gapp_flexblog/src目录下
3,把WEB-INF/lib目录下的三个jar文件拷贝到war/WEB-INF/lib目录下
4,把WEB-INF/flex目录直接拷贝到war/WEB-INF目录下
5,把WEB-INF/web.xml的内容与war/WEB-INF/web.xml进行merge,完成后内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

 

<web-app>

<!– parameter used by the ActiveMQ broker initializer. The parameter
       contains the path to start the broker on –>
   
  <context-param>
      <param-name>ActiveMQBrokerPath</param-name>
      <param-value>tcp://localhost:61616?trace=true</param-value>
  </context-param>

  <filter>
    <filter-name>fileuploadfilter</filter-name>
    <filter-class>weborb.util.upload.MultiPartFilter</filter-class>
    <init-param>
        <param-name>deleteFiles</param-name>
        <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>fileuploadfilter</filter-name>
    <url-pattern>*.wo</url-pattern>
  </filter-mapping> 
  
  <listener>
      <listener-class>weborb.thirdparty.ActiveMQStarter</listener-class>
   </listener> 

  <listener>
      <listener-class>weborb.ORBServletContextListener</listener-class>
  </listener>   
  
  <!– Servlets –>
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>sban.flexblog.server.GreetingServiceImpl</servlet-class>
  </servlet>
  
  <servlet>
    <servlet-name>helloWorld</servlet-name>
    <servlet-class>sban.flexblog.server.HelloWorldServlet</servlet-class>
  </servlet>
  
  <servlet>
    <servlet-name>weborb</servlet-name>
    <servlet-class>weborb.ORBServlet</servlet-class>
    <load-on-startup> 1 </load-on-startup>
  </servlet>
  
  <servlet>
    <servlet-name>download</servlet-name>
    <servlet-class>weborb.DownloadServlet</servlet-class>
    <load-on-startup> 1 </load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>download</servlet-name>
    <url-pattern>/codegen.wo</url-pattern>
  </servlet-mapping>       
  
  <servlet-mapping>
    <servlet-name>weborb</servlet-name>
    <url-pattern>*.wo</url-pattern>
  </servlet-mapping>
  
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/gapp_flexblog/greet</url-pattern>
  </servlet-mapping>
  
  <servlet-mapping>
    <servlet-name>helloWorld</servlet-name>
    <url-pattern>/gapp_flexblog/hello</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>     
    <welcome-file>index.html</welcome-file>      
  </welcome-file-list>
  
</web-app>

welcome-file-list是欢迎页面列表。

 

二,使用remoting

在sban.flexblog命名空间下,添加一个HelloWorld.java文件,代码如下:

package sban.flexblog;

 

public class HelloWorld {
        public String greet(String name)
        {
                return "Hi " + name + ", this message comes from remoting.";
        }
}

呵呵,这个类非常简单,也不用继承HttpServlet什么的。好了,现在来写我们的客户端代码。把原Index.mxml文件修改内容如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009">

 

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        
                        private function greetViaRemoting() : void
                        {
                                var ro : RemoteObject = new RemoteObject("GenericDestination");
                                ro.source = "sban.flexblog.HelloWorld";
                                ro.endpoint = "http://localhost:8080/weborb.wo";
                                
                                var op : AbstractOperation = ro.getOperation("greet");
                                op.addEventListener(
                                        ResultEvent.RESULT, 
                                        function(event : ResultEvent) : void
                                        {
                                                Alert.show( event.result.toString() ) 
                                        }
                                );
                                op.addEventListener(FaultEvent.FAULT,
                                        function(event : FaultEvent) : void
                                        {
                                                Alert.show(event.fault.toString());
                                        }
                                );
                                
                                op.send(vNameTxt.text);
                        }
                ]]>
        </Script>
        
        <VGroup>
                <FxTextInput id="vNameTxt" text="sban" />
                <HGroup>
                        <FxButton id="vSendBtn2" label="remoting greet"click="greetViaRemoting()" />
                </HGroup>
        </VGroup>
        
</FxApplication>

修改app_flexblog_client的输出目录为gapp_flexblog的输出目录,即${DOCUMENTS}\gapp_flexblog\war。两个项目现在合二为一了,重新编译一下。这样以后再测试项目,就不用运行两个project了。
helloworld-entry

helloworld-alert

ok,上面弹出窗所示,正是服务端返回的内容。单出eclipse工具栏中的Deploy Google App按纽。发布完毕,运行,不好,出现了error:

endpoing-error.jpg

这是由于我们把endpoint写成了本地测试地址(http://localhost:8080/weborb.wo)所致。既然现在Flex程 序的输出目录与server端输出目录是相同的,我们可以把endpoing修改为”/weborb.wo”,修改后局部代码如下:

var ro : RemoteObject = new RemoteObject("GenericDestination");
ro.source = "sban.flexblog.HelloWorld";
ro.endpoint = "/weborb.wo";

编译,再次布署,访问,现在已经没有问题了:

remote-ok.jpg

helloworld-alert

远程也可以了,但是,
1,为什么RemotingObject的destination要用GenericDestination?为什么endpoint是/weborb.wo?
2,WEB-INF/flex目录下的四个文件的作用是什么?
3,为什么要拷贝weborb的三个jar文件到lib目录下?

等等类似的与Remoging有关的问题,准备下一课一探究竟。既然使用Remoting,我希望对server端,对client端的配置及工作原理都是一清二楚的。

 

上一课:flex4 lesson 2 : dynamic hello world interacting with server
本课源码:flex4-lesson3.zip

[设计模式]我的实用设计模式之四-Simple Factory,Factory Method和Abs

mikel阅读(736)

Simple Factory

先从Simple Factory开始讲起,假设模拟一个电玩店的试玩系统,这个电玩店专卖出售PS3的游戏机和提供试玩服务,当一个用户想试玩的时候,需要选择一种游戏类 型进行试玩,系统会选择生成其中一个游戏盘的对象:竞赛游戏(PS3RacingGameDisk),射击游戏 (PS3ShootingGameDisk)以及格斗游戏(PS3FightingGameDisk),这些游戏盘子类都分别继承自同一个游戏盘抽象类 AbstractGameDisk。

图1

    public abstract class AbstractGameDisk
    {
        
public string Name { getset; }
        
public abstract void Load();
    }
    
public class PS3RacingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 racing game.");
        }
    }
    
public class PS3ShootingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 shooting game.");
        }
    }
    
public class PS3FightingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 fighting game.");
        }
    }
    
public enum GameDiskType
    {
        RACING,
        SHOOTING,
        FIGHTING
    }
    
public class PS3Player
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk; 
            
switch (type)
            {
                
case GameDiskType.RACING:
                    disk 
= new PS3RacingGameDisk();
                    
break;
                
case GameDiskType.SHOOTING:
                    disk 
= new PS3ShootingGameDisk();
                    
break;
                
case GameDiskType.FIGHTING:
                    disk 
= new PS3FightingGameDisk();
                    
break;
                
default:
                    disk 
= null;
                    
break;
            }
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }

代码1

从上述代码看,如果我们需要增加新的游戏盘,例如角色扮演游戏(RolePlayGameDisk),那么生成游戏盘部分(见注释create a game disk处)需要增加case分支,这里的对具体游戏盘对象实例化存在着变化的需求。根据设计原则 "封装变化(Encapsulate what varies)" 对这一对象实例化的需求进行封装。 引入一个新的类来封装和处理对象生成的需求,这个类叫做PS3DiskFacotry。 

    public class PS3Player
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = PS3DiskFactory.CreateGameDisk(type); 
            
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }
    
public sealed class PS3DiskFactory
    {
        
public static AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
switch (type)
            {
                
case GameDiskType.RACING:
                    
return new PS3RacingGameDisk();
                
case GameDiskType.SHOOTING:
                    
return new PS3ShootingGameDisk();
                
case GameDiskType.FIGHTING:
                    
return new PS3FightingGameDisk();
                
default:
                    
return null;
            }
        }
    }

代码2
从上面的代码看PS3DiskFactory专门负责游戏盘(AbstractGameDisk的具体子类)的实例化过程,当有新的 游戏盘增加时,也就是实例化过程的需求发生变化时,全部变化会单独发生在PS3DiskFactory里面,也就是可变化的需求被封装到一个类里面了,这 就是Simple Factory的实现。

图2
由于对实例化需求的变化的封装,从图可见PS3DiskManager和具体的游戏盘类 (PS3RacingGameDisk,PS3ShootingGameDisk和PS3FightingGameDisk等)彻底的解 耦,PS3DiskManager只是依赖于他们共同的抽象类AbstractGameDisk和工厂类PS3DiskFactory。Simple Factory的作用就是用来封装“对象实例化可变化的需求”。

Factory Method

随着这个电玩店的发展,店里开始支持Wii游戏机销售和试玩,原先的系统需要更新符合这一新需求。参考Simple Factory的实现,我们可以很快速的实现Wii的需求。

    public class WiiPlayer
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = WiiDiskFactory.CreateGameDisk(type); 
            
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }
    
public sealed class WiiDiskFactory
    {
        
public static AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
switch (type)
            {
                
case GameDiskType.RACING:
                    
return new WiiRacingGameDisk();
                
case GameDiskType.SHOOTING:
                    
return new WiiShootingGameDisk();
                
case GameDiskType.FIGHTING:
                    
return new WiiFightingGameDisk();
                
default:
                    
return null;
            }
        }
    }
 
 
public class WiiRacingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii racing game.");
        }
    }
    
public class WiiShootingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii shooting game.");
        }
    }
    
public class WiiFightingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii fighting game.");
        }
    }

代码3

图3
第一眼看是不是很容易实现了新的需求?Copy & Paste,稍稍修改一下就完了。可是有没有发现两个Player类除了对GameDisk的实例化以外,其他的一模一样。我们引进第二个设计原则 "面向抽象编程,而不是面向具体编程(Depend on Abstractions, not on Concretions)" ,增加对各个具体Player的抽象类AbstractPlayer.Client面向的是Player的抽象类而不是具体的Player。

    public abstract class AbstractPlayer
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = CreateGameDisk(type);
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
        
protected abstract AbstractGameDisk CreateGameDisk(GameDiskType type);
    }
    
public class PS3Player : AbstractPlayer
    {
        
protected override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return PS3DiskFactory.CreateGameDisk(type);
        }
    }
    
public class WiiPlayer : AbstractPlayer
    {
        
protected override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return WiiDiskFactory.CreateGameDisk(type);
        }
    }

代码4

图4
引入对具体各个Players的抽象类AbstractPlayer后,在程序中可以声明AbstractPlayer的引用,而具体 的Player对象的实例化过程留给具体的Player类(PS3Player或者WiiPlayer)来实现。AbstractPlayer声明 CreateGameDisk方法负责实例化AbstractGameDisk的子类对象,具体的Player类负责实例化具体的 AbstractGameDisk的子类。这个Method(CreateGameDisk)的行为就是一个Factory,所以称为Factory Method。下面是一个典型的Factory Method的UML图。

图5
从图5和图4可以看,AbstractPlayer就是一个Creator,而PS3Player和WiiPlayer是一个 ConcreteCreator,CreateGameDisk()就是FactoryMethod(),AbstractPlayer只是定义实例化方 法,但是不知道具体如何实例化对象,实例化那个具体的对象,这些都是由PS3Player和WiiPlayer负责的。在上述的实现,PS3Player 和WiiPlayer是借助于Simple Factory来实例化对象。

Factory Method是一个推迟实例化的过程,在抽象类(Creator)定义实例化的行为(FactoryMethod),然后由具体的子类 (ConcreteCreator)决定具体实例化的对象。其实在OO中,抽象类负责定义行为(在C#中为Method或者Property),子类负责 实现行为,运行时动态调用不同行为称为 多态(polymorphism)。但是构造函数不能实现多态,Factory Method就是解决对象实例化的多态问题。Factory Method的别名也叫Virtual Constructor,为什么这样叫了,因为在C++里面实现多态都要定义Virtual Function(虚函数),但是Constructor是不能定义为virtual的,Factory Method恰恰解决这个问题,所以也就Virtual Constructor了。

Abstract Factory

上面讲述的Simple Factory和Factory Method解决了游戏盘(GameDisk)的对象的实例化过程,如果在Player中其他使用到的引用(例如游戏主机GameConsole和手柄 Joystick)都需要实现实例化不同的对象。那么就不仅仅需要CreateGameDisk(),而且需要CreateGameConsole()和 CreateJoystick()来实例化具体的对象。抽象类AbstractPlayer组合这些Factory Methods,实现如下。

    public abstract class AbstractPlayer
    {
        
public abstract AbstractGameDisk CreateGameDisk(GameDiskType type);
        
public abstract AbstractGameConsole CreateGameConsole();
        
public abstract AbstractJoystick CreateJoystick();
    }
    
public class PS3Player : AbstractPlayer
    {
        
public override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return PS3DiskFactory.CreateGameDisk(type);
        }
        
public override AbstractGameConsole CreateGameConsole()
        {
            
return new PS3Console();
        }
        
public override AbstractJoystick CreateJoystick()
        {
            
return new PS3Joystick();
        }
    }
    
public class WiiPlayer : AbstractPlayer
    {
        
public override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return WiiDiskFactory.CreateGameDisk(type);
        }
        
public override AbstractGameConsole CreateGameConsole()
        {
            
return new WiiConsole();
        }
        
public override AbstractJoystick CreateJoystick()
        {
            
return new WiiJoystick();
        }
    }
    
public abstract class AbstractGameDisk
    {
        
public string Name { getset; }
        
public abstract void Load();
    }
    
public abstract class AbstractGameConsole
    {
        
public void InsertGameDisk(AbstractGameDisk disk) { }
        
public void PluginJoystick(AbstractJoystick joystick) { }
    }
    
public abstract class AbstractJoystick
    {
    }
    
public class PS3Console : AbstractGameConsole
    {
    }
    
public class PS3Joystick : AbstractJoystick
    {
    }
    
public class WiiConsole : AbstractGameConsole
    {
    }
    
public class WiiJoystick : AbstractJoystick
    {
    }

代码5
AbstractPlayer不再负责PlayAGame的功能,只是声明了一系列产品的实例化的Methods。AbstractPlayer还是Factory Method。 我们需要实现PlayAGame的功能,基于设计原则"组合优于继承(Favor Object composition over inheritance)",我们定义一个新的类(Player),然后把工厂类(AbstractPlayer)作为一个引用组合到这一个类里面,这就是Abstract Factory模式的实现。

 

    public enum GameDevice
    {
        PS3,
        WII
    }
    
public class Player
    {
        
private AbstractPlayer playerFactory;
        
public Player(GameDevice device)
        {
            
switch (device)
            {
                
case GameDevice.PS3:
                    playerFactory 
= new PS3Player();
                    
break;
                
case GameDevice.WII:
                    playerFactory 
= new WiiPlayer();
                    
break;
            }
        }
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            AbstractGameConsole console = CreateGameConsole();
            
//Get a joystick
            AbstractJoystick joystick = CreateJoystick();
            console.PluginJoystick(joystick);
            
//create a game disk
            AbstractGameDisk disk = CreateGameDisk(type);
            
//insert disk to console
            console.InsertGameDisk(disk);
            
//load game 
            
//play and enjoy it
        }
        
private AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return playerFactory.CreateGameDisk(type);
        }
        
        
private AbstractGameConsole CreateGameConsole()
        {
            
return playerFactory.CreateGameConsole();
        }
        
private AbstractJoystick CreateJoystick()
        {
            
return playerFactory.CreateJoystick();
        }
    }

代码6 

图6

以下为一个经典Abtract Factory的实现,用于对比参考

图7

Player(Client)完全不知道GameConsole,Joystick和GameDisk到底如何实例化的,这些都又具体工厂 (PS3Player或者WiiPlayer)来负责一系列相关联的产品(也就是对象)的实例化,这一系列相关联的产品称为产品族(product family)。Player根据GameDevice借助AbstractPlayer实例化产品族,产品族下的所有产品具体协同工作,而Player 只是依赖于产品组的产品的抽象类而不是具体类,也就是说,产品族的替换不会影响Player类的PlayAGame()。这一特性适合可替换产品族的设 计,例如不同桌面主题的设计和不同数据库访问组件的设计,ADO.net的数据库访问层就是基于Abstract Factory模式设计的。

Player的构造函数中选择具体工厂也是用了条件选择(switch),这里可以通过Simple Factory来对具体工厂的实例化。

Abstract Factory有几个特点:
1.每次产生一系列相关联的产品,例如 WiiGameConsole,WiiJoystick和WiiRacingGameDisk等等,他们之间是协调工作,例如CreateGame有 InsertGameDisk方法,表示WiiGameConsole和WiiRacingGameDisk协调工作。
2.不同产品族直接的产品不可以相互替换,例如PS3Joystick不能用于WiiGameConsole。
3. 使用Abstract Factory一般在产品族相当固定的情景下,例如XBox游戏机也是有GameConsole,Joystick和GameDisk,那么实现 XBoxPlayer等产品族就可以支持XBox游戏机,但是如果需要更改产品族的产品,例如某新型游戏机不使用GameDisk而是使用 HardDisk来Load游戏的话,那么现有的设计就不能支持这一新型游戏机。

这些我对Simple Factory,Factory Method和Abstract Factory的想法,欢迎指教。
   

Jake's Blog in 博客园 — 精简开发 无线生活

[JQuery]扩展JQuery Ajax请求错误机制,实现服务器端消息回馈。

mikel阅读(763)

JQuery使我们在开发Ajax应用程序的时候提高了效率,减少了许多兼容性问题,但时间久了,也让我们离不开他。比如简单的JQuery Ajax请求的封装让我们忘却了最原始的XmlHttpRequest对象和他的属性,方法,也让我们远离事情的真相。

在Ajax项目中,经常遇到需要服务器端返回错误的消息提示,或者消息码之类的数据。查过一些帮助,解决方案,很多网站是返回错误的消息JSON数据或者 脚本,这种方式当我们用JQuery.ajax()的时候出现了问题,jQuery.ajax()回调函数success(data)的data参数可能 是 xmlDoc, jsonObj, html, text, 等等…这取决于我们dataType设置和MIME.很多时候我们的错误处理都是统一的处理,不管你请求的是XML,JSON…。不光不好统一, 还容易出现解析错误等等情况。

参考了Prototyp框架的做法,做了一个jQuery的错误扩展。

原理:Prototype思路是把服务器处理结果状态信息写在Header里面,这种方式既保证了reponse body的干净,同时适应XML,JSON,HTML,Text的返回情况。

   服务器端只需要 Response.AddHeader("Error-Json""{code:2001,msg:'User settings is null!',script:''}");

实现:为了不影响原有的jQuery.ajax方法,同时不破坏jquery库源文件,做了如下扩展,代码很简单都能看懂:

;(function($){
    
var ajax=$.ajax;
    $.ajax
=function(s){
        
var old=s.error;
        
var errHeader=s.errorHeader||"Error-Json";
        s.error
=function(xhr,status,err){
            
var errMsg = window["eval"]("(" + xhr.getResponseHeader(errHeader) + ")");
            old(xhr,status,errMsg
||err);
        }

        ajax(s);
    }

})(jQuery);

 

使用方法:

  服务器端:我们是对错误进行扩展,如果要让jquery.ajax破获错误,必须要服务器端返回非200的错误码,由于Opera浏览器下面对 400以上的错误码,都无法获得请求的Header,建议如果要支持Opera,最好返回30*错误,这是Opera能接受Header的错误范围。没有 做包装,可以再单独把Catch内容出来。

try {
                context.Response.Write(GetJson(context));
                
throw new Exception("msg");
            }

            
catch {
                context.Response.ClearContent();
                context.Response.StatusCode 
= 300;
                context.Response.AddHeader(
"Error-Json""{code:2001,msg:'User settings is null!',script:''}");
                context.Response.End();
            }

  客户端:

  

$.ajax({
            url: 
this.ajaxUrl,
            type: 
"POST",
            success: callback,
            error: 
function(xhr,status,errMsg){
        alert(errMsg.code
+"<br/>"+errMsg.msg);
            }

        }
);

也许不是最好的,但觉得用起来很方便,忘了个写新增参数errorHeader:"Error-Json",这个header头key根据你后台设定配置。

[Flex]flex选修课之第二课:与servlet服务端交互

mikel阅读(893)

flex4 lesson 2 : dynamic hello world interacting with server

flex选修课
基于flex4技术从零开发flex博客系统
sban 2009-4-19

第二课:与servlet服务端交互

上一节课,我们讲了如何配置开发环境,包括客房端flex开发环境,以及服务端java开发环境,并且编写了一个客房端示例程序 helloworld,但遗憾的是,目前这一个helloworld不是动态的。如果客户端不能与服务端进行数据交互,那么我感觉我并没有真正入门。

我审视了一下eclipse为我创建的gapp_flexblog项目,它包括以下目录结构:

Guestbook/
  src/
    …Java source code…
    META-INF/
      …other configuration…
  war/
    …JSPs, images, data files…
    WEB-INF/
      …app configuration…
      lib/
        …JARs for libraries…
      classes/
        …compiled classes…

src为java源码目录,war为程序布署目录,包括了编译之后的所有文件(注:Google App Engine采用标准的war格式组织web程序目录)。与flex中的bin-release目录相当。flex默认把编译之后的文件放在bin- Debug目录下,这是在Debug模式下,如果是发布模式,则放在bin-release目录下。

我在sban.flexblog.server下添加了一个HelloWorldServlet.java文件:

package sban.flexblog.server;

 

import java.io.IOException;
import javax.servlet.http.*;

public class HelloWorldServlet extends HttpServlet {
        public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException 
        {
                String[] name = req.getParameterValues("name");
                
                resp.setContentType("text/plain");
                resp.getWriter().println("Hi " + name[0] + ",Hello, world.");
        }
}

Google App Engine采用java servlet处理客户端与服务端的数据交互。该类继承于HttpServlet,doGet用于处理客户端的get指求。(注:如果是post请求,将报错)

那么Google App Engine是如何处理客户端请求的呢,或者说对于url是怎么处理的?在war/WEB-INF目录下,有一个web.xml文件。当服务端接收到一个 url时,web.xml负责把特定的url地址映射到特定的servlet类。我希望把/gapp_flexblog/hello请求映射到刚才添加的 类HelloWorldServlet上,我添加了如下内容:

<servlet>
    <servlet-name>helloWorld</servlet-name>
    <servlet-class>sban.flexblog.server.HelloWorldServlet</servlet-class>
  </servlet>
  …
  <servlet-mapping>
    <servlet-name>helloWorld</servlet-name>
    <url-pattern>/gapp_flexblog/hello</url-pattern>
  </servlet-mapping>

它可以帮我把/gapp_flexblog/hello请求交给HelloWorldServlet处理。

点击运行,eclipse打开Google Web Toolkit Hosted mode窗口,弹出浏览器运行窗口,表示服务端程序已经到位,客户端可以发出请求了。
google web tookit server

google-app-browser

如果你安装了curl,可以就可以用它测试服务端代码是否工作正常,打开cmd,输入:

curl http://localhost:8080/gapp_flexblog/hello?name=sban

curl hello

返回正确。我修改了客户端的静态的helloworld程序,如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009">

 

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        
                        private function greet() : void
                        {
                                new URLLoader( newURLRequest("http://localhost:8080/gapp_flexblog/hello?name=" + vNameTxt.text) )
                                        .addEventListener(Event.COMPLETE
                                                function(event : Event) : void
                                                {
                                                        Alert.show(event.currentTarget.data);
                                                        vSendBtn.enabled = true;
                                                }
                                        );
                                vSendBtn.enabled = false;
                        }
                ]]>
        </Script>
        
        <HGroup>
                <FxTextInput id="vNameTxt" text="sban" />
                <FxButton id="vSendBtn" label="greet" click="greet()" />
        </HGroup>

</FxApplication>

我用URLLoader及URLRequest向服务端发起一个http get请求,参数为name。我对URLLoader注册了一个事件监听,监测其complete事件,该事件发生在请求返回数据之后,而 URLloader的data属性便记录了返回结果。因为URLLoader在实例化如果有URLRequest,它会自动调用load方法,故而无须再 显式调用load方法。

input your name:

result

本课最终源码:source.zip

 

http://blog.sban.com.cn/

[SQL]数据库系统的维护与优化(二)

mikel阅读(1368)

二、数据库系统的性能监视与优化

1.、性能监视:

       1)增长

       测量并预测增长,需要收集四种主要的信息:处理器,网络,存储,内存。

      

对像类型

 

要收集的值

Processor

 

使用率百分比

Network

 

流量,总字节数

Storage

 

总传输数,以操作或块为单位

Memory

 

使用中的MB数或GB

Database

 

每个数据库的大小

 

       如果没有以前管理人员留下来的数据库性能基准线,则可以自己制做自己的数据库性能监视基准线。

       方法:

       以使用一个较小的时间间隔(5-10)进行一周,每天24小时,在之后24小时的测量改为每周一次,持续一个月,最后每月一次,并持续两个月。

有了这些数字之后,首先检查每天的范围。按照时间排列这些数字并为它们创建一个图表。这些数字将显示一种趋势,如果数字显示出一个增长趋势的

可预测模式,则要注意周测试的最后一次测量,并根据它预测后三个星期的增长模式。如果预测正确,它们应该与你实际采集的周数据接近。采用相同

的方法对月度数据进行测试。如果度量值初始显示出一个平稳的曲线,你应当进行更长时间的测试,每周一次,至少进行三个月以上。如果测量值呈下

降趋势,则延迟一周后再进行每天测量。

 

2、活动和性能

      

对像类型

 

要收集的值

Processor

 

使用率百分比,特别是应用程序相关的进程

Network

 

读和写的字节数

Storage

 

读和写操作

Memory

 

SQL server需要使用的值,以及SQL server正在使用的值

Database

 

数据库活动,连接和锁

       得到这些值之后,按时间顺序组织它们,计算出最小值,最大值和平均值。如果使得平均有意义,还要计算出集合的标准差,标准差越接近0,平均值越可信。

 

3、性能监视

对像

计数器

含义

说明

Processor

%Processor Time

显示在监视时间内处理器的使用百分比。

平均低于75%(低于50%更佳)

 

Memory

Available Mbytes

显示还剩多少内存。从总内存数量中减去这个值就可以算出正在使用的数量

应该保持在50MB

 

 

Page/sec 

 

平均低于20(低于15更佳)

 

LogicalDisk   

%Disk Time

显示在监视时间内的读,写百分比

 

Network Interface

Bytes Total/sec

显示发送和接收的字节数

用于规划网络带宽的大小

 

SQL Server:Databases

Active Transactions

       显示实例范围内数据库的所有活动事务

 

 

SQLServer:General      

User Connections      

显示被统计的连接到服务器的用户数量                        

用于规划内存

 

                                                                                                                                                                                                                                                                  

       我通常会使用系统监视器的日志功能,并把结果发送一个制表符分隔的文件。如果只监视一天,我会把收集时间间隔设置为5秒一次,如果要监视更长的时间

,则设置为5分钟一次。

       然后我创建了一个三张表的Excel文件:监视,评估,建议。我会把系统监视器的结果读到“监视”表中,然后更改列的格式,例如去掉服务器名称。对于处理

器内存,磁盘,网络和Sql server计数器,我会使用不同的颜色标记。

       在“评估”表中我会创建引用“监视”表中数据的公式,这些公式会计算出最大值,最小值,平均值,标准偏差等。然后我会对这些列中的数据进行评估并使用

不同的颜色标注,例如红色表示非常正常,绿色表示好,蓝色表示差。在这些数字下面我会解释为什么觉得它好或是不好,以及其他影响它的因素。我还会对测试环

境进行简单描述。

       在“建议”表中我会解释使用什么方法来解决问题。例如如果连接数以某种频率增长,并且同时内存使用量多于服务器上安装的数量,那么服务器正在进行换页

操作,那么它需要更多内存。

 

 

       磁盘增长方式

线性增长:

       未来使用率=当前使用率+(增长数量*周期个数)

       例如:如果数据库当前每分钟处理40个事务,并且每年每分钟增长10个事务,就可以通过这些数值放置到公式中计算出未来3年内数据库使用的情况

       未来3年内使用率=40+(10*3)

 

几何增长:

       未来使用率=当前使用率+(+增长率)^周期个数

       例如:如果数据库当前是600GB,并且增长率每个月为2%,则未来三年内数据库大小=600(1+0.02)^36

 

4、磁盘系统的优化

 

 

对于磁盘子系统规划监视

对像

计数器

含义

说明

System

Processor Queue      

每个处理器平均低于2

例如,在一个双处理器的机器上,应该保持在低于4的状态

Physical Disk

%Disk Time  

显示在监视时间内的读,写百分比   

 

平均低于50%

 

Avg.Disk QueueLength       

平均每个磁盘应该低于2

例如,对于一个5磁盘的阵列,此值应该低于10

 

      

Avg.Disk Reads/sec

用户规划磁盘和CPU

该低于磁盘容量的85%

 

 

vg.Disk Writes/sec

用户规划磁盘和CPU

应该低于磁盘容量的85%

SQL Server:Buffer Manager

Buffer Cache HitrATIO

应该超过90%(理想状态下接近99%          

 

      

Page Life Expectancy    

用于规划内存(应该保持在300秒以上)

                    

用于规划内存

 

 

Transactions/sec

用户规划磁盘和CPU       

 

 

 

Data Files Size KB

用于规划磁盘子系统

 

 

 

Percent Log Used

用于规划磁盘子系统

 

 

 

 

 

 

 

 假设:数据库的工作负载在峰值时段内为每秒600个读事务,200个写事务, 每个磁盘的标准是300IOPS及最大255 IOPS

 1.使用RAID1R的配置中,写事务的数量是双倍,因此它将此吞吐量的级别调整为600个读事务,400个写事务.或者1000 IOPS.如果这些I/O负载分摊到两个磁盘,

那么每个磁盘就是500IOPS,远远超出了每个磁盘的标准值.

所以RAID1不满足这个数据库的负载.

 

 2.RAID 5配置中,由于增加了4倍的写事务个数,所以提高了总体的I/O.由此I/O吞吐量级别上升到了每秒1400(600+200*4).不像RAID1,RAID5配置固定磁盘个数,

所以在RAID5 配置中,你可以使用多个磁盘来分担吞吐量,以符合每个磁盘最大255IOPS的负载.

       计算出磁盘数量:1400/255=5.49.由于5.49不是一个整数值,所以应该将其上升到下一个整数值,所以应该是6.

 3.RAID 10配置中,RAID1配置一样的写事务的数量翻一倍.

       计算出磁盘数量:1000/255=3.9 .所以最少要4个物理磁盘才能满足.

 

4.内存:

 并发是指300秒持续时间内的执行数量.

 1.连接上下文指的是需要支持用户连接的数据库结构.连接到sql server的每个用户大约需要500KB.确定并发用户的最大数量之后,500*最大并发连接数,就可以确定用户的内在需要.

 2.存储过程缓冲内存:

    1) 首先收集在数据库应用程序中包含所有的查询类型.然后计算每个查询文本在内在中需要的存储空间数量.请记住,执行此计算时,文本中每个字符都是一个字节.

 :

 如果一个特定的存储过程中包含了4000个字符,则此查询的文本在内存中大概占据4KB.

     2) 对每个查询评估最大的并发执行数量.

     :

       你可能估算出相同的存储过程的并发执行的最大的数量为500.则需要的缓存为4*501 (一个用于共享的查询计划,500用于执行上下文)2004KB.

 3.缓冲区内存🙁这个是最大的内存用户)

    :

       查询A:30个用户并发执行,每个查询有400KB数据输出, 120MB

       查询B:20个用户并发执行,每个查询有300KB数据输出,60MB

       查询C: 50个用户并发执行,每个查询有100KB数据输出,50MB

       三个种查询总共需要230MB内存,做为缓冲区.

[ASP.NET]提高ASP.net性能的十种方法

mikel阅读(685)

今天无意中看了一篇关于提高ASP.NET性能的文章,个人感觉还不可以,整理了一下,分享给大家。抛砖引玉,也希望高手们留下自己对提高性能上的高见!如果感觉有什么不对的观点还望见谅,多多指教。

 

一、返回多个数据集

  检查你的访问数据库的代码,看是否存在着要返回多次的请求。每次往返降低了你的应用程序的每秒能够响应请求的次数。通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量。

  如果用动态的SQL语句来返回多个数据集,那用存储过程来替代动态的SQL语句会更好些。是否把业务逻辑写到存储过程中,这个有点争议。但是我认为,把业务逻辑写到存储过程里面可以限制返回结果集的大小,减小网络数据的流量,在逻辑层也不用在过滤数据,这是一个好事情。

  用SqlCommand对象的ExecuteReader方法返回一个强类型的业务对象,再调用NextResult方法来移动数据集指针来定位数据集。返回多个ArrayList强类型对象。只从数据库中返回你需要的数据可以大大的减小你的服务器所耗用的内存。

二、对数据进行分页

  ASP.NETDataGrid有一个非常有用的功能:分页。如果DataGrid允许分页,在某一时刻它只下载某一页的数据,另外,它有一个数据分页的济览导航栏,它让你可以选择浏览某一页,而且每次只下载一页的数据。

  但是它有一个小小的缺点,就是你必须把所有的数据都绑定到DataGrid中。也就是说,你的数据层必须返回所有的数据,然后DataGrid再根据当前页过滤出当前页所需要的数据显示出来。如果有一个一万条记录的结果集要用DataGrid进行分页,假设DataGrid每页只显示25条数据,那就意味着每次请求都有9975条数据都是要丢弃的。每次请求都要返回这么大的数据集,对应用程序的性能影响是非常大的。

  一个好的解决方案是写一个分页的存储过程,例如对Northwind数据库orders表的分页存储过程。你只需要传当前页码,每页显示的条数两个参数进来,存储过程会返回相应的结果。

  在服务器端,我专门写了一个分页的控件来处理数据的分页,在这里,我用了第一个方法,在一个存储过程里面返回了两个结果集:数据记录总数和要求的结果集。

  返回的记录总数取决于要执行查询,例如,一个where条件可以限制返回的结果集的大小。因为在分页界面中必须要根据数据集记录的大小来计算总的页数,所以必须要返回结果集的记录数。例如,如果一共有1000000条记录,如果用where条件就可以过滤成只返回1000条记录,存储过程的分页逻辑应该知道返回那些需要显示的数据。

三、连接池

  用TCP来连接你的应用程序与数据库是一件昂贵的事情(很费时的事情),微软的开发者可以通过用连接池来反复的使用数据库的连接。比起每次请求都用TCP来连一次数据库,连接池只有在不存在有效的连接时才新建一个TCP连接。当关闭一个连接的时候,它会被放到池中,它仍然会保持与数据库的连接,这样就可以减少与数据库的TCP连接次数。

  当然,你要注意那些忘记关的连接,你应在每次用完连接后马上关闭它。我要强调的是:无论什么人说.net framework中的GC(垃圾收集器)总会在你用完连接对象后调用连接对象的Close或者Dispose方法显式的关闭你的连接。不要期望CLR会在你想象的时间内关掉连接,虽然CLR最终都要销毁对象和关闭边接,但是我们并不能确定它到底会在什么时候做这些事情。  

  要用连接池优化,有两条规则,第一,打开连接,处理数据,然后关闭连接。如果 你必须在每次请求中多次打开或关闭连接,这好过一直打开一个边接,然后把它传到各个方法中。第二,用相同的连接字符串(或者用相同的用户标识,当你用集成 认证的时候)。如果你没有用相同的连接字符串,如你用基于登录用户的连接字符串,这将不能利用连接池的优化功能。如果你用的是集成的论证,因为用户很多, 所以你也不能充分利用连接池的优化功能。.NET CLR提供了一个数据性能计数器,它在我们需要跟踪程序性能特性的时候非常有用,当然也包括连接池的跟踪了。     

  无论你的应用程序什么时候要连在另一台机子的资源,如数据库,你都应该重点优化你连资源所花的时间,接收和发送数据的时间,以及往返回之间的次数。优化你的应用程序中的每一个处理点(process hop),它是提高你的应用的性能的出发点。  

  应用程序层包含与数据层连接,传送数据到相应的类的实例以及业务处理的逻辑。例如,在Community Server中,要组装一个Forums或者Threads集合,然后应用业务逻辑,如授权,更重要的,这里要完成缓存逻辑。

四、ASP.NET缓存API

   在写应用程序之前,你要做的第一件事是让应用程序最大化的利用ASP.NET的缓存功能。

  如果你的组件是要在Asp.net应用程序中运行,你只要把System.Web.dll引用到你的项目中就可以了。然后用HttpRuntime.Cache属性就可访问Cache了(也可以通过Page.CacheHttpContext.Cache访问)。  

  有以下几条缓存数据的规则。第一,数据可能会被频繁的被使用,这种数据可以缓存。第二,数据的访问频率非常高,或者一个数据的访问频率不高,但是它的生存周期很长,这样的数据最好也缓存起来。第三是一个常常被忽略的问题,有时候我们缓存了太多数据,通常在一台X86的机子上,如果你要缓存的数据超过800M的话,就会出现内存溢出的错误。所以说缓存是有限的。换名话说,你应该估计缓存集的大小,把缓存集的大小限制在10以内,否则它可能会出问题。在Asp.net中,如果缓存过大的话也会报内存溢出错误,特别是如果缓存大的DataSet对象的时候。

这里有几个你必须了解的重要的缓存机制。首先是缓存实现了“最近使用”原则( a least-recently-used algorithm),当缓存少的时候,它会自动的强制清除那些无用的缓存。其次 “条件依赖”强制清除原则(expiration dependencies),条件可以是时间,关键字和文件。以时间作为条件是最常用的。在asp.net2.0中增加一更强的条件,就是数据库条件。当数据库中的数据发生变化时,就会强制清除缓存  

五、预请求缓存

  在前面,我们只对某些地方作了一个小小的性能改进也可以获得大的性能提升,用预请求缓存来提升程序的性能是很不错的。

  虽然Cache API设计成用来保存某段时间的数据,而预请求缓存只是保存某个时期的某个请求的内容。如果某个请求的访问频率高,而且这个请求只需要提取,应用,修改或者更新数据一次。那么就可以预缓存该请求。我们举个例子来说明。

  在BS的论坛应用程序中,每一个页面的服务器控件都要求得到用于决定它的皮肤(skin)的自定义的数据,以决定用哪个样式表及其它的一些个性化的东西。这里面的某些数据可能要长时间的保存,有些时间则不然,如控件的skin数据,它只需要应用一次,而后就可以一直使用。

  要实现预请求缓存,用Asp.net HttpContext类,HttpContext类的实例在每一个请求中创建,在请求期间的任何地方都可以通过HttpContext.Current属性访问。HttpContext类有一个Items集合属性,在请求期间所有的对象和数据都被添加到这个集合中缓存起来。和你用Cache缓存访问频率高数据一样,你可以用HttpContext.Items缓存那些每个请求都要用到的基础数据。它背后的逻辑很简单:我们向HttpContext.Items中添加一个数据,然后再从它里面读出数据。

六、后台处理

  通过上面的方法你的应用程序应该运行得很快了,是不是?但是在某些时候,程序中的一次请求中可能要执行一个非常耗时的任务。如发送邮件或者是检查提交的数据的正确性等。

  当我们把asp.net Forums 1.0集成在CS中的时侯,发现提交一个新的帖子的时候会非常的慢。每次新增一个帖子的时侯,应用程序首先要检查这个帖子是不是重复提的,然后用“badword”过滤器来过滤,检查图片附加码,作帖子的索引,把它添加到合适的队列中,验证它的附件,最后,发邮件到它的订阅者邮件箱中。显然,这个工作量很大。

  结果是它把大量的时间都花在做索引和发送邮件中了。做帖子的索引是一项很耗时的操作,而发邮件给订阅都需要连接到SMTP服务,然后给每一个订阅者都发一封邮件,随着订阅用户的增加,发送邮件的时间会更长。

  索引和发邮件并不需要在每次请求时触发,理想状态下,我们想要批量的处理这些操作,每次只发25封邮件或者每隔5分钟把所有的要发的新邮件发一次。我们决定使用与数据库原型缓存一样的代码,但是失败了,所以又不得不回到VS.NET 2005

  我们在System.Threading命名空间下找到了Timer类,这个类非常有用,但却很少有人知道,Web开发人员则更少有人知道了。一旦他建了该类的实例,每隔一个指定的时间,Timer类就会从线程池中的一个线程中调用指定的回调函数。这意味着你的asp.net应用程序可以在没有请求的时候也可以运行。这就是后以处理的解决方案。你就可以让做索引和发邮件工作在后台运行,而不是在每次请求的时候必须执行。

  后台运行的技术有两个问题,第一是,当你的应用程序域卸载后,Timer类实例就会停止运行了。也就是不会调用回调方法了。另外,因为CLR的每个进程中都有许多的线程在运行,你将很难让Timer获得一个线程来执行它,或者能执行它,但会延时。Asp.net层要尽量少的使用这种技术,以减少进程中线程的数量,或者只让请求用一小部分的线程。当然如果你有大量的异步工作的话,那就只能用它了。

七、页面输出缓存和代理服务

  Asp.net是你的界面层(或者说应该是),它包含页面,用户控件,服务器控件(HttpHandlers HttpModules)以及它们生成的内容。如果你有一个Asp.net页面用来输出htmlxml,imgae或者是其它的数据,对每一个请求你都用代码来生成相同的输出内容,你就很有必要考虑用页面输出缓存了。

只要简单的把下面的这一行代码复制到你的页面中就可以实现了:

<%@ PageOutputCache VaryByParams=none Duration=60 %>

就可以有效的利用第一次请求里生成的页面输出缓存内容,60秒后重新生成一道页面内容。这种技术其实也是运用一些低层的Cache API来实现。用页面输出缓存有几个参数可以配置,如上面所说的VaryByParams参数,该参数表示什么时候触发重输出的条件,也可以指定在Http GetHttp Post 请求模式下缓存输出。例如当我们设置该参数为VaryByParams=Report”的时候,default.aspx?Report=1或者default.aspx?Report=2请求的输出都会被缓存起来。参数的值可以是多个用分号隔开参数。

  许多人都没有意识到当用页面输出缓存的时候,asp.net也会生成HTTP头集(HTTP Header)保存在下游的缓存服务器中,这些信息可以用于Microsoft Internet安全性中以及加速服务器的响应速度。当HTTP缓存的头被重置时,请求的内容会被缓在网络资源中,当客户端再次请求该内容时,就不会再从源服务器上获得内容了,而直接从缓存中获得内容。

  虽然用页面输出缓存不提高你的应用程序性能,但是它能减少了从的服务器中加载已缓存页面内容的次数。当然,这仅限于缓存匿名用户可以访问的页面。因为一旦页面被缓存后,就不能再执行授权操作了。

八、 IIS6.0Kernel Caching

  如果你的应用程序没用运行在IIS6.0(windows server 2003)中,那么你就失去了一些很好的提高应用程序性能的方法。在第七个方法中,我讲了用页面输出缓存提高应用程序的性能的方法。在IIS5.0中,当一个请求到来到IIS后,IIS会把它转给asp.net,当应用了页面输出缓存时,ASP.NET中的HttpHandler会接到该请求,HttpHandler从缓存中把内容取出来并返回。

  如果你用的是IIS6.0,它有一个非常好的功能就是Kernel Caching,而且你不必修改asp.net程序中任何代码。当asp.net接到一个已缓存的请求,IISKernel Cache会从缓存中得到它的一份拷贝。当从网络中传来一个请求的时,Kernel层会得到该请求,如果该请求被缓存起来了,就直接把缓存的数据返回,这样就完工了。这就意味着当你用IISKernel Caching来缓存页面输出时,你将获得不可置信的性能提升。在开发VS.NET 2005 asp.net时有一点,我是专门负asp.net性能的程序经理,我的程序员用了这个方法,我看了所有日报表数据,发现用kernel model caching的结果总是最快的。它们的一个共同的特征就是网络的请求和响应量很大,IIS只占用了5%CPU资源。这是令人惊奇的。有许多让你使用用IIS6.0的理由,但kernel cashing是最好的一个。

九、 Gzip压缩数据

  除非你的CPU占用率太高了,才有必要用提升服务器性能的技巧。用gzip压缩数据的方法可以减少你发送到服务端的数据量,也可以提高页面的运行速度,同时也减少了网络的流量。怎么样更好的压缩数据取决于你要发送的数据,还有就是客户端的浏览器支不支持(IIS把用gzip压缩后的数据发送到客户端,客户端要支持gzip才能解析,IE6.0Firefox都支持)。这样你的服务器每秒能多响应一些请求,同样,你也减少了发送响应的数据量,也就能多发送一些请求了。

  好消息,gzip压缩已经被集成在IIS6.0中了,它比IIS5.0gzip更好。不幸的是,在IIS6.0中启用gzip压缩,你不能在IIS6.0的属性对话中设置。IIS开发团队把gzip压缩功能开发出来了,但他们却忘了在管理员窗口中让管理员能很方便的启用它。要启用gzip压缩,你只能深入IIS6.0xml配置文件中修改它的配置。

  除了阅读本文以外看看Brad Wilson写的 IIS6 压缩一文:http://www.dotnetdevs.com/articles/IIS6compression.aspx;另外还有一篇介绍aspx压缩基础知识的文章,Enable ASPX Compression in IIS。但是要注意,在IIS6中动态压缩和kernel cashing是互斥的。

十、 服务器控件的ViewState

  ViewStateasp.net中的一个特性,它用于把生成页面要用的一状态值保存在一个隐藏域中。当页面被回传到服务器时,服务器要解析,校验和应用ViewState中的数据以还原页面的控件树。ViewState是一个非常有用的特性,它能持久化客户端的状态而不用cookie或者服务器的内存。大部分的服务器控件都是用ViewState来持久化那些在页面中与用户交互的元素的状态值。例如,用以保存用于分页的当前页的页码。

  用ViewState会带来一些负面的影响。首先,它加大的服务器的响应和请求的时间。其次,每次回传时都增加了序列化和反序列化数据的时间。最后,它还消耗了服务器更多的内存。

  许多的服务器控件很趋于使用ViewState,如DataGrid,而有时候是没有必须使用的。默认情况下是允许使用ViewState的,如果你不想使用ViewState的话,你可以在控件或页面级别把关闭它。在控件中,你只要把EnableViewState属性设为False就可以了;你也可以在页面中设置,使它的范围扩展到整个页面中: <%@ Page EnableViewState=false %> 如果页面无需回传或者每次请求页面只是呈现控件。你就应该在页面级别中把ViewState关掉。

[Flex]第一课:flex4开发环境配置与第一个应用程序hello world

mikel阅读(788)

第一课:flex4开发环境配置与第一个应用程序hello world

我原本是学教育学的,因为对flex技术略感兴趣,所以业余就想研究一下。我认为学习flex技术,最好的方法莫过于阅读官方文档,其次便是做项目。在我最初学习的时候,我在想,如果有一个人能够把他的学习的过程晒出来,大家沿着他的学习轨迹学习,一定会比较好学一点。

对 于初学者,学习内容若太难了,一下子难于理解。太容易了,又丧失了学习的兴趣与动力。CookBook居说很好,例子很丰富,但CookBook只是单个 例子的集合,针对单个例子没有问题,但正因为如此,才容易给初学者产生误导。写程序,不只是解决问题,对于一个项目,整体的架构与设计很重要,而这些在 CookBook中是看不到的。CookBook有时候为了说明一个问题,并不讲究编码方法与规范,这也往往误导初学者,让初学者以为,这样写就是对的, 就是最好的,其实不然。教程一方面可以帮助初学者理解问题,也可能是在误导。

如果不看CookBook,而读官方livedoc文档,又录如何?也有问题,因为大多了,往往不知从何看起。往往看过后面的,就忘记前面的。昨天 看了一个类的用法,今天再看,觉得还是有一些不明白。对于Flex SDK,它有一个整体的架构,它为什么这么设计,对于一个组件或一个类,它为什么要有这个属性,为什么要有这个方法?这在livedoc中是没有办法直接 学不到的。所以从livedoc学习,是枯燥的,是不高效的,就好像想通过背诵字典来提高文化素养一样。

开源程序员喜欢写一些开源程序open给大家,供大家免费使用,以及学习交流。阅读开源代码,也是学习flex技术的一个好途径。但是,越是完善的 开源框架,越难看得明白。一个开源程序,只所以会如此如此设计,肯定有它的历史演化原因,这些原因i湮没在历史的版本中,很难从最终版本中窥斑见豹。而如 果从svn中究极它的所有版本,再一一消化细细揣磨加以对照,这种精神也不是我等凡人所能具备的。网上有人会对一些开源程序的框架写一些分析评论,但恨往 往只是只言片言,很少有人有耐心把它从头到脚剖析完备的。所以阅读开源代码只能做为学习flex技术的备选项,而非上上策。

看书居说是很不错的选择,但可惜国内目前关于flex方面的好书还是不多,抛去翻译国外的,为赚稿费搪瑟字数的,照搬livedoc的,就廖廖无几 了。当然如果你的英语够好,不妨去读英文原著。最后我便想到第一段中提到的学习方法,即做项目,迭代式的做项目,从一点一滴做起,从零做起,在做的过程中 发现问题,解决问题。所思所想涉及项目的架构与设计,设计模式,与服务端的交互等。我不是flex高手,是一名地地道道的flex初学者,爱好者,我将用 我诚实的笔忠诚的记录我的详细学习过程,希望对读者有益。

我将用flex4技术,以及相关的技术或软件如Flash CS4等,开发一个flex博客系统,它具有常用的博客的一些功能。今天是第一天,首选配置我flex4开发环境,以及编写第一个应用程序hello world。

一, 需要安装的软件

jdk 1.6eclipse 3.4.2Adobe Flex Builder 3.0.2 Professional Eclipse Plug-in,在eclipse中安装jdt及Google Plugin for Eclipse

上述软件的安装比较简单,不在废话。之所以要使用eclipse作为开发环境,是因为同时编写java与flex代码方便,因为我想用java做为 server端的开发语言。安装Google Plugin for Eclipse,是因为我要使用免费的Google App Engine作为web测试服务器。

因为我要使用flex4技术,所以我下载了一个Max Preview版本,即4.0.0.4021版本,安装配置不再详言。

打开Eclipse,新建一个Web Application Project项目,名为gapp_flexblog,默认命名空间为sban.flexblog,用于写server代码。再新建一个Flex Project,名为gapp_flexblog_client,用于编写客户端逻辑。

撰写我的第一个flex4页面:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009">

 

        <TextGraphic text="hello, world!" />
        
</FxApplication>

flex4的默认命名空间为http://ns.adobe.com/mxml/2009,且默认没有缀,为空,这样编写反而更方便了一些。ok,运行一下,没有问题:
hello world

我创建的Google App Engine app id为flex-blog,站点为http://flex-blog.appspot.com/。首先我把默认创建的Web Applicaton Project布置到Google App Engine。布置方法很简要,点击工具栏中的Deploy按纽。布署完成之后,查看http://flex-blog.appspot.com,没有问 题:
default deploy page

下一步要做的事件是让hello world有动态内容:
1,把flex发布的代码布署到app engine上去
2,flex从server端获取简要数据,实现数据交互

本课最终源码:lesson1.zip

 

http://blog.sban.com.cn/