利用memcached java client一个简单的应用

利用memcached java client一个简单的应用


1.memcached java client一个实现的下载地址


http://www.whalin.com/memcached/#download
2.  利用memcached java client 一个简单的应用


java 代码




  1. package com.danga.MemCached.test;   
  2.   
  3. import java.util.Date;   
  4.   
  5. import com.danga.MemCached.MemCachedClient;   
  6. import com.danga.MemCached.SockIOPool;   
  7.   
  8.   
  9. public class Test {       
  10.     protected static MemCachedClient mcc = new MemCachedClient();      
  11.       
  12.     static {      
  13.         String[] servers ={“192.168.40.4:12000”};      
  14.       
  15.         Integer[] weights = { 3 };      
  16.       
  17.         //创建一个实例对象SockIOPool    
  18.         SockIOPool pool = SockIOPool.getInstance();      
  19.       
  20.         // set the servers and the weights   
  21.         //设置Memcached Server   
  22.         pool.setServers( servers );      
  23.         pool.setWeights( weights );      
  24.       
  25.         // set some basic pool settings      
  26.         // 5 initial, 5 min, and 250 max conns      
  27.         // and set the max idle time for a conn      
  28.         // to 6 hours      
  29.         pool.setInitConn( 5 );      
  30.         pool.setMinConn( 5 );      
  31.         pool.setMaxConn( 250 );      
  32.         pool.setMaxIdle( 1000 * 60 * 60 * 6 );      
  33.       
  34.         // set the sleep for the maint thread      
  35.         // it will wake up every x seconds and      
  36.         // maintain the pool size      
  37.         pool.setMaintSleep( 30 );      
  38.       
  39. //        Tcp的规则就是在发送一个包之前,本地机器会等待远程主机   
  40. //        对上一次发送的包的确认信息到来;这个方法就可以关闭套接字的缓存,   
  41. //        以至这个包准备好了就发;   
  42.         pool.setNagle( false );      
  43.         //连接建立后对超时的控制   
  44.         pool.setSocketTO( 3000 );   
  45.         //连接建立时对超时的控制   
  46.         pool.setSocketConnectTO( 0 );      
  47.       
  48.         // initialize the connection pool      
  49.         //初始化一些值并与MemcachedServer段建立连接   
  50.         pool.initialize();   
  51.               
  52.       
  53.         // lets set some compression on for the client      
  54.         // compress anything larger than 64k      
  55.         mcc.setCompressEnable( true );      
  56.         mcc.setCompressThreshold( 64 * 1024 );      
  57.     }      
  58.           
  59.     public static void bulidCache(){      
  60.         //set(key,value,Date) ,Date是一个过期时间,如果想让这个过期时间生效的话,这里传递的new Date(long date) 中参数date,需要是个大于或等于1000的值。   
  61.         //因为java client的实现源码里是这样实现的 expiry.getTime() / 1000 ,也就是说,如果 小于1000的值,除以1000以后都是0,即永不过期   
  62.         mcc.set( “test”“This is a test String” ,new Date(10000));   //十秒后过期   
  63.              
  64.     }      
  65.      
  66.     public static void output() {      
  67.         //从cache里取值   
  68.         String value = (String) mcc.get( “test” );      
  69.         System.out.println(value);       
  70.     }      
  71.           
  72.     public static void main(String[] args){      
  73.         bulidCache();     
  74.         output();          
  75.     }    
  76.       
  77. }      

 



运行输出值为:



This is a test String  


 


3.注释掉buildCache();


十秒后运行,输出值为 null


Memcache的使用和协议分析详解(php)

Memcache的使用和协议分析详解


作者:heiyeluren
博客:
http://blog.csdn.net/heiyeshuwu
时间:2006-11-12
关键字:PHP Memcache Linux 缓存



Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力。(关于Memcache的更多信息请Google)
Memcache官方网站:http://www.danga.com/memcached



【安装Memcache服务器端】


我目前的平台,服务器是Fedora Core 1(内核:2.4.22),客户端是Windows XP SP2,需要安装的就是服务器的Memcached的守护进程和客户端的PHP扩展php_memcache两个东西。现在我分别来讲。


服务器端主要是安装memcache服务器端,目前的最新版本是 memcached-1.2.0 。
下载:http://www.danga.com/memcached/dist/memcached-1.2.0.tar.gz
另外,Memcache用到了libevent这个库用于Socket的处理,所以还需要安装libevent,libevent的最新版本是libevent-1.2。(如果你的系统已经安装了libevent,可以不用安装)
官网:http://www.monkey.org/~provos/libevent/
下载:http://www.monkey.org/~provos/libevent-1.2.tar.gz


我分别把两个东东下载回来,放到 /tmp 目录下:
# cd /tmp
# wget
http://www.danga.com/memcached/dist/memcached-1.2.0.tar.gz
# wget http://www.monkey.org/~provos/libevent-1.2.tar.gz


先安装libevent:
# tar zxvf libevent-1.2.tar.gz
# cd libevent-1.2
# ./configure –prefix=/usr
# make
# make install


然后看看我们的libevent是否安装成功:
# ls -al /usr/lib | grep libevent
lrwxrwxrwx    1 root     root          21 11?? 12 17:38 libevent-1.2.so.1 -> libevent-1.2.so.1.0.3
-rwxr-xr-x       1 root     root          263546 11?? 12 17:38 libevent-1.2.so.1.0.3
-rw-r–r–        1 root     root          454156 11?? 12 17:38 libevent.a
-rwxr-xr-x       1 root     root          811 11?? 12 17:38 libevent.la
lrwxrwxrwx    1 root     root          21 11?? 12 17:38 libevent.so -> libevent-1.2.so.1.0.3


还不错,都安装上了,再来安装memcache,同时需要安装中指定libevent的安装位置:


# cd /tmp
# tar zxvf memcached-1.2.0.tar.gz
# cd memcached-1.2.0
# ./configure –with-libevent=/usr
# make
# make install


如果中间出现报错,请仔细检查错误信息,按照错误信息来配置或者增加相应的库或者路径。
安装完成后会把memcached放到 /usr/local/bin/memcached ,我们看以下是否安装了:


# ls -al /usr/local/bin/mem*
-rwxr-xr-x    1 root     root       137986 11?? 12 17:39 /usr/local/bin/memcached
-rwxr-xr-x    1 root     root       140179 11?? 12 17:39 /usr/local/bin/memcached-debug


恩,安装完成了,现在我们看以下memcache的帮助:


# /usr/local/bin/memecached -h
memcached 1.2.0
-p <num>            port number to listen on
-s <file>               unix socket path to listen on (disables network support)
-l <ip_addr>        interface to listen on, default is INDRR_ANY
-d                          run as a daemon
-r                           maximize core file limit
-u <username> assume identity of <username> (only when run as root)
-m <num>          max memory to use for items in megabytes, default is 64 MB
-M                         return error on memory exhausted (rather than removing items)
-c <num>            max simultaneous connections, default is 1024
-k                          lock down all paged memory
-v                          verbose (print errors/warnings while in event loop)
-vv                        very verbose (also print client commands/reponses)
-h                         print this help and exit
-i                          print memcached and libevent license
-b                         run a managed instanced (mnemonic: buckets)
-P <file>             save PID in <file>, only used with -d option
-f <factor>          chunk size growth factor, default 1.25
-n <bytes>         minimum space allocated for key+value+flags, default 48


参数不算多,我们来启动一个Memcache的服务器端:
# /usr/local/bin/memcached -d -m 10  -u root -l 192.168.0.200 -p 12000 -c 256 -P /tmp/memcached.pid


-d选项是启动一个守护进程,-m是分配给Memcache使用的内存数量,单位是MB,我这里是10MB,-u是运行Memcache的用户,我这里是root,-l是监听的服务器IP地址,如果有多个地址的话,我这里指定了服务器的IP地址192.168.0.200,-p是设置Memcache监听的端口,我这里设置了12000,最好是1024以上的端口,-c选项是最大运行的并发连接数,默认是1024,我这里设置了256,按照你服务器的负载量来设定,-P是设置保存Memcache的pid文件,我这里是保存在 /tmp/memcached.pid,如果要结束Memcache进程,执行:
# kill `cat /tmp/memcached.pid`

也可以启动多个守护进程,不过端口不能重复。


【安装Memcache的PHP扩展】


Memcache就是在服务器监听端口,通过一定的协议交互来写入数据到服务器内存中,或者获取一些值。如果你了解Memcache的交互协议,完全可以自己构建Memcache的客户端,目前网上也有很多构建好的Memcache客户端的PHP Class,可以直接用,不过我这里为了效率,还是决定使用PECL中Memcache的专用扩展,因为毕竟是用C写的,效率比较高,而且安装部署比较方便。


下载PECL中的Memcache,因为我的客户端是Windows XP,所以需要下载dll版,我的PHP版本是PHP 5.1.4,必须下载PHP 5.1专用的扩展。
PECL官网:http://pecl.php.net (For Linux)
                       http://pecl4win.php.net(For Windows)
扩展下载: http://pecl4win.php.net/download.php/ext/5_1/5.1.2/php_memcache.dll


如果你的PHP是其他版本,请到 http://pecl4win.php.net/ext.php/php_memcache.dll 选择你相应的版本,如果是Linux下的PHP,请到 http://pecl.php.net/package/memcache 选择相应想要下载的版本。


下载完了以后,我把php_memcache.dll 拷贝到 c:\php5\ext 目录下,如果你的扩展目录是在是缺省路径,(就是没有修改过php.ini中的扩展路径) 请拷贝到 c:\windows\ 目录下,如果是Linux平台,请自己编译安装,可以在程序中使用dl()函数加载,或者在编译php的时候加载进去。最后重启Web服务器,IIS/Apache。


我的网站目录是在:d:\mysite 目录下,现在建立一个 phpinfo.php 文件在网站根目录下,代码是:
<?phpinfo()?>
看有没有成功加载 php_memcache.dll 扩展。如果显示了 Memcache 选项和相应的版本信息,则说明成功加载了,否则请仔细检查上面的步骤。


如果一切正确无误,那么说明安装成功。


 


【Memcache初试】


[ 接口介绍 ]
服务器端和客户端都安装配置好了,现在我们就来测试以下我们的成果。Memcache客户端包含两组接口,一组是面向过程的接口,一组是面向对象的接口,具体可以参考PHP手册 “LXXV. Memcache Functions” 这章。我们为了简单方便,就使用面向对象的方式,也便于维护和编写代码。Memcache面向对象的常用接口包括:


Memcache::connect — 打开一个到Memcache的连接
Memcache::pconnect — 打开一个到Memcache的长连接
Memcache::close — 关闭一个Memcache的连接
Memcache::set — 保存数据到Memcache服务器上
Memcache::get — 提取一个保存在Memcache服务器上的数据
Memcache::replace — 替换一个已经存在Memcache服务器上的项目(功能类似Memcache::set)
Memcache::delete — 从Memcache服务器上删除一个保存的项目
Memcache::flush — 刷新所有Memcache服务器上保存的项目(类似于删除所有的保存的项目)
Memcache::getStats — 获取当前Memcache服务器运行的状态


[ 测试代码 ]
现在我们开始一段测试代码:



<?php
//连接
$mem = new Memcache;
$mem->connect(192.168.0.200, 12000);

//保存数据
$mem->set(key1, This is first value, 0, 60);
$val = $mem->get(key1);
echo Get key1 value:  . $val .<br>;

//替换数据
$mem->replace(key1, This is replace value, 0, 60);
$val = $mem->get(key1);
echo Get key1 value:  . $val . <br>;

//保存数组
$arr = array(aaa, bbb, ccc, ddd);
$mem->set(key2, $arr, 0, 60);
$val2 = $mem->get(key2);
echo Get key2 value: ;
print_r($val2);
echo <br>;

//删除数据
$mem->delete(key1);
$val = $mem->get(key1);
echo Get key1 value:  . $val . <br>;

//清除所有数据
$mem->flush();
$val2 = $mem->get(key2);
echo Get key2 value: ;
print_r($val2);
echo <br>;

//关闭连接
$mem->close();
?>



如果正常的话,浏览器将输出:
Get key1 value: This is first value
Get key1 value: This is replace value
Get key2 value: Array ( [0] => aaa [1] => bbb [2] => ccc [3] => ddd )
Get key1 value:
Get key2 value:



基本说明我们的Memcache安装成功,我们再来分析以下上面的这段程序。



[ 程序分析 ]


初始化一个Memcache的对象:
$mem = new Memcache;


连接到我们的Memcache服务器端,第一个参数是服务器的IP地址,也可以是主机名,第二个参数是Memcache的开放的端口:
$mem->connect(“192.168.0.200”, 12000);


保存一个数据到Memcache服务器上,第一个参数是数据的key,用来定位一个数据,第二个参数是需要保存的数据内容,这里是一个字符串,第三个参数是一个标记,一般设置为0或者MEMCACHE_COMPRESSED就行了,第四个参数是数据的有效期,就是说数据在这个时间内是有效的,如果过去这个时间,那么会被Memcache服务器端清除掉这个数据,单位是秒,如果设置为0,则是永远有效,我们这里设置了60,就是一分钟有效时间:
$mem->set(‘key1‘, ‘This is first value’, 0, 60);


从Memcache服务器端获取一条数据,它只有一个参数,就是需要获取数据的key,我们这里是上一步设置的key1,现在获取这个数据后输出输出:
$val = $mem->get(‘key1′);
echo “Get key1 value: “ .
$val;


现在是使用replace方法来替换掉上面key1的值,replace方法的参数跟set是一样的,不过第一个参数key1是必须是要替换数据内容的key,最后输出了:
$mem->replace(‘key1’, ‘This is replace value’, 0, 60);
$val = $mem->get(‘key1’);
echo “Get key1 value: ” . $val;


同样的,Memcache也是可以保存数组的,下面是在Memcache上面保存了一个数组,然后获取回来并输出
$arr = array(‘aaa’, ‘bbb’, ‘ccc’, ‘ddd’);
$mem->set(‘key2’, $arr, 0, 60);
$val2 = $mem->get(‘key2’);
print_r($val2);


现在删除一个数据,使用delte接口,参数就是一个key,然后就能够把Memcache服务器这个key的数据删除,最后输出的时候没有结果
$mem->delete(‘key1’);
$val = $mem->get(‘key1’);
echo “Get key1 value: ” . $val .
“<br>”;


最后我们把所有的保存在Memcache服务器上的数据都清除,会发现数据都没有了,最后输出key2的数据为空,最后关闭连接
$mem->flush();
$val2 = $mem->get(‘key2’);
echo “Get key2 value: “;
print_r($val2);
echo
“<br>”;


 



【Memcache协议分析】


如果你不喜欢 php_memcache.dll 扩展或者服务器器目前不支持这个扩展,那么就可以考虑自己构建,需要构建Memcahe的客户端,要先了解Memcache协议的交互,这样才能开发自己的客户端,我这里就简单的分析以下Memcache的协议。
(更详细的协议内容请在Memcache服务器端的源码的 doc/protocol.txt 文件中,本文基本来源于此)


Memcache既支持TCP协议,也支持UDP协议,不过我们这里是以TCP协议的协议作为主要考虑对象,想了解UDP协议的过程,请参考 doc/protocol.txt 文件。


[ 错误指令]
Memcache的协议的错误部分主要是三个错误提示之提示指令:
普通错误信息,比如指令错误之类的
ERROR\r\n


客户端错误
CLIENT_ERROR <错误信息>\r\n


服务器端错误
SERVER_ERROR <错误信息>\r\n


[ 数据保存指令]
数据保存是基本的功能,就是客户端通过命令把数据返回过来,服务器端接收后进行处理。
指令格式:
<命令> <键> <标记> <有效期> <数据长度>\r\n


<命令>command name
主要是三个储存数据的三个命令, set, add, replace
set 命令是保存一个叫做key的数据到服务器上
add 命令是添加一个数据到服务器,但是服务器必须这个key是不存在的,能够保证数据不会被覆盖
replace 命令是替换一个已经存在的数据,如果数据不存在,就是类似set功能


<键>key
就是保存在服务器上唯一的一个表示符,必须是跟其他的key不冲突,否则会覆盖掉原来的数据,这个key是为了能够准确的存取一个数据项目


<标记>flag
标记是一个16位的无符号整形数据,用来设置服务器端跟客户端一些交互的操作


<有效期>expiration time
是数据在服务器上的有效期限,如果是0,则数据永远有效,单位是秒,Memcache服务器端会把一个数据的有效期设置为当前Unix时间+设置的有效时间


<数据长度>bytes
数据的长度,block data 块数据的长度,一般在这个个长度结束以后下一行跟着block data数据内容,发送完数据以后,客户端一般等待服务器端的返回,服务器端的返回:


数据保存成功
STORED\r\n


数据保存失败,一般是因为服务器端这个数据key已经存在了
NOT_STORED\r\n



[ 数据提取命令]
从服务器端提取数据主要是使用get指令,格式是:
get <键>*\r\n


<键>*key
key是是一个不为空的字符串组合,发送这个指令以后,等待服务器的返回。如果服务器端没有任何数据,则是返回:
END\r\n


证明没有不存在这个key,没有任何数据,如果存在数据,则返回指定格式:
VALUE <> <标记> <数据长度>\r\n
<数据块>\r\n


返回的数据是以VALUE开始的,后面跟着key和flags,以及数据长度,第二行跟着数据块。


<键> -key
是发送过来指令的key内容


<标记> – flags
是调用set指令保存数据时候的flags标记


<数据长度> – bytes
是保存数据时候定位的长度


<数据块> – data block
数据长度下一行就是提取的数据块内容


 


[ 数据删除指令]
数据删除指令也是比较简单的,使用get指令,格式是:
delete <键> <超时时间>\r\n


<键> – key
key是你希望在服务器上删除数据的key键


<超时时间> – timeout
按照秒为单位,这个是个可选项,如果你没有指定这个值,那么服务器上key数据将马上被删除,如果设置了这个值,那么数据将在超时时间后把数据清除,该项缺省值是0,就是马上被删除


删除数据后,服务器端会返回:
DELETED\r\n
删除数据成功
NOT_FOUND\r\n
这个key没有在服务器上找到


如果要删除所有服务器上的数据,可以使用flash_all指令,格式:
flush_all\r\n

这个指令执行后,服务器上所有缓存的数据都被删除,并且返回:
OK\r\n


这个指令一般不要轻易使,除非你却是想把所有数据都干掉,删除完以后可以无法恢复的。



[其他指令]
如果想了解当前Memcache服务器的状态和版本等信息,可以使用状态查询指令和版本查询指令。


如果想了解当前所有Memcache服务器运行的状态信息,可以使用stats指令,格式
stats\r\n
服务器将返回每行按照 STAT 开始的状态信息,包括20行,20项左右的信息,包括守护进程的pid、版本、保存的项目数量、内存占用、最大内存限制等等信息。

如果只是想获取部分项目的信息,可以指定参数,格式:
stats <参数>\r\n
这个指令将只返回指定参数的项目状态信息。


如果只是想单独了解当前版本信息,可以使用version指令,格式:
version\r\n
将返回以 VERSION 开头的版本信息


如果想结束当前连接,使用quit指令,格式:
quit\r\n


将断开当前连接


另外还有其他指令,包括incr, decr 等,我也不太了解作用,就不做介绍了,如果感兴趣,可以自己去研究。


 


【Memcache在中型网站的使用】


使用Memcache的网站一般流量都是比较大的,为了缓解数据库的压力,让Memcache作为一个缓存区域,把部分信息保存在内存中,在前端能够迅速的进行存取。那么一般的焦点就是集中在如何分担数据库压力和进行分布式,毕竟单台Memcache的内存容量的有限的。我这里简单提出我的个人看法,未经实践,权当参考。


[ 分布式应用]
Memcache本来支持分布式,我们客户端稍加改造,更好的支持。我们的key可以适当进行有规律的封装,比如以user为主的网站来说,每个用户都有User ID,那么可以按照固定的ID来进行提取和存取,比如1开头的用户保存在第一台Memcache服务器上,以2开头的用户的数据保存在第二胎Mecache服务器上,存取数据都先按照User ID来进行相应的转换和存取。


但是这个有缺点,就是需要对User ID进行判断,如果业务不一致,或者其他类型的应用,可能不是那么合适,那么可以根据自己的实际业务来进行考虑,或者去想更合适的方法。


[ 减少数据库压力]
这个算是比较重要的,所有的数据基本上都是保存在数据库当中的,每次频繁的存取数据库,导致数据库性能极具下降,无法同时服务更多的用户,比如MySQL,特别频繁的锁表,那么让Memcache来分担数据库的压力吧。我们需要一种改动比较小,并且能够不会大规模改变前端的方式来进行改变目前的架构。


我考虑的一种简单方法:
后端的数据库操作模块,把所有的Select操作提取出来(update/delete/insert不管),然后把对应的SQL进行相应的hash算法计算得出一个hash数据key(比如MD5或者SHA),然后把这个key去Memcache中查找数据,如果这个数据不存在,说明还没写入到缓存中,那么从数据库把数据提取出来,一个是数组类格式,然后把数据在set到Memcache中,key就是这个SQL的hash值,然后相应的设置一个失效时间,比如一个小时,那么一个小时中的数据都是从缓存中提取的,有效减少数据库的压力。


缺点是数据不实时,当数据做了修改以后,无法实时到前端显示,并且还有可能对内存占用比较大,毕竟每次select出来的数据数量可能比较巨大,这个是需要考虑的因素。


上面只是我两点没有经过深思熟虑的简单想法,也许有用,那就最好了。


 


【Memcache的安全】


我们上面的Memcache服务器端都是直接通过客户端连接后直接操作,没有任何的验证过程,这样如果服务器是直接暴露在互联网上的话是比较危险,轻则数据泄露被其他无关人员查看,重则服务器被入侵,因为Mecache是以root权限运行的,况且里面可能存在一些我们未知的bug或者是缓冲区溢出的情况,这些都是我们未知的,所以危险性是可以预见的。


为了安全起见,我做两点建议,能够稍微的防止黑客的入侵或者数据的泄露。


[ 内网访问]
最好把两台服务器之间的访问是内网形态的,一般是Web服务器跟Memcache服务器之间。普遍的服务器都是有两块网卡,一块指向互联网,一块指向内网,那么就让Web服务器通过内网的网卡来访问Memcache服务器,我们Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。


# memcached -d -m 1024  -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid


Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接



[ 设置防火墙]
防火墙是简单有效的方式,如果却是两台服务器都是挂在网的,并且需要通过外网IP来访问Memcache的话,那么可以考虑使用防火墙或者代理程序来过滤非法访问。
一般我们在Linux下可以使用iptables或者FreeBSD下的ipfw来指定一些规则防止一些非法的访问,比如我们可以设置只允许我们的Web服务器来访问我们Memcache服务器,同时阻止其他的访问。


# iptables -F
# iptables -P INPUT DROP
# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT
# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT


上面的iptables规则就是只允许192.168.0.2这台Web服务器对Memcache服务器的访问,能够有效的阻止一些非法访问,相应的也可以增加一些其他的规则来加强安全性,这个可以根据自己的需要来做。


 


【Memcache的扩展性】


Memcache算是比较简洁高效的程序,Memcache 1.2.0 的源代码大小才139K,在Windows平台上是不可想象的,但是在开源世界来说,这是比较正常合理的。
Memcache目前都只是比较简单的功能,简单的数据存取功能,我个人希望如果有识之士,能够在下面两方面进行扩展。


1. 日志功能
目前Memcache没有日志功能,只有一些命令在服务器端进行回显,这样是很不利于对一个服务器的稳定性和负载等等进行监控的,最好能够相应的加上日志的等功能,便于监控。


2. 存储结构
目前的数据形式就是: key => data 的形式,特别单一,只能够存储单一的一维数据,如果能够扩展的话,变成类似数据库的格式,能够存储二维数据,那样会让可以用性更强,使用面更广,当然相应的可能代码效率和存取效率更差一些。


3. 同步功能
数据同步是个比较重要的技术,因为谁都不能保证一台服务器是持久正常的运行的,如果能够具有类似MySQL的 Master/Slave 的功能,那么将使得Memcache的数据更加稳定,那么相应的就可以考虑存储持久一点的数据,并且不用害怕Memcache的down掉,因为有同步的备份服务器,这个问题就不是问题了。


以上三点只是个人拙见,有识之士和技术高手可以考虑。


 


【结束语】


我上面的内容都只是自己安装和使用的一些想法,不能保证绝对正确,只是给需要的人一个参考,一个推广Memcache的文章,希望更多的人能够认识和了解这个技术,并且为自己所用。


我花费了整整一个晚上的时间洋洋洒洒的写了这么长,无非是对于这项开源技术的热爱,我想开源世界能够繁荣起来,就是源于大家的热爱并且愿意做出贡献,开源世界才这么精彩。


希望本文能够给需要的人一些帮助,希望不会误导他们,呵呵。

Spring中基于aop命名空间的AOP


本文地址:http://www.blogjava.net/cmzy/archive/2008/08/23/223870.html
下篇地址:Spring中基于aop命名空间的AOP 二(声明一个切面、切入点和通知)


    在某些时候,我们工程中使用的JDK 不一定就是1.5 以上,也就是说可能不支持Annotation 注解,这时自然也就不能使用@AspectJ 注解驱动的AOP 了,那么如果我们仍然想使用AspectJ 灵活的切入点表达式,那么该如何呢?Spring 为我们提供了基于xml schematic 的aop 命名空间,它的使用方式和@AspectJ 注解类似,不同的是配置信息从注解中转移到了Spring 配置文件中。在这里,我们将详细介绍如何使用Spring 提供的<aop:config/> 标签来配置Spring AOP 。



1 、一点准备工作和一个例子


    使用<aop:config/> 标签,需要给Spring 配置文件中引入基于xml schema 的Spring AOP 命名空间。完成后的Spring 配置文件如下(在该节,所有例程的配置文件中添加了Spring AOP 命名空间,除非特殊情况外,为了节约空间,这部分将在给出的代码中省略),粗体内容即为我们需要添加的内容:






  1. <?xml version=“1.0” encoding=“UTF-8”?>  

  2. <beans xmlns=“http://www.springframework.org/schema/beans”  

  3.         xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  

  4.         xmlns:aop=“http://www.springframework.org/schema/aop”  

  5.         xsi:schemaLocation=”http://www.springframework.org/schema/beans   

  6.               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd    

  7.               http://www.springframework.org/schema/aop    

  8.               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd >  

  9. ………… Spring配置信息   

  10. </beans>  


    关于aop命名空间的标签,我们前面使用过的有<aop:aspectj-autoproxy/>,在这一节,我们将以<aop:config/>标签作为重点。事实上,我们在这一节介绍的所有标签都是该标签的子标签。



   下面有一个例程来直观的展示如何使用<aop:config/>标签来配置Spring AOP(完整代码见例程4.15)。在例子中,我们使用<aop:config/>配置一个切面并拦截目标对象Peoples的SayHello()方法,在它执行前输出提示信息。
首先创建工程AOP_Test4.15,添加Spring IoC和Spring AOP库后,创建aop.test包,新建目标类People,代码如下:






  1. package aop.test;   

  2.   

  3. /**  

  4.  * 该类将作为目标对象对应的类。  

  5.  * @author zhangyong  

  6.  * */  

  7. public class People{   

  8.   

  9.         public String SayHello(String str){   

  10.                 System.out.println(this.getClass().getName()+ “说:”+str);   

  11.                 return str;   

  12.         }   

  13. }   


    修改Spring xml配置文件,将该类注册为一个受管Bean:






  1. <bean id=“TestBean” class=“aop.test.People” />  


    创建含有main()方法的测试类TestMain,从Spring IoC容器中获取Peoples对象,并调用其SayHello()方法,代码如下:






  1. package aop.test;   

  2.   

  3. // import省略   

  4. public class TestMain {   

  5.         public static void main(String[] args) {   

  6.                 // 实例化Spring IoC容器   

  7.                 ApplicationContext ac = new ClassPathXmlApplicationContext(   

  8.                                 “applicationContext.xml”);   

  9.                 // 获取受管Bean的实例   

  10.                 People p = (People) ac.getBean(“TestBean”);   

  11.                 p.SayHello(“传入的参数值”);   

  12.         }   

  13. }   


   创建MyAspect类,添加一个beforeAdvice()方法作为前置通知方法,代码如下:






  1. package aop.test;   

  2.   

  3. import org.aspectj.lang.JoinPoint;   

  4.   

  5. public class MyAspect {   

  6.            

  7.         public void beforeAdvice(JoinPoint point) {   

  8.             System.out.println(“前置通知被触发:” +    

  9.                                 point.getTarget().getClass().getName()+    

  10.                                 “将要” + point.getSignature().getName());   

  11.         }   

  12. }   


    修改xml配置文件,为其添加aop命名空间,并把MyAspect注册为一个受管Bean,作为我们下面定义切面的backing bean。代码如下:






  1. <?xml version=“1.0” encoding=“UTF-8”?>  

  2. <beans xmlns=“http://www.springframework.org/schema/beans”  

  3.         xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  

  4.         xmlns:aop=“http://www.springframework.org/schema/aop”  

  5.         xsi:schemaLocation=”http://www.springframework.org/schema/beans   

  6. http://www.springframework.org/schema/beans/spring-beans-2.5.xsd    

  7.                http://www.springframework.org/schema/aop    

  8. http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>  

  9.   

  10.         <bean id=“MyAspect” class=“aop.test.MyAspect” />  

  11.         <bean id=“TestBean” class=“aop.test.People” />  

  12.            

  13.         <aop:config proxy-target-class=“true”>  

  14.                 <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  15.                         <aop:pointcut id=“testPointcut”  

  16.                                 expression=“execution(* aop..*(..))” />  

  17.                         <aop:before pointcut-ref=“testPointcut”  

  18.                                 method=“beforeAdvice” />  

  19.                 </aop:aspect>  

  20.         </aop:config>  

  21. </beans>  


    运行主类,输出如下:


例程4.15输出结果


例程4.15输出结果


 


2、声明一个切面
      在基于AOP命名空间的Spring AOP中,要声明一个切面,需要使用<aop:config/>的子标签<aop:aspect>。<aop:aspect>标签有一个ref属性必须被赋值,它用于指定和该切面关联的受管Bean(backing bean,以后我们都将使用Backing Bean来称呼这样的Bean)。正如下例所示,该Bean对应的java类是一个普通的java类,在该类中定义了切面的通知方法。此外,<aop:aspect>标签还有两个可选的order属性和id属性,order属性用于指定该切面的加载顺序,id属性用于标识该切面。范例如下:





  1. <?xml version=“1.0” encoding=“UTF-8”?>  

  2. <beans ……>  

  3.         <bean id=“MyAspect” class=“aop.test.MyAspect” />  

  4.         <aop:config proxy-target-class=“true”>  

  5.                 <aop:aspect ref=“MyAspect” order=“1” id=“TestAspectName”>  

  6.                         ……切面其他配置   

  7.                 </aop:aspect>  

  8.         </aop:config>  

  9. ……其他配置   

  10. </beans>    

 



3、声明一个切入点
      要声明一个切入点,可以使用<aop:aspect>的子标签<aop:pointcut>,在Spring2.5中它有两个属性id和expression,分别用于标示该切入点和设定该切入点表达式。例如:






  1. <?xml version=“1.0” encoding=“UTF-8”?>  

  2. <beans ……>  

  3.     <bean id=“MyAspect” class=“aop.test.MyAspect”/>  

  4.     <aop:config proxy-target-class=“true”>  

  5.         <aop:aspect ref=“MyAspect” order=“1” id=”TestAspectName”>  

  6.               <aop:pointcut id=“test”  

  7.                 expression=“execution(* aop.test.TestBean.*(..))”/>  

  8.               <aop:before pointcut=“aop.test.MyAspect.Pointcut1()”    

  9.                                method=“beforeAdvice” />  

  10.         </aop:aspect>  

  11.     </aop:config>  

  12. ……其他配置   

  13. </beans>  

 




<aop:pointcut>标签的expression属性使用前面介绍的切入点表达式语言,也就是说支持AspectJ切入点表达式。但是由于xml对”&&”、”||”、”!”等逻辑运算符不友好,@AspectJ切入点表达式语言中使用的这些逻辑运算符在xml配置中需要分别用”and”、”or”和”not”来代替。
有时候,我们也需要在xml中使用@Pointcut注解声明的切入点,那么该如何呢?大家可能记得,我们可以在切入点表达式中可以引用另一个切入点。对了,就在这里,我们使用该特性可以完成这个任务,如下:






  1. <aop:pointcut id=“test”   expression=“aop.test.MyAspect.Pointcut1()” />  

 



注意:这里我们必须使用全路径来标示引用的切入点。


4、 声明一个通知
      和@AspectJ一样,基于AOP命名空间的配置也可以定义五种通知类型,并且使用方式和特性类似。与@AspectJ不同的是,配置信息从Annotation中转移到了xml配置文件。
    1)、前置通知
    声明一个前置通知可以使用<aop:aspect>的子标签<aop:before/>。该标签的属性说明如下表:

<aop:before/>标签属性说明





















属性


说明


pointcut


指定该通知的内置切入点


pointcut-ref


通过 id 引用已定义的切入点


method


指定通知对应的方法,该方法必须已在切面的 backing bean 中被声明


arg-names


通过方法的参数名字来匹配切入点参数


      对于一个通知来说,切入点和对应的通知方法是必须的。也就是说,在这些属性中,method属性是必须的,我们必须要给通知指定一个对应的方法;pointcut属性和pointcut-ref必须有一个被指定,以此确定该通知的切入点。范例如下:




  1. <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  2.     <aop:pointcut id=“testPointcut”  

  3.         expression=“execution(* aop.test.TestBean.*(..))”/>  

  4.     <aop:before pointcut-ref=“testPointcut” method=“beforeAdvice”/>  

  5. </aop:aspect>  

 



     2)、 后置通知
     声明一个后置通知使用<aop:after/>标签,它的属性等和<aop:before/>标签类似,下面是范例:




  1. <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  2.     <aop:pointcut id=“testPointcut”  

  3.         expression=“execution(* aop.test.TestBean.*(..))” />  

  4.     <aop:after  pointcut-ref=“testPointcut” method=“AfterAdvice”/>  

  5. </aop:aspect>  

 



     3)、 返回后通知
      <aop:after-returning/>标签可以声明一个返回后通知,该标签的属性和<aop:before/>相比它多了一个returning属性。该属性的意义类似于@AfterReturning注解的returning属性,用于将链接点的返回值传给通知方法。用法如下:




  1. <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  2.     <aop:pointcut id=“testPointcut”  

  3.         expression=“execution(* aop.test.TestBean.*(..))” />  

  4.     <aop:after-returning pointcut-ref=“testPointcut”  

  5.         method=“AfterReturnAdvice” returning=“reVlue” />  

  6. </aop:aspect>  

 



       4)、 异常通知
        声明一个异常通知使用<aop:after-throwing />标签,它有一个类似于throwing属性又来指定该通知匹配的异常类型。用法如下:




  1. <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  2.      <aop:pointcut id=“testPointcut”  

  3.         expression=“execution(* aop.test.TestBean.*(..))” />  

  4.      <aop:after-throwing pointcut-ref=“testPointcut”  

  5.         method=“afterThrowingAdvice” throwing=“throwable” />  

  6. </aop:aspect>  

 



      5)、 环绕通知
      环绕通知是所有通知中功能最强大的通知,用<aop:around/>标签来声明。用法如下:




  1. <aop:aspect ref=“MyAspect” order=“0” id=“Test”>  

  2.     <aop:pointcut id=“testPointcut”  

  3.         expression=“execution(* aop.test.TestBean.*(..))” />  

  4.         <aop:around pointcut-ref=“testPointcut” method=“aroundAdvice”/>  

  5. </aop:aspect>  




(完)


click Web Framework 1.4发布

click Web Framework 1.4发布,Click是一个高性能的J2EE Web应用程序框架适用商业Java开发者,它是基于页面和组件的java web框架,基于事件编程模型,使用Velocity模板作为页面视图,没有复杂的抽象和定义,简单易学,商业开发者能够在一天内把它运行起来。

1.4版本的新功能包括:

1。Stateful page支持,使得开发复杂页面和页面流程更加简单




Java代码


  1. package com.mycorp.page;   

  2.   

  3. import java.io.Serializable;   

  4.   

  5. import net.sf.click.Page;   

  6.     

  7. public class SearchPage extends Page implements Serializable {   

  8.   

  9.     public SearchPage() {   

  10.         setStateful(true);   

  11.         ..   

  12.     }   

  13. }   



2。新的Control event函数 – onInit(), onRender() and onDestroy().

3。执行性能增强

4。新的PerformanceFilter使你的应用程序付出微小代价就能采用Yahoo style performance


 


5。新增对Cayenne ORM versions 2.x and 3.x的支持


 


。。。


 


如果对click Web Framework 感兴趣 请访问:


Developing Applications with the Java APIs for Bluetooth (JSR-82)

Developing Applications with the Java APIs for Bluetooth (JSR-82)



This paper covers the Java API for Bluetooth (JSR-82) with respect to Sony Ericsson devices. It starts by introducing the Bluetooth technology, followed by the Java APIs for Bluetooth, and how to use them.


Currently, these APIs are currently available in the Sony Ericsson P900/P908 handsets.


Click here to view “Developing Applications with the Java APIs for Bluetooth (JSR-82)”



Click here to view the Training Materials (.zip) associated with this article.


AS3日积月累(1) – AS3的面向对象特点概述

原文章地址:http://as3blog.com/as3/as3tip-oop/


本文是我(aw)在整理了相关文档和讨论之后,结合自己的亲自实验总结出来的一些经验和心得。我尽量描述详尽,避免模糊概念,当然也希望所有看官提出批评意见。为了表述方便,其中术语不限定语言,如我可能会一会儿用class,一会儿用“类”。


面向对象的难点部分就是理解变量作用域修饰符(modifier)其实也就是面向对象中我们已经熟悉的public、protected、private等等。本文还深入讨论了ActionScript3中新增的internal等概念。下面我依次列出:


一、关于package以及internal
package,用“形而上学”的方式理解,就是物理目录下的类集合。在AS2中只需要保证文件系统的路径匹配,然后用类似“import com.awflasher.someUtils”的方法导入即可。而AS3则要求您在所有的类中声明package关键词。package的大括号对“{}”内,我们只能定义一个类,我们可以在这个大括号外面定义一些辅助类,不过这些类只能被当前这个类(你在package大括号对内定义的类)访问。当然,一个package大括号对内只有一个类,这并不代表一个package内只有一个类。你可以在同一目录下定义多个属于该package(指代这个目录)的类。它的意义绝不是简单的“类文件集合容器”,而是一个让各种应该协同工作的类集中到一起的项目包。值得一提的是,所谓“协同工作”是指至少有一个class要引入其他一些class来进行功能设计,而这时候采用internal修饰可以省去很多getters和setters。我自己回忆起在湖南卫视的项目中用AS2开发的Vplayer,两个类AVCore和AVControl就有很多getter和setter,搞的特别麻烦。internal类似public,但限定在一个package内了。在同一个package内的类可以访问同一个package内其他类的internal变量,而其他包内的类无法访问。package与类的继承性毫无关系,比如TextField和Sprite、MovieClip都继承自DisplayObject类,但TextField属于flash.text包,而MovieClip和Sprite属于flahs.display包。也就是说,包对类的限定是与继承链毫无关联的、一个新的“维度”的限定。
附:使用一个类的时候,我们必须import这个类,或者包含这个类的package。AS2时直接写完整包路径的使用方法在AS3中不管用了,本文后面有详细介绍。


二、关于public
public定义的类或者属性可以在任何作用域内由任何来源访问。构造函数永远都是public的,Flex中的应用程序类(Application Class)和Flash CS3中的文档类(Document Class)必须是public的。且不能缺省public这个关键词声明。我在测试中发现,如果不声明public,Flash根本就不会获取类的定义,进而编译无法通过。
非常有必要啰嗦一下:public可以穿越package,而类又不能使用namespace(参阅FlashCS3帮助文档:Applying namespaces)。因此,所有被文档类调用的其它包中的类,应该一致声明为public的 。因为文档类在一个独立的包中。


三、关于protected
protected声明类似AS2的private,它定义的属性只能在自己子类中可见,而其它场合都是不可见的。这一点与Java等传统OOP语言类似。


四、关于private
注意AS3的private和AS2的private大不相同,它定义的属性只属于自己,子类可以定义毫无牵连的同名属性。
dynamic 和原来AS2的dynamic一样,用dynamic声明的类可以动态的加入属性。这些属性也可以通过delete来删除。动态加入的属性一旦被切断所有的引用就会被垃圾回收机制自动回收。有时候用System.totalMemory检测不到内存释放是因为垃圾回收机制并不是即时运行的。


五、关于dynamic
动态(dynamic)类允许在运行时动态地添加属性,常见的动态类有MovieClip和顶级(top-level)的Array。如果您的自定义类要继承于动态类,那么请也定义为动态的,不要省略dynamic关键词。如果您喜欢阅读英文教程,会看到很多“sealed class”,其含义即“非dynamic class”。只不过并没有sealed这个关键词(AS3中,类默认就是sealed的)。注意,在AS2中,“骨子里”是没有sealed class的!在run-time时,所有的AS2代码都变成了AS1的语法,sealed class无从说起了。可以说这是AS3的一个新产物。更多相关内容可以参见:http://as3blog.com/as3/as3tip-take-care-of-resource


六、关于继承(extends)和override
继承其实并不太复杂,唯一要说明的就是:子类的构造函数一定要用“super”调用一次父类的构造函数,否则报错!对于继承后的子类,如果要重新定义父类的非private方法,必须使用override关键词。在override的时候,如果我们需要调用父类的方法,可以使用super关键词(由于继承方法在逻辑上与父类往往有相似性,因此没有必要把方法逻辑完全重写)官方帮助中的这个例子非常易懂:
package {
import flash.display.MovieClip;
public class SuperExample extends MovieClip
{
public function SuperExample()
{
var myExt:Extender = new Extender()
trace(myExt.thanks()); // output: Mahalo nui loa
}
}
}


class Base {
public function thanks():String
{
return “Mahalo”;
}
}


class Extender extends Base
{
override public function thanks():String
{
return super.thanks() + ” nui loa”;
}
}


override不能用于重载变量(成员属性)。但是却可以用于重写getter和setter函数,例如:(官方帮助的例子)
package
{
import flash.display.MovieClip;
public class OverrideExample extends MovieClip
{
public function OverrideExample()
{
trace(currentLabel)
}
override public function get currentLabel():String
{
var str:String = “Override: “;
str += super.currentLabel;
return str;
}
}
}
这个例子中,我们直接重写了MovieClip类的currentLabel属性。注意调用父类属性的时候,用了super.currentLabel。
关于静态方法,比较麻烦。首先,静态方法是无法被重载的。必须通过类来访问。但是您也可以自己定义与静态方法同名的方法,我把官方的例子做了一下修改就一目了然了:
package
{
import flash.display.MovieClip;
public class StaticExample extends MovieClip
{
public function StaticExample()
{
var myExt:Extender = new Extender();
}
}
}


class Base {
public static var test:String = “static”;
}


class Extender extends Base
{
private var test:String = “instance”;
public function Extender()
{
trace(Base.test); // output: static
trace(test); //added by awflasher.com, output: instance
}
}
七、关于import语法
在AS2时代,“import”语法只是为了让编程时代码简洁(省去了包名),比如我们import了mx.transitions.Tween之后,就可以直接new Tween()了。而如果不import,我们也可以直接用全类名来构造实例,例如:
new mx.transitions.Tween();
然而,在AS3中,无论是否采用全名类声明,只要你用到这个类,就必须import。
import估计是传统Flash程序员所需要养成的一个最大的习惯,在刚切入AS3开发平台的时候,我常常忘记import一些常用的类,例如从IDE指向过来的文本和渲染元件,以及flash.event.*、flash.display.StageAlign等比较泛用的类。
AS3中不像AS2那样,我们不能用_root和Stage[”displayState”]来进行hacks了。(参见上面“五-关于 dynamic”)


八、关于编译时的注意事项
AS3不再像AS2那样简单地Complie-Time(编译时,即FlashCS3/FlexBuidler/其他编译器发布ActionScript及所有资源为SWF文件的那一时刻)进行类型检测了,AS3在Run-Time(运行时,级Flashplayer或者其他播放SWF的软件在播放SWF的时候)也有类型检测,因此AS2的Hacks(强制访问不能访问的属性)将不再有效。


九、一个不得不说的好消息
AS3中类所有的变量(属性)和函数(方法)的作用域都是运行时跟类/实例走的。这与AS2大有不同,我们不再需要去Delegate了。只要是类自己的方法,在调用的过程中,this永远指向类自己(实例)。


简要总结:
1、如果我需要属性公开,并且可以被自己的子类继承,那么定义成public的。子类也可重写(override)之。
2、如果我需要属性隐藏,但是可以被自己的子类继承,那么定义成protected的。与public类似,子类也可重写(override)之。
3、如果我的某一个类有一个属性不想在任何位置可见,包括其子类,那么定义为private的。其子类无需重写(override),因为它根本就不存在于子类中。


AS3日积月累(2) – 从AS1和AS2到AS3的观念转变


原文章地址:http://as3blog.com/as3/as3tip-new-philosophy/


AS1/2-AS3观念的转变(Meet with new philosophy)
对于AS1、AS2的开发模式来说,灵活是最大的优势。然而,灵活却造成了不稳定、紊乱。这是开发复杂的、长久的项目所忌讳的。关于(AS1/2/1+2)灵活轻便与稳定持久(AS3)的权衡,我个人觉得可以理解为”鱼和熊掌不可兼得”,但我希望已经习惯了AS1、AS2的朋友们不要把这个结论想得太悲观。


AS3是纯粹面向对象的,相比过去的AS2,我认为是更加敏捷的。纵然有着更多的约束,但在package内直接建立多个辅助类(Helper Class),不失为一个非常好的消息。就凭这一点,我觉得至少与笨拙的AS2相比,AS3的开发效率就不会打多大折扣。我们需要的其实只是语法、习惯,尤其是观念的转变而已。当然,这需要时间。我作为一个AS1/2的长期发开人员,在转变到AS3的过程之中,也遇到了很多问题和疑惑。但我很乐于与大家分享、交流我所获得的收获以及观念转变的心路历程。


ActionScript编程自它问世的那一天就是多姿多彩的。技术,尤其是Adobe产品线的技术体系,也绝然不是呆板的”学究式体系”。我希望我的”罗嗦”能让您获得一个更轻松的心态。


言归正传,先说说我在AS1/2(1+2)转变到AS3时所遭遇的最大困惑吧:
开局(How, and especially where, to get start) – 玩过星际争霸的朋友们一定知道,针对不同的地图,如Lost Temple和WCG-groky park(原来WCG有一个岛关,我忘记了),都有各自的经典、流行的开局方式。从AS1/2转变到AS3,无非是从Lost Temple转变到WCG-groky park的过程,你也许要先采气矿,造空军,才能顺利发展。


其实Flash从AS1到AS3,也有各自固定的、流行的开局方式。
对于习惯了用AS1编程的人来说,制作一个Flash的开局是非常灵活的:你一进入Flash就有一个长长时间轴以供使用。你往往需要一个loading,你可以用1-5帧先做一个loading(还记得N年前流行的FlashMTV制作教程么?);你也可以取一帧,放一个loading的MovieClip然后在这个MovieClip上写一个onEnterFrame来监听swf文件加载的进度(我热衷的做法)。接下来,你可以在第二帧或者第N帧部署程序界面。MovieClip强大的帧API能让你灵活地完成许多有趣的逻辑(gotoAndPlay、gotoAndStop、prevFrame等)。编程的时候也可以很随意地寻找自己要控制的资源,我现在还记得刚接触AS的时候,一个_root一个_global,曾经让我屡试不爽。每次遇到问题了就用这两个东西解决。
AS2的开局其实没有本质的变化,至少我是这么认为的。唯一的进步就是比AS1的OOP,模块封装的更加彻底。甚至还有些许退步,比如清一色基于MovieClip+attachMovie的模式,仍然容易造成运行时(Run-Time)效率低下,而且开发起来概念也模糊了。因为Library中设置了linkage,new的明明是自己的Class,attach的还是MovieClip。
于是很多人采用AS1+2的方式,这也是我所喜欢的。现在想起来,还是比较灵活快速的。
然而在AS3中,你却仿佛陷入一片黑暗。FlexBuilder没有时间轴。即便用”似曾相识”的FlashCS3的IDE开发,AS3也不支持MovieClip和Button上的代码。写在帧上也无法简单地使用”onRelease=functioin”了。上网搜一搜教程,往往得到如下的写法:
aw.addEventListener(”click”,fun);
function fun(e:Event){trace(1);}
实在让习惯了AS1、2的朋友们郁闷。


一方面看到人家用AS3设计出来的精彩demo羡慕不已,一方面又对程序入口摸不着边际。这种尴尬我想不是看一两篇教程就能解决的。


我们需要”洗心革面”,我们需要”忘记过去”(try to forget the past)。大胆地告诉自己,onRelease=function不仅已经被”杀死”,而且根本就不是好的写法,哪怕你仍然觉得它看起来那么顺眼。大胆地告诉自己,AS3中,所有的变量、函数都属于类(对象的属性和方法),而不再属于时间轴、帧,哪怕上面列举的两行代码也可以写在时间轴上生效。


我个人建议,传统AS1/2的程序员从Flash CS3 IDE入手AS3,比较合适。因为Flash CS3的入口(开局)非常明确:Document Class(文档类)。


运行FlashCS3,打开fla文件,在IDE下面属性面板中,找到”Document Class”,填入一个名字(由于是类名,最好是首字母大写,比如MyMainClass)。然后在fla文件所在的文件夹下面建立同名的as文件。当然,也可以把fla和类文件全部分离,这就需要设定类路径(File-Publish Settings-ActionScript version:Settings)。下面可以输入类路径。我个人建议输入相对路径。相对,意即相对当前的fla文件;路径,即我们电脑文件系统中的文件夹。不写死”x:\xxx”是为了让项目可以在不同的环境上运行,也可以更好的支持多人开发。相对路径的写法就是用”.”表示当前路径,用”..”表示上一级路径。比如可以写:
“./classes/”或者”../classes/”。
这里再补充说明一下,我的建议是把原文件放在一起,输出的swf放在别的目录(通常叫做”bin”)。输出目录在刚才面板中的”format”标签下,可以把原文件放到目录”src”中,然后把swf格式的file名设置为”../bin/somefile.swf”,建议只输出swf。HTML还是自己写的好。
别看我罗嗦了这么多篇幅讲这些设置,但它们真的对于规范你的开发习惯和开发观念有好处。让你潜移默化的接受AS3的Philosophy中的”分离”思想。


补充内容: 关于Flash CS3类路径的设置,如果您希望设计自己的package。那么则需要把这个package放在classpath下面,而不要把package文件夹自己设置为classpath


切入正题,我们逐步开局:
一、建立文档类(Document Class)
现在我们可以开始建立Document Class了。Flash CS3方便地提供了一个”编辑图标”,你可以方便地打开类文件。回忆一下,上一篇文章提到关于类的书写:每一个类都应该在一个package中。我个人的理解,觉得Document Class应该在一个单独的、无具体名称的”generic”package中,即:
package
{
import flash.display.Sprite;
public class MyMainClass extends Sprite
{
public function MyMainClass()
{
init();
}
private function init()
{
// do sth
}
}
}
// We can even use some help classes
class MyMainClassHelper{}
这里,我们就成功”开局”了。
注意,这个文档类必须为public的。而辅助类则不能定义为public、private的,必须是internal的。文档类必须继承自Sprite或者MovieClip。因为这个文档类代表了这个swf,显然swf是一个需要在屏幕上渲染显示(flash.display.DisplayObject)并提供资源承载能力(flash.display.InteractiveObject)的基础容器。


二、逻辑开局(Initialize the logic)
我们所有的逻辑入口都是从这个类的构造函数开始的。AS3的loading有一些麻烦,我们暂时跳过(稍后会介绍)。
构造函数一般要保持简洁,不妨用流行的init方式开局,即在构造函数内调用一个init函数。记住一点,AS3中,”_root”已死,这里就是传统意义上的”_root”了。你看到的这个类(文档类),第一反应应该是这个swf文件(就如同你原来看到”_root”就应该反应到swf文件一样)!在这里可以找到原来我们需要的许多资源,例如我们可以找到通过loaderInfo:LoaderInfo属性(继承自DisplayObject),获取外部参数:xxx.swf?somevar=1传进来的”somevar”,也可以通过stage:Stage属性(继承自DisplayObject),来进行原来的Stage类的各种操作。我也可以用contextMenu:ContextMenu属性(继承自InteractiveObject),来控制flash右键菜单的内容。
这一切都在文档类的init以及其他所属方法中进行。所有的其它功能,可以封装成别的类、包进行”模块式”调用。


三、事件机制(The new Event System)
习惯新的事件机制所花的勇气,我认为和开局相当。我曾经热衷于xxx_mc.onRelease = function(){}的写法,而且做过N多这样的项目。然而当我真正开始用addEventListener的时候,才发现这是多么优雅的写法。优雅在哪:



  1. 统一:只有addEventListener,没有addListener、没有on(…),代码可以统一地放置。
  2. 清晰:事件处理函数作为类的方法(Methods)列举分明,试想一个跟在onXXX后面的赋值函数放在代码当众多难找。
  3. 信息翔实、准确:新的事件机制通过传递Event对象让事件的信息完整无漏地传达给接受方;函数(方法)与类绑死,Delegate终于可以光荣退休了。

四、总结



  1. 接受新的OOP开发体系:类/对象(class/object)+构造函数(constructor)+成员属性(properties)+成员方法(methods),除了这些东西以外,ActionScript没有别的存在形式!把时间轴和实例上的代码都忘记吧!
    我们要”拥抱”类的概念!AS3中所有的一切围绕着类的概念进行。swf就是一个类,用Flex开发,叫做Application,用CS3,叫做Document Class(往往继承自Sprite)。任何变量(属性)都属于一个类,MovieClip有成员属性currentScene,它是Scene类的一个实例;Sprite有成员属性contextMenu和stage,它们继承自DispatchObject类,分别是ContextMenu类和Stage类的实例。
  2. 没有_root,所有的_root有关的操作,封装到文档类中的成员函数(方法)进行。_root不再是swf的代表,取而代之的是Document Class(或者Flex中的Application Class)
  3. 功能模块化、分离:
    不要把所有的事情都塞到文档类中去做,哪怕你可以定义很多辅助类,毕竟独立出来的文件更加便于管理、集成、再使用。而且界面(图形、动画)和代码要分离(事实上AS3的Document Class和addChild的内容管理体系帮我们完成了这个操作) 。无论你是一个人搞定代码+设计还是有一个团队协作分工编程与动画。分离的好处是让你的功能更加强大和易维护。

AS3日积月累(3) – 利用AS3的图形界面开发及资源管理攻略

原文章地址:http://as3blog.com/as3/as3tip-take-care-of-resource


摒弃了attachMovie之后的AS3,采用了类似DOM的操作方式。addChild、removeChild、getChildAt等方法开始成为AS3中显示(在屏幕上渲染)、操作图形的主要方法。由于AS1、AS2完全是依赖于attchMovie的思想,因此对于传统Flash开发人员来说,转变到新的addChild的确需要下一番功夫。


由于新的“DisplayObject”在内存的使用上非常“敏感”。往往由于不良的编程习惯会造成不必要的内存泄漏,因此,我们不得不比AS1、AS2时代更加深入到内存管理了。我想,每一个Flash开发人员,包括我自己,都应该花一番功夫仔细体会“内存管理”这几个字的含义。毕竟我们学习AS3是为了开发比AS1、2时代更加先进、高效而且内存占用小的应用程序,如果还是开发一些简单的应用,也就失去我们每一个人使用AS3的意义了。


我觉得,由于GC是Flash应用程序内存管理的核心,我应该先从AS3中的Garbage Collector(简称GC)开始说起。AS3的GC功能比AS1、2中的要强大的多。然而,强大的同时,也带来了一定程度的复杂性。但是也不至于非常复杂,我觉得比C++等传统语言要容易掌握得多。


在研究内存如何回收之前,先说一下变量的创建:
在Flash中,我们每建立一个非原生变量时(Boolean, String, Number, uint, int这些是原生变量),这个变量名只是一个reference(指向,有时候也成为“引用”)而已,而并非这个变量本身。例如:
var a:int = 5; //a就是5
var b:int = a; //b是a,也就是5
a = 4;
trace(b); // 是5,而不是4!
//改变b的值,a不发生变化。反之亦然
var c:Object = {name:”aw”, blog:”www.awflasher.com/blog”}; //c只是指向一个内部的Object,为了描述方便,称其为“O”
var d:Object = c; //d指向c,指向了同一个Object“O”
c.name = “bw”
trace(d.name); //不是aw,而是bw了
//改变c也好,改变d也罢,其实是改变了那个“O”,因此改变c的时候,d的值也就变了。因此d.name已经被改变为了“bw”。


首先,明确一点,GC会按照一定的时间周期进行内存清理(memory sweep)。因此不要因为delete掉一个object后检查System.totalMemory内存就没有反应而怀疑GC是否正常。


那么GC为什么要按照一定的时间周期进行清理呢。这还要得从GC的具体工作原理说起。


GC的两个回收体系:
1、“Reference Counting” – 引用计数器
这个体系是自从AS1时代就有的体系,它的工作原理非常简单:系统计算每一个对象被指向,或者说引用的次数。比如
var a:Object = {name:”aw”, blog:”www.awflasher.com/blog”};
这时候,这个Object(我们仍然称为“O”),有一次引用,它的引用计数器为1(来自a)。
我们再建立一个对象b,并指向到a:
var b:Object = a;
这时候“O”引用计数器变为了2(来自a、b)
我们删除一个,比如先删除b:
delete b;
这时候引用计数器为(2-1=1)1,GC不操作
再删除另外一个a:
delete a;
“O”引用计数器变为(1-1=0)0,GC出面干掉这个对象。
这套体系很轻便,CPU压力较小。但是它也有缺陷。当我们的对象内部互相引用的时候,麻烦就来了。例如:
var a:Object = {name:”aw”, description:”unknown”};
// 建立一个对象a,仍然假设内部对象为“O”,这时O的引用次数为1
var b:Object = {nameObj:a, url:”awflasher.com”};
// b引用了a,同时创建了新的内部对象“P”。这时O的引用次数为2,P为1
a.myDescription = b;
// a的myDescription属性指向到了b。这样,P的引用次数也为2了。
// 是不是有点头晕?静下来,画个图慢慢看看:)
delete a;
delete b;
两次delete操作后,O、P的引用次数都是1,它们将继续占用你的内存。“Reference Counting” 体系无能为力了。


2、“Mark Sweeping” – 标记清除法则
GC的第一种机制“Reference Counting”,在FlashPlayer8之前是GC唯一的机制。FlashPlayer6和7由于引入了复杂的OOP开发模式,尤其是7引入了类似Java、C++等强大OOP语言的语法。利用Flash设计的复杂项目越来越多。由于Flash开发人员大多不了解GC,而Java、C++的开发人员又已经习惯了强大的GC(无论是自动的还是手动的)。因此FlashPlayer6、7的内存问题开始浮现出来。
Okay,Flash Player8引入了新的“Mark Sweeping”机制。我想这也是当年Marcomedia(Adobe)急于推出Player8的原因吧!(还记得当年Flash8的介绍视频么,效率提高是一个革命性的改进)
下面就来讲述“Mark Sweeping” – 标记清除法则的工作原理。
FlashPlayer会从root开始,遍历系统的每一个变量,并对有指向的对象之间,记录一次联系。在遍历结束之后,凡是与root不相联系的对象,被FlashPlayer无情地干掉:)
Okay,回到刚才的例子,当我们delete a,并delete b之后,root与O、P就划清了界线。这时候,GC就可以进行一次肃清了。
然而,由于这种“Mark Sweeping”要遍历所有的对象,因此非常消耗资源。这也就回到了当初的问题:“GC为什么要按照一定的时间周期进行清理“ – 因为不能给CPU造成太大的负担。
我个人猜测,GC的内部清除策略应该是在某一次事件(例如delete)发生后,在CPU比较空闲、RAM分配相对合理的情况下执行的。


3、关于delete。Gskinner《Understanding Delete》说的很清楚:
delete在AS3中,只能删除dynamic类的对象实例的属性/方法了。这意味着你不能像AS2那样用delete随意删除成员了。当然,替代方法还是有的:把成员设置为null即可。例如myobj.name=null;


Okay,不要以为有了“Mark Sweeping”就万事大吉了。由于GC不会即时进行,因此你的对象会在一段时间内“阴魂不散”!对于一个追求完美的开发人员来说,这意味着它们内部的某些机制会在被删除之后继续工作:AS语句会继续执行、声音会继续播放、事件会继续触发!


KirupaForum有网友说,“All you got to do is pray the garbage collector doesn’t break down.”,确实,如果一个应用程序要运行上一个多小时,那么慢慢流逝的内存会让你的用户对你的产品失望(例如游戏)。因此我们需要有一个良好的资源管理策略。