11.1.3注册 native

2016-06-24 08:57:00 weixin_33866037 阅读数 32

卢海东

 

第1章 揭开神秘面纱——Android系统简介 1  
1.1 认识Android系统 2  
1.1.1 Android成长历程 2  
1.1.2 发行版本 3  
1.1.3 得到大家的认可——Android系统的市场份额 3  
1.2 Android架构解析 4  
1.2.1 Android系统架构图 4  
1.2.2 应用程序(Applications) 5  
1.2.3 应用程序框架层(Framework) 6  
1.2.4 系统运行库(Libraries) 7  
1.2.5 Linux内核 11  
1.3 小结 11  
第2章 工欲善其事,必先利其器——Android开发初识 12  
2.1 开发准备工作 13  
2.2 开发环境搭建 13  
2.3 创建第一个Android项目 16  
2.3.1 创建新项目 16  
2.3.2 程序工程架构图 18  
2.3.3 HelloWorld程序架构解析 19  
2.4 Android开发工具 22  
2.4.1 Android开发工具Adb 22  
2.4.2 Android开发工具DDMS 23  
2.5 小结 25  
第3章 亲密接触——Android入门程序开发 26  
3.1 水果的营养成分分析器 27  
3.2 手把手教你开发项目 27  
3.2.1 手动创建Activity 28  
3.2.2 创建和加载布局 28  
3.2.3 在Activity中使用Toast 32  
3.2.4 在Activity中加入菜单(Menu) 32  
3.2.5 在Activity中使用Dialog 35  
3.2.6 Spinner控件的使用 35  
3.2.7 界面切换 38  
3.3 Intent的使用 40  
3.3.1 使用显式Intent 40  
3.3.2 使用隐式Intent 41  
3.3.3 隐式Intent的多种用法 42  
3.3.4 向Activity传递数据 44  
3.3.5 返回数据给Activity 46  
3.4 Android日志 48  
3.4.1 添加LogCat到Eclipse 48  
3.4.2 使用Android的日志工具Log 48  
3.5 小结 51  
第4章 美丽由我掌控——UI的开发 52  
4.1 常用控件 53  
4.1.1 EditText 53  
4.1.2 TextSwitcher 55  
4.1.3 ImageView 56  
4.1.4 ImageSwitcher 57  
4.1.5 ListView的使用 58  
4.1.6 提升ListView的运行效率 60  
4.1.7 RecyclerView 63  
4.1.8 ViewStub 69  
4.2 Android七种常见布局 72  
4.2.1 LinearLayout 72  
4.2.2 elativeLayout 75  
4.2.3 FrameLayout 77  
4.2.4 TableLayout 78  
4.2.5 GridLayout 79  
4.2.6 AbsoluteLayout 81  
4.2.7 CardView 82  
4.3 自定义控件 86  
4.3.1 加载布局 87  
4.3.2 自定义控件 89  
4.4 Android View详解 90  
4.5 Android Surface详解 93  
4.6 小结 98  
第5章 组件之一——活动(Activity) 99  
5.1 活动的生命周期 100  
5.2 活动的启动模式详解 105  
5.2.1 standard模式 106  
5.2.2 singleTop模式 107  
5.2.3 singleTask模式 109  
5.2.4 singleInstance模式 110  
5.3 平板手机都兼顾——碎片(Fragments) 112  
5.3.1 概述 112  
5.3.2 设计思想 112  
5.3.3 Activity添加Fragment界面 113  
5.3.4 动态替换Fragment 115  
5.3.5 Fragment生命周期 117  
5.3.6 一个简单的阅读器 120  
5.4 Android中的多线程 127  
5.4.1 Android Handler 129  
5.4.2 使用AsyncTask 131  
5.5 小结 134  
第6章 默默无闻的后台——服务(Service) 135  
6.1 Java层服务 136  
6.2 服务的生命周期 137  
6.3 服务的启动 140  
6.3.1 startService()启动Service 140  
6.3.2 bindService()启动Service 144  
6.4 Android AIDL 148  
6.4.1 创建一个AIDL文件 150  
6.4.2 实现接口 150  
6.4.3 使用parcelables进行参数的值传递 152  
6.4.4 客户端获取接口 153  
6.4.5 IInterface接口类 156  
6.5 提高手机电池续航能力——JobScheduler技术 157
6.6 小结 162  
第7章 广播机制——Android广播接收器 163  
7.1 静态注册监听广播 164  
7.2 动态注册接收Broadcast广播 166  
7.3 本地广播 167  
7.4 小结 169  
第8章 数据仓库——Android数据存储 170  
8.1 轻量级存储类——SharedPreferences 171  
8.1.1 使用SharedPreferences存取数据 171  
8.1.2 存取复杂(图像、音频)类型的数据 173  
8.2 PreferenceActivity的使用 174  
8.2.1 PreferenceActivity实现 174  
8.2.2 PreferenceFragement实现 177  
8.3 SQLite Databases 178  
8.3.1 SQLite在Windows系统中的使用 179  
8.3.2 SQLite基本操作 180  
8.3.3 SQLite事务 180  
8.4 Android中使用SQLite数据库 180  
8.4.1 SQLite常用的操作方法 181  
8.4.2 SQLiteOpenHelper类 182  
8.4.3 在SQLite中执行查询 185  
8.4.4 在Activity中显示和操作SQLite数据库 187  
8.5 Android文件的使用 189  
8.5.1 将数据保存到文件 189  
8.5.2 存取SD卡中的文件 190  
8.5.3 读取XML文件 192  
8.6 小结 194  
第9章 跨程序共享数据——内容提供器(ContentProvider) 195  
9.1 获取用于通信录的电话及电话内容 197  
9.1.1 从通信录中获取好友数据 197  
9.1.2 通信录中添加好友 198  
9.2 创建共享数据 199  
9.2.1 创建共享数据 200  
9.2.2 外部应用访问 204  
9.3 小结 206  
第10章 美图秀秀——开发2D绘图 207  
10.1 屏幕绘图基础 208  
10.1.1 Canvas类 208  
10.1.2 Paint类 210  
10.1.3 Color类 212  
10.1.4 Path类 213  
10.1.5 Drawable类 216  
10.2 使用文字 217  
10.3 使用位图 219  
10.4 Frame动画 222  
10.5 Tween动画 224  
10.6 小结 227  
第11章 多媒体开发 228  
11.1 音频播放 229  
11.1.1 支持的音频格式 229  
11.1.2 MediaPlayer播放MP3文件 230  
11.1.3 AudioTrack播放原始音频文件 230  
11.1.4 SoundPool播放音频文件 231  
11.2 视频播放 232  
11.2.1 SurfaceView播放视频 232  
11.2.2 VideoView视频播放器 234  
11.3 多媒体存储 235  
11.4 小结 237  
第12章 Android手机特色开发 238  
12.1 使用手机Camera 239  
12.1.1 调用摄像头拍照 239  
12.1.2 使用摄像头拍照 240  
12.2 使用百度地图 242  
12.2.1 申请API Key 243  
12.2.2 显示百度地图 245  
12.2.3 标注覆盖物 247  
12.3 定位服务 248  
12.3.1 LocationManager的基本用法 249  
12.3.2 确定自己的位置 250  
12.4 使用手机中的Sensor 252  
12.5 使用手机录音 255  
12.5.1 AudioManager 256  
12.5.2 音频处理 259  
12.6 手机Wifi的使用 261  
12.7 小结 267  
第13章 三维游戏的基础——OpenGL ES 3.0 268  
13.1 OpenGL常识普及 269  
13.2 GLSurfaceView的使用 272  
13.3 绘图概念 274  
13.4 平面图形 277  
13.5 三维图形 279  
13.6 平面纹理 286  
13.7 三维纹理 289  
13.8 小结 294  
第14章 C++的魅力——Android本地编程 295  
14.1 Android NDK简介 296  
14.2 使用命令行编译Native部分 296  
14.3 C++(HelloWorld)项目 297  
14.4 Eclipse编译C++(HelloWorld)项目 299  
14.5 HelloJNI实例解析 301  
14.6 Android.mk文件语法规范 304  
14.7 Application.mk文件语法规范 308  
14.8 Android JNI简介 309  
14.9 Android Native Activity 312  
14.9.1 NativeActivity 313  
14.9.2 Android Native生命周期 314  
14.10 使用C/C++编写OpenGL ES程序入门 315  
14.10.1 初始化OpenGL 316  
14.10.2 创建自己的Renderer 316  
14.11 小结 318  
第15章 Android网络编程 319  
15.1 WebView控件用法 320  
15.2 使用HTTP协议访问资源 321  
15.2.1 HttpURLConnection的使用 321  
15.2.2 HttpClient的使用 324  
15.3 Android Socket 325  
15.3.1 Android TCP通信 326  
15.3.2 Android UDP通信 330  
15.3.3 Native TCP/UDP通信 334  
15.4 小结 338  
第16章 Android的灵魂——底层服务 339  
16.1 Android进程间通信 340  
16.2 Android IPC binders 344  
16.2.1 Binders概述 344  
16.2.2 面向对象的Binder IPC 345  
16.2.3 Binder通信模型 345  
16.3 Binder的Native实现(libbinder) 347  
16.4 Android Native Service 349  
16.4.1 Native Service接口介绍 351  
16.4.2 简单的Native Service项目 354  
16.5 Android Parcel 364  
16.6 小结 367  
第17章 综合实例解析——音乐播放器 368  
17.1 音乐播放器 369  
17.2 设计原则 369  
17.3 框架分析 370  
17.4 Application 371  
17.5 程序主界面 372  
17.6 音乐播放界面 374  
17.7 Music Service的编写 385  
17.8 FavroiteActivity的编写 394  
17.9 MusicOnlineActivity的编写 394  
17.10 发布App 397  
17.10.1 生成正是签名的APK文件 397  
17.10.2 申请Google Play账号 399  
17.10.3 上传和发布应用程序 401  
17.11 小结 404

2017-03-18 21:18:47 zhangchen124 阅读数 1992

本章简介

前几章介绍了Flex应用开发的主要内容,本章将介绍Flex应用性能优化相关的知识,比如如何减少SWF文件的大小和内存泄漏问题以及改善代码性能的技巧等。很多时候,影响应用性能的主要因素是设计。不好的设计是导致应用性能低下的主要原因,而针对不同特点的应用,采用何种设计方法往往与设计者本身的经验和素质相关。在排除了设计的因素之后在Flex应用开发中还有很多具体细节和技巧可以提高Flex应用的性能,本章将介绍RSL技术以减小SWF文件的体积,和Flex垃圾回收原理,以及预防内存泄露的一些基本技巧。此外还介绍了Flex应用中进行打印机打印的常见方法。


核心技能部分

Flex应用的性能优化除了设计的因素外,主要集中在两个方面

Ø 如何解决SWF文件过大的问题。

Ø 如何解决Flex内存泄漏的问题

本章会从这两个方面着手 讲解如何使用RSL技术降低SWF文件的体积以及Flex内存泄漏的原因、如何避免Flex应用内存泄漏、如何确定是否有内存泄漏、几个Flash提供的能移检查内存使用情况和Flash Player自身相关信息的系统类,以及其他提高性能的技巧。

1.1 RSL技术

Flex1.0的时候,MXML文件和它的资源文件全部编译到一个SWF文件中,导致SWF文件非常大。SWF文件中包含了基础的application模型(MODEL)组件(如Button CheckBox

Panel等组件)、图片资源、嵌人数据和自定义组件。

SWF文件导致的直接后果就是下载时间较长(虽然在没有修改的情况下只下载一次)。在多数情况下,一个Flex客户端包含多个应用,这些应用包含了很多相同的资源。但是由于每个应用都被编译成SWF文件,相同的资源被编译进不同的SWF,在下载不同应用的同时也下载了重复的资源。

Flex1.5出现了运行期共享库(Runtime Shared Libraries,RSL)的概念,通过RSL将共享资源提取成独立的文件,可以有效地减小应用SWF文件的大小。这些RSL文件可以分开下载并且在客户端缓存,它们能够被大量地应用在运行期共享使用,但是只需要传输到客户端一次

如果客户端有多个应用,并且这些应用共享一个图片文件、组件和其他资源的核心资源包,用户只需要下载一次包含这些资源的RSL,这明显减小了主应用文件的尺寸。如果某个RSL内的资源发生了变化,Flex可以重编译这个RSL,由客户端重新单独下载,这个过程不需要重编译那些引用资源的应用和其他的资源RSL

理解Flex的链接形式有利于我们理解RSL,如何工作。Flex的编译器支持两种连接方式,静态链接和动态链接.所谓静态链接就是将应用所需要的代码以及这些代码所依赖的代码都编译进应用的SWF文件,这样获得的SWF就会非常大,下载很慢,但运行很快,因为所有的代码都己经在swF里了。而动态链接是指Flex应用中引用的一些类放到了应用SWF文件之外的其他文件中,并且在运行器加载,使用RSL就是动态链接的一种形式。

下面我们首先通过配置编译环境,来看看使用系统RSL的效果。

1.1.1 使用系统RSL---Framework

首先我们新建一个空白的Application,文件名为Blank.mxml

示例9.1

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

 xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

</s:Application>

打开工程属性窗口,查看编译选项,其中的Library path有一个Framework linkage选项,如

9.1.1所示。

 

9.1.1 Application的链接属性设置

默认的选项为Merged into code(合并到代码中 即静态链接),将系统Frame框架库文件内容编译进应用文件代码中。保存设置,系统完成文件编译,查看得知blank.swf文件的大小为362KB。注意:这是一个空白的应用,仅有一个Application组件。造成文件较大的根本原因是文件中很大一部分是Flex基础代码,这部分代码几乎每个Application都会加载使用,如果它们不能够被共享使用,那么空间的浪费是相当巨大的。

下面我们把Framework linkage的选择设置为另外一个选项。Runtime Shared Libraries(RSL)重新构建整个工程,再查看得知blank.swf坟件的大小为142KB ,约减少到原来的1/3,这说明,使用RSL能够有效减少Flex应用SWF文件的大小,当然,减少的部分不是消失了,而是以RSL的形式在运行期动态加载了。

1.1.2 其他的系统RSL

使用Framework RSL使得应用的大小有了明显减少,但是还剩下的一百多KB是些什么东西

?让我们再次打开工程属性窗口,查看编译选项。仔细查看Library Path页,如图9.1.2所示。

 

                              9.1.2 工程引用库文件列表

除了Framework之外,工程中还引用了其他的库文件。对于一个空白的应用文件来说rpc.swc文件也可以设为运行期加载。展开rpc.swc属性,选择Link Type项,点击窗口右侧的Edit按钮,弹出库文件修改窗口如图9.1.3所示。

 

9.1.3 选择库文件是否采用摘要

选择Digests(摘要 摘要将在下一小节进行详细介绍)选项,点击【添加】按钮,在【部署路径】会增加一条记录,名字默认为rpc_3.0.0.477.swz,这个文件将被生成到发布目录,供客户端下载。文件名中的3.0.0.477是当前编辑环境所使用的RPC库的版本号。点击【OK】后系统将重新构建工程,编译后查看blank.swf文件的大小减到了80KB,这基本是在Flex环境下一个空白应用文件能够达到的最小尺寸了。

对于使用chart等组件通过图形方式展示数据的应用,将datavisualization库设置为动态加载后,SWF文件会明显减小。

我们知道了可以通过修改库文件的编译方式来实现RSL的使用。在配置中,我们使用了Digests(摘要)选项,它到底是做什么的呢?下面将详细介绍Digests(摘要)。

1.1.3 RSL摘要

Flash Player缓存是Flash Player 9.0.115.0版本提供的新功能,允许有Adobe签名的文件由Flash Player进行缓存,这些文件以swz为后缀。

与浏览器缓存相比,Flash Player缓存有几个好处。首先,由于缓存的swz文件是Adobe

名的,因此可以在多个域中共享,而不需要考虑这个文件是从哪个域下载的。或者说,只要文件的版本相同,经过前面的库文件可以为整个客户端的所有应用共享使用。

其次,由于,swz文件存储在Flash Player的缓存中,不会因为浏览器清理缓存内容被清理掉。Flash player会自行管理旧的swz文件,当缓存容量达到限制时,不使用的swz文件会被回收清理。

另外,签名的swz文件可以跨浏览器使用,这意味若如果在IE中已经下载了某个swz文件,

那么在Firefox中也可以共享该文件,而不需要另外下载一份。

如何使用具有摘要的RSL?Flex的编译环境中可以很方便地配置它。我们在Library path属性页中,从SDK库中找到framework.swc,如图9.1.4所示。

 

图图9.1.4 库文件配置

选择Link Type项,点击【Edit】编辑按钮,进入属性编辑界面,如图9.1.5所示。

 

9.1.5 库文件的发布形式

 

在属性编辑界面中,选择验证的方式为Digests(摘要),可以看到Deployment pahts(发布路径)有两个文件。一个是framework_3.0.0.477.swz,这个文件就是当前编译环境所使用的Frameworkswz文件,它将会被放到发布路径中供客户端下载;而第二个文件是在swz文件下载出现异常(Digests验证失败等)的情况下作为swz的替代文件进行下载,从而保证客户端能够正常运行。

属性设置中的policy File可以指向cross-domain.xml文件,不指定表示该文件可以任意下载。

Flex3是从Flash Player缓存获得好处的第一个版本,前面我们已经讲到了如何在Flex3中设

置编译环境,从而使用Framework RSL。但是,我们注意到,在编译时包含的库文件都是SWC

格式的,这是为什么呢?

在编译期需要SWC格式的库文件有两个原因:第一个就是要从SWC中读取库文件的摘要,这个摘要将被缓冲的framework进行校验,Flex3中使用的摘要是使用SHA-256加密算法创建的。

一个RSL的摘要是从SWC文件中提取出来并且编译到应用中,当运行时应用从网络或者本

地读取RSL时,会校验RSL的摘要与应用中包含的摘要是否一致,如果两个摘要不匹配,系统将显示一个错误,并放弃RSL的加载。

开发者可以使用未签名的RSL,这个RSL以普通的SWF形式缓存在浏览器中,会因为浏览

器缓存的清理而被清空,并且由于没有认证,也不能实现跨域共享。

1.2 Flex的内存垃圾回收机理

内存问题一直是程序员比较关心的问题之一,每个程序员都希望自己开发的程序足够健壮,在运行过程中不会因内存泄漏而导致程序运行变慢甚至崩溃。

现在,面向对象语言(比如Java)增强了内存管理机制,能够自动回收不被使用的内存,或者说能够回收垃圾内存,这种内存管理机制通常被称为“garbage collection(垃圾回收)“,简称GC

Flex开发中所使用的ActionScript语言(简称AS)也是一种支持GC的语言。经过编译后的AS代码运行在AS虚拟机(简称AVM)中,由AVM自动完成垃圾内存回收的工作。Flash Player就是一个AVM,所以有时候大家会将二者混为一谈。

既然AVM能够自动完成垃圾回收的功能,那么是不是Flex程序员就可以认为自己所开发的Flex应用不存在内存泄漏问题呢?答案是否定的。在某些情况下,处理不妥当的代码仍然会导致内存泄漏.如何才能避免内存泄漏?应该说,只有在 AS程序员清楚地了解了Flash Player的垃圾回收的基本原理,并且高度重视内存泄漏这个问题后,才能有效避免内存泄漏情况的发生。

Flash Player垃圾回收工作是由垃圾回收器(garbage collection)完成的。垃圾回收器是运行在后台的一个进程,它释放那些不再被应用所使用的对象所占用的内存。不再被应用所使用的对象是指那些不会再被活动着(正在工作的)的对象所引用的对象。在AS中,对于非基本类型(BooleanString, Number,Unit,Int)的对象,在对象之间传递的都是对象引用,而不是对象本身。删除一个变量只是删除对象的引用,而不是删除对象本身。一个对象可以被多处引用,通过这些不同的引用所操作的都是同一个对象。

通过示例9.2和示例9.3我们可以了解基本类型和非基本类型对象的差异。

示例9.2 基本类型的值传递

    private function testPrimitiverTypes():void

{

var s1:String="abcd";

var s2:String = s1;

s2+="efg";

trace("s1:",s1);

trace("s2:",s2);

var n1:Number=100;

var n2:Number=n1;

n2=n2+100;

trace("n1:",n1);

trace("n1:",n1);

}

 

示例9.3 非基本类型对象的引用传递

private function testNonPrimitiverTypes():void

{

var a:Object={foo:'bar'};

var b:Object=a;

delete(a);

trace(b.foo);

对于非基本类型对象,AS采用两种方法来判定一个对象是否还有活动的引用,从而决定是否可以将其回收。一种方法是引用计数法,另一种方法是标记清除法。

1.2.1 引用计数法

引用计数法是判定对象是否有活动引用的最简单方法,并且从AS1.0就开始在Flash中使用。

当创建一个对对象的引用后,对象的引用计数就加一,当删除一个引用时,对象的引用计数就减一。如果对象的引用计数为0,那么它被标记为可被GC删除,如示例9.4所示。

示例9.4 对象的引用计数示例

var a:Object={foo:'bar'};

//现在对象的引用计数为1(a)

var b:Object=a;

//现在对象的引用计数为2(ab)

delete(a);

//对象的引用计数又回到了1(b)

delete(b);

//对象的引用计数变成0,现在这个对象可以被GC释放

 

引用计数法很简单,并且不会增加CPU的开销,可惜的是,当出现对象之间的循环引用时,它就不起作用了。所谓循环引用就是指对象之间直接或间接地彼此引用,尽管应用已经不使用这些对象,但是它们的引用计数仍然大于0,因此,这些对象就不会从内存中移除,如示例9.5所示。

示例9.5 对象的循环引用

    //创建第一个对象

      var aObject={};

    //创建第二个对象来引用第一个对象

     var bObject={foo:a};

    //使第一个对象也引用第二个对象

   a.foo= b;

      //删除两个活动引用

      delete(a);

      delete(b):

在上面的例子中,两个活动引用都已被删除,因此在应用程序中再也无法访问这两个对象。

但是它们的引用计数都是1,因为它们彼此相互引用。这种对象间的相互引用可能会更加复杂(a引用bb引用aC又引用a,诸如此类),并且难以在代码中通过删除引用来使得引用计数变为0Flash Player6Flash Player7中就因为XML对象中的循环引用而痛苦,每个XML节点既引用了该节点的子节点,又引用了该节点的父节点,因此这些XML对象永远不会释放内存。好在Flash Player8之后增加了一种新的GC技术—标记清除。

1.2.2 标记清除法

AS3使用的第二种查找不活动对象的GC策略就是标记清除。Flash Player从应用的根(root )节点开始,遍历所有其上的引用,标记它所发现的每个对象,然后迭代遍历每个被标记的对象,标记它们的子对象。这个过程递归进行,直到Flash Player遍历了应用的整个对象树,并标记了它所发现的每个对象,在这个过程结束的时候,可以认为内存中那些没有被打标记的对象没有任何活动引用,因此可以被安全地释放,通过图9.1.6可以很直观地了解这种机制。

在图9.1.6中,深色(有底纹)的引用在标记清除过程中被遍历,深色对象被打上了标记,白色(无底纹)对象将被释放内存。

 

9.1.6 Flex内存对象树

标记清除机制非常准确。但是这种方法需要遍历整个对象结构,从而增加CPU占用率。因此Flash Player9为了减少这种开销,只是在需要的时候偶尔执行标记清除活动。

1.2.3 垃圾回收的时机

Flash Player在运行时请求内存的速度受限于浏览器。因此,Flash Player采取小量请求大块内存,而不是大量请求小块内存的内存请求策略。同样,Flasb Player在运行时释放内存的速度也相对较慢,所以Flasb Player会减少释放内存的次数, 只有在必要的时候才释放内存。也就是说,Flasb Player的垃圾回收只有在必要的时候才会执行。

Flasb Player9和之后的版本中,Flasb Player垃圾回收的时机是在Flasb Player需要请求新的内存之前。这样,Flasb Player可以重新利用垃圾对象所占用的内存资源,并且可以重新评估需要另外请求的内存数量,这也会节省时间。

程序的实际运行中并不是每次应用申请内存时都会导致垃圾回收的执行,只有当Flash占用的内存达到一定程度时,才会执行真止的垃圾回收。如果应用中内存开销增长是匀速的,那么计算机物理内存越大,则垃圾回收触发周期越长。如果计算机有2G的物理内存,直到打开Flash应用的浏览器占用700M物理内存之后才会导致Flash Player回收垃圾内存。

来自Adobe公司的Alex Harui总结了两点:

Ø 何时真正执行垃圾回收不可预知。

Ø 垃圾回收总是在请求内存时触发,而不是在对象删除时发生。

最后,有关Flash Player中垃圾回收的一件非常重要的事情就是:垃圾回收不是立即进行的,而是延迟的。当没有任何对象引用某个对象后,该对象也不会立即从内存中清除,相反地,它们会在将来某个不确定的时候被移出(从开发者的角度来看)。但这些对象在没有被垃圾回收之前,它们仍然在工作(占用内存和CPU }。尽管这不是我们所希望的。

虽然我们不能让Flash Player立即回收内存,但在程序确定不再引用一个正在工作的对象之前,应终止其工作。比如,停止已经启动的Timer ,停止正在播放的视频或声音,以防止其继续占用CPU

1.2.4 强制执行垃圾回收的技巧

很多程序员都想能够在程序中指定计算机进行垃圾回收。目前,Adobe官方没有公布能够强制执行垃圾回收操作的相关API。不过,可以利用Flash Playerbug来实现强制回收内存,主要是通过人为抛出某种特别的异常,从而让Flash Player回收内存,如示例9.6所示。

示例9.6 强制回收内存的代码

try{

var lc1:LocalConnection = new LocalConnection();

var lc2: LocalConnection = new LocalConnection();

lc1.connect(‘gcConnection’);

lc2.connect(‘gcConnection’);

}

catch(e:Error){

 

}

这种强制回收内存的方法并不是根据官方API而是利用系统的某些漏洞。因此,在开发

应用时,不要依赖于这种方法来回收内存,只能将其视为辅助方法。

1.3 导致内存泄漏的常见情况

通过上面的讨论我们可以知道,只要对象被其他活动对象(仍在运行的)所引用,那么这个对象就不会被垃圾回收,从而可能造成内存泄漏。

在开发中,如下的一些情形会导致内存泄漏:

(1) 不再使用被全局对象所引用的对象时,开发者忘记从全局对象上清除对它们的引用,这

时就会产生内存泄漏。常见的全局对象有Stage,Application ,类的静态成员变量以及采用Singleton模式创建的实例等。如果使用第三方框架,比如PureMVC,Cairongorm等,要注意这些框架的实现原理,尤其要注意框架里面采用Singleton模式创建的ControlerModel

无限次触发的Timer会导致内存泄漏。不管无限次触发的Timer是否为全局对象,它本身

以及注册在Timer中的监听器对象都不会被垃圾回收,如示例9.7所示。

示例9.7 含有无限次触发Timer的内存泄漏测试组件

<?xml version="1.0" encoding="utf-8"?>

<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"

 xmlns:s="library://ns.adobe.com/flex/spark"

 xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300"

 title="测试组件"

 creationComplete="panel1_creationCompleteHandler(event)"

 >

<s:layout>

<s:BasicLayout/>

</s:layout>

<fx:Script>

<![CDATA[

import mx.events.FlexEvent;

[bindable]

private var timeer:Timer = new  Timer(1000);

private var memoryBlocks:Array = new Array();

protected function panel1_creationCompleteHandler(event:FlexEvent):void

{

var mBlock:Array = this.allocateMemory();

memoryBlocks.push(mBlock);

this.timeer.addEventListener(TimerEvent.TIMER,onTime);

this.timeer.start();

}

protected function onTime(event:TimerEvent):void

{

trace(this.toString());

}

protected function allocateMemory():Array

{

  var memoryBlock:Array = new Array(25600000);

for(var i:uint=1;i<=25600000;i++){

memoryBlock[i-1]=i;

}

trace('allcate 100M memory!');

return memoryBlock;

}

 

]]>

</fx:Script>

<fx:Declarations>

<!-- 将非可视元素(例如服务、值对象)放在此处 -->

</fx:Declarations>

<s:Label x="119" y="50" text="内存测试组件" width="139" color="#c14717" fontSize="19" fontWeight="bold"/>

</s:Panel>

上面的代码自定义了一个测试内存泄漏的Canvas组件,这个组件在初始化时开辟了100M内存(为了方便查看内存的回收情况),同时创建了一个每隔1秒钟无限次数触发的Timer,并且启动了这个Timer

针对上面的组件,下面我们给出一个测试应用,其界面如图9.1.7所示。

 

9.1.7 Flex验证内存泄漏组件的应用程序界面

该测试应用上有三个按钮,分别是“强制回收内存’、“创建内存消耗组件”和“移出内存消耗组件”。点击“创建内存消耗组件”按钮就会执行创建一个用于内存泄漏测试的Canvs对象,并将其作为container的子对象显示到界面上,点击“移出内存消耗组件”按钮则会将“创建内存消耗组件”按钮所创建的Canvs对象从containe的子对象列表中删除,并且永远不再使用。

应用运行后,先点击“创建内存消耗组件”按钮,然后再点击“移出内存消耗组件”按钮,重复这样的操作,我们发现,由于Canvs对象上的无限次触发的Timer对象已经启动,导致Canvs对象所占用的内存无法被回收,内存会一直增加,最终导致浏览器崩溃。如果我们将Canvs初始化代码中启动Timer的语句注释掉,重复上述的测试操作,内存会在某个时候减少,这说明占用内存的Canvs对象己经被垃圾回收。

通过这个简单的侧试程序测试了Timer的情况,当然,将其稍加改造也可以用来测试其他情况,在本教材中所列举的内存泄漏的情况都是经过测试程序得到的结论。

上述程序代码如示例9.8所示。

示例9.8 验证内存泄润组件的应用程序

<?xml version="1.0" encoding="utf-8"?>

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

<s:layout>

<s:BasicLayout/>

</s:layout>

<fx:Declarations>

<!-- 将非可视元素(例如服务、值对象)放在此处 -->

</fx:Declarations>

<fx:Script>

<![CDATA[

private var memoryTester:TimerTest=null;

//添加测试组件

private function addTest():void{

memoryTester = new TimerTest();

this.container.addChild(memoryTester);

}

//删除测试组件

private function removeTest():void{

this.container.removeChild(this.memoryTester);

this.memoryTester=null;

}

//回收内存

private function gc():void{

try{

var lc1:LocalConnection  = new LocalConnection();

var lc2:LocalConnection  = new LocalConnection();

lc1.connect('gcConnection');

lc2.connect('gcConnection');

}

catch(e :Error){

}

}

]]>

</fx:Script>

<mx:VBox id="container" x="0" y="0" width="100%" height="100%">

<s:Button label="强制收回内存" fontSize="12" width="120" click="this.gc();"/>

<s:Button label="创建内存消耗组件" fontSize="12" width="120" click="this.addTest();"/>

<s:Button label="删除内存消耗组件" fontSize="12" width="120" click="this.removeTest();"/>

 

</mx:VBox>

</s:Application>

 

(2)通过隐式方式建立的对象之间的引用关系更容易被程序员所忽略,从而导致内存泄漏。最常见的以隐式方式建立对象之间的引用就是“绑定”和“为对象添加事件监听器”。通过测试我们发现,“绑定”不会造成内存泄漏,对象可以放心地绑定全局对象,而调用addEventListener()方法“为对象添加事件监听器”则可能产生内存泄漏,大多数内存泄漏都因此而发生。

下面的代码:

   a.add.EventListener(Event.EVENT_TYPE,b.listenerFunction);

使得a对象引用了b对象,如果a对象是一个全局对象(全局对象在应用运行期间始终存在),则b对象永远不会被垃圾回收,可能会造成内存泄漏。比如下面的代码就有造成内存泄漏的可能:

this.stage.addEventListener(Event.RESIZE,onResize);

上面代码中的stageUIComponentstage属性,表示当前Flex应用运行的“舞台“。不过,通过以下三种方式使用addEventListener方法不会造成内存泄漏。

Ø 用弱引用方式注册监听器。就是调用时将addEventListener的第5个参数设置为true,例如:

SomeObject.addEventlistener(MouserClick.CLICK,otherObject.handlerFunction,false,0,true);

Ø 自引用的方式。即为对象添加的监听处理函数是对象本身的方法。例如:

this.addEventListener(MouseClick,CLICK,this.handlerFunction);

Ø 子对象引用。即为子对象添加的监听处理函数是父对象的方法。例如:

private var childObject:UIComponent=new UIComponent;

addChild(childObject);

childObject.addEventListener(MouseEvent.CLICK,this.clickHandler);

1.4 Flash Builder的内存泄漏分析工具

Flash Builder带有一个“剖析(Profiler)”工具,可以用来帮助我们识别内存泄漏。在Flash Builder中选择要被“剖析(Profiler)”的应用后,点击鼠标右键,在右键菜单中选择“Profile As”就可以运行“剖析(Profiler)“工具来分析所选择的应用,如图9.1.8所示。

 

                            9.1.8打开Flex内存泄漏分析工具的菜单

“剖析(Profiler)”工具运行后的界面如图9.1.9所示。

 

9.1.9  Flex内存泄漏分析工具界面

 

在这个工具中,有一个Force Garbage Collection(强制垃圾回收)”按钮,当应用被剖析时或者以剖析方式运行时,点击这个按钮然后观察“活动对象”列表,可以帮助我们分析内存泄漏。如果确定已经完全移除和清除对对象的引用,那么“活动对象”列表中的“类’就会减少。

通过“累计实例数”栏可以看到有多少个对象曾被创建,而通过“实例数”栏可以看到当前存在的对象实例有多少。如果在创建和移除对象之后运行“Force GC”,“果计实例数”的数量和“实例数”的数量相同,则可能存在内存泄漏。

“内存使用情况”图提供了另一种确定内存泄漏的方法,但只适合小应用。灰线代表最大的内存使用,而黑线则代表当前的内存使用。如果黑线和灰线从不分离,则说明有内存泄漏,

1.5 用于性能查看的Flash的系统API

Flash中提供了一些系统级的类,可以帮助开发者获取Flash Player的一些信息,并且可以提供一定的控制能力,这就是flash.system包。

该包中的flash.system.System类提供控制内存剪切板(Clipboard ) ,摄像头、麦克风、共享对象的控制,通过该类的totalMemory属性则可以查看当前Flash Player所占用的内存。

该包中的flash.system.Capabilities类则提供了很多属性用于说明Flash Player的版本、操作系统版本和所具备的能力,比如hasPrinting可以告诉开发者当前系统是否支持打印功能。

在本章并不准备详细介绍这些API,学员可以在应用系统里面可以开发一个应用界面,用来显示这些系统的信息,通过特定的快捷键(比如Alt+Ctrl+M组合键)来调出这个应用界面,为维护者提供一些有用的参考信息。

1.6 其他有关内存泄漏的问题

知道Flash内存管理机制和内存泄漏的原因之后还要注意以下两类问题。

(1) Flex SDK本身的Bug导致的内存泄漏。这通常由于程序员在开发程序的时候会有各种疏忽或者代码不够严谨导致,Adobe的程序员也不例外,因此,Flex SDK中有些组件或类也存在一些内存泄漏问题。

常见的如下:

Ø 如果对组件应用了效果(Effect),则删除该组件时,需要把应用在该组件及其子组件上的效果停止,然后再把效果的targt属性设置为null,如果不停止效果而直接将其target属性设置为null将不能正常移除对象。

Ø Flex SDK 3.2中,从ListBase下派生出来的ListGri d组件都存在内存泄漏。因为Adobe程序员在ListBase组件的mouseDownHandler()方法中以“强引用”的方式向SystemManager添加了一个Mouse_up事件监听器,代码如下:

SystemManager.getSandboxRoot().addEventlistener(MouseEvent.MOUSE_UP,mouseUPHandler,true,0,true);

Adobe公司在Flex SDK 3.3中已解决了这个问题,因此读者发现内存泄漏后应该检查一下自己的Flex SDK版本,看看是否需要更新Flex SDk,可能在更高版本中会有所改进。

(2)和其他语言一样,尽管Flash Player有内存回收机制,但这不代表所有的资源都可以回收。由程序员使用的外部资源或者系统类,必须由程序员自己释放或清理。比如:

Ø 使用完BindingUtils.bindSetter(),ChangeWatcher.watch()函数后需要调用ChangeWatcher.unwatch()函数来清除引用。

Ø 使用ModuleLoader1oadModule()方法加载一个模块,在关闭这个模块后应当使用unLoadModule()方法将其卸载,并将该模块对象的引用置为null

Ø 在开发图形应用时,如果使用Graphics画图,要将不再显示的图使用clear()方法清除。

Ø Image对象使用完毕后要把source设置为null

Ø 当不需要一个音乐或视频时需要停止音乐、删除对象,将引用设置为null

1.7 有关提高F lex应用性能的技巧

http://www.insideria.com/2009/04/51-actionscript-30-and-flex-op.html中列出了一些可以改进Flex程序性能的技巧。感兴趣的学员可以查看原文及其相关讨论。

(1)不要使用new操作符创建数组,应使用:

var a =[];

而不是:

var a = new Array();

(2)创建数组的性能开销很大,请谨慎进行如下操作:

Var vanityCollection01 : Array = new Array();

Var vanityCollection02 : Array = new Array();

Var vanityCollection03 : Array = new Array();

Var vanityCollection04 : Array = new Array();

(3)最快的数组复制方法:

Var copy:Array = sourceArray.concat();

(4)为数组中的元素赋值都是较慢的操作,比如:

employees.push(employee);

employees.[2]=employee;

(5)从数组中读取元素的速度是为元素赋值速度的2倍。

var employee:Employee=employees[2];

(6)使用静态方法不需要实例化对象,可以提高性能(某些工具函数可以这样用全部都用静态方法则违反了面向对象的方法论)

(7)将应用生命周期内都不会改变的属性声明为常量。

public const APPLICATION_PUBLISHER:String = “Company,Inc.”;

(8)当确定一个类不会派生子类时,应使用final修饰符。

public final class StringUtils;

9)ActionScript3中,方法和变量的名称长度不会对性能造成影响(在其他语言中也一样)

someCreazyLongMethodNameDoesntReallyImpactPerformanceTooMuch();

(10)在一行代码中进行多个变量赋值不会提高性能(在其他语言中也一样)

var i=0;j=10;k=200;

(11)使用if语句和switch语句无内存开销的差异。

使用if语句:

 If(condition){

//handle condition

}

使用switch语句

switch(condition){

case ‘A’:

//logic to handle case A

Break;

case ‘B’:

//logic to handle case B

Break;

 

}

(12)使用if语句时,尽可能地按照最有可能发生的情况的顺序进行判断和处理。

(13) AVM在循环体内部进行计算时,将整型(int)数据提升为浮点型Number进行处理(从版本9到版本10,ActionScript虚拟机已经有所改变,int ,unit, number之间的转换速度不再像之前那么慢了)

(14)要解决类型转换,就要先解决未知和不正确的对象类型。

(15)谨慎使用unit,它可能会较慢(从版本9到版本10 ActionScript虚拟机已经有所改变int,   uint,  number之间的转换不再像之前那么慢了)

Var footerHex : unit=oxooccff;

(16)应在for循环中使用int:

for(var i: int =0;i<n;i++)

而不是Number:

for(var i: Number =0;i<n;i++)

(17)不要用int类型来表示小数,应使用:

var decimal:Number = 14.65;

而不是:

var decimal:int = 14.65;

(18)乘法性能高于除法:不要用5000/1000而要用5000*0.001

(19)如果一个值是通过for或者while语句循环多次计算出来的(或者其他耗费较高性能才能得到的值),而且这个值需要在局部多次使用,那么应当在本地将该值存储。而不是每次访问该值时都重新计算。

for(..){a*180/Math.PI;}

声明:toRadians = a*180/Math.PI; 在循环体外

(20)尽量避免在循环体判断条件中进行计算或者方法调用,应当使用

var len:int = myArray.length;

for(var i=0;i<len;i++){}

而不是:

vor(var i=0;i<myArray.length;i++){}

(21)使用正则表达式RegEx进行校验,使用字符串的方法进行查找。

(22)尽量重用对象来保持“内存平稳’,这些对象包括DisplayObjects,URLLoader等。

(23)遵循Flex组件模式(失效机制)

createChildren();

commitProperties();

updateDisplayList();

(24)把使用DataGrids组件作为最后的显示手段(如果确信真的没有办法使用常规的手段来实现想要的功能,才使用Datagrids

(25)避免将Repeaters用于能够滚动的数据.

(26)避免使用setStyle()方法(Flex框架中最消耗性能的方法之一)

(27)使用过多的容器将会严重降低系统性能。

<mx:Panel>

        <mx:VBox>

                <mx:HBox>

                    <mx:Label  text=Label 1/>

                          <mx:VBox>

                             <mx:Label  text=Label2/>

                          </mx:Vbox>

                               <mx:HBox>

                            <mx:Label  text=Label3/>

<mx:VBox>

   <mx:Label  text=Label4/>

</mx:Vbox>

</mx:HBox>

</mx:HBox>

</mx:Vbox>

</mx:Panel>

(28)定义组件时不一定总要使用一个容器来作为该组件的顶级标签,也不一定需要顶级容器标签。

(29)清除不必要的容器包装以减少容器的嵌套。

(30)避免在标签内嵌套VB ox容器(消除冗余)

(31)mx :A pplication标签内部尽量避免使用VBox标签(消除冗余)

(32)设置RepeaterrecycleChildren属性为true可以提高Repeater对象的性能(重新利用已创建的子组件而不是再创建一个新的子组件)

(33)应用的frameRate应设为60fps以内。

(34)避免在一帧中进行过多的显示操作。

(35)对于重复的操作,应使用Enter_frame事件替代Timer事件:

(36)在多帧中延迟对象的创建,使用:

<mx:Container creationPolicy=”queued”/>

(37)使组件不可见时使用Alpha=0visible=false不是一回事(对象被标记为不可见将不会被渲染与布局处理),应当使用:

loginButton.visible=false;

而不是:

loginButton.alpha=0;

 

通过本节的内容。应该可以了解到以一下内容:

Ø Flash  Player的内存垃圾回收机理。

Ø 如何在开发中规避内存泄漏。

Ø 如何通过工具来发现内存泄漏?

Ø 哪些系统级的API可以帮助我们在运行期间杳看系统内存的使用情况?

Ø 提高程序性能的编程方式和技巧。

1.8 Flex打印

Web打印的方案有很多,根据它们实现方式的不同,大致可以分为以下三类:

Ø 原生打印(Native Print),利用嵌入在浏览器中的AppletFlash Player等插件提供的打印接口进行打印。由子打印的全部过程都在插件中完成,所以称之为原生打印。

Ø 宿主打印(Host Print),使用浏览器提供的打印接口进行打印。我们把使用浏览器插件增强打印功能的打印方式也归为此类,因为这些插件仅仅用于弥补浏览器打印接口的不足之处。

Ø 外部打印(External Print) ,先根据打印格式生成目标文档,然后使用此文档的宿主程序打开并打印。

1.8.1 原生打印

这类打印方案的特点是直接使用SDK提供的API,具有响应迅速,跨平台跨浏览器,不依赖外部工具的优点。常见的有如下几种。

Ø JavaApplet:利用JDK提供的打印API,可以向打印机输出各种文字、图形图像和swing组件。

Ø Flex:利用Flex Framework提供的打印API,可以将FlexFlash可视化组件输出至打印机。

1.8.2 宿主打印

由于Web页面必须通过浏览器来呈现,而常见的基于图形的浏览器都提供打印功能,因此可以通过浏览器实现打印。常见的有如下几种。

HTML:利用浏览器提供的打印功能,直接将其呈现的HTML输出至打印机。可以使用层叠样式表来控制HTML元素在打印纸上的呈现方式。

ActiveX+HTML:使用JavaScript调用专用的ActiveX控件来打印HTML元素或页面。这个ActiveX控件可以利用Win32打印API和浏览器的开发接口将HTML输出至打印机。

1.8.3 外部打印

严格来说,这种方案不能叫做打印,它实际上是一种数据导出方案。它将应用的数据导出到外部工具,然后利用此工具来进行打印。常见的有如下几种。

Ø PDF打印:PDF格式是行业标准,通过PDF开发包(如基于JavaiText ,基于FlexAliverPDF)可以将应用的数据形成PDF文档,然后由Adobe Reader或其他工具负责打印。

Ø office打印:Microsoft  Office套件中的WordExcel使用广泛,而且许多其他Office软件都能兼容,因此可以将应用的数据导出为WordExcel文档,然后再打印。

外部打印又可以按照文档形成的时机分为服务器端打印和客户端打印。前者的打印过程一般是由客户端发出打印命令、服务器根据打印的数据生成目标文档,然后传送至客户端。客户端再使用宿主程序打开此文档并打印。与前者不同。后者的文档直接在客户端生成。上述方案各具优缺点,在进行选择时需要综合考虑项目、团队和用户等方面的因素。表8-1-1对上述方案进行了对比,可以作为选型时的参考。

8-1-1  Web打印常见方案及其比较

类别

方案

有点

缺点

原生打印

JavaApplet

能够通过程序选择打印机,设置纸张、页边距,打印方向和任务名称等打印任务参数;能够打印图片、文字Swing组件等、延迟生成打印内容,多页打印效率高      

依赖于JRE,因此可能需要客户端下载

FLex

能够打印Flex中所有的可视化组件; 延迟生成打印内容,多页打印效率高

不能够通过程序选择打印机。也不能设置纸张、页边距、打印方向等打印任务参数         

宿主打印

HTML

不需任何插件、能够打印HTML页面,可以使用CSS控制页面在打印时的呈现方式  

需要事先生成好所有打印内容,多页打印时可能使页面过大.共他缺点同上

ActiveX+HTML

一般能够通过程序选择打印机,能够设置纸张,页边距、打印方向等打印任务参数,能够打印HTML页面或HTML元素,可以使用CSS控制页面在打印时的呈现方式    

需要下载额外的插件;需要事先生成好所有的打印内容,多页打印时可能使HTML页面过大  

外部打印

PDF打印

Office打印

打印内容丰富,布局灵活,数据的导出非常容易    

需要安装相应的工具软件;同时也具有HTML方案的缺点  

1.8.4 为什么使用Flex来打印

尽管Flex无法通过程序选择打印机和设置打印参数,但是,如果选择了Flex技术作为系统的前端展现,那么PrintJob天生就能与系统的其他功能模块无缝集成。没有进程调用,没有数据转换,能够快速响应打印请求,无需引入额外技术或语言,实现单一技术的易维护性,并且也具有Flex跨主流平台和浏览器的优势。

由于Flex打印的是可视化组件,所以无论多么复杂,只要界面上能展示,那么Flex就能打印出来。依托于Flex布局容器的自动布局功能,可以实现像屏幕显示那样动态地调整元素的布局。

因此,如果不想在项目中引入额外的技术或工具,并且又能说服客户接受Flex打印的缺点,那么可以考虑使用这种“便宜”而且实用的方案。

1.8.5 使用PrintJob打印

如果你开发过Win32打印程序,你一定会对打印心有余悸。不过别担心,现在我们有了PrintJob。相信你第一眼看到它一定会对它的简洁感到吃惊。

首先,让我们通过一段简单的代码来看看PrintJob是如何工作的,如示例9.9所示。

示例9.9典型的PrintJob打印过程

var job = new PrintJob();//创建一个PrintJob的实例

if(job.start()){//启动PrintJob

job.addPage(Application.application);//将整个application作为一页添加到打印内容

job.send();//发送数据

下面,让我们来分析整个打印过程。

Ø Start():调用这个方法后,Flash Player将会为我们查找系统的默认打印机,并且使用此打印机的默认设置显示打印对话框。如果用户选择取消,那么方法返false,此时如果继续调用addPagesend方法,将会引发F1ash Player异常。如果用户选择打印,那么方法返回true,并且用户通过打印对话框进行的设置将会被保存在PrintJob的这个实例中。

PrintJob5个属性,分别记录打印方向、纸张大小和可打印区域,其中纸张大小和可打印区域的单位都已映射为像素,这是由Flash Player根据打印机的默认分辨率完成的。

Ø addPage():这个方法告诉PrintJob开始新的打印页,并且在此页上绘制指定的Sprite实例。在一个PrintJob中可以通过多次调用addPage实现多页打印。这个方法是打印过程中最重要和最复杂的,稍后将详细介绍。

Ø send():将数据发送至操作系统的打印任务池并结束这个PrintJob

除了打印内容的生成外,这几乎是打印功能所需要的所有代码了,当然,你还可以添加异常处理代码以增强程序的健壮性。

1.8.6 深入了解PrintJob

从上一节的分析可以看出,打印前的谁备工作很繁琐,幸运的是,F1ash Playr提供的PrintJob. Start()方法为我们完成了这部分工作。

既然知道了用户选择的纸张和打印方向,那么就可以使用PrintJob.addPage()把打印纸映射成屏幕区域,然后以一种我们熟悉的、与屏幕显示类似的方式向打印机输出内容了。

PrintJob.addPage()4个参数,下面是它的方法签名:

addPage(sprite:Sprite,printArea:Rectangle=null,options:PrintJobOptions=null,frameNum:int=0):void

只有第1个参数是必需的,其他参数可以忽略。

(1) sprite    

这里必须是一个flash.display.Sprite的实例,它是要打印的根组件。我们知道,Flex中的所有显示组件都是从sprite派生而来,因此这些组件都可以安全地传递给addPage()。但是,要想正确地打印,这个sprite须位于显示列表中。与屏幕显示不同的是,打印时会忽略这个sprit。的visible属性,也就是说,打印内容不必在屏幕上可见。这个例外仅局限于打印的根组件,如果这个sprite包含一个mx.controls.Label且其visible=false,那么打印时也是不可见的,这是Flex考虑得比较周到的地方。示例9.10展示的小例子说明了这个特性,请注意组件的visibile属性。

示例9.10 组件在打印时的可见性演示

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

layout="vertical" applicationComplete="doPrint();" >

<mx:Script>

<![CDATA[

import com.flexbook.blazeds.DataRow;

private function doPrint():void{

var job:PrintJob=new PrintJob();

if(job.start()){

job.addPage(printContent);

job.send();

}

}

]]>

</mx:Script>

<!--设置背景色和边框以证明VBox确实输出至打印纸了-->

<mx:VBox id="printContent" visible="false" borderStyle="outset" backgroundColor="0xA1CE78" >

<mx:Label text="第一个Label可见"/>

<mx:Label text="第二个Label可见" visible="false"/><!--不会被打印-->

<mx:Label text="第三个Label可见"/>

</mx:VBox>

</mx:Application>

编译并运行示例9.10,程序将在应用启动后打印出如图9.10所示的画面。由此可见printContent作为打印的根组件,它的visible属性不影响打印。但printContent的内部的三个三个Lablevisible属性对可见性的影响仍然遵循“与屏幕显示一致“的规则:visibletrue时可见,否则不可见。

9.10是使用虚拟打印机(pdfFactory)打印出来的结果,因此可能会与物理打印机打印出的实际效果有所不同。

 

9-10 printContent的打印结果

 

(2) printArea

打印范围,它与前述PrintJob保存的打印机的可打印区域不同,它用于指定第一个参数sprite的需要输出的区域范围,而后者表示的是打印机受到物理或机械限制在特定纸张中的打印能力。sprite中不在此区域范围内的内容不会被输出。此区域的左上角坐标被映射成打印机可打印区域的左上角坐标,如果此区域表示的打印的范围超过了打印机的可打印区域,右下方的内容将会被裁剪掉。假设我们想将左图窗口中灰色区域部分打印到A4纸上,那么打印的根对象就是整个左图窗口,灰色区域表示printArea指定的打印范围,右图的外部实线框表示纸张,内部实线框表示打印机的可打印区域,最终我们指定的打印区域会被打印到右图纸张的灰色区域部位。掌握了这一点,我们就可以精确地在纸张的任意位置上打印内容了。

(3)options

指示打印输出位图还是矢量图,如果打印内容不含位图图片,那么使用矢量图可以获得更

高的打印质量。

(4)frameNum

用于指定要打印的帧的序号。一般的Flex应用只有两帧,所以不用指定此参数,默认是打

印当前帧。

小结

Flex打印中 应该注意一下事项:

Ø PrintJobstart方法是阻塞的,也就是说,在它返回之前,Flash Player会阻塞ActionScript代码的执行。

Ø start,addPagesend三个方法调用间隔不能大于1秒,否则会出现脚本超时异常,这在Adobe的文档中有详细说明。

Ø PrintJob. pageHeightPrintJob. pageWidth记录的是打印机的可打印区域,并未刨除用户设置的页边距,这样我们就可以在程序中控制输出的页边距。

Ø 在将从UIComponent继承而来的显示组件传递给addPage前,必须保证组件已经在显示列表中并被有效化(validated)。这可以通过调用UIomponent.validateNowFlex立即更新组件,这在动态打印中非常重要。

Ø mx.printing.FlexPrintJob也是Flex提供的打印API ,它实际上封装PrintJob并加入了对UIComponent的支持,在打印前后会分别调用UIComponentprepareToPrintfinishPrint方法,同时也加入了简单的分页功能。

Ø Flex提供了mx.printing.PrntDataGrid,可用于打印简单的表格。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

任务实训部分 

实训任务1:使用RSI技术减少SWF文件体积

训练技能点

RSI的应用。

需求说明

使用RSI技术减少SWF文件体积

实现思路

(1)创建MXML应用程序。

(2)分别设置工程的框架链接类型为 静态 和动态 并观察生成的SWF文件体积的变化。

设置rpc.swc  datavisualization.swc库的摘要,并观察SWF文件体积的变化。

实训任务2:检测内存泄露

训练技能点

Ø 内存泄露分析工具的使用。

Ø 优化程序的内存使用。

实现思路:

(1)导入第六章上机任务3

(2)启动Flash内存泄露检测工具,分析系统的内存使用情况 找出存在的问题。

(3)结合理论内容8.6  8.7小节的内容 修改程序代码,优化内存使用,防止内存泄露。

 

 

 

 

 

 

 

 

 

 

 

 

巩固练习

选择题

1.  关于减少SWF文件的体积 ,错误的说法是 ()

A.  可以使用动态链接降低SWF体积。

B.  可以使用静态链接降低SWF体积。

C.  可以使用RSL技术降低SWF体积。

D.  通过垃圾回收可以降低SWF体积。

2.  以下关于Flex程序垃圾回收的说法,正确的是 ()

A.  ActionScript语言是一种支持GC的语言。经过编译后的AS代码运行在AS虚拟机(简称AVM)中,由AVM自动完成垃圾内存回收的工作。不会发生内存泄露。

B.  删除变量就意味着释放掉对象占用的内存。

C.  AS根据引用计数法就可以精确的判断对象是否需要释放。

D.  垃圾回收总是在请求内存时触发,而不是在对象删除时发生。

3.  以下哪些操作可能引发内存泄露()

A.  使用对象绑定。

B.  使用addEventListener()方法“为对象添加事件监听器”。

C.  用弱引用方式注册监听器。

D.  自引用的方式注册监听器。

4.  Flex打印分为多种方式 分别是()

A.  原生打印。

B.  本地打印。

C.  宿主打印。

D.  外部打印。


 

第2章 复习指导

2.1 知识总结

2.1.1  阶段核心技能目标

本阶段课程要掌握如下技能和知识:

Ø 理解RIAFlex的的概念和特点

Ø 熟练掌握AS基本语法

Ø 会熟练使用Flex常用控件和视图状态构建程序界面

Ø 会熟练使用FLex技术与外部进行数据通信,并整合后台各种框架

Ø 会熟练使用客户端MVC框架

Ø 会熟练使用AIR技术开发应用

Ø 熟练掌握美化Flex界面以及性能优化的基本原理和技巧

 

2.1.2  知识体系总结

 

图10.1.1 Flex知识结构图

 

2.2 综合实训

2.2.1 任务描述

本次任务实现一个Flv格式的视频播放器,能够播放flv格式的视频文件,并具有最大化 最小化 开始 暂停 快捷键和系统托盘等功能,使用AIR结合cairngorm框架进行开发。

2.2.2 系统功能

视频播放器的设计应该说是非常直观的。视频播放功能的实现其实并不复杂,前面我们已经学习过视频播放必须具备的某些核心功能(如Flexflv播放器),现在马上就可以开始播放器的开发工作了,其实也就是将Flexflv播放器集成到我们的程序界面中,在添加一些常用功能就可以了,但如果想充分运用AIR的功能来创建视频播放程序,那么还需要投入更多的精力进行规划设计。

我们的视频播放器具有AIR Video player的所有核心功能,这些功能可以大致分为两类,一类是视频播放,一类是程序控制。这些功能如下所示。

Ø 播放控制:所有的视频播放器都至少具有一个play(播放)按钮和一个停止按钮。Play按钮可以在暂停和播放两个状态之间切换,并相应的改变按钮的外观。除了Play Stop控件外,还应该具有一个能让你跳到视频任何部分的时间轴滑轨,一个只是播放进度的时间显示控件和音量控制器。

Ø 易于调整的布局:AIR视频视频播放器的一大特征就是可以实现视频的全屏播放。为了实现该特征,我们需要创建一个能随着视频尺寸变化而调整的布局。例如,如果视频的尺寸由300*250变为640*480或者全屏,那么播放控件要能根据调整后的尺寸显示视频。

Ø 网络摄像头抓图功能:这一功能并不是必须的,但给视频播放器添加这一功能是简单和有趣的事情。这部分工作将会留给学员扩展完成。

在这个视频播放器的主要控件完成后,需要在主容器(<mx:WindowedApplication>)中提供以下这些功能:

Ø 全屏支持:Flash播放器支持全屏播放模式。这一特色对于像AIR视频播放器这样的应用程序来说非常有用,当让也很容易做到。该功能将使用户的电脑变成一个虚拟的影院。

Ø 个性化的主窗口,这也是AIR程序的一大特色,可以随意定制程序主窗口的形状,达到更好的效果,以及窗口的拖拽功能。

Ø 支持快捷键播放 暂停。

Ø 主窗口可以最小化到系统托盘。

Ø 在网络环境下,还可以在程序窗口中打开迅雷看看高清电影的主页,以实现在线播放的功能。

2.2.3 实现步骤

1)构建目录结构

建立新的AIR工程并导入Cairngorm框架,按照MVC模式在工程中建立目录,目录结构如图10.1.1所示。

 

10.1.1 目录结构

目录建好之后将程序所需要的各种资源(图片)放入assets/images目录下备用。下面就可以开始进入视频播放器的开发环节了。

2)建立Cairngorm框架组件

本任务中并没有严格的按照Cairngorm的模式来实现功能,只是在播放功能中部分使用了Cairngorm的模式。学员们可以在完成主要功能后再严格按照Cairngorm结构重构系统。

 

OpenVideoEvent.as 该事件将会在选择要播放的视频文件后被派发。

package com.xi.flvPlayer.event

{

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.control.*;

public class OpenVideoEvent extends CairngormEvent

{

public function OpenVideoEvent()

{

super(FlvControl.EVENT_OPEN_VIDEO);

}

}

}

 

OpenVideoCommand.as  用来处理OpenVideoEvent事件的命令。

package com.xi.flvPlayer.commands

{

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.business.*;

import com.xi.flvPlayer.model.FlvModel;

import com.adobe.cairngorm.commands.*;

import com.adobe.cairngorm.view.*;

import com.xi.flvPlayer.control.FlvControl;

import com.xi.flvPlayer.view.*;

    import flash.events.*;

    import flash.net.*;

    import com.xi.flvPlayer.event.*;

public class OpenVideoCommand implements Command

{

public function execute(event:CairngormEvent):void

{

//定义FlvPlayerViewHelper实例

var fpViewHelper:FlvPlayerViewHelper=FlvPlayerViewHelper(ViewLocator.getInstance().getViewHelper("flvPlayerViewHelper"));

         //不相同的视频或从未点击过的视频,需要加载

         if(FlvModel.currVideo==""||FlvModel.currVideo!=FlvModel.videoSource)

         {   

           fpViewHelper.loadMediaPlay(FlvModel.videoSource);

           FlvModel.currVideo=FlvModel.videoSource;

            }

            else//相同的视频则重新开始播放

            {

             fpViewHelper.restartVideo();

            }

}

}

}

 

FlvModel.as 数据模型。

package com.xi.flvPlayer.model

{

import com.adobe.cairngorm.model.ModelLocator;

//import com.asiatom.englishr.vo.PlayListVO;

    import mx.controls.ToolTip;

public class FlvModel implements ModelLocator

{

private static var flvModel : FlvModel = new FlvModel();

public static function getInstance() : FlvModel {

return flvModel;

}

 

public function initialise() : void

{

}

// 当前播放视频地址

public static var currVideo : String="";

//将要加载的视频地址

public static var videoSource:String="";

//是否为全屏状态

public static var isFullSrceen:Boolean=false;

        //---------- 资源定义 ---------------------------------------------------------------

[Bindable]

[Embed(source="/assets/images/btn_moveover_break.png")]

     public var btn_moveover_break:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_fullscreen.png")]

     public var btn_moveover_fullscreen:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_play.png")]

     public var btn_moveover_play:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_stop.png")]

     public var btn_moveover_stop:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_break.png")]

     public var btn_normal_break:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_fullscreen.png")]

     public var btn_normal_fullscreen:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_play.png")]

     public var btn_normal_play:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_stop.png")]

     public var btn_normal_stop:Class;

    

     [Bindable]

     [Embed(source="/assets/images/ico_break.png")]

     public var ico_break:Class;

}

}

 

FlvPlayerViewHelper.as 辅助操作视图类。使用ViewHelper类可在AS文件中修改其他MXML文件中的视图。

package com.xi.flvPlayer.view

{

import com.adobe.cairngorm.view.ViewHelper;

import mx.core.Application;

 

public class FlvPlayerViewHelper extends ViewHelper

{

public function FlvPlayerViewHelper()//构造函数

{

super();

}

//加载视频处理函数

public function loadMediaPlay(src:String):void

{

view.mainDisplay.source=src;

view.mainDisplay.load();

view.mainDisplay.play();

}

//重新播放视频处理函数

public function restartVideo():void

{

view.mainDisplay.playheadTime=0;

}

}

}

 

FlvControl.as 前端控制器

package com.xi.flvPlayer.control{

import com.adobe.cairngorm.control.FrontController;

import com.xi.flvPlayer.commands.*;

  

public class FlvControl extends FrontController {

public function FlvControl() : void

{

initialiseCommands();

}

public function initialiseCommands() : void

{

   addCommand( FlvControl.EVENT_OPEN_VIDEO, OpenVideoCommand );  

}

    //------------------- 打开视频 ---------------------------------//

    public static var EVENT_OPEN_VIDEO:String="openVideo";

}

}

 

Utils.as 工具类 主要用来格式化时间和字符串。

package com.xi.flvPlayer.utils

{

//import com.adobe.cairngorm.view.ViewLocator;

//import com.xi.flvPlayer.control.FlvControl;

//import com.xi.flvPlayer.model.FlvModel;

//import com.adobe.cairngorm.control.CairngormEventDispatcher;

public  class Utils

{

public static function formatVideoTime(t : Number) : String//格式化时间处理函数

{

var s : Number = Math.floor(t);//取整数

var hour : Number = int(s/3600);//取小时

var minute : Number = int((s-hour*3600)/60);//取分钟

var second : Number = s-hour*3600-minute*60;//取秒

var p : Number = Math.round( ( t - s ) * 1000 );//取毫秒

//转特定格式“hh:mm:ss xxx

return padStr(hour.toString(), 2) + ":" + padStr(minute.toString(), 2) + ":" + padStr(second.toString(), 2) + " " + padStr(p.toString(), 3);

}

public static function padStr(src : String, len : int ) : String//添加前导零

{

if ( src.length > len )//若字符串长度超过指定长度,则截断字符串

return src.substr(0, len);

var s : String = src;

for ( var i : int = s.length;  s.length < len; )//添加前导零

{

s = "0" + s;

}

return s;

}

}

}

 

(3)开发主界面

首先建立主程序Player.MXML,然后修改Player-app.xml,在第50行 添加以下代码。

 <systemChrome>none</systemChrome>

 

 <transparent>false</transparent>

用来取消主窗口的标题栏和边框。

主程序代码如下。

<?xml version="1.0" encoding="utf-8"?>

<mx:WindowedApplication

xmlns:mx="http://www.adobe.com/2006/mxml"

xmlns:view="com.xi.flvPlayer.view.*"

xmlns:control="com.xi.flvPlayer.control.*"

layout="absolute"

backgroundColor="#ccffff"

minWidth="587"

minHeight="439"

showFlexChrome="false"

width="587" height="439" xmlns:s="library://ns.adobe.com/flex/spark">

<mx:Script>

<![CDATA[

import com.xi.flvPlayer.model.FlvModel;

import com.xi.flvPlayer.utils.Utils;

//拖动条显示提示处理函数

private function SliderToolTipFormatter(val : Number) : String

{

return Utils.formatVideoTime(val);//格式化为时间格式“00:00:000 000

}

]]>

</mx:Script>

 

<!--添加FlvPayer.mxml的视图定位器“FlvPlayerViewHelper-->

<view:FlvPlayerViewHelper id="flvPlayerViewHelper"/>

<!--添加前台控制器“FlvControl-->

<control:FlvControl id="flvControl"/>

<mx:VBox  verticalGap="0" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<!--添加菜单组件-->

<mx:ApplicationControlBar  id="me" height="25" width="586">

<mx:Button id="closebut"  width="13"  height="13"    toolTip="关闭"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="openbut"  width="13"  height="13"    toolTip="打开影片"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="movebut"   width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="minbut" width="13"  height="13"    toolTip="最小化"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

</mx:ApplicationControlBar>

<mx:Canvas  id="canvas1">

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 autoRewind="false"

 volume=" {videoVolume.value / 100 } "

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

 width="586"

 height="330"/>

<mx:Image id="img_break" source="{FlvModel.getInstance().ico_break}"

  width="{WindowedApplication(mx.core.Application.application).width*(60/624)}"

  height="{img_break.width}"

  alpha="0.7"

  x="{(mainDisplay.width-img_break.width)/2}"

  y="{(mainDisplay.height-img_break.height)/2}"

  visible="false"/>

</mx:Canvas>

<mx:VBox id="controlPanel" width="586" height="81">

<mx:HBox width="582">

<mx:Label id="videoState" text="Ready"/>

<mx:Spacer width="100%"/>

<mx:Label id="videoCurrTime" text="00:00:00 000"/>

<mx:Label text="/" width="10"/>

<mx:Label id="videoTotalTime" text="00:00:00 000"/>

</mx:HBox>

<!--添加视频拖动条-->

<mx:HSlider id="videoSlider"

liveDragging="true" showDataTip="true"

dataTipFormatFunction="SliderToolTipFormatter"

width="100%"

allowTrackClick="true"

value="{mainDisplay.playheadTime}"

enabled="false"/>

<!--添加视频控件按钮-->

<mx:HBox width="100%" paddingLeft="3" paddingRight="3" verticalAlign="middle" paddingBottom="1" paddingTop="1">

<mx:Spacer width="5"/>

<mx:Button id="play"

   width="35" height="35"

   icon="{FlvModel.getInstance().btn_normal_play}"  cornerRadius="25" enabled="false" toolTip="播放/暂停" />

<mx:Button id="btnStop"  cornerRadius="25"

   width="19" height="19" icon="{FlvModel.getInstance().btn_normal_stop}" overIcon="{FlvModel.getInstance().btn_moveover_stop}"  enabled="false" toolTip="停止" />

<mx:Spacer width="100%"/>

<mx:HBox horizontalGap="0" verticalAlign="middle" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<mx:Button id="btnQueit" width="13"  height="13"  paddingLeft="0" paddingRight="0" enabled="false" toolTip="静音" cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:HSlider id="videoVolume" width="70" height="16"

liveDragging="true" value="50"

enabled="false"

maximum="100" tickInterval="50" showDataTip="false"/>

<mx:Button id="btnFullScreen"

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

</mx:HBox>

</mx:HBox>

</mx:VBox>

</mx:VBox>

</mx:WindowedApplication>

运行程序,效果如图10.1.2所示。

 

图10.1.2 播放器界面

 

(4)完成基本播放功能

<?xml version="1.0" encoding="utf-8"?>

<mx:WindowedApplication

xmlns:mx="http://www.adobe.com/2006/mxml"

xmlns:view="com.xi.flvPlayer.view.*"

xmlns:control="com.xi.flvPlayer.control.*"

layout="absolute"

backgroundColor="#ccffff"

minWidth="587"

minHeight="439"

showFlexChrome="false"

applicationComplete="initApp()"

width="587" height="439" xmlns:s="library://ns.adobe.com/flex/spark">

<mx:Script>

<![CDATA[

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.commands.*;

import com.xi.flvPlayer.control.*;

import com.xi.flvPlayer.event.*;

import com.xi.flvPlayer.model.FlvModel;

import com.xi.flvPlayer.utils.*;

import flash.filesystem.File;

import mx.controls.Alert;

import mx.core.Application;

import mx.events.CloseEvent;

import mx.events.MenuEvent;

import mx.events.SliderEvent;

//定义FileFilter,只允许.flv格式

private var displayTypes:FileFilter = new FileFilter("播放格式(*.flv)", "*.flv");

private var allTypes:FileFilter=new FileFilter("全部(*.*)","*.*");

private var fileFilter:Array = new Array(displayTypes, allTypes);

//定义File实例,用以存储打开的文件

private var choosedFile:File = new File();

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

}

/**        

 

 * 用户决定窗口是隐藏还是关闭

 

 * @Author: S.Radovanovic

 

 */

private function closingApplication(evt:Event):void {

// 以下语句防止默认的关闭动作发生

evt.preventDefault();

// 弹出窗口,让用户判断是否要关闭窗口

//Alert.buttonWidth = 110;

Alert.yesLabel = "关闭";

Alert.noLabel = "最小化";

Alert.show("关闭还是最小化?", "关闭?", 3, this, alertCloseHandler);

}

// 响应弹出窗口中用户的选择

private function alertCloseHandler(event:CloseEvent):void {

closeApp(event);

}

/**

 

 * 关闭程序

 

 */

private function closeApp(evt:Event):void {

stage.nativeWindow.close();

}

protected function closebut_clickHandler(event:MouseEvent):void

{

WindowedApplication(mx.core.Application.application).close();//退出应用程序

}

protected function openbut_clickHandler(event:MouseEvent):void

{

choosedFile.browse(fileFilter);//打开选择对话框

choosedFile.addEventListener(Event.SELECT,selectHandle);//添加选择文件后的监听

}

//选择文件后的处理函数

private function selectHandle(e:Event):void

{

FlvModel.videoSource="file:///"+choosedFile.nativePath;//获取视视频路径

//加载视频

var evt:OpenVideoEvent=new OpenVideoEvent();

CairngormEventDispatcher.getInstance().dispatchEvent(evt);

}

//拖动条放开事件处理函数

private function thumbReleased(event:SliderEvent):void

{

mainDisplay.pause();//暂停播放

mainDisplay.playheadTime=videoSlider.value;//设置时间点

mainDisplay.play();//继续播放

}

//开始/暂停事件处理函数

public function toggle() : void

{

if (mainDisplay.playing)//当前状态正在播放时,暂停视频

{

play.setStyle("icon",FlvModel.getInstance().btn_normal_break);//设置“play”按钮的icon样式

play.setStyle("overIcon",FlvModel.getInstance().btn_moveover_break);//设置“play”按钮的overIcon样式

img_break.visible=true;//显示“暂停”图标

mainDisplay.pause();//暂停视频    

}

else//当前状态是暂停,播放视频

{

play.setStyle("icon",FlvModel.getInstance().btn_normal_play);//设置“play”按钮的icon样式

play.setStyle("overIcon",FlvModel.getInstance().btn_moveover_play);//设置“play”按钮的overIcon样式

img_break.visible=false;//隐藏“暂停”图标

mainDisplay.play();//播放视频

}

}

//视频停止播放事件处理函数

public function stopPlay() : void

{

mainDisplay.playheadTime=0;//设置时间点为0

mainDisplay.stop();//停止播放    

}

//“静音”处理函数

private function LowerVolume():void

{

videoVolume.value = 0;//设置音量最小

}

//拖动条显示提示处理函数

private function SliderToolTipFormatter(val : Number) : String

{

return Utils.formatVideoTime(val);//格式化为时间格式“00:00:000 000

}

//拖动条按下事件处理函数

private function thumbPressed(event:SliderEvent):void

{

if ( mainDisplay.playing )//正在播放,则暂停播放

{

mainDisplay.pause();

}

}

//playheadTime定时更新事件 处理函数

private function videoPlayheadTimeChanged() : void

{

if(Math.abs(mainDisplay.playheadTime-videoSlider.maximum)<0.1)//拖曳滑动条到结束位置时,重置时间

{

mainDisplay.playheadTime=0;//设置时间点为0

mainDisplay.pause();//暂停播放

}

else

{  

videoCurrTime.text = Utils.formatVideoTime(mainDisplay.playheadTime);//修改当前播放时间

}

}

//视频状态改变事件处理函数

private function videoStateChange() : void

{

videoState.text = mainDisplay.state;//显示当前视频状态

}

//视频准备完毕事件处理函数

private function videoReady() : void

{

WindowedApplication(mx.core.Application.application).width=mainDisplay.videoWidth;//设置应用程序宽度为实际视频宽度

WindowedApplication(mx.core.Application.application).height=mainDisplay.videoHeight*(458/352);//设置应用程序高度

videoTotalTime.text = Utils.formatVideoTime(mainDisplay.totalTime);//显示总时间

videoSlider.maximum =mainDisplay.totalTime;//设置拖动条数值长度

enableAllCtrl(true);//设置播放组件状态为可用

}

//播放控件enable设置处理函数

public function enableAllCtrl(b : Boolean) : void

{

videoSlider.enabled = b;

play.enabled = b;

videoVolume.enabled = b;

btnStop.enabled=b;

btnQueit.enabled=b;

}

]]>

</mx:Script>

<!--添加FlvPayer.mxml的视图定位器“FlvPlayerViewHelper-->

<view:FlvPlayerViewHelper id="flvPlayerViewHelper"/>

<!--添加前台控制器“FlvControl-->

<control:FlvControl id="flvControl"/>

<mx:VBox  verticalGap="0" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<!--添加菜单组件-->

<mx:ApplicationControlBar  id="me" height="25" width="586">

<mx:Button id="closebut" click="closebut_clickHandler(event)" width="13"  height="13"    toolTip="关闭"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="openbut" click="openbut_clickHandler(event)" width="13"  height="13"    toolTip="打开影片"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="movebut"   width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="minbut" width="13"  height="13"    toolTip="最小化"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

</mx:ApplicationControlBar>

<mx:Canvas  id="canvas1">

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 stateChange="videoStateChange()"

 autoRewind="false"

 ready="videoReady()"

 volume=" {videoVolume.value / 100 } "

 playheadUpdate="videoPlayheadTimeChanged()"

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

 width="586"

 height="330"/>

<mx:Image id="img_break" source="{FlvModel.getInstance().ico_break}"

  width="{WindowedApplication(mx.core.Application.application).width*(60/624)}"

  height="{img_break.width}"

  alpha="0.7"

  x="{(mainDisplay.width-img_break.width)/2}"

  y="{(mainDisplay.height-img_break.height)/2}"

  visible="false"/>

</mx:Canvas>

<mx:VBox id="controlPanel" width="586" height="81">

<mx:HBox width="582">

<mx:Label id="videoState" text="Ready"/>

<mx:Spacer width="100%"/>

<mx:Label id="videoCurrTime" text="00:00:00 000"/>

<mx:Label text="/" width="10"/>

<mx:Label id="videoTotalTime" text="00:00:00 000"/>

</mx:HBox>

<!--添加视频拖动条-->

<mx:HSlider id="videoSlider"

liveDragging="true" showDataTip="true"

thumbRelease="thumbReleased(event);"

thumbPress="thumbPressed(event);"

dataTipFormatFunction="SliderToolTipFormatter"

width="100%"

allowTrackClick="true"

value="{mainDisplay.playheadTime}"

enabled="false"/>

<!--添加视频控件按钮-->

<mx:HBox width="100%" paddingLeft="3" paddingRight="3" verticalAlign="middle" paddingBottom="1" paddingTop="1">

<mx:Spacer width="5"/>

<mx:Button id="play"

   width="35" height="35"

   icon="{FlvModel.getInstance().btn_normal_play}" click="toggle();" cornerRadius="25" enabled="false" toolTip="播放/暂停" />

<mx:Button id="btnStop"  cornerRadius="25"

   width="19" height="19" icon="{FlvModel.getInstance().btn_normal_stop}" overIcon="{FlvModel.getInstance().btn_moveover_stop}" click="stopPlay();" enabled="false" toolTip="停止" />

<mx:Spacer width="100%"/>

<mx:HBox horizontalGap="0" verticalAlign="middle" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<mx:Button id="btnQueit" width="13" click="LowerVolume();" height="13"  paddingLeft="0" paddingRight="0" enabled="false" toolTip="静音" cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:HSlider id="videoVolume" width="70" height="16"

liveDragging="true" value="50"

enabled="false"

maximum="100" tickInterval="50" showDataTip="false"/>

<mx:Button id="btnFullScreen"  

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

</mx:HBox>

</mx:HBox>

</mx:VBox>

</mx:VBox>

</mx:WindowedApplication>

运行程序 效果如图10.1.3所示。

 

10.1.3 播放器基本功能

 

(5)添加窗口移动功能

由于主窗口取消了边框和标题栏,所以窗口现在无法使用鼠标拖动,现在我们需要在程序的<mx:ApplicationControlBar>上添加move事件,在事件处理方法中添加窗口移动代码即可。

 

protected function me_mouseDownHandler(event:MouseEvent):void

{

this.nativeWindow.startMove();

}

//省略部分代码

 

<mx:ApplicationControlBar mouseDown="me_mouseDownHandler(event)"  id="me" height="25" width="586">

 

 

 

(6)添加全屏和退出全屏功能

通过设置舞台(stage)的displayState属性设置为 StageDisplayState.NORMAL(非全屏) StageDisplayState.FULL_SCREEN(全屏)即可将Flash播放器改为全屏播放模式或者退出全屏。

在主程序中添加如下代码。

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

}

protected function fullScreenHandler():void

{

if(FlvModel.currVideo!="")//不在播放视频时不能全屏

{

if(stage.displayState==StageDisplayState.NORMAL)//当前是非全屏状态,则进入全屏状态

{

stage.displayState=StageDisplayState.FULL_SCREEN;//进入应用程序全屏

}

else //当前是全屏状态,则非全屏

{

stage.displayState=StageDisplayState.NORMAL;//应用程序非全屏

}

}

}

//当发生全屏或者退出全屏的事件 则调整控件大小以适应舞台

public function fullScreenreSize(event:Event):void

{

if( stage.displayState==StageDisplayState.NORMAL)//如果退出应用程序全屏则恢复 控件的原始大小

{ this.mainDisplay.width=586;

this.mainDisplay.height=330;

this.me.width=586;

this.controlPanel.width=586;

}

else {//如果进入全屏 则放大控件

this.mainDisplay.width=this.width;

this.me.width=this.width;

this.controlPanel.width=this.width;

this.mainDisplay.height=this.height-this.controlPanel.height-this.me.height;

}

}

//省略部分代码

 

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 stateChange="videoStateChange()"

 autoRewind="false"

 ready="videoReady()"

 volume=" {videoVolume.value / 100 } "

 playheadUpdate="videoPlayheadTimeChanged()"

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

doubleClick="fullScreenHandler()"

 width="586"

 height="330"/>

//省略部分代码

 

<mx:Button id="btnFullScreen"  click="fullScreenHandler()"

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

 

 

(7)添加快捷键

所谓快捷键 ,其实就是在主窗口监听器keydown事件,当特定键按下的时候,执行指定操作即可。

在主程序中添加以下代码。

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

//添加快捷键的监听

WindowedApplication(mx.core.Application.application).addEventListener(KeyboardEvent.KEY_DOWN,KeyboardClickHandle);

//添加定时器

var timer:Timer=new Timer(100,0);

timer.addEventListener(TimerEvent.TIMER,checkFocusHandle);

timer.start();    

}

//定时设置focus

public function checkFocusHandle(e:TimerEvent):void

{

WindowedApplication(mx.core.Application.application).setFocus();

}

//快捷键处理函数        

public function KeyboardClickHandle(e:KeyboardEvent):void

{

switch(e.keyCode)

{

case 32:  //空格键按下,则暂停or播放

toggle();

break;

case 83://按下“S”时停止播放

stopPlay();

break;

case 113://按下“s”时停止播放

stopPlay();

break;

}          

}

定时调用checkFocusHandle方法主要是为了解决文件选择框的焦点问题。

 

8)最小化到系统托盘

所谓最小化到系统托盘,其实质就是将主窗口隐藏,并在系统托盘中添加一个图标而已。

//定义变量 作为系统图标

private var dockImage:BitmapData;

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

//添加快捷键的监听

WindowedApplication(mx.core.Application.application).addEventListener(KeyboardEvent.KEY_DOWN,KeyboardClickHandle);

//添加定时器

var timer:Timer=new Timer(100,0);

timer.addEventListener(TimerEvent.TIMER,checkFocusHandle);

timer.start();  

//进行系统托盘的设置

//loader对象载入一张图像作为系统托盘图标

var loader:Loader = new Loader();

loader.contentLoaderInfo.addEventListener(Event.COMPLETE, prepareForSystray);

loader.load(new URLRequest("btn_normal_quiet.png"));

}

// 响应弹出窗口中用户的选择

private function alertCloseHandler(event:CloseEvent):void {

if (event.detail==Alert.YES) {

closeApp(event);

} else {

//用户单击关闭按钮的时候,如果选择了最小化 则调用dock方法。最小化到系统托盘

dock();

}

}

/**

 

 * 设置基本属性。

 

 

 */

public function prepareForSystray(event:Event):void {

// 把图像作为系统图标

dockImage = event.target.content.bitmapData;

// windows 支持系统托盘图标, 苹果系统同样也支持,

if (NativeApplication.supportsSystemTrayIcon){

setSystemTrayProperties();

// 设置系统托盘菜单           

SystemTrayIcon(NativeApplication.nativeApplication .icon).menu = createSystrayRootMenu();

}

}

/**

 

 * 创建系统托盘菜单

 

 

 

 */

private function createSystrayRootMenu():NativeMenu{

// 添加菜单元件,每个元件响应相应的方法

var menu:NativeMenu = new NativeMenu();

var openNativeMenuItem:NativeMenuItem = new NativeMenuItem("Open");

var exitNativeMenuItem:NativeMenuItem = new NativeMenuItem("Exit");

// 当用户双击元件时发生的事件

openNativeMenuItem.addEventListener(Event.SELECT, undock);

exitNativeMenuItem.addEventListener(Event.SELECT, closeApp);

// 把菜单元件添加到菜单中

menu.addItem(openNativeMenuItem);

menu.addItem(new NativeMenuItem("",true));

menu.addItem(exitNativeMenuItem);

return menu;

}

/**

 

 * 设置隐藏和激活的一些事件侦听

 

 */

private function setSystemTrayProperties():void{

// 当鼠标悬停在系统托盘图标上时显示文字。       

SystemTrayIcon(NativeApplication.nativeApplication .icon).tooltip = "Systray test application";

// 双击系统托盘图标打开程序

SystemTrayIcon(NativeApplication.nativeApplication .icon).addEventListener(MouseEvent.CLICK, undock);

// 侦听窗口显示状态的改变,这样就可以捕获最小化事件     

stage.nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, nwMinimized); //捕获最小化事件

}

/**

 

 * 窗口的状态改变时执行合适的动作。

 

 * 当用户点击最小化按钮,隐藏程序到系统托盘

 

 *

 

 

 

 */

private function nwMinimized(displayStateEvent:NativeWindowDisplayStateEvent):void {

if(displayStateEvent.afterDisplayState == NativeWindowDisplayState.MINIMIZED) {

displayStateEvent.preventDefault(); // 当按下最小化按钮是,要阻止默认的最小化发生

dock();

}

}

/**

 

 * 在系统托盘中显示程序图标

 

 

 

 */

public function dock():void {

// 隐藏当前窗口

stage.nativeWindow.visible = false;

// 设置 bitmaps 数组,在系统托盘中显示程序图标。

NativeApplication.nativeApplication .icon.bitmaps = [dockImage];

}

/**

 

 * 重新显示程序窗口,从系统托盘中移除图标

 

 

 

 */

public function undock(evt:Event):void {

// 把窗口设置为可见, 并确保程序在最前端。

stage.nativeWindow.visible = true;

stage.nativeWindow.orderToFront();

// 清空 bitmaps 数组,同时清空了系统托盘中的程序图标。

NativeApplication.nativeApplication .icon.bitmaps = [];

}

运行程序效果如图10.1.4所示 ,程序最小化到了系统托盘 通过菜单可以将程序恢复。

 

图10.1.4 系统托盘

9)在播放器中添加片库

所谓添加片库,就是使用一个<mx:HTML>控件,在程序界面中加载迅雷看看的首页,进行在线搜索和播放。在程序中添加以下代码。

protected function moviebut_clickHandler(event:MouseEvent):void

{

if(this.currentState=="movie"){

this.currentState="default";

}

else{

this.currentState="movie";

}

}

]]>

</mx:Script>

<mx:states>

<mx:State name="default"/>

<mx:State name="movie">

<mx:RemoveChild target="{mainDisplay}"/>

<mx:RemoveChild target="{canvas1}"/>

<mx:RemoveChild target="{controlPanel}"/>

<mx:AddChild position="lastChild">

<mx:HTML x="10" y="33" width="565" height="394" location="http://www.xunlei.com/"/>

</mx:AddChild>

</mx:State>

</mx:states>

//省略部分代码...

<mx:Button id="movebut" click="moviebut_clickHandler(event)"  width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

 

运行程序 效果如图10.1.5所示。

 

10.1.5 片库

 

 

 

 

 

 

 

 

 

 

 

第3章 项目案例:图书进销存系统

3.1 训练技能点

Ø 使用Flex控件实现视图界面

Ø 使用Spring+Hibernate完成后台

Ø 使用BlazeDS与后台交互

3.2 需求说明

3.2.1 需求概述

在现实生活中,从事商业活动的企业需要经常进行进货、销售等业务,进货即采购,就是企业购入了一些商品,并需要记录采购商品的名字、价格、数量等信息,同时企业中的原商品库存量相应的增加;销售就是企业把采购的商品以高于采购价的价格售出,并需要记录售出商品的名字、价格、数量等信息,同时企业中的原商品库存量相应的减少。

在激烈的市场竞争环境里,企业需要及时地了解进货、销售等所有进销存环节上的实际情况或具体数据,并以此做出商业决策。随着Internet的发展和计算机的普及,人们发现通过计算机系统进行商品进销存管理的优势十分明显,所以进销存工作也进入了信息时代,众多的进销存管理系统纷纷出现。

进销存管理系统是一个典型的数据库应用程序,根据企业的需求,能有效解决企业账目混乱,库存不准,信息反馈不及时等问题,集采购、销售、库存管理为一体,能帮助企业处理日常的进销存业务,同时提供丰富的实时查询统计功能。

本此项目为书店开发一个图书进销存管理系统,涵盖了基本的进货、销售、查询等功能。

3.2.2 系统用例图

1. 图书进销存管理系统的权限分成两类:管理员和操作员。管理员主要对整个系统进行管理,包括用户管理、出版社管理、类别管理和各种查询,如图11.1.1所示。

图11.1.1 管理员业务

Ø 用户管理:管理员可以对用户进行添加和删除,也可以修改用户的权限。

Ø 出版社管理:管理员可以对出版社进行增删改查操作。

Ø 图书类别管理:管理员可以对图书类别进行增删改查操作。

Ø 图书进货查询:管理员可以根据条件查询图书的进货记录。

Ø 图书销售查询:管理员可以根据条件查询图书的销售记录。

Ø 图书库存查询:管理员可以根据条件查询图书的当前库存数量。

2. 操作员没有管理权限,只能进行日常的进货和销售操作,如图11.1.2所示。

图11.1.2 操作员业务

Ø 图书进货:当书店新采购一批图书时,操作员需要通过本系统记录图书的采购数据

Ø 图书销售:当书店销售图书时,操作员需要通过本系统记录图书的销售数据

3. 无论是管理员还是操作员,都有一些相同的业务,如图11.1.3所示。

11.1.3 相同业务

Ø 登录:要使用本系统首先要能正常进行登录,在登录时系统会自动判断用户的权限,如果用户是管理员就进入管理员后台;如果用户是操作员就进入操作员后台。

Ø 修改密码:方便用户修改自己的登录密码。

Ø 退出:不使用本系统时可以退出并回到登录页面。

 

3.2.3 开发环境

Ø JDK 1.5以上

Ø MyEclipse8.5以上

Ø Oracle 10g以上

Ø Tomcat 6以上

3.3 设计与思路分析

3.3.1 功能分析

图11.1.4 功能模块

图11.1.4详细列出了本项目中的功能模块,下面我们对这些功能模块牵涉到的数据、业务和界面进行详细介绍。

Ø 用户登录:用户只有输入正确的登录名称和登录密码才能进入系统,如果登录失败就进行错误提示,如图11.1.5所示。管理员登录成功后进入如图11.1.6所示的后台,操作员登录成功后进入如图11.1.7所示的后台。

 

图11.1.5 用户登录

 

11.1.6 管理员后台

 

图12.7 操作员后台

Ø 用户管理:单击管理员后台的【用户管理】菜单打开如图11.1.8所示的页面,该菜单有两个选项,添加用户和编辑用户。在这里可以对用户进行集中管理。

 

11.1..8 用户管理

单击【添加用户】超链接打开如图11..9所示的页面,填写用户名和密码并选择权限。

 

图11.1.9 添加用户

Ø出版社管理和类别管理都是简单的增删改查操作,这里不再多述。

Ø 图书库存查询:管理员可以按照图书ISBN、图书名称、出版社、图书类别进行图书当前库存数量的查询,图书进货/销售查询与次类似,只是多了一个时间段条件,如图11.1.10所示。

 

图11.1.10图书库存查询

单击【提交】按钮后以表格的形式显示查询到的数据信息。

Ø 图书进货:单击操作员后台的【图书进货】菜单打开如图11.1.11所示的页面。填写表单并单击【提交】按钮即可完成图书进货操作。注意:出版社和图书类别下拉列表框中的选项在页面打开时就需要填充,图书销售功能与此类似。

 

图11.1.11 图书进货

Ø 其他功能由于比较简单,这里不再多述。

Ø 上述功能中凡是涉及到用户输入的都有进行输入验证。

3.3.2 数据库设计

根据上述需求描述和功能分析设计出以下六个表。

 

图11.1.12 出版社表

 

图11.1.13 图书类别表

 

图11.1.14 用户表

 

图11.1.15 图书库存表

 

图11.1.16 图书进货记录表

 

图11.1.17 图书销售记录表

 

图11.1.18 权限表

3.4 实现步骤

(1) 通过Oracle建库建表

(2) 在MyEclipse中创建Flex工程,指定服务器技术为J2EE,并使用BlazeDS

(3) 为项目导入JDBC驱动并添加Spring,Hibernate支持

(4) 使用逆向工程生成映射文件和实体类等,并创建业务类

(5) SpringFlex进行整合,并配置业务类为远程调用

(6) 开发Flex界面,并与后台通信完成功能

3.5 关键代码

图书进销存项目中,后台业务部分并没有什么难度,基本上就是增删改查而已,主要的难点就集中在构建前台界面和各种效果的实现,以及与后台进行通信这部分。下面就通过初始界面部分的代码来展示其中的一些细节。在初始界面阶段主要是显示界面的基本元素,并与后台通信获取到基本权限,并在界面中据此生成基本功能的桌面菜单,包括登陆,退出等。

(1) 后台部分

Right.java

package com.zzzy.flexjxc.entity;

 

 

 

/**

 * Right entity. @author MyEclipse Persistence Tools

 */

 

public class Right  implements java.io.Serializable {

 

 

    // Fields    

 

     private String id;

     private String parent;

     private String righttext;

     private String righturl;

     private String source;

     private Integer type;

 

 

    // Constructors

 

    /** default constructor */

    public Right() {

    }

 

/** minimal constructor */

    public Right(String parent, String righttext, Integer type) {

        this.parent = parent;

        this.righttext = righttext;

        this.type = type;

    }

    

    /** full constructor */

    public Right(String parent, String righttext, String righturl, String source, Integer type) {

        this.parent = parent;

        this.righttext = righttext;

        this.righturl = righturl;

        this.source = source;

        this.type = type;

    }

 

   

    // Property accessors

 

    public String getId() {

        return this.id;

    }

    

    public void setId(String id) {

        this.id = id;

    }

 

    public String getParent() {

        return this.parent;

    }

    

    public void setParent(String parent) {

        this.parent = parent;

    }

 

    public String getRighttext() {

        return this.righttext;

    }

    

    public void setRighttext(String righttext) {

        this.righttext = righttext;

    }

 

    public String getRighturl() {

        return this.righturl;

    }

    

    public void setRighturl(String righturl) {

        this.righturl = righturl;

    }

 

    public String getSource() {

        return this.source;

    }

    

    public void setSource(String source) {

        this.source = source;

    }

 

    public Integer getType() {

        return this.type;

    }

    

    public void setType(Integer type) {

        this.type = type;

    }

   }

 

RightBizImpl.java

package com.zzzy.flexjxc.biz;

 

import java.util.List;

 

import com.zzzy.flexjxc.dao.RightDAO;

 

public class RightBizImpl {

private RightDAO rightdao;

public List getBaseRight(){

return rightdao.findByType(2);

}

public RightDAO getRightdao() {

return rightdao;

}

 

public void setRightdao(RightDAO rightdao) {

this.rightdao = rightdao;

}

}

 

(2) 界面部分

自定义时间控件 deskTop.DIYTimer.mxml

<?xml version="1.0" encoding="utf-8"?>

<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" backgroundAlpha="0.0" layout="absolute" width="230" height="210" showCloseButton="true" headerHeight="12"

  cornerRadius="35">

<mx:Script>

<![CDATA[

import mx.controls.Alert;

import mx.managers.PopUpManager;

]]>

</mx:Script>

<mx:DateChooser width="100%" height="100%" x="0" y="0" cornerRadius="0"/>

<mx:close>

<![CDATA[

PopUpManager.removePopUp(this);

jxc.b_timer = false;

]]>

</mx:close>

</mx:TitleWindow>

 

主界面 jxc.mxml

<?xml version="1.0" encoding="utf-8"?>  

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"

   xmlns:new="components.*"

   xmlns:flexlib="http://code.google.com/p/flexlib/"

   creationComplete="init()"

   xmlns:local="*">

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<!--引入css-->

<fx:Style source="Main.css"/>

<fx:Declarations>

<!--定义RemoteObject 远程获取基本权限-->

<mx:RemoteObject id="initRight" destination="rightbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" fault="init_faultHandler(event)" result="init_resultHandler(event)"/>

<!--定义Glow效果 用来实现鼠标进入 移出的效果-->

<mx:Glow id="imgInGlow"

 alphaTo="0.8"

 blurXTo="80.0"

 blurYTo="80.0"

 color="#FFFFFF">

</mx:Glow>

<!-- color="#68b3e6"  #91d314-->

<mx:Glow id="imgOutGlow"

 alphaFrom="0.8"

 alphaTo="0"

 blurXFrom="80.0"

 blurXTo="0.0"

 blurYFrom="80.0"

 blurYTo="0.0"

 color="#FFFFFF">

</mx:Glow>

</fx:Declarations>

<fx:Script>

<![CDATA[

import deskTop.DIYTimer;

import deskTop.MyToolTip;

import mx.collections.ArrayCollection;

import mx.controls.Alert;

import mx.events.ToolTipEvent;

import mx.managers.PopUpManager;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

public static var theWidth:int=996;

public static var theHight:int=600;

[Bindable]

public var thisYears:String="获取失败!";

public var timers:Timer;

[Bindable]

public var currentTime:String="正在获取系统时间!";

public var thisTime:Date=new Date();

//所有权限的集合

public var ops:ArrayCollection;

//拼凑当前时间

private function getTime(event:TimerEvent):void

{   

thisTime=new Date();

currentTime=""+thisTime.getHours()+":"+thisTime.getMinutes()+":"+thisTime.getSeconds();

}

//隐藏开始菜单

private function hideStart(evt:MouseEvent):void

{

if(evt.target==this)

{

if(start.visible==true)

start.setVisible(false);

}

}

public function init():void

{

this.initRight.getBaseRight();

start.setVisible(false);

theWidth= this.width;

theHight= this.height;

thisTime=new Date();

thisYears=thisTime.getFullYear()+""+(thisTime.getMonth()+1)+""+thisTime.getDate()+"";

timers=new Timer(1000);

timers.addEventListener(TimerEvent.TIMER,getTime);

timers.start();

this.addEventListener(MouseEvent.CLICK,hideStart,false);

}

protected function init_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

 

private function showLogin(event:Event):void{

}

private function exit(event:Event):void{

}

protected function init_resultHandler(event:ResultEvent):void

{

ops=event.result as ArrayCollection;

var imgnum:Number = 1;

for each(var v in ops){

var opimg:Image = new Image();

opimg.source=v.source;

opimg.width=64;

opimg.height=64;

opimg.id=v.id;

opimg.toolTip=v.tip;

opimg.setStyle("rollOverEffect",this.imgInGlow);

opimg.setStyle("rollOutEffect",this.imgOutGlow);

if(v.righturl!=''){

opimg.addEventListener(MouseEvent.CLICK,this[v.righturl]);

 

}

opimg.toolTip=v.righttext;

/* opimg.toolTip=" ";

opimg.addEventListener(ToolTipEvent.TOOL_TIP_CREATE,createToolTip);

如果要注册自定义提示方法 一定记得opimg.toolTip=" ";

*/

if(imgnum<6){

this.sg1.addElement(opimg);

}

else{

this.sg2.addElement(opimg);

}

imgnum++;

}

}

 

private var songFlag:Boolean = true;

private function nextSong():void

{

if(songFlag)

{

song.source='assets/images/nosong_logo.png'

songFlag=false;

}

else

{

song.source='assets/images/song_logo.gif'

songFlag=true;

}

}

public static var b_timer:Boolean =false;

private var mdiTimer:DIYTimer;

private function showTimer():void

{

if(b_timer==false)

{

mdiTimer= new DIYTimer();

PopUpManager.addPopUp(mdiTimer,this,false);

mdiTimer.x = this.width - mdiTimer.width -5;

mdiTimer.y = this.height-mdiTimer.height-10;

b_timer=true;

}

}

]]>

</fx:Script>

<s:BorderContainer backgroundImage="@Embed(source='assets/bg3.jpg')" width="100%" height="100%">

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/6.swf" autoLoad="true" id="rainFlash" />

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/27.swf" autoLoad="true" id="globuleFlash" />  

<mx:VBox width="100%" height="100%" top="0" left="0">

<flexlib:MDICanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off"

   width="100%"

   snapDistance="10" tilePadding="10"

   effectsLib="flexlib.mdi.effects.effectsLib.MDILinearEffects" height="100%">

<s:Group id="sg1" width="155" height="511" y="25" x="32">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<s:Group id="sg2" width="155" height="521" y="25" x="199">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<mx:Panel width="200" height="350"  color="#020202" layout="absolute" id="start" title="印水" bottom="0" left="0"

  showEffect="wipeUp"

  hideEffect="wipeDown" titleIcon="@Embed(source='assets/images/logo_32.png')" status="进销存管理系统" backgroundAlpha="0.5" >

<mx:Accordion width="160" height="300" horizontalCenter="0"  verticalCenter="0" fontSize="12" id="startCan" backgroundAlpha="0.5" >

</mx:Accordion>

</mx:Panel>

</flexlib:MDICanvas>

<mx:ApplicationControlBar bottom="0" width="100%" left="0" fillAlphas="[0.75, 0.0]" fillColors="[#FDFFFB, #85CB21]" color="#020202">

<mx:VRule height="25" y="0" left="0"/>    

<mx:Canvas width="100%" height="100%" >

<mx:LinkButton visible="false" label="开   始" id="startButton"

   rollOverEffect="{imgInGlow}"

   rollOutEffect="{imgOutGlow}"

   toolTip="单击这里开始" fontWeight="bold"  left="0" verticalCenter="0">

</mx:LinkButton>

<mx:VRule height="100%" y="0" left="58"/>

<mx:Tile id="tile" label="Tile" borderColor="#41DA18" direction="horizontal" bottom="0" left="65" width="707" height="100%">

</mx:Tile>

<mx:VRule height="100%" y="0" right="150" width="15"/>

<mx:Image source="assets/images/song_logo.gif" click="nextSong()"

  rollOverEffect="{imgInGlow}"

  rollOutEffect="{imgOutGlow}" right="85" top="5" width="20" height="16"

  id="song" />

<mx:Label text="{currentTime}" id="time" toolTip="{thisYears}" right="10" color="#050505" top="3" click="showTimer()"/>

</mx:Canvas>

</mx:ApplicationControlBar>

</mx:VBox>

</s:BorderContainer>

</s:Application>

 

(3) 添加登录退出功能

登录和退出功能本身比较简单,主要是我们要实现登录框最小化到任务栏,和缩略图的功能,这部分代码比较复杂 首先我们要定义一个自定义控件,用来在任务栏上添加最小化窗口的图标。

ButtonTitle.Mxml

<?xml version="1.0" encoding="utf-8"?>

<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml"

 label="{labelTW}" click="mw.setVisible(true)"

 toolTip="{labelTW}"

 toolTipCreate="createToolTip(event)"

 toolTipShow="positionToolTip(event)"

 rollOverEffect="{imgInGlow}" rollOutEffect="{imgOutGlow}">

  <mx:Glow id="imgInGlow"

alphaTo="0.8"

blurXTo="100.0"

blurYTo="100.0"

color="#FFFFFF">

</mx:Glow>

<mx:Glow id="imgOutGlow"

alphaFrom="0.8"

alphaTo="0"

blurXFrom="100.0"

blurXTo="0.0"

blurYFrom="100.0"

blurYTo="0.0"

color="#FFFFFF">

</mx:Glow>

<mx:Script>

<![CDATA[

import mx.core.IUIComponent;

import mx.graphics.ImageSnapshot;

private function takeSnapshot(source:IBitmapDrawable,st:ShowTW):void {

var imageSnap:ImageSnapshot = ImageSnapshot.captureImage(this.mw);

var imageByteArray:ByteArray = imageSnap.data as ByteArray;

st.load(imageByteArray);

st.width=240;

st.height=200;

}

import flexlib.mdi.containers.MDIWindow;

import mx.events.ToolTipEvent;

[Bindable]

public var labelTW:String="获取失败!";

public var mw:MDIWindow;

public function show(mw:MDIWindow):void

{

this.mw=mw;

labelTW=mw.title;

}

private function createToolTip(e:ToolTipEvent):void

   {

    var st:ShowTW = new ShowTW();

    e.toolTip = st;

    takeSnapshot(mw,st);

   }

   

   //ToolTip定位在距离该组件上方(55)的位置

   private function positionToolTip(e:ToolTipEvent):void

   {

    var pt:Point = new Point();

    pt.x = 0;

    pt.y = 0;

    

    pt = this.localToGlobal(pt);

    e.toolTip.x = pt.x;

    e.toolTip.y = pt.y-203;

   }

]]>

</mx:Script>

</mx:Button>

 

定义一个所有弹出框的基类,程序中所有弹出例如登录框,添加出版社对话框等都会继承该类。

MyWindow.mxml

<?xml version="1.0" encoding="utf-8"?>

<code:MDIWindow xmlns:fx="http://ns.adobe.com/mxml/2009"

xmlns:s="library://ns.adobe.com/flex/spark"

xmlns:mx="library://ns.adobe.com/flex/mx"

xmlns:code="http://code.google.com/p/flexlib/"

layout="absolute" width="400" height="300"

fontSize="12" cornerRadius="8" backgroundColor="#FFFFFF"

titleIcon="@Embed(source='../../../assets/images/dis/Sketches0.png')"

backgroundAlpha="0.8"

alpha="1.0"  borderAlpha="0.2" fontWeight="normal" headerHeight="25"

color="#297AD6" hideEffect="{irisOut}" showEffect="{irisIn}"

>

<fx:Declarations>

<mx:Iris id="irisOut" duration="1000" showTarget="true"/>

<mx:Iris id="irisIn" duration="1000" showTarget="false"/>

</fx:Declarations>

<fx:Script>

<![CDATA[

import deskTop.ButtonTile;

import flexlib.mdi.containers.MDICanvas;

import flexlib.mdi.events.MDIWindowEvent;

import mx.containers.Tile;

import mx.controls.Alert;

import mx.events.CloseEvent;

import mx.events.FlexEvent;

public var button:ButtonTile;

public var mainframe:Object;

public function set Mainframe(m:main):void

{

this.mainframe=m;

}

private function initlize(event:MouseEvent):void

{

this.unMinimize();

}

public function minlize(event:MDIWindowEvent):void

{

event.preventDefault();

this.setVisible(false);

}

public function removeBT(flag:int):void

{

mainframe.mdiCanvas.windowManager.remove(this);

mainframe.tile.removeChild(button);

switch(flag){

case 1:{

main.b_login=true;

this.mainframe.mdilogin=null;

break;

}

case 2:{

main.b_addUser=true;

this.mainframe.adduser=null;

break;

}

case 3:{

main.b_editUser=true;

this.mainframe.edit=null;

break;

}

case 4:{

main.b_updateUser=true;

this.mainframe.updateUser=null;

break;

}

case 5:{

main.b_BookIn=true;

this.mainframe.mdiBookIn=null;

break;

}

case 6:{

main.b_stock=true;

this.mainframe.mdiStock=null;

break;

}

case 7:{

main.b_stocklist=true;

this.mainframe.mdiStockList=null;

break;

}

}

}

 

protected function init():void

{

this.button=new ButtonTile();

this.addEventListener(MDIWindowEvent.MINIMIZE,minlize);

button.addEventListener(MouseEvent.CLICK,initlize);

button.show(this);

mainframe.tile.addChild(button);

this.mainframe.start.setVisible(false);

 

}

 

 

 

]]>

</fx:Script>

</code:MDIWindow>

 

定义VO Userinfo.as 与后台实体类对应。

package com.oa.vo

{

[RemoteClass(alias="com.zzzy.flexjxc.entity.User")]

public class Userinfo

{

public function Userinfo()

{

}

public var userid:int;

public var roleid:int;

public var loginpwd:String;

public var loginname:String;

}

}

定义登录框 LoginWindow.mxml

<?xml version="1.0" encoding="utf-8"?>

<view:MyWindow xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx"

   xmlns:view="com.oa.view.*"

   layout="absolute" width="400" height="300"

   title="登录"

 initialize="mywindow1_initializeHandler(event)"

 

   >

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<fx:Style source="../../../Main.css"/>

 

<fx:Script>

<![CDATA[

import com.oa.vo.Userinfo;

import flexlib.mdi.events.MDIWindowEvent;

import mx.collections.ArrayCollection;

import mx.containers.Accordion;

import mx.containers.HBox;

import mx.containers.VBox;

import mx.controls.Alert;

import mx.controls.Image;

import mx.events.FlexEvent;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

import mx.validators.Validator;

protected function sub_clickHandler(event:MouseEvent):void

{

var check:Array = Validator.validateAll([v1,v2]);

if(check.length!=0){

return;

}

var user:Userinfo = new Userinfo();

user.loginname=this.uname.text;

user.loginpwd=this.pwd.text;

this.mainframe.userremoting.login(user);

}

protected function button1_clickHandler(event:MouseEvent):void

{

this.pwd.text="";

this.uname.text="";

}

 

public  function alert(event:MDIWindowEvent):void

{

event.preventDefault();

removeBT(1);

}

protected function mywindow1_initializeHandler(event:FlexEvent):void

{

super.init();

main.b_login=false;

this.addEventListener(MDIWindowEvent.CLOSE,alert);

}

 

 

 

]]>

</fx:Script>

<fx:Declarations>

<mx:StringValidator id="v1" source="{uname}" property="text" maxLength="12" minLength="2" trigger="{sub}" triggerEvent="click"/>

<mx:StringValidator id="v2" source="{pwd}" property="text" maxLength="8" minLength="3" trigger="{sub}" triggerEvent="click"/>

</fx:Declarations>

<mx:Form horizontalCenter="0" verticalCenter="-9" height="156">

<mx:FormItem required="true" label="用户名">

<s:TextInput id="uname"/>

</mx:FormItem>

<mx:FormItem required="true" label="密码">

<s:TextInput id="pwd" displayAsPassword="true"/>

</mx:FormItem>

<s:HGroup  horizontalAlign="center" gap="20" textAlign="right" width="184">

<mx:Button label="登录" id="sub" click="sub_clickHandler(event)"/><mx:Button label="重置" click="button1_clickHandler(event)"/>

</s:HGroup>

</mx:Form>

</view:MyWindow>

 

在主界面jxc.mxml添加远程调用对象的定义和showLogin exit等方法修改后的代码如下。

<?xml version="1.0" encoding="utf-8"?>  

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"

   xmlns:new="components.*"

   xmlns:flexlib="http://code.google.com/p/flexlib/"

   creationComplete="init()"

   xmlns:local="*">

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<!--引入css-->

<fx:Style source="Main.css"/>

<fx:Declarations>

<!--定义RemoteObject 远程获取基本权限-->

<mx:RemoteObject id="initRight" destination="rightbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" fault="init_faultHandler(event)" result="init_resultHandler(event)"/>

<mx:RemoteObject id="userremoting" destination="userbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" >

<mx:method

name="login"

result="loginremoting_resultHandler(event)"

fault="loginremoting_faultHandler(event)"

/>

</mx:RemoteObject>

<!--定义Glow效果 用来实现鼠标进入 移出的效果-->

<mx:Glow id="imgInGlow"

 alphaTo="0.8"

 blurXTo="80.0"

 blurYTo="80.0"

 color="#FFFFFF">

</mx:Glow>

<!-- color="#68b3e6"  #91d314-->

<mx:Glow id="imgOutGlow"

 alphaFrom="0.8"

 alphaTo="0"

 blurXFrom="80.0"

 blurXTo="0.0"

 blurYFrom="80.0"

 blurYTo="0.0"

 color="#FFFFFF">

</mx:Glow>

</fx:Declarations>

<fx:Script>

<![CDATA[

import com.oa.view.LoginWindow;

import deskTop.DIYTimer;

import deskTop.MyToolTip;

import mx.collections.ArrayCollection;

import mx.containers.HBox;

import mx.controls.Alert;

import mx.controls.Button;

import mx.events.ToolTipEvent;

import mx.managers.PopUpManager;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

public static var theWidth:int=996;

public static var theHight:int=600;

[Bindable]

public var thisYears:String="获取失败!";

public var timers:Timer;

[Bindable]

public var currentTime:String="正在获取系统时间!";

public var thisTime:Date=new Date();

//所有权限的集合

public var ops:ArrayCollection;

//建立tooltip组件

private function createToolTip(e:ToolTipEvent):void

{

var tip:MyToolTip = new MyToolTip();

e.toolTip = tip;

}

//拼凑当前时间

private function getTime(event:TimerEvent):void

{   

thisTime=new Date();

currentTime=""+thisTime.getHours()+":"+thisTime.getMinutes()+":"+thisTime.getSeconds();

}

//隐藏开始菜单

private function hideStart(evt:MouseEvent):void

{

if(evt.target==this)

{

if(start.visible==true)

start.setVisible(false);

}

}

public function init():void

{

this.initRight.getBaseRight();

start.setVisible(false);

theWidth= this.width;

theHight= this.height;

thisTime=new Date();

thisYears=thisTime.getFullYear()+""+(thisTime.getMonth()+1)+""+thisTime.getDate()+"";

timers=new Timer(1000);

timers.addEventListener(TimerEvent.TIMER,getTime);

timers.start();

this.addEventListener(MouseEvent.CLICK,hideStart,false);

}

protected function init_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

 

protected function init_resultHandler(event:ResultEvent):void

{

ops=event.result as ArrayCollection;

var imgnum:Number = 1;

for each(var v in ops){

var opimg:Image = new Image();

opimg.source=v.source;

opimg.width=64;

opimg.height=64;

opimg.id=v.id;

opimg.toolTip=v.tip;

opimg.setStyle("rollOverEffect",this.imgInGlow);

opimg.setStyle("rollOutEffect",this.imgOutGlow);

if(v.righturl!=''){

opimg.addEventListener(MouseEvent.CLICK,this[v.righturl]);

 

}

opimg.toolTip=v.righttext;

/* opimg.toolTip=" ";

opimg.addEventListener(ToolTipEvent.TOOL_TIP_CREATE,createToolTip);

如果要注册自定义提示方法 一定记得opimg.toolTip=" ";

*/

if(imgnum<6){

this.sg1.addElement(opimg);

}

else{

this.sg2.addElement(opimg);

}

imgnum++;

}

}

 

private var songFlag:Boolean = true;

private function nextSong():void

{

if(songFlag)

{

song.source='assets/images/nosong_logo.png'

songFlag=false;

}

else

{

song.source='assets/images/song_logo.gif'

songFlag=true;

}

}

public static var b_timer:Boolean =false;

private var mdiTimer:DIYTimer;

private function showTimer():void

{

if(b_timer==false)

{

mdiTimer= new DIYTimer();

PopUpManager.addPopUp(mdiTimer,this,false);

mdiTimer.x = this.width - mdiTimer.width -5;

mdiTimer.y = this.height-mdiTimer.height-10;

b_timer=true;

}

}

public var loginstate:Boolean = false;

public static var username:String;

public var mdilogin:LoginWindow;

public static var b_login:Boolean=true;

private function showLogin(event:Event):void{

if(!this.loginstate){

if(b_login)

{

mdilogin= new LoginWindow();

mdilogin.mainframe=this;

mdiCanvas.windowManager.add(mdilogin);

mdilogin.x = mdiCanvas.width/2-mdilogin.width/2;

mdilogin.y = mdiCanvas.height /2-mdilogin.height/2;

}

else

{

mdilogin.setVisible(true);

}

}

}

private function exit(event:Event):void{

if(this.loginstate){

Alert.show("确定退出?","进销存",Alert.YES|Alert.NO,null,function(evt){

if(evt.detail==Alert.YES){

loginstate=false;

startCan.removeAllChildren();

startButton.visible=false;

}

});

}

}

protected function loginremoting_resultHandler(event:ResultEvent):void

{

if(event.result!=null){

loginstate=true;

initMenu(event);

username = mdilogin.uname.text;

this.mdilogin.removeBT(1);

}

else{

Alert.show('用户名密码错误');

}

}

public  function initMenu(event:ResultEvent):void{

var sys_rights:ArrayCollection = event.result as ArrayCollection;

for each(var v in sys_rights){

if(v.parent=="root_menu"){

var vb1 :VBox= new VBox;

vb1.label=v.righttext;

vb1.id=v.id;

for each( var sr in sys_rights){

if(sr.parent==v.id){

var hb:HBox = new HBox();

var img:Image = new Image();

var but:Button = new Button();

img.width=32;

img.height=32;

img.source=sr.source;

but.label=sr.righttext;

but.addEventListener(MouseEvent.CLICK,this[sr.righturl]);

hb.addChild(img);

hb.addChild(but);

vb1.addChild(hb);

}

}

startCan.addChild(vb1);

startButton.visible=true;

}

}

}

protected function loginremoting_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

private function showStart():void

{ if(start.visible==true){

start.setVisible(false);

}else{

start.setVisible(true);

}

}

public function addUser(event:Event):void{}

public function editUser(event:Event):void{}

public function addPublisher(event:Event):void{}

public function editPublisher(event:Event):void{}

public function addCategroy(event:Event):void{}

public function editCategory(event:Event):void{}

public function bookInFind(event:Event):void{}

public function bookOutFind(event:Event):void{}

public function bookStoreFind(event:Event):void{}

]]>

</fx:Script>

<s:BorderContainer backgroundImage="@Embed(source='assets/bg3.jpg')" width="100%" height="100%">

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/6.swf" autoLoad="true" id="rainFlash" />

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/27.swf" autoLoad="true" id="globuleFlash" />  

<mx:VBox width="100%" height="100%" top="0" left="0">

<flexlib:MDICanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off"

   width="100%"

   snapDistance="10" tilePadding="10"

   effectsLib="flexlib.mdi.effects.effectsLib.MDILinearEffects" height="100%">

<s:Group id="sg1" width="155" height="511" y="25" x="32">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<s:Group id="sg2" width="155" height="521" y="25" x="199">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<mx:Panel width="200" height="350"  color="#020202" layout="absolute" id="start" title="印水" bottom="0" left="0"

  showEffect="wipeUp"

  hideEffect="wipeDown" titleIcon="@Embed(source='assets/images/logo_32.png')" status="进销存管理系统" backgroundAlpha="0.5" >

<mx:Accordion width="160" height="300" horizontalCenter="0"  verticalCenter="0" fontSize="12" id="startCan" backgroundAlpha="0.5" >

</mx:Accordion>

</mx:Panel>

</flexlib:MDICanvas>

<mx:ApplicationControlBar bottom="0" width="100%" left="0" fillAlphas="[0.75, 0.0]" fillColors="[#FDFFFB, #85CB21]" color="#020202">

<mx:VRule height="25" y="0" left="0"/>    

<mx:Canvas width="100%" height="100%" >

<mx:LinkButton visible="false" label="开   始" id="startButton" click="showStart()"

   rollOverEffect="{imgInGlow}"

   rollOutEffect="{imgOutGlow}"

   toolTip="单击这里开始" fontWeight="bold"  left="0" verticalCenter="0">

</mx:LinkButton>

<mx:VRule height="100%" y="0" left="58"/>

<mx:Tile id="tile" label="Tile" borderColor="#41DA18" direction="horizontal" bottom="0" left="65" width="707" height="100%">

</mx:Tile>

<mx:VRule height="100%" y="0" right="150" width="15"/>

<mx:Image source="assets/images/song_logo.gif" click="nextSong()"

  rollOverEffect="{imgInGlow}"

  rollOutEffect="{imgOutGlow}" right="85" top="5" width="20" height="16"

  id="song" />

<mx:Label text="{currentTime}" id="time" toolTip="{thisYears}" right="10" color="#050505" top="3" click="showTimer()"/>

</mx:Canvas>

</mx:ApplicationControlBar>

</mx:VBox>

</s:BorderContainer>

</s:Application>

运行程序效果如图11.1.19  11.1.20 所示

 

11.1.19 登录界面

 

11.1.20 登录后生成功能菜单

其他的功能则与此大致相同,在这里就不再详述了,另外,在进行图书进货时,首先要判断新采购的图书在数据库中是否存在,如果存在则只需更新一下库存量即可,然后需要向图书进货表中插入一条进货记录;如果不存在则需要向库存表中插入一条新的图书信息并同时向图书进货表中插入一条进货记录。

在进行图书销售时,除了要向图书销售表中插入一条销售记录外,还需要更新库存表中的库存量。图书销售和图书进货的数据库操作相对复杂,建议通过存储过程并结合事务来实现。

 

3.6 时间分配

阶段一:分析和讲解需求(50分钟)

Ø 教员在教师机上运行参考项目并讲解需求

Ø 学员分析并理解需求

阶段二:学员实现数据库(30分钟)

阶段三:学员实现业务功能(300分钟)

阶段四:运行并测试(20分钟)

2017-08-14 23:20:16 jlusuoya 阅读数 1846

【分享说明】:

我会花很多时间或浅或深的研读一本书,然后总结一些提炼出来的精华,用简短的语言,让其他人能够用很少的时间大致知道这本书能带给自己的价值,如果适用自己,鼓励买一本正本实体书细读和收藏。

通篇会以原文目录为结构,给出提炼内容,如果不重要或者一看目录就懂的,会保留目录,有不明白的,以原文学习为参照。

所有分享内容,为了区分,会以》开头,可能有多行缩进,或差异化颜色表示。


【书名】:《分布式服务框架原理与实践》李林锋(华为PaaS平台架构师)著

【发行】:2016年

【适用】:分布式0基础人员,希望强化实践的,复杂服务平台构建者,微服务方向,java更宜,不适用于用c++做定制化创造框架组件方向的(我个人在这个方向上,后文简单过了一下),更偏基于各种已有组件,构建大型功能集成平台。

【总结】:

1、书不是很长,章节非常多。前面从宏观上讲了需求目标和大体架构,后文分章节细分和实践讲解。

2、整体架构形态讲解的很细致到位,了解全面。

3、后面太长,有重复和冗余,连续读心累,最好后面章节边实践练习边研讨。

4、读完之后视野有开拓的感觉,值得回味再读一遍。

【章节总结】

1章应用框架演变讲了从MVC、RPC、SOA到微服务框架的演变原因、特性和设计原则,深刻体会一下需求场景,有了框架的宏观认识。

2章分布式服务框架入门通过几个常用的框架的分析,整体的讲了分布式设计的原理和组成组件。

3-9章围绕各主要组件,详细的讲了设计考虑因素和一些具体实践。

10-19章围绕分布式架构的一些特殊场景,详细的讲了设计考虑因素和一些具体实践。

20章细节的讲了微服务框架。

21章总结了分布式设计实践中遇到的一些其他方面的总结。


【正式分享】

1章应用架构演进1
1.1
传统垂直应用架构2

LAMPMVC为典型
1.1.1
垂直应用架构介绍2

》简单说就是单线,上是访问入口,最底层是数据存储

》高负责时,通过上层分流(F5七层负载均衡或SLB)做分流,后面是对等逻辑部署(每个分流完全一致)
1.1.2
垂直应用架构面临的挑战4

1复杂应用维护成本高,效率低:一次编译出错全部重新打包

2团队效率差,公共功能重复开发

3系统可靠性变差,雪崩效应,一个模块故障,其他模块压力暴增

4维护和定制困难,代码量增加导致

5新功能上线周期变长:测试周期长,功能无法独立上线

》进化:一些公共功能抽以出来,提供api访问,跨进程形成rpc调用
1.2RPC
架构6

》远程调用框架
1.2.1RPC
框架原理6

4个核心技术点

1、远程服务需要以某种形式提供服务调用相关信息

2、远程代理对象:将本地调用封闭成远程服务调用

3、通信

4、数据序列化
1.2.2
最简单的RPC框架实现8
1.2.3
业界主流RPC框架14

》介绍4

      1FaceBook开发的ApacheThrift:高效完备,丰富的选择

      2Hadoop子项目Avro-RPC:良好的定制化支持,传输和业务分离

      3caucho提供基于binary-RPCHessian:轻量简单易用

      4google gRPC:高性能通用
1.2.4RPC
框架面临的挑战17

》负载均衡实现路由,随着服务增加,单点压力过大

》需要额外增加一个服务注册中心,客户端通过大量缓存路由来降压

》随着业务增多,服务关系复杂,启动顺序难以理清

》服用调用增大,质量问题突显

》服务上线容易下线难

》进化:单纯的RPC治理能力都不健全,需要通过服务框架+服务治理解决
1.3SOA
服务化架构18

》粗粒度松耦合的以服务为中心的框架
1.3.1
面向服务设计的原则18

1、服务可复用

2、服务共享一个标准契约

3、服务是松耦合的,尽可能不依赖其他

4、服务是底层逻辑的抽象

5、服务是可组合可编排的:多个服务组合成一个新服务

6、服务是自治的,逻辑由服务控制,不依赖其他服务

7、服务是无状态的

8、服务是可被自动发现

1.3.2服务治理19

》至少7条挑战

1、分布式框架下的服务调用性能

2、服务化框架如何支持线性扩展

3、如何实现高效、实时监控

4、大规模分布式下的故障快速定界和定位

5、分布下式海量日志检索、模糊查询

6、服务的流控(业务流、事务)、超时控制、服务升降机(增删设备)

7、服务的划分原则,如何实现最大程度复用

8、更多……

SOA的治理

1、服务定义:对服务进行标识,与使用团队协调确保满足需求,避免重复工作

2、服务生命周期治理:计划(未实现)、测试(不服务或有限服务)、运行(服务阶段)、弃用(标识弃用,只减不加)、废弃(下线,从注删中心移除或标记移除)

3、服务版本治理

4、服务注册中心

5、服务监控

6、运行期质量保证:限流、迁入和迁出、升降机、权重调节、服务超时控制

7、快速故障定界定位手段:1、日志汇总索引2、事件跟踪

8、服务安全:主要指服务权限
1.4
微服务架构21

》通过将功能分散到离散的各个服务中以实现对解决方案的解耦
1.4.1
什么是微服务21

》主要特征

1、原子服务,专注于一件事

2、高密度部署

3、敏捷交付

4、微自治
1.4.2
微服务架构对比SOA22

》两者差异

1、拆分粒度不同:SOA粗,重点是异构服务化

2、服务依赖:微服务解耦,尽可能不依赖

3、服务规模不同:SOA打包为主,微服务部署规模膨胀

4、服务治理:SOA静态治理,微服务动态治理

5、微服务敏捷交付为主,小团队研发
1.5
总结23
2章分布式服务框架入门25

》服务化改造的核心技术就是:分布式服务框架
2.1
分布式服务框架诞生背景26
2.1.1
应用从集中式走向分布式26?

》尽可能拆分

      纵向拆分:按属性不同分类处理

横向拆分:按本质不同分块处理,拆分公共等
2.1.2
亟需服务治理28
2.2
业界分布式服务框架介绍29
2.2.1
阿里Dubbo30

》主要质量属性

1、连通性:组件间长连接和缓存

2、健壮性:很多组件挂掉不影响服务

3、伸缩性:服务中心对等集群、服务提供者无状态

4、扩展性:插件设计+管道设计
2.2.2
淘宝HSF33

》框架总结

1、配置化开发,对业务代码低侵入

2、插件管理系统

3、异步NIO(一种通信框架名称)通信,多种序列化方式

4、灵活的路由能力

5、多协议地

6、多种服务治理策略
2.2.3
亚马逊CoralService35

》特点总结

1、支持多协议

2、轻量级框架,非常容易与已有系统集成

3、配置化开发

4、与亚马逊的其他基础设施集成,实现DevOps
2.3
分布式服务框架设计36
2.3.1
架构原理36

》通常抽象成3

1RPC层:本质是通信层,通信和序列化

2FilterChain层:本质是控制和框架层,负载均衡、统计、通知、重试等

3service层:业务对接层

》功能上看核心:服务治理中心和服务注册中心
2.3.2
功能特性37

》服务订阅和发布

配置化发布和引用服务

服务自动发现机制

服务在线注册和去注册

》服务路由

默认提供随机路由、轮循、权重路由等

粘滞链接:总向同一个提供方发请求,记忆功能

路由定制

》集群容错

Failover:失败重试其他节点,读操作或幂等性写操作

Failback:失败自动恢复,重试等,通常用于消息机制

Failfash:只发起一次调用,失败立即报错

》服务调用:同步、异步、并行

》多协议:私有协议、公有协议

》序列化:二进制、文本

》配置中心:本地静态和配置中心动态
2.3.3
性能特性39

》高性能、低时延、性能线性增长(不随着服务增大、负载增大而明显增长)
2.3.4
可靠性39

》服务注册中心

服务健康状态检测

故障切换

HA:高可用,全部挂掉不影响已存在服务

》清除单点状态

服务无状态:任意一台宕掉不影响服务

服务集群容错:只要集群还有1台就正常

》链接健壮性

心跳检测

断连重连
2.3.5
服务治理40

》服务运行状管控

服务路由:高峰修改路由导流

服务限流

服务迁入迁出:高峰迁出

服务降级:强制减用资源

服务超时控制:保持成功率

》服务监控

性能统计

统计报表

告警

》服务生命周期管理

上线审批

下线通知

服务灰度发布

》故障快速定界定位

分布式日志采集

海量日志在线检索

调用链可视化展示

运行日志故障定位

》服务安全

敏感服务的授权策略

链路安全:针对调用者,客户端
2.4
总结41
3章通信框架42
3.1
关键技术点分析43
3.1.1
长连接还是短连接43

》通常是长连接,节约资源
3.1.2BIO
还是NIO43

BIO:同步阻塞模式 NIO:支持多路复用非阻塞
3.1.3
自研还是选择开源NIO框架46

BIO有不足,开源经历实践,netty
3.2
功能设计47
3.2.1
服务端设计48

》重要设计原则

1、只提供上层api,不绑定具体协议

2、提供的api屏蔽底层通信细节

3、服务端功能不要求全,而是扩展性
3.2.2
客户端设计50
3.3
可靠性设计53
3.3.1
链路有效性检测54

ping-pong:发心跳,立即回,请求-回应型

ping-ping:对等心跳,双向心跳
3.3.2
断连重连机制56
3.3.3
消息缓存重发57
3.3.4
资源优雅释放58
3.4
性能设计59
3.4.1
性能差的三宗罪59

》罪一:网络传输方式问题,同步通信压力增大,通信变差

》罪二:序列化性能差

》罪三:线程模型问题导致开了大量线程
3.4.2
通信性能三原则60

》同步的3
3.4.3
高性能之道61
3.5
最佳实践61
3.6
总结64
4章序列化与反序列化65

》跳过
4.1
几个关键概念澄清66
4.1.1
序列化与通信框架的关系66
4.1.2
序列化与通信协议的关系66
4.1.3
是否需要支持多种序列化方式67
4.2
功能设计67
4.2.1
功能丰富度67
4.2.2
跨语言支持68
4.2.3
兼容性69
4.2.4
性能70
4.3
扩展性设计71
4.3.1
内置的序列化/反序列化功能类71
4.3.2
反序列化扩展72
4.3.3
序列化扩展75
4.4
最佳实践77
4.4.1
接口的前向兼容性规范77
4.4.2
高并发下的稳定性78
4.5
总结78
5章协议栈79

》简单过了一下
5.1
关键技术点分析80
5.1.1
是否必须支持多协议80
5.1.2
公有协议还是私有协议80
5.1.3
集成开源还是自研81
5.2
功能设计82
5.2.1
功能描述82
5.2.2
通信模型82
5.2.3
协议消息定义84
5.2.4
协议栈消息序列化支持的字段类型85
5.2.5
协议消息的序列化和反序列化86
5.2.6
链路创建89
5.2.7
链路关闭90
5.3
可靠性设计90
5.3.1
客户端连接超时90
5.3.2
客户端重连机制91
5.3.3
客户端重复握手保护91

》禁止客户端重复连接以避免客户端异常消耗大量句柄
5.3.4
消息缓存重发92
5.3.5
心跳机制92
5.4
安全性设计92

IP白名单机制
5.5
最佳实践协议的前向兼容性94
5.6
总结95
6章服务路由96

》简单过一下
6.1
透明化路由97
6.1.1
基于服务注册中心的订阅发布97
6.1.2
消费者缓存服务提供者地址98
6.2
负载均衡98
6.2.1
随机98
6.2.2
轮循99
6.2.3
服务调用时延99
6.2.4
一致性哈希100
6.2.5
粘滞连接101

》长连接记录状态
6.3
本地路由优先策略102
6.3.1injvm
模式102

》优先寻找本jvm,用本地调用替换远程调用
6.3.2innative
模式102

》优先寻找本机jvm,用本机调用替换远程调用
6.4
路由规则103
6.4.1
条件路由规则103
6.4.2
脚本路由规则104
6.5
路由策略定制105
6.6
配置化路由106
6.7
最佳实践——多机房路由107
6.8
总结108
第7章集群容错109
7.1
集群容错场景110
7.1.1
通信链路故障110
7.1.2
服务端超时111
7.1.3
服务端调用失败111
7.2
容错策略112
7.2.1
失败自动切换(Failover112

》失败重新回到路由入口
7.2.2
失败通知(Failback113

》将失败告诉调用者
7.2.3
失败缓存(Failcache113

》失败保存,等下次:周期性的、可预测恢复的、延时不敏感的、通知类的等
7.2.4
快速失败(Failfast114

》高峰时非核心业务,就调用一次
7.2.5
容错策略扩展114
7.3
总结115
8章服务调用116

》简单看一下
8.1
几个误区117
8.1.1NIO
就是异步服务117
8.1.2
服务调用天生就是同步的118
8.1.3
异步服务调用性能更高120
8.2
服务调用方式120
8.2.1
同步服务调用120
8.2.2
异步服务调用121
8.2.3
并行服务调用125
8.2.4
泛化调用129
8.3
最佳实践130
8.4
总结131
第9章服务注册中心132

》简单看一下
9.1
几个概念133
9.1.1
服务提供者133
9.1.2
服务消费者133
9.1.3
服务注册中心133
9.2
关键功能特性设计134
9.2.1
支持对等集群135
9.2.2
提供CRUD接口136
9.2.3
安全加固136
9.2.4
订阅发布机制137
9.2.5
可靠性138
9.3
基于ZooKeeper的服务注册中心设计139
9.3.1
服务订阅发布流程设计139
9.3.2
服务健康状态检测141
9.3.3
对等集群防止单点故障142
9.3.4
变更通知机制144
9.4
总结144
10章服务发布和引用145
10.1
服务发布设计146
10.1.1
服务发布的几种方式146
10.1.2
本地实现类封装成代理148
10.1.3
服务发布成指定协议148
10.1.4
服务提供者信息注册149
10.2
服务引用设计150
10.2.1
本地接口调用转换成远程服务调用150
10.2.2
服务地址本地缓存151
10.2.3
远程服务调用151
10.3
最佳实践152
10.3.1
对等设计原则152
10.3.2
启动顺序问题153

》不可控,需要支持乱序
10.3.3
同步还是异步发布服务153

》集群或启动比较快,异步发布是可以的(启动成功,但还有未就绪的)
10.3.4
警惕网络风暴154
10.3.5
配置扩展154
10.4
总结156
11章服务灰度发布157
11.1
服务灰度发布流程设计158
11.1.1
灰度环境准备158

》对灰度环境进行划分、隔离和准备
11.1.2
灰度规则设置159
11.1.3
灰度规则下发160
11.1.4
灰度路由161
11.1.5
失败回滚162
11.1.6
灰度发布总结163
11.2
总结163
12章参数传递164
12.1
内部传参165
12.1.1
业务内部参数传递165
12.1.2
服务框架内部参数传递168
12.2
外部传参169
12.2.1
通信协议支持169
12.2.2
传参接口定义170
12.3
最佳实践171
12.3.1
防止参数互相覆盖171

》没有好办法,系统可以预先定义声明给业务方不要覆盖
12.3.2
参数生命周期管理171

map无限追加,需要控制
12.4
总结172
13章服务多版本173

》简单看一下,主要思路就是版本号管理
13.1
服务多版本管理设计174
13.1.1
服务版本号管理174
13.1.2
服务提供者175
13.1.3
服务消费者175
13.1.4
基于版本号的服务路由176
13.1.5
服务热升级177
13.2
OSGi的对比178
13.2.1
模块化开发179
13.2.2
插件热部署和热升级184
13.2.3
不使用OSGi的其他理由185
13.3
总结185
14章流量控制186

》简单看一下
14.1
静态流控187
14.1.1
传统静态流控设计方案187
14.1.2
传统方案的缺点188
14.1.3
动态配额分配制188
14.1.4
动态配额申请制190
14.2
动态流控191
14.2.1
动态流控因子192
14.2.2
分级流控192
14.3
并发控制193
14.3.1
服务端全局控制193
14.3.2
服务消费者流控194
14.4
连接控制195
14.4.1
服务端连接数流控195
14.4.2
服务消费者连接数流控195
14.5
并发和连接控制算法195
14.6
总结197
15章服务降级198

》跳过
15.1
屏蔽降级199
15.1.1
屏蔽降级的流程199
15.1.2
屏蔽降级的设计实现200
15.2
容错降级202
15.2.1
容错降级的工作原理202
15.2.2
运行时容错降级204
15.3
业务层降级205
15.4
总结205
16章服务优先级调度207

》跳过
16.1
设置服务优先级208
16.2
线程调度器方案209
16.3Java
优先级队列210
16.4
加权优先级队列211
16.5
服务迁入迁出212
16.6
总结213
17章服务治理214

》跳过
17.1
服务治理技术的历史变迁215
17.1.1SOAGovernance215

17.1.2分布式服务框架服务治理217
17.1.3AWS
云端微服务治理217
17.2
应用服务化后面临的挑战218
17.2.1
跨团队协作问题219
17.2.2
服务的上下线管控220
17.2.3
服务安全220
17.2.4
服务SLA保障221
17.2.5
故障快速定界定位221
17.3
服务治理222
17.3.1
服务治理架构设计223
17.3.2
运行态服务治理功能设计225
17.3.3
线下服务治理232
17.3.4
安全和权限管理234
17.4
总结237
18章分布式消息跟踪239

》跳过
18.1
业务场景分析240
18.1.1
故障的快速定界定位240
18.1.2
调用路径分析241
18.1.3
调用来源和去向分析242
18.2
分布式消息跟踪系统设计242
18.2.1
系统架构243
18.2.2
埋点日志244
18.2.3
采样率247
18.2.4
采集和存储埋点日志248
18.2.5
计算和展示249
18.2.6
调用链扩展251
18.3
总结251
19章可靠性设计253

》简单看一下,主要策略就是隔离分散风险
19.1
服务状态检测254
19.1.1
基于服务注册中心状态检测254
19.1.2
链路有效性状态检测机制255
19.2
服务健康度检测256
19.3
服务故障隔离257
19.3.1
进程级故障隔离257
19.3.2VM
级故障隔离259
19.3.3
物理机故障隔离260
19.3.4
机房故障隔离261
19.4
其他可靠性特性262
19.4.1
服务注册中心262
19.4.2
监控中心262
19.4.3
服务提供者262
19.5
总结263
20章微服务架构264

》简单看一下
20.1
微服务架构产生的历史背景265
20.1.1
研发成本挑战265
20.1.2
运维成本高267
20.1.3
新需求上线周期长268
20.2
微服务架构带来的改变268
20.2.1
应用解耦268
20.2.2
分而治之270
20.2.3
敏捷交付271
20.3
微服务架构解析271
20.3.1
微服务划分原则272
20.3.2
开发微服务272
20.3.3
基于Docker容器部署微服务274
20.3.4
治理和运维微服务277
20.3.5
特点总结278
20.4
总结279
21章服务化最佳实践280
21.1
性能和时延问题281
21.1.1RPC
框架高性能设计281
21.1.2
业务最佳实践285
21.2
事务一致性问题286
21.2.1
分布式事务设计方案287

》尽力避免,因为性能取决于最慢的

》分阶段处理:先通用准备、都准备好了通用处理、失败通知回滚
21.2.2
分布式事务优化288

》选择一个中间人
21.3
研发团队协作问题289
21.3.1
共用服务注册中心290
21.3.2
直连提供者290
21.3.3
多团队进度协同291
21.3.4
服务降级和Mock测试291
21.3.5
协同调试问题292
21.3.6
接口前向兼容性292
21.4
总结292


2018-02-26 22:30:16 wengcheng_k 阅读数 184

第二部分。 JMX代理规范

5.代理架构(Agent Architecture)

本章概述了Java管理扩展(JMX)代理架构及其基本概念。 它作为JMX代理的介绍规范。

5.1概述(Overview)

JMX代理是在Java虚拟机(JVM)中运行的管理实体充当MBeans和管理应用程序之间的联络人。 一个JMX代理由一个MBean服务器组成,一组代表被管理的MBean资源,作为MBean实现的最少数量的代理服务,并且通常至少有一个协议适配器或连接器。

JMX代理体系结构中的关键组件可以进一步定义如下:

  • 代表管理资源的MBean,如第I部分“JMX”中所述 仪表规范“
  • MBean服务器,这个架构和中央注册中心的关键的MBean。所有应用于MBean的管理操作都需要通过 MBean服务器。
  • 代理服务可以是本规范中定义的组件或由第三方开发的服务。由JMX定义的代理服务MBean 规范提供:
    • 允许代理使用Java实例化MBean的动态加载服务类和从网络动态下载的本地库
    • 监视MBean中的属性值的功能;该服务通知其 听众在检测到某些条件后
    • 定时器服务,可以按预定时间间隔发送通知并充当 一个调度程序
    • 定义MBeans之间的关联并维护该关系的关系服务 关系的一致性

远程管理应用程序可以通过不同的协议访问代理适配器和连接器。 这些对象是代理应用程序的一部分,但它们是不属于JMX代理规范的一部分。
图5-1显示了业务代表的组件如何相互关联和相互关联管理应用。
……
FIGURE 5-1 Key Concepts of the JMX Agent Architecture

JMX体系结构允许对象在JMX上执行以下操作剂。 这些对象可以位于代理端应用程序中,也可以位于远程端管理应用。 他们能:

  • 通过以下方式管理现有的MBean:
    • 获取其属性值
    • 更改其属性值
    • 调用它们的操作
  • 获取任何MBean发出的通知
  • 通过以下方式实例化和注册新的MBean:
    • 已经加载到代理JVM中的Java类
    • 从本地机器或从网络下载的新类
    • 使用代理服务实施涉及现有的管理策略的MBean

在JMX体系结构中,所有这些操作都是直接执行或执行的间接地通过JMX代理的MBean服务器。

5.2 JMX标准代理(JMX Compliant Agent)

代理规范中描述的所有代理组件和类都是强制性的。 为了符合代理规范,JMX代理实现必须遵守提供以下组件:

  • MBean服务器实现
  • 所有代理服务:
    • 动态类加载
    • 监测 Monitoring
    • 计时器 Timer
    • 关系 Relation
      所有这些组件都在本文档和相关的API中指定由Javadoc工具生成的文档。 代理兼容性测试套件将检查所有组件是否由实现的实际提供规范。

5.3协议适配器和连接器(Protocol Adaptors and Connectors)

协议适配器和连接器使代理可以从远程管理访问应用。 它们通过MBean的特定协议提供视图在MBean服务器中实例化并注册。 他们启用管理JVM之外的应用程序可以:

  • 获取或设置现有MBean的属性
  • 对现有MBean执行操作
  • 实例化并注册新的MBean
  • 注册并接收MBeans发出的通知

Connectors用于将代理连接到启用远程JMX的管理应用程序,即使用分布式开发的管理应用程序JMX规范的服务。 这种通信涉及一个连接器代理中的服务器和管理器中的连接器客户端。

这些组件透明地传达管理操作的点对点一个特定的协议。 管理端的分布式服务提供了一个远程服务
接口到管理应用程序可以通过其执行操作的MBean服务器。 连接器是特定于给定协议的,但管理是特定的应用程序可以无差别地使用任何连接器,因为它们具有相同的远程设备接口。

Protocol adaptors通过给定的方式提供JMX代理的管理视图协议。 他们将MBean和MBean服务器的操作调整为在给定的协议中的表示,并且可能成为不同的信息型号,例如SNMP。

连接到协议适配器的管理应用程序通常特定于给定的协议。 传统管理解决方案通常就是这种情况依赖于特定的管理协议。 他们不通过访问JMX代理远程表示MBean服务器,但是通过映射的操作到那些MBean服务器。

连接器服务器和协议适配器都使用MBean服务器的服务将他们收到的管理操作应用于MBeans,并转发通知给管理应用程序。

要使代理可管理,它必须至少包含一个协议适配器或连接服务器。 但是,代理人可以包含任何数量的代理人,允许代理人执行此操作通过不同的协议同时进行远程管理。

JMX实现提供的适配器和连接器规范应该作为MBean来实现。 这允许他们按照管理以及根据需要动态加载和卸载。

6.基础类(Foundation Classes)

基础类描述用作参数类型或返回的对象各种Java管理扩展(JMX)API的方法中的值。该本章介绍的基础类有:

  • ObjectName
  • ObjectInstance
  • Attribute and AttributeList
  • JMX exceptions
    以下课程也被视为基础类; 他们被描述在第60页的“MBean元数据类”
  • MBeanInfo
  • MBeanFeatureInfo
  • MBeanAttributeInfo
  • MBeanOperationInfo
  • MBeanConstructorInfo
  • MBeanParameterInfo
  • MBeanNotificationInfo

所有基础类都包含在JMX Instrumentation API中,以便使用MBeans可以完全根据仪器规范进行开发,但可以进行操作由JMX代理。

6.1 ObjectName类

对象名称唯一标识MBean服务器中的MBean。 管理应用程序使用此对象名称来标识要在其上执行的MBean管理运作。 类ObjectName表示一个对象名称由两部分组成:

  • A domain name
  • An unordered set of one or more key properties

对象名称的组件如下所述。

6.1.1域名(Domain Name)

域名是区分大小写的字符串。 它提供了命名的结构JMX代理或全局管理解决方案中的空间。 域
名称部分可以在对象名称中省略,因为MBean服务器可以提供一个默认域。 当需要完全匹配时(请参阅“模式匹配”)第117页),省略域名将具有与使用默认值相同的结果由MBean服务器定义的域。

域名的结构如何取决于应用程序。 域名字符串可以包含除终止域的冒号(:)以外的任何字符名称和星号(*)和问号(?),它们是通配符。JMX总是处理域名作为一个整体,因此任何语义子定义在字符串内对于JMX实现是不透明的。

为了避免不同供应商提供的MBeans之间的冲突,一个有用的约定是以域名的反向DNS名称开始
指定MBeans的组织,后跟句点和字符串的解释由该组织决定。 例如,指定的MBeanSun Microsystems Inc.,DNS名称sun.com将拥有诸如com.sun.MyDomain。 这与Java语言的惯例基本相同包名称。

建议域不应该包含字符串“//”,它是保留供将来使用。

6.1.2关键属性列表(Key Property List)

关键属性列表允许您为给定的MBean分配唯一的名称域。 关键属性是属性值对,属性不需要以对应于MBean的实际属性。

关键属性列表必须包含至少一个关键属性。 它可以包含任何关键属性的数量,其顺序不重要。

键属性中的值是一个任意字符串,但不能包含任何字符串这些人物:
:”=*?
如果需要具有这些特殊字符的字符串,则存在引用机制。 使用ObjectName.quote将任何字符串转换为可用作键的引用形式属性值和ObjectName.unquote转换回原始字符串。

一个有用的约定是在每个对象名称中包含一个type属性。 就这样所有类型为user的MBean都可以匹配模式“:type = user,”。

6.1.3名称的字符串表示(String Representation of Names)

对象名称通常使用它们的字符串表示来构建和显示具有以下语法:
[domainName]:property=value[,property=value]*
可以省略域名来指定默认域。
对象的规范名称是对象的特定字符串表示名称,其中关键属性按照词汇顺序排序。 这种表示对象名称用于执行选择MBean的词典对比基于它们的对象名称。

6.1.4模式匹配(Pattern Matching)

大多数基本的MBean操作(例如create,get和set属性)需要通过其对象名称唯一标识一个MBean。 在那种情况下,完全匹配的名字被执行。

另一方面,对于查询操作,可以通过选择一系列MBean提供一个对象名称表达式。 MBean服务器将使用模式匹配对象的名称。 名称组件的匹配功能是在以下各节中进行介绍。

Domain Name
匹配语法与标准文件通配符一致,即:

  • 星号(*)与任何字符序列匹配,包括空字符
  • 问号(?)匹配任何一个单个字符

Key Property List
通配符匹配也可以使用键来对关键属性的值执行相同的匹配语法(*和?字符)。 此外,关键属性的列表可能不完整并用作模式。

也是整个关键属性的通配符; 它取代了任何数量的可以采取任何价值的关键属性。 如果整个关键属性列表被给出为,这将匹配选定域中的所有对象。 如果至少有一个键属性在列表模式中给出,通配符可以位于给定模式中的任何位置,前提是它仍然是以逗号分隔的列表:“:property = value,”和“:,property = value”都是有效的模式。 在这种情况下,对象具有给定关键属性作为其关键属性列表的子集将被选中。

如果不使用通配符,则只使用与完整关键属性列表匹配的对象名称将被选中。 同样,该列表是无序的,所以列表模式中的关键属性可以以任何顺序给出。

6.1.4.1模式匹配示例(Pattern Matching Examples)

如果在MBean服务器中注册了具有以下名称的示例MBean:

MyDomain:description=Printer,type=laser
MyDomain:description=Disk,capacity=2
DefaultDomain:description=Disk,capacity=1
DefaultDomain:description=Printer,type=ink
DefaultDomain:description=Printer,type=laser,date=1993
Socrates:description=Printer,type=laser,date=1993

以下是可以使用模式匹配执行的查询的一些示例:

  • ”将匹配MBean服务器的所有对象。 一个空字符串对象或 用作模式的空字符串(“”)名称等同于“”。
  • “:*”将匹配默认域的所有对象
  • “MyDomain:*”将匹配MyDomain中的所有对象
  • “??Domain:*”也将匹配MyDomain中的所有对象
  • “* Dom*:*”将匹配MyDomain和DefaultDomain中的所有对象
  • :description=Printer,type=laser,”将匹配以下对象:
    MyDomain:description=Printer,type=laser
    DefaultDomain:description=Printer,type=laser,date=1993
    Socrates:description=Printer,type=laser,date=1993
  • Domain:description=Printer,”将匹配以下对象:
    MyDomain:description=Printer,type=laser
    DefaultDomain:description=Printer,type=ink
    DefaultDomain:description=Printer,type=laser,date=1993
  • Domain:description=P ”将匹配与前面相同的对象, 因为P 匹配打印机而不是磁盘。

6.2 ObjectInstance类(ObjectInstance Class)

ObjectInstance类用于表示MBean对象之间的链接名称和它的Java类。 它是MBean中MBean的完整描述服务器,但它不允许您通过引用访问MBean。
ObjectInstance类包含以下元素:

  • 相应MBean的Java类名称
  • 为相应的MBean注册的ObjectName
  • 与另一个ObjectInstance相等的测试

在创建并使用MBean时返回ObjectInstance随后进行查询。

6.3属性和属性列表类(Attribute and AttributeList Classes)

这些类用于表示MBean属性及其值。 他们包含属性名称字符串及其值作为Object实例强制转换。
JMX定义了以下类:

  • Attribute类表示单个属性 - 值对
  • AttributeList类表示属性值对的列表

Attribute和AttributeList对象通常用于传达MBean的属性值,作为getter操作的结果,或作为参数的设置操作。

6.4 JMX异常

JMX异常是由不同的方法引发的一组异常JMX接口。 本节描述哪些错误情况被这些封装例外。
主要发生JMX异常:

  • MBean服务器或JMX代理程序服务在MBean上执行操作
  • 当MBean代码引发用户定义的异常时

定义的JMX异常的组织基于错误的性质大小写(运行时间与否)以及生产地点(经理,代理人或者客户)在沟通过程中)。
只有代理提出的例外才属于本版本的范围规范。 本节仅介绍由MBean引发的异常服务器。 代理服务还定义并抛出特定的异常,这些都是描述在由Javadoc工具生成的各自的API文档中。

6.4.1 JMException类和子类

6.4.2 JMRuntimeException类和子类

6.4.3 JMX异常的描述

6.4.3.1 JMException类

6.4.3.2 ReflectionException类

6.4.3.3 MBeanException类

6.4.3.4 OperationsException类

6.4.3.5 InstanceAlreadyExistsException类

6.4.3.6 InstanceNotFoundException类

6.4.3.7 InvalidAttributeValueException类

6.4.3.8 AttributeNotFoundException类

6.4.3.9 IntrospectionException类

6.4.3.10 MalformedObjectNameException类

6.4.3.11 NotCompliantMBeanException类

6.4.3.12 ServiceNotFoundException类

6.4.3.13 MBeanRegistrationException类

6.4.3.14 JMRuntimeException类

6.4.3.15 RuntimeOperationsException类

6.4.3.16 RuntimeMBeanException类

6.4.3.17 RuntimeErrorException类

7. MBean服务器(MBean Server)

本章介绍作为核心的Managed Bean服务器或MBean服务器Java管理扩展(JMX)代理基础结构的组件。

7.1 MBean服务器的角色(Role of the MBean Server)

MBean服务器是代理中MBean的注册表。 MBean服务器是组件,它提供用于操作MBean的服务。 所有管理在MBean上执行的操作通过MBeanServer接口完成。

通常,在MBean服务器中注册以下几种MBean:

  • 为管理目的代表管理资源的MBean。 这些 资源可以是任何种类的:应用程序,系统或网络资源提供Java接口或Java包装器。
  • 为代理添加管理功能的MBean。 这个功能可以 完全通用的,例如,提供日志或监控功能,或者它 可以是特定于技术或应用领域的。 其中一些MBean由JMX规范定义,其他将由。提供 管理应用开发者。
  • 基础结构的某些组件,例如连接器服务器和 协议适配器,可以实现为MBean。这允许这样的组件 从动态管理基础设施中受益。

7.1.1 MBean服务器工厂(MBean Server Factory)

JMX代理具有一个工厂类,用于通过。查找或创建MBean服务器工厂的静态方法。 这允许更灵活的代理应用程序和可能代理中的多个MBean服务器。

MBeanServer接口定义JMX代理上可用的操作。 一个JMX代理规范的实现提供了一个实现该类的类MBeanServer接口。 在整篇文档中,我们使用术语MBean服务器来参考一个可用的MBeanServer接口的实现剂。

MBeanServerFactory是一个静态方法返回实例的类实现类。 此对象作为MBeanServer的实例返回接口,从而将其他对象与MBean的任何依赖隔离服务器的实际实现类。 在创建MBean服务器时,调用者可以还要指定它所代表的JMX代理中使用的默认域的名称。

代理应用程序使用这些方法创建单个或多个MBean包含其MBean的服务器。 JMX代理规范仅定义行为的单个MBean服务器。 JMX代理中需要额外的行为包含多个MBean服务器不在本规范的范围内。

工厂还定义了用于查找已有的MBean服务器的静态方法被创建。 通过这种方式,加载到JVM中的对象可以访问现有的MBean服务器,而无需事先了解代理应用程序。

从Java 2平台标准版(J2SE 5.0平台)的5.0版开始,每个Java应用程序都有一个可以使用的平台MBean服务器java.lang.management.ManagementFactory.getPlatformMBeanServer()。此MBean Server包含特定数量的由指定的MBeanjava.lang.management包也可以作为一种方便的方式来使用在应用程序的不同模块之间共享应用程序MBean。

7.1.2 MBean服务器权限检查(MBean Server Permission Checking)

访问MBeanServerFactory类的静态方法由MBeanServerPermission类。 MBeanServerPermission扩展了基本的Java权限,并授予对以下MBean服务器操作的访问权限:

  • createMBeanServer
  • findMBeanServer
  • newMBeanServer
  • releaseMBeanServer

权限检查将在第12章“安全性”中进一步介绍。

7.1.3 MBean的注册(Registration of MBeans)

MBean服务器的首要职责是成为MBean的注册中心。MBean可以由代理应用程序或其他MBean注册。 界面的MBeanServer类允许两种不同类型的注册:

  • 实例化新MBean并在单个操作中注册此MBean。 在这种情况下,可以通过使用来加载MBean的Java类 一个默认的类加载器,或者通过明确指定要使用的类加载器。
  • 注册一个已经存在的MBean实例。

object name在注册时被分配给MBean。 对象名称是字符串,其结构在第115页的“ObjectName类”中详细定义对象名称允许在MBean的上下文中唯一地标识MBean服务器。 这种唯一性在注册时由MBean服务器检查,将拒绝具有重复名称的MBean。

7.1.3.1 MBean注册控制(MBean Registration Control)

MBean开发人员可以对注册和注销进行一些控制MBean服务器中的MBean。 这可以通过实现MBean来完成MBeanRegistration接口。 在注册和注销注册之前和之后MBean,MBean服务器动态检查MBean是否实现了MBeanRegistration接口。 如果是这种情况,则适当的回调是调用。

MBeanRegistration接口实际上是JMX的一个API元素仪器规范。 这里描述的是因为它是执行MBean服务器定义了注册控制机制的行为。

实现这个接口也是MBeans可以获得的唯一方法引用它们所注册的MBeanServer。 这意味着他们有关于他们的管理环境的信息并变得有能力对其他MBean执行管理操作。

如果MBean开发人员选择实施MBeanRegistration接口,必须提供以下方法:

  • preRegister - 这是MBean服务器之前调用的回调方法 注册MBean。 如果发生任何异常,MBean不会被注册 这种方法。该方法可能会抛出MBeanRegistrationException 将被MBean服务器再次抛出。 任何其他异常都会 被MBean服务器捕获,封装在一个 MBeanRegistrationException并再次抛出。 该方法可用于:
    • 允许MBean在其MBean服务器上保留一个引用。
    • 在MBean暴露之前执行需要完成的任何初始化到管理运作。
    • 对对象名称执行语义检查,并在可能时提供名称该对象创建时没有名称。
    • 获取有关环境的信息,例如,检查是否存在MBean所依赖的服务。 当这些所需的服务不是可用的,MBean可能会尝试实例化它们,或者提出一个ServiceNotFoundException异常。
  • postRegister - 这是MBean服务器将调用的回调方法注册MBean后。 如果MBean是,它的布尔参数将为true注册成功,如果MBean无法注册,则为false。 如果注册失败,这种方法可以释放预注册中分配的资源。
  • preDeregister - 这是MBean服务器之前调用的回调方法注销一个MBean。此方法可能会引发抛出的MBeanRegistrationException再次由MBean服务器保持不变。 任何其他异常都会被捕获MBean服务器,封装在MBeanRegistrationException中并抛出再次。 如果此方法引发任何异常,则MBean不会被注销。
  • postDeregister - 这是MBean服务器调用的回调方法注销MBean后。

图7-1描述了MBeanRegistration的方法被调用的方式MBean服务器在执行MBean注册或注销时执行。该以粗框显示的方法是MBeanServer方法,其他方法是在MBean中实现。
……

7.1.4 MBean上的操作(Operations on MBeans)

MBeanServer接口的方法定义了以下管理要在已注册的MBean上执行的操作:

  • 通过object name检索特定的MBean。
  • 通过对名称进行模式匹配来检索MBean的集合, 并且可选地借助于应用于它们的属性值的过滤器。这样的过滤器 可以通过使用“查询”中定义的查询表达式来构造 第135页。
  • 获取MBean的一个或多个属性值。
  • 调用MBean上的操作。
  • 发现MBean的管理界面,即其属性和 操作。 这就是所谓的MBean的内省。
  • 注册MBean发出的通知的兴趣。

MBean服务器的方法是通用的:它们都采用一个对象名称确定执行操作的MBean。 MBean的作用服务器将解析此对象名称引用,确定请求的操作是否被允许在指定的对象上,如果是,则调用MBea方法执行操作。 如果出现结果,则MBean服务器将其值返回给呼叫者。

在MBean服务器中调用方法需要适当的权限。权限在第12章“安全”中描述。
API中给出了所有MBean服务器操作的详细描述由Javadoc工具生成的文档。

7.1.5 MBean代理(MBean Proxies)

作为调用MBeanServer接口的通用方法的替代方法直接访问特定MBean的代码可以为其构建代理。 代理是一个实现与MBean本身相同接口的Java对象。 一种方法代理上的此接口通过MBean服务器路由到MBean。

在可能的地方使用代理而不是调用代码更简单,更不易出错MBean服务器的方法直接。

代理使用newMBeanProxy和newMXBeanProxy方法构造在类javax.management.JMX中。 该类的Javadoc详细解释如何构建和使用它们。

7.2 MBean服务器委托MBean(MBean Server Delegate MBean)

MBean服务器定义了一个名为“JMImplementation”的域,其中包含一个域MBeanServerDelegate类的MBean已注册。 这个对象标识和描述了注册的MBean服务器。 它也是广播公司由MBean服务器发出的通知。 换句话说,这个MBean就像一个代表它所代表的MBean服务器。

此代理对象的完整对象名称由JMX指定规范,如下所示:“JMImplementation:type = MBeanServerDelegate”。

委托对象提供有关MBean服务器的所有信息其中显示为字符串类型的只读属性:

  • MBeanServerId标识代理。 这个字符串的格式不是 指定,但它旨在为MBean服务器提供唯一标识符, 例如,基于主机名和时间戳。
  • 规格名称表示规格的完整名称 MBean服务器实现是基于的。 这个属性的值必须是 “Java管理扩展”。
  • SpecificationVersion指明了JMX规范的版本 这是MBean服务器实现的基础。 对于这个版本,价值该属性必须是“1.4”。 SpecificationVendor指示JMX供应商的名称 MBean服务器实现所基于的规范。 的价值这个属性必须是“Sun Microsystems”。
  • ImplementationName给出了MBean服务器的实现名称。该字符串的格式和内容由实现者给出。
  • ImplementationVersion提供了MBean的实现版本 服务器。该字符串的格式和内容由实现者给出。
  • ImplementationVendor提供MBean服务器的供应商名称 实现。该字符串的内容由实现者给出。

MBeanServerDelegate类实现NotificationBroadcaster接口并发送由此发出的MBeanServerNotificationsMBean服务器。 对于接收这些通知的对象,他们必须注册委派对象(请参阅第134页的“MBean服务器通知”)。

注 - “JMImplementation”域名保留供JMX Agent使用实现。 MBeanServerDelegate MBean不能从中注销MBean服务器。

7.3 MBean上的远程操作(Remote Operations on MBeans)

使用代理中适当的连接器服务器进行远程管理应用程序能够通过相应的操作在MBean上执行操作连接器客户端,一旦建立连接。

通常,远程客户端能够执行该操作中的一部分操作MBeanServer接口,通过该接口的父级MBeanServerConnection。因为远程连接可能会失败,所以MBeanServerConnection中的每个方法都会失败在throws子句中声明IOException。 请参阅“MBeanServerConnection接口”一节第140页。

图7-2显示了如何从远程传播管理操作管理应用程序添加到代理端的MBean。 这个例子说明了用于获取标准MBean的“State”属性的方法的传播以下情况:

  • 管理应用程序调用一个通用的getValue方法 连接器客户端,它充当MBean服务器的远程表示。 这个动态调用类型通常与MBean结合使用 内省功能可动态发现管理界面 的MBean,甚至是远程应用序。
  • 管理应用程序直接在代理上调用getState方法通常从MBean类自动生成的对象(在这种情况下的Java应用程序)。 代理对象依赖于连接器的接口客户端将请求传送给代理并最终传送到MBean。该响应遵循反向返回路径。

….

7.4 MBean服务器通知(MBean Server Notifications)

MBean服务器将在MBean注册时始终发出通知注销。 为此定义了Notification类的特定子类用途:MBeanServerNotification类,包含对象名称列表参与该行动。

MBean服务器对象本身不会广播通知:它的唯一代理MBean实现NotificationBroadcaster接口来广播通知在它的地方。

要注册MBean服务器通知,监听器将调用与注册时一样,MBean服务器的addNotificationListener方法MBean通知,但它将提供MBean的标准化对象名称服务器委托对象(请参阅第121页的“MBean服务器委托MBean”)。

在接收MBean通知时,对象必须实现NotificationListener接口接收MBean服务器通知。

通过它的委托,MBean服务器发出以下两种类型的通知:

  • JMX.mbean.registered - 此通知指示一个或多个MBean已被注册。 通知将传达这些对象名称的列表 的MBean。
  • JMX.mbean.unregistered - 此通知指示一个或多个MBean已被注销。 通知传达了这些列表MBeans的对象名称。

注 - 当注册的属性时,MBean服务器不发送通知MBean更改值。 实施时,处理这种类型的通知直接由MBean执行,如第58页上的“属性更改通知”中所述。

7.5查询(Queries)

查询根据它们的对象从MBean服务器中检索MBean集合名称,它们的当前属性值或两者。 JMX规范定义了用于构建查询表达式的类。 这些对象然后传递给MBeanServer接口执行查询的方法。

执行查询的MBeanServer接口的方法是:

  • queryMBeans(ObjectName名称,QueryExp查询) - 返回一个Set包含MBean的对象实例(对象名称和类名称对) 匹配名称和查询。
  • queryNames(ObjectName名称,QueryExp查询)返回一个Set 包含与名称和查询匹配的MBean的对象名称。

这两种方法的参数含义是相同的。 对象名称参数定义了一个模式:查询的范围是其对象的MBeans集合名字满足这种模式。 查询表达式是用户定义的标准基于其属性值过滤作用域内的MBean。 如果任一查询方法找不到在给定范围内或满足给定查询的MBean表达式或两者,返回的Set将不包含元素。

当对象名称模式为空时,范围等同于所有MBeanMBean服务器。 当查询表达式为空时,MBean不被过滤,结果等同于范围。 当两个参数都为空时,结果就是集合所有在MBean服务器中注册的MBean。

在MBean服务器中注册的所有MBean的集合始终包含该委托MBean,以及任何注册MBean的计数。 其他查询也可以返回委托MBean,如果其对象名称在范围内并且它满足查询表达式(如果有)(请参阅第121页的“MBean Server委托MBean”)。

7.5.1查询的范围(Scope of a Query)

范围由对象名称模式定义:请参阅第117页上的“模式匹配”。只有那些对象名称与模式匹配的MBean才会被考虑查询。 查询表达式必须应用于作用域中的每个MBean,以过滤查询的最终结果。 如果查询机制得到正确实施并且用户给出了相关的对象名称模式,查询的范围可以大大的提高减少查询的执行时间。

该模式可能是一个完整的对象名称,这意味着该对象的范围该查询是单个MBean。 在这种情况下,查询等同于测试存在具有该名称的注册MBean,或者如果查询表达式不存在null,测试该MBean的属性值。

7.5.2查询表达式(Query Expressions)

查询表达式是根据属性值的约束(例如“等于”和“小于”的数字值和“匹配”的字符串)。这些限制可以然后由关系运算符(和,或者,而不是)形成复杂关联涉及几个MBean属性的表达式。

例如,代理或经理应该能够表达一个查询,例如:“检索属性年龄至少为20的MBean和属性名字以G开头,以ling结尾“。

查询表达式一次在单个MBean上进行评估,当且仅当表达式为真,则MBean包含在查询结果中。 MBean服务器测试表达式在查询范围内针对每个MBean单独表达。不是这样查询表达式可能适用于多个MBean:没有用于定义交叉MBean约束的机制。

如果对范围中的给定MBean的查询表达式进行评估,则结果为异常,MBean从查询结果中被忽略。例外不是传播给queryMBeans或queryNames的调用者。错误(的子类)java.lang.Error)可以传播,但是。

为开发查询表达式定义了以下类和接口:

  • QueryExp接口标识完整查询表达式的对象。这些对象可以在查询中使用或组成更复杂的查询
  • ValueExp和StringValueExp接口标识代表的对象 数字和字符串值,分别用于限制属性 值.
  • AttributeValueExp接口标识表示属性的对象 参与约束。 Query类支持查询的构造。它包含静态方法返回相应的QueryExp和ValueExp对象。
  • ObjectName类(请参阅第115页的“ObjectName类”)实现QueryExp接口并可用于查询中。

实际上,用户不会实例化ValueExp和QueryExp实现直接上课。 相反,他们依靠Query类的方法来返回值和表达式,将它们组合在一起以形成最终的查询表达式。

7.5.2.1查询类的方法(Methods of the Query Class)

Query类的静态方法用于构造值,约束,和查询表达式的子表达式。

以下方法返回可用作a的一部分的ValueExp实例约束,如下所述:

  • classattr - 结果表示MBean的类名称,并且只能是 用于字符串约束。
  • attr - 结果表示指定属性的值。这个结果可以用于布尔值,数字或字符串约束,取决于类型 属性。属性也可以受到其他属性值的限制 等效类型。这个方法被重载以获取类名:这是相当于为MBean的类的名称设置一个约束。 如果作用域中的MBean没有给定名称的属性,或者如果有一个类名参数,它不匹配MBean的类,然后是 查询结果中省略了MBean。
  • value - 结果表示方法参数的值,并使用它在一个约束中。该方法被重载以采用以下任何一种类型:
    • java.lang.String
    • java.lang.Number
    • int
    • long
    • float
    • double
    • boolean
      在所有这些情况下,必须在一个约束中使用结果值 等价的属性值。
  • plus,minus,times,div -这些方法每个都需要两个ValueExp参数并返回一个表示操作结果的ValueExp对象。这些 操作仅适用于数值。这些方法对于 在同一个MBean的两个属性之间构造约束。

以下方法表示对一个或多个值的约束。他们拿ValueExp对象并返回一个QueryExp对象,该对象指示约束是否为在运行时满意。这个返回对象可以用作查询表达式,或者它可以用逻辑运算符组成一个更复杂的表达式。

  • gt,geq,lt,leq,eq -
    这些方法表示标准的关系运算符在两个数值之间,分别为:大于,大于或等于,小于,小于或等于,等于。如果关系满足约束条件对于给定顺序的参数是正确的。
    between - 此方法表示第一个参数所在的约束 严格在其他两个参数所定义的范围内。所有参数都必须 是数字值。
    in -此方法等同于a之间的多个“等于”约束 数值参数和数值的数组。约束是 如果数值等于任何一个数组元素,则满足(true)。
    match -此方法表示属性值与a之间的相等性 给定字符串值或字符串模式。该模式承认通配符(*和?),字符集([Aa])和字符范围([A-Z])具有标准含义。 该属性必须有一个字符串值,如果匹配,则该约束满足 该模式。
    initialSubString,finalSubString,anySubString - 这些方法表示属性值和给定子串之间的子串约束 值。如果子字符串是前缀,后缀或任何子字符串,则约束满足 的属性字符串值,分别。
    isInstanceOf - 此方法表示MBean是一个约束 给定类的实例,由一个字符串命名。此查询类型不存在于 在1.3版本之前的这个规范的版本,所以它不应该在什么时候使用 与实施早期版本的代理进行远程交互。

一个约束可以被看作是计算一个布尔值并且可以被用作a子表达式转换为以下方法。 约束还返回一个QueryExp对象可以在查询中使用,也可以作为更复杂的子表达式使用相同的方法进行查询:

  • and- 结果表达式是两个子表达式的逻辑与 参数。 如果第一个子表达式为假,则不计算第二个子表达式。
  • or-结果表达式是两个子表达式的逻辑或 参数。 如果第一个子表达式为真,则不评估第二个子表达式。
  • not- 结果表达式是单个子表达式的逻辑否定论据。

7.5.2.2查询表达式示例(Query Expression Examples)

使用这些方法,本节开头提到的示例查询是建设如下。 当在字符串值上构造约束时,星号(*)是a通配符可以替换任意数量的字符,包括零。或者,程序员可以使用查询的子字符串匹配方法类。

CODE EXAMPLE 7-1 Building a Query

QueryExp exp = Query.and(
    Query.geq(Query.attr("age"),
        Query.value(20)),
    Query.match(Query.attr("name"),
        Query.value("G*ling")));

大多数查询都遵循上述模式:MBean的命名属性为受程序员定义的值约束,然后组成查询几个属性。 提供的所有公开属性都可用于过滤目的它们可以受数值,布尔值或字符串值的约束。

也可以根据Java类的名称执行查询使用Query类的classattr方法实现MBean。 (这个然而,功能大部分被Query.isInstanceOf查询取代。)代码示例7-2显示了如何构建用于过滤所有MBean的查询虚构类managed.device.Printer。 这个约束也可以组成对属性值进行约束以形成更具选择性的查询表达式。

CODE EXAMPLE 7-2 Building a Query Based on the MBean Class

QueryExp exp = Query.eq(
    Query.classattr(),
    Query.value(“managed.device.Printer”));

7.5.3查询例外(Query Exceptions)

执行查询可能会导致某些特定于过滤的异常方法。 如果对给定MBean的查询的评估生成其中的一个异常时,查询结果中将省略MBean。 应用程序代码不会在通常情况下查看这些例外。 只有当应用程序本身抛出异常,或者如果它调用QueryExp.apply,它是否会看到这些异常。

7.5.3.1 BadAttributeValueExpException类

7.5.3.2 BadStringOperationException类

7.5.3.3 BadBinaryOpValueExpException类

7.5.3.4 InvalidApplicationException类

7.6 MBeanServerConnection接口

JMX 1.2规范引入了一个新的接口MBeanServerConnection,MBeanServer的父接口。 这个接口的目的是提供一个用于访问MBean服务器的通用类型,无论它是否是远程的,即通过连接器或本地访问,并直接作为一个访问Java对象。

MBeanServerConnection接口类似于MBeanServer,但有两个主要区别:

  • 它省略了仅适用于本地访问的以下方法 MBean服务器:
    • instantiate。此方法对创建参数实例很有用 MBean方法或构造函数,当它们是类未知的类时 调用者的类加载器。但远程客户端的类加载器必须知道类 能够反序列化它们。
    • registerMBean。此方法将本地对象注册为MBean MBean服务器。以这种方式注册远程对象是没有意义的。
    • getClassLoader,getClassLoaderFor,getClassLoaderRepository。 这些方法对连接器的服务器端很有用。 (请参阅“使用 更正类参数的类加载器“。)类加载器和类加载器存储库通常不可序列化,因此它们不能传输到远程客户端。无论如何,它不适合遥控器 客户端可以访问这些信息。
    • deserialize.。这个方法返回一个ObjectInputStream,这不是一个 可序列化的类,所以它不能传输到远程客户端。方法 仅用于连接器的服务器端,甚至在那里它被取代 通过getClassLoader(etc)方法。
  • 其余的每个方法都在其throws子句中包含java.io.IOException。

与MBeanServerConnection交互的应用程序代码与本地应用程序一起工作MBean服务器或连接器的客户端,无论它是否是连接到服务器或连接器。

由于MBeanServerConnection的所有方法都可以抛出IOException,调用它们的应用程序代码必须准备好处理这个异常妥善处理。 对于本地MBean服务器,这些例外情况不能发生,但代码必须处理它们。 这是要付出的代价在本地和远程情况下都以同样的方式运行。

7.7更改MBean服务器实现(Changing the MBean Server Implementation)

从JMX规范的1.2版本开始,系统属性可以设置javax.management.builder.initial来替换默认值
用不同的实现来实现MBeanServer接口。当createMBeanServer或newMBeanServer方法的时候
MBeanServerFactory类被调用,它会查询这个属性。如果存在一个值,那么它必须命名一个作为其子类的公共类javax.management.MBeanServerBuilder。该类被实例化并用于创建一个MBeanServer实例。

一个MBeanServerBuilder必须能够创建一个实例MBeanServerDelegate和MBeanServer的一个实例。该MBeanServerDelegate可以是标准例如,javax.management.MBeanServerDelegate或自定义子类,重写实现名称属性。 MBeanServer可以是一个完成MBeanServer接口的重新实现,或者它可以建立在MBeanServer上标准实现通过实例化javax.management.MBeanServerBuilder,调用它的newMBeanServer方法,并将生成的对象包装到另一个MBeanServer对象中。

8.高级动态加载(Advanced Dynamic Loading)

本章介绍基于Java的类加载器的动态加载服务功能来提供使用新的检索和实例化MBean的能力
Java类和可能的本地库。 这些类和库的起源是在部署MBean服务器时不一定是已知的,并且可以包含代码从远程服务器加载。

动态加载通常由管理小程序(m-let)服务执行用于实例化从远程URL获取的MBean(通用资源定位器)在网络上。

Java管理扩展(JMX)规范也定义了较低级别类加载机制,允许开发人员扩展功能m-let服务或在没有它的情况下加载类。

本章介绍所有兼容JMX代理的强制功能。

8.1 M-Lets概述

m-let服务允许您从一个实例化并注册一个或多个MBean远程URL,位于MBean服务器中。 m-let服务通过加载m-let来完成此操作文本文件,指定要获取的MBean的信息。 信息每个MBean都在与XML中使用的标签类似的标签中指定,称为MLET标签。M-let文本文件的位置由URL指定。 当一个m-let文本文件是加载后,下载MLET标签中指定的所有类,并为每个类指定一个实例文件中指定的MBean已创建并注册。

m-let服务本身作为一个MBean实现,并在MBean中注册服务器,所以它可以被其他MBean,代理应用程序或远程使用管理应用。

图8-1说明了m-let服务的操作。

……
FIGURE 8-1 Operation of the M-Let Service

8.2 MLET标签(The MLET Tag)

m-let文件可以包含任意数量的MLET标签,每个标签用于实例化不同的标签JMX代理中的MBean。 MLET标签具有以下语法:

<MLET
CODE = class | OBJECT = serfile
ARCHIVE = "archivelist"
[CODEBASE = codebaseURL]
[NAME = MBeanName]
[VERSION = version]
>
[arglist]
</MLET>

这个标签的元素解释如下:

  • CODE = class 该属性指定完整的Java类名称,包括包名称 MBean被获得。必须包含MBean的已编译的.class文件在由ARCHIVE属性指定的其中一个JAR文件中。代码或者 OBJECT属性必须存在。
  • OBJECT = serfile 该属性指定包含序列化表示的.ser文件 要获得的MBean。该文件必须包含在其中一个JAR文件中由ARCHIVE属性指定。如果JAR文件包含目录层次结构, 此属性必须指定该层次结构中文件的路径,否则为a 匹配不会被发现。
  • ARCHIVE = archiveList 这个强制属性指定了一个或多个包含MBean或.jar的JAR文件MBean使用的其他资源。其中一个JAR文件必须 包含由CODE或OBJECT属性指定的文件。如果存档列表包含 多个文件:
  • 每个文件必须用逗号(,)与下一个文件分开
  • 整个列表必须用双引号(“”)括起来 归档列表中的所有JAR文件必须存储在 代码库URL,或者与m-let文件相同的目录,即默认代码当没有给出基础时。
  • CODEBASE = codebaseURL 此可选属性指定要获取的MBean的代码库URL。它标识包含由ARCHIVE指定的JAR文件的目录 属性。当JAR文件与目录不在同一目录中时使用此属性m-let文本文件。如果未指定此属性,则为m-let文本的基本URL 文件被视为代码库的URL。
  • NAME = MBeanName 此可选属性指定要分配的对象名称的字符串格式当m-let服务将其注册到MBean服务器时,将其发送到MBean实例。
  • VERSION =版本 此可选属性指定MBean的版本号和关联的 JAR文件被获取。 该版本号可用于指定JAR文件是否需要从服务器加载以更新已由m-let服务加载的服务。 该版本必须是一系列由每个分隔的非负十进制整数 小数点(。),例如2.14。
  • arglist MLET标记的可选内容指定一个或多个参数列表 传递给MBean的构造函数进行实例化。 m-let服务寻找具有与指定的参数类型相匹配的签名的构造函数 在arglist中。使用非默认构造函数实例化对象 构造函数仅限于有字符串的构造函数参数 表示。arglist中的每个项目都对应于构造函数中的参数。使用 以下语法来指定argList: 哪里:
  • argumentType是参数的类(例如Integer)
  • argumentValue是参数值的字符串表示形式

8.3 M-Let服务(The M-Let Service)

m-let服务的类是javax.management.loading的成员包。 MLet类实现包含方法的MLetMBean用于远程访问。 这意味着m-let服务本身就是一个MBean和可以像这样管理。

MLet类还扩展了java.net.URLClassLoader对象,这意味着它本身就是一个类加载器。 这允许几个快捷方式来加载没有的类需要一个m-let文件。

8.3.1从URL加载MBean(Loading MBeans From a URL)

m-let服务的getMBeansFromURL方法执行类加载基于远程服务器上的m-let文本文件。 m-let文件和类文件需要可以在服务器上使用,如第144页的“MLET标签”中所述此方法的重载版本将URL参数作为字符串或java.net.URL对象。

m-let文件中的每个MLET标记描述了一个要下载和创建的MBean在MBean服务器中。 当对getMBeansFromURL方法的调用成功时,新下载的MBean在JMX代理中实例化并注册使用MBean服务器。 这些方法返回MBeans的对象实例被成功创建,并为那些不成功的对象抛出一个可抛出的对象。

MLet类的其他方法管理本地库的目录以JAR文件格式下载并由某些MBeans使用。 请参阅API文档由Javadoc工具生成更多细节。

8.3.2类加载器功能(Class Loader Functionality)

m-let服务使用其类加载器功能来访问中给出的代码库一个m-let文件或由URL本身提供。这个代码库可以在m-let中使用用于从相同的代码库下载其他MBean的服务。

例如,一个m-let文件可以指定一些MLET标签来填充所有的JMX代理中的MBean。一旦getMBeansFromURL方法被调用做到这一点,m-let服务可以用来再次实例化这些MBean中的任何一个,或者在相同的代码库中的任何其他类。

这是通过将m-let服务的对象名称作为类加载器参数传递给MBean服务器的createMBean方法(请参阅相应的API由Javadoc工具生成的文档)。因为代码库已经被m-let服务访问,其类加载器功能可以访问代码再次基地。在这种情况下,MLET标签中的信息不再被采纳帐户,尽管可以使用createMBean方法的参数指定参数给类构造函数。

因为MBeanServer接口的createMBean方法需要该对象类加载器的名称,此功能也可用于远程管理在JMX代理中没有直接对象引用的应用程序。

m-let服务MBean还公开用于指定代码的addURL方法而不需要访问任何m-let文件。这些方法添加代码库由指定的URL指定给m-let服务的类加载器。 MBean类在此代码库中可以直接下载并在MBean服务器中创建通过createMBean方法,再次使用作为类给出的m-let服务加载器对象。

注 - 使用m-let服务的类加载器从中加载创建类任意代码库或从m-let代码库重新加载类意味着代理应用程序或MBean开发人员对代码库有一些预先知识运行时的内容。

8.3.2.1本地库(Native libraries)

m-let服务充当它加载的任何MBean的类加载器。这意味着如果MBean包含本地方法,并且它们加载包含该方法的本地库这些方法的代码使用System.loadLibrary,然后是m-let类加载器findLibrary方法将被调用来查找库。此方法被覆盖来自java.lang.ClassLoader。它会试图找到一个资源(通常是一个在JAR文件中输入),其名称是库的名称,在系统相关中修改办法。如果它找到了,它会将内容复制到目录中的文件中由MLet.getLibraryDirectory()方法返回,并返回名称该文件作为findLibrary的结果。这在API中进一步描述MLet.findLibrary规范。

并非所有系统都以这种方式支持本地库。一个不会的系统抛出getLibraryDirectory和UnsupportedOperationException异常MLet类中的setLibraryDirectory方法。

即使在支持此功能的系统上,最好不要依赖它因为它带来的可移植性问题。

8.4类加载器库(The Class Loader Repository)

MBean服务器在类加载器存储库中维护一个类加载器列表。该类加载器存储库有时也被称为默认加载器存储库。

类加载器存储库在以下情况下使用:

  • 在MBeanServer的createMBean和实例化方法中 接口。 类加载器存储库用于查找和加载名为。的类 这些方法的className参数。 这些方法存在于几个重载的表单中。 一些表单有一个指定作为类加载器的MBean的ObjectName参数。 班上 装载机存储库不被这些表单使用。
  • 当m-let在其URL中找不到类时,它会尝试加载该类 类加载器存储库。 这种行为可以在m-let时禁用 创建。
  • MBeanServer接口中的方法getClassLoaderRepository为MBean服务器的客户端提供了一种访问其类加载器存储库的方法 直。

如果在同一个Java虚拟机中为几个MBean服务器创建例如调用MBeanServerFactory.createMBeanServer的程序几次,每个人都有自己的类加载器存储库,独立于其他。

8.4.1如何将加载器添加到类加载器库(How to Add Loaders to the Class Loader Repository)

在创建MBean服务器时,其类加载器存储库包含类加载器用于加载MBeanServer实现类。 此后,一堂课如果注册为MBean,则加载器将添加到存储库中。 如果MBean是随后未注册,则将其从存储库中删除。

换句话说,如果一个MBean被注册为一个后代java.lang.ClassLoader,它被添加到类加载器库中。

如果MBean是java.lang.ClassLoader的后代,但实现了接口javax.management.loading.PrivateClassLoader,那么它永远不会添加到类加载器存储库。

因为类javax.management.loading.MLet是的后代java.lang.ClassLoader,m-let在添加到类加载器库时它们在MBean服务器中注册。 JMX规范包含一个类PrivateMLet子类MLet并实现PrivateMLet。 一个除了永远不会被添加到外,PrivateMLet的行为就像MLet一样类加载器存储库。

8.4.2类加载器库中的加载器的顺序(Order of Loaders in the Class Loader Repository)

存储库中类加载器的顺序非常重要。当一个类被加载使用存储库,每个类加载器依次被要求加载类。如果一个装载机成功加载课程,搜索停止。如果加载程序抛出ClassNotFoundException,搜索继续执行列表中的下一个加载器。如果没有加载器成功加载类,尝试结果为aClassNotFoundException异常。

类加载器存储库中的第一个加载器是用于加载该类的第一个加载器MBeanServer实现类。此后,每个条目都是一个MBeanjava.lang.ClassLoader的后代。这些装载机的顺序是英寸中的顺序MBeans被注册了。

更正式地说,如果createMBean或者.MBean m1出现在MBean m2之前,那么MBean m1会出现MBean m2之前registerMBean操作注册了m1之前完成的操作注册m2开始。如果两个操作都没有在另一个操作完成之前完成MBean同时在不同的线程中注册,并且之间的顺序m1和m2是不确定的。

8.4.3 M-Let委托给类加载器库(M-Let Delegation to the Class Loader Repository)

m-let是一个类加载器,因此它遵循一个类的标准行为加载器使用loadClass方法加载类时:

  • 首先,它向其父类加载器发送一个请求以加载该类。 父类装载程序是在创建m-let时指定的。 默认情况下,它是系统类加载器。
  • 如果父类加载器无法加载类,则m-let尝试加载它 本身通过它的URL列表。 MLet类是的一个子类java.net.URLClassLoader以及通过列表加载类的行为 的URL从URLClassLoader继承。

如果这两个尝试均未找到该类,则m-let尝试加载该类通过类加载器存储库。我们说它委托给类加载器库。只有当这种尝试也失败时,m-let才会抛出aClassNotFoundException异常。

当m-let委托给类加载器存储库时,存储库中的每个加载器被依次要求加载班级。但是,如果m-let本身就在类加载器中存储库,只要到达m-let,搜索就会停止。也就是说,只有m-let委托给存储库中的加载器。

类加载器存储库可用作使公共类可用的一种方式到来自不同来源的MBean。常见的类放在存储库中,和m-let委托给存储库可以找到它们。因为只有m-let委托给存储库中它们之前的加载程序,加载程序的顺序注册是重要的。如果m-let m1定义了mlet中的类使用的类m2,那么m1必须在m2之前注册。

当创建一个m-let时,可以控制它是否委托给其他人通过存储库的装载机,以及其他装载机是否委托给它:

  • 如果m-let是使用Boolean delegateToCLR参数构造的,则为false将不会委托给类加载器存储库。
  • 如果m-let是PrivateMLet的一个实例,则它不会被添加到该类中装载机存储库,所以其他装载机不会委托给它。

8.4.3.1 JMX 1.2规范中的新语义(New Semantics in the JMX 1.2 Specification)

在1.2之前的JMX规范的版本中,m-let委托给完整列表类加载器存储库中的加载器。也就是说,如果一个m-let本身没有找到一个类,存储库中的每一个其他装载器都被咨询过。装载机被咨询不管它们是在库中的m-let之前还是之后。

这种行为对于某些Java虚拟机的一个微妙问题是开放的。注意一个类不一定是通过显式调用loadClass方法来加载的一些类加载器。更经常的是,一个类被加载,因为它被另一个类引用类,例如,因为它是该类中的字段的类型,或者是参数或类的字段类型在方法中返回值,或者它是该类的超类或超接口,或者是其中的一个该类中的方法构造它的一个实例,或者引用一个静态字段或方法。简化一下,我们可以说一个阶级的确切时刻加载无法预测。

当一个类c1第一次引用另一个类A时,A使用c1的类加载装载机。如果c1由m-let m1加载,则A也将使用m1加载。

如果m1没有通过它的父类加载器或通过它的列表找到类A.URL,它将委托给类加载器存储库。

参考图8-2,设想在另一个线程中同时出现一个类c2,由m-let m2装载,首次提到B类。 再次,B将被加载使用m2,如果m2没有找到类本身,它将委托给类加载器库。

如果m1搜索存储库中除本身之外的所有加载程序,m2会执行此操作同样,那么m1最终会发送一个请求到m2来加载A,m2将最终结束向m1发送请求以加载B.

某些没有的Java虚拟机实现会出现问题一次允许多个线程通过给定的类加载器加载类。因为线程1正在通过m1加载类A,线程2不能同时加载B级到m1级。 因为线程2正在通过m2加载B类,线程1不能同时通过m2加载A级。 每个线程都必须等待另一个线程在它可以继续之前完成,创造一个典型的死锁情况。

……

FIGURE 8-2 Deadlock scenario for m-let delegation

语义的变化避免了这种情况,因为两个m-let必须有一个在类加载器存储库中出现在另一个之后。 如果m2出现在m1之后,则m1将会出现从来没有尝试使用m2加载一个类,因为它只委托加载器出现的时间早于存储库中的时间。 所以僵局不会发生。

如果你准备好运行僵局的风险,或者你确定这样的场景由于上述情况不会发生,所以直接将MLet子类化并覆盖它loadClass方法来恢复以前的委托给所有加载器的语义在存储库中,无论是在m-let之前还是之后。 但是,您应该记住,如果通过这样一个MLet子类加载的类引用另一个没有的类存在之前,请参阅类加载程序存储库中的所有m-let抛出ClassNotFoundException。

8.5为参数使用正确的类加载器(Using the Correct Class Loader for Parameters)

类加载的一个微妙缺陷是由类加载器cl1创建的类a.b.C与类加载器cl2创建的类a.b.C不同。 在这里,“创建”指的是实际上使用其defineClass创建类的类加载器方法。 如果cl1和cl2都通过委托给另一个类加载器cl3来找到a.b.C,则它是同一班。

由cl1创建的“a.b.C”类型的值不能分配给变量或由cl2创建的“a.b.C”类型的参数。 试图这样做会导致一个异常,如ClassCastException。

对于JMX规范,这可能会造成方法参数的问题createMBean,invoke,setAttribute和MBean服务器的setAttributes。
假设您有一个具有以下接口的MBean:
CODE EXAMPLE 8-1 Simple MBean interface:

public interface AnMBean {
public void m(SomeClass x);
}

如果MBean服务器包含由该类创建的该MBean的一个实例装载机cl1。 在某个阶段,无论是在加载过程中,还是在第一次被引用时,cl1将加载类SomeClass。

现在假设您正在编写一个连接器。 在接收端(服务器端)连接器,您将获得调用MBean上的m的请求。 发件人将发送一个您必须重新创建SomeClass的实例,例如,通过反序列化它。 如果你用cl1以外的任何类加载器重新创建它,你会在得到一个异常时您尝试将其传递给方法m。

这意味着你的连接器服务器必须有一个实例化接收的方法使用正确的类加载器的对象。

为此,MBeanServer接口中有三个与类相关的方法加载:

  • getClassLoaderFor
  • getClassLoaderRepository
  • getClassLoader

以下各节将介绍这些方法。

8.5.1 getClassLoaderFor

代码示例8-1在152页适当的方法是:
public ClassLoader getClassLoaderFor(ObjectName name);

通过使用MBean的ObjectName调用此方法,您可以获取该类加载器创建了MBean的类,在我们的示例中为cl1。 然后你可以得到正确的类,SomeClass,使用cl1。

getClassLoaderFor方法适用于invoke,setAttribute,和setAttributes操作,因为适当的类加载器
这些操作的参数是目标MBean的类加载器。

getClassLoaderFor方法在JMX的1.2版本中引入规范。 以前,连接器服务器必须使用反序列化之一
MBeanServer接口中的方法。 这些方法现在已被弃用。因此,请使用getClassLoaderFor而不是反序列化(ObjectName名称,byte []数据)。

8.5.2 getClassLoader和getClassLoaderRepository

对于createMBean操作,目标MBean不存在,因为整个操作的目的是创建它。 有两类createMBean操作,具体取决于是否有指定的loaderName参数作为用于加载MBean类的类加载器的MBean的名称。

  • 对于不包含loaderName的createMBean操作,MBean 类使用类加载器存储库进行加载。 如果构造函数被调用创建MBean有参数,那么这些也应该使用类加载 加载器存储库,以避免上面针对invoke解释的问题。因此,MBeanServer接口包含一个方法:
    public ClassLoaderRepository getClassLoaderRepository();
    可以使用ClassLoaderRepository接口中的方法loadClass通过类加载器存储库加载类。
  • 对于包含loaderName的createMBean操作,MBean类为 使用以给定名称注册为MBean的类加载器加载。 如果构造函数有参数,它们的类应该由同一个类加载 装载机。因此,MBeanServer接口包含一个方法:
    public ClassLoader getClassLoader(ObjectName name);

类似的考虑适用于MBeanServer的实例化方法接口。 但是,这些方法通常不会通过连接器暴露。

方法getClassLoader和getClassLoaderRepository是在JMX规范版本1.2中引入。 以前,连接器服务器必须在MBeanServer接口中使用三种反序列化方法之一。 这些方法现在已被弃用。

因此,使用getClassLoaderRepository而不是
deserialize(String className,byte [] data)
也可以使用getClassLoader代替
deserialize(String className,ObjectName loaderName,byte [] data)

9.监测(Monitoring)

本章指定了允许您观察的监视器MBean族其他MBean中的属性值的随时间变化,并在发送通知
阈值事件。 它们统称为监测服务。

监视服务是遵从JMX的代理的强制性部分规范,并且必须全面实施。

9.1概述(Overview)

使用监视服务,可以从一个或多个给定属性中获取观察值其他MBean(观察到的MBean)会按照指定的时间间隔进行监视粒度周期。 该值是属性值或其中包含的值复杂类型的属性值。 对于每个观察到的MBean,监视器派生出一个来自这个观察的第二个值,称为派生量表。 这个派生的标准是要么是确切的观测值,要么是两者之间的差异数值类型的连续观测值。

每个监控服务发送一个特定的通知类型的派生量表满足一组条件之一。 条件被指定当监视器初始化时,或者通过监视器MBean的动态管理界面。 监视器也可以在某些错误情况下发送通知在监视属性值时遇到。

9.1.1监视器的类型(Types of Monitors)

有关MBean中属性值的信息由三个提供不同类型的显示器:

  • CounterMonitor - 使用Java整数类型(字节,整数, 短,长),表现得像一个柜台,即:
    • 它们的值总是大于或等于零。
    • 只能增加它们
    • 它们可以翻转,并且在那种情况下定义模值。
  • GaugeMonitor - 使用Java整数或浮点类型观察值(Float, Double), 表现得像一个衡量(任意增加和减少)。
  • StringMonitor - 观察String类型的值。

所有类型的监视器都扩展了抽象的Monitor类,它定义了常见的类属性和操作。 观测值的类型必须被支持使用特定的监视器子类。

但是,监视器会验证返回的对象实例的类型属性的值,而不是观察到的MBean元数据中声明的属性类型。例如,这允许字符串监视器观察声明为Object的值在其元数据中,只要实际值是String实例。

每个监视器也是一个标准的MBean,允许它们被创建和由其他MBean或管理应用程序动态配置。

9.2 MonitorNotification Class

通知类的特定子类被定义为供所有监控使用服务:MonitorNotification类。

此通知用于报告以下情况之一:

  • 检测到监视器的触发条件之一,例如高仪表的阀值达到了
  • 观察属性期间发生错误,例如观察到的错误MBean不再注册

MonitorNotification实例中的通知类型字符串标识特定的监视事件或错误情况,如图9-1所示。 a的领域MonitorNotification实例包含以下信息:

  • 观察到的MBean的对象名称
  • 观察到的属性名称或属性名称加上要观察的复杂类型属性值中的值
  • 派生量表,即从观察计算得出的最后一个值
  • 触发此通知的阈值或字符串

所有可以由生成的通知类型的树表示形式监控服务在图9-1中给出。 错误类型对所有人都是通用的监视器并在下面进行描述。 每个阈值事件都是特定于它的监视器并在相应的章节中进行介绍。

……

FIGURE 9-1 Tree Representation of Monitor Notification Types

9.2.1通用监视器通知类型(Common Monitor Notification Types)

以下通知类型对所有显示器都是通用的,并被发送到反映错误情况。 显示器启动时进行第一次测量:

  • jmx.monitor.error.mbean - 当其中一个观察到的MBean不是时发送在MBean服务器中注册。
    观察的对象名称在提供通知。
  • jmx.monitor.error.attribute - 当观察属性不发送时发送存在于其中一个观察对象中。观察对象名称和观察属性名称在通知中提供。
  • jmx.monitor.error.type -在观察对象实例时发送属性值为null或不是给定监视器的适当类型。该观察对象名称和观察属性名称在通知中提供。
  • jmx.monitor.error.runtime -所有异常(除了描述的情况上面)在尝试获取观察属性的值时发生由监视器捕获,并将在此类通知中报告。

以下通知类型对于计数器和计量监视器是通用的; 它被发射以反映特定的错误情况:

  • jmx.monitor.error.threshold - 在发生任何不一致的情况下发送监视器参数的配置:
    • Counter monitor:阈值,偏移量或模量不相同键入作为观察计数器属性。
    • Gauge monitor:低阈值或高阈值与类型不同观察到的规格属性。

9.3 CounterMonitor类

计数器监视器在观察到的计数器值达到或时发送通知超过了被称为阈值的比较水平。

计数器到达时可以翻转(也称为环绕)最大值。 在这种情况下,每次计数器触发通知达到或超过阈值,只要在阈值以下观察到自从之前的通知。 计数器不一定会翻转为零,但是这不会影响处理一般情况的显示器。

另外,偏移机制可以检测计数间隔,如下所示:

  • 只要监视器的偏移值非零,就会启用偏移机制。
  • 当显示器检测到计数器达到或超过阈值时,通知被触发并且阈值增加了偏移值。该阈值会根据需要多次增加偏移值阈值再次超过计数器值,但仍然只发送一个通知。
  • 如果监视的计数器在达到最大值时翻转,则模数值需要设置为该最大值。那么门槛将会如此当它严格超过模数值时也“翻转”。当。。。的时候阈值“翻转”时,它会重置为最近指定的值在任何偏移量之前调用监视器的setInitThreshold方法应用。
  • CounterMonitor类的getThreshold方法总是返回阈值的当前值,其中包括任何偏移增量。
  • 所有增加或翻转阈值都被认为是发生即刻,即在计数递增之前。因此,如果粒度周期适当设置,监视器每触发一次阈值通知计数时间增加一个等于偏移值的时间间隔。

如果使用计数器差异选项,那么派生的计量器的值是计算为两个连续观察计数器值之间的差值观察结果。如果计数器会翻转,那么必须定义模量计数器差异是积极的。当计数器翻转时,两者之间的差异观察结果将是负值,模数的值需要加上。在时间t计算计数器差值的派生量表值(V [t])使用以下算法,其中GP是粒度周期:

  • 当t小于StartDate + 2GP时,V [t] =(整数)0
  • 如果(counter [t] - counter [t-GP])大于或等于零,则V [t] =计数器[t] - 计数器[t-GP]
  • 如果(counter [t] - counter [t-GP])为负数,则V [t] =计数器[t] - 计数器[t-GP] +模量

计数器监视器有以下限制:

  • 阈值,偏移值和模值属性必须是与观察属性相同的整数类型。

图9-2显示了偏移量为2的计数器监视器的操作。

监视器观察到随时间t变化的计数器C(t)。 粒度周期是GP并且比较级别是T. CounterMonitor何时发送通知计数器的值达到或超过比较(阈值)级别。 后通知已经发送,阈值增加了偏移值直到比较级别大于计数器的当前值。

9.3.1计数器监视器通知类型(Counter Monitor Notification Types)

除了监视器错误通知类型外,CounterMonitor MBean还可以广播以下通知类型:

  • jmx.monitor.counter.threshold - 此通知类型在触发时触发派生量表已达到或超过阈值。

9.4 GaugeMonitor Class

仪表监视器观察表现为仪表的数值。 滞后机制是为了避免重复触发通知时仪表在阈值附近产生小振荡。 这种能力是通过成对指定阈值来提供; 一个是高门槛值另一个是低阈值。 阈值之间的差异是滞后区间。

GaugeMonitor MBean具有以下结构:

  • HighThreshold属性定义量表必须达到的值或超过以触发仅在NotifyHigh时才会广播的通知布尔属性为true。
  • LowThreshold属性定义量表必须落入或落下的值触发只在NotifyLow时才会播放的通知布尔属性设置为true。

仪表监视器有以下限制:

  • 阈值高值和阈值低值属性相同键入为观察属性。
  • 阈值高值大于或等于阈值低值。

仪表监视器具有以下行为:

  • 最初,如果NotifyHigh为true并且计量器值等于或大于此值当仪表增加时,高于阈值,然后定义通知被触发。高阈值的后续交叉点不会触发进一步的通知,直到量表值变得等于或小于低阈值。
  • 最初,如果NotifyLow为true并且量表值变得等于或小于量表减少时LowThreshold值,然后定义通知被触发。低阈值的后续交叉点不会导致进一步的通知,直到量表值变得等于或大于HighThreshold值。
  • 创建仪表时,如果NotifyHigh为真,仪表值为已经等于或大于HighThreshold值,则定义通知被触发。同样,如果NotifyLow为true并且量表值为已经等于或小于LowThreshold值,则定义通知也被触发。

如果使用标准差选项,则派生标准的值为计算为连续两次观测值的差值观察结果。

在t时刻使用计算出的量表差值的派生量表值(V [t])以下算法,其中GP是粒度周期:

  • 当t小于StartDate + 2GP时,V [t] =(整数)0
  • 否则,V [t] = gauge [t] - gauge [t-GP]

假设两者都是GaugeMonitor的操作,如图9-3所示通知开关是真的。

……
FIGURE 9-3 Operation of the GaugeMonitor

9.4.1仪表监视器通知类型(Gauge Monitor Notification Types)

除了监视器错误通知类型外,GaugeMonitor MBean还可以广播以下通知类型:

  • jmx.monitor.gauge.high - 此通知类型在触发时触发派生量表已达到或超过高阈值
  • jmx.monitor.gauge.low - 此派生的通知类型被触发仪表已降至或低于低阈值。

9.5 StringMonitor类

字符串监视器观察String类型的值。 在这种情况下派生的标准是始终是观察值。 字符串监视器被配置为一个值字符串称为字符串比较,并能够检测以下两个条件:

  • 派生量表匹配字符串与比较。 如果NotifyMatch属性的显示器为真,则发送通知。在随后的观察中时间(由粒度周期定义),则不会发送其他通知只要属性值仍然匹配字符串与比较。
  • 派生量表的值与字符串与比较值不同。如果监视器的NotifyDiffer属性为true,然后发送通知。 在随后的观察时间,只要没有其他通知将被发送属性值与字符串 -比较不同。

假设选择了两个通知,则此机制可确保匹配和不同的是严格交替的,每次都是在第一次观察条件时发生。
字符串监视器的操作如图9-4所示。 粒度周期是GP,字符串比较是“XYZ”。

……
FIGURE 9-4 Operation of the StringMonitor

9.5.1字符串监视器通知类型(String Monitor Notification Types)

除了监视器错误通知类型外,StringMonitor MBean还可以广播以下通知类型:

  • jmx.monitor.string.matches - 此通知类型在触发时触发派生量表首先匹配要比较的字符串
  • jmx.monitor.string.differs - 此通知类型在触发时触发派生量表首先不同于要比较的字符串。

9.6监视MBean的实现(Implementation of the Monitor MBeans)

图9-5提供了各种监视器MBean类的包图他们实现的接口。 由Javadoc生成的API文档工具提供了所有监视服务接口的完整描述类。
……
FIGURE 9-5 The Package and Class Diagram of the Monitor MBeans

10.定时服务(Timer Service)

计时器服务会在特定日期和时间触发通知。 它也可以触发通知以固定的时间间隔重复。 通知被发送到所有对象注册接收定时器发出的通知。 定时器服务是一个可以管理的MBean,允许应用程序设置可配置的调度。

从概念上讲,Timer类管理发送的日期通知列表当他们的日期和时间到达。 提供这个类的方法来添加和从列表中删除通知。 实际上,通知类型是由。提供的用户,以及日期和可选的周期以及重复次数。该定时器服务总是发送其特定的通知实例TimerNotification类。

10.1计时器通知(Timer Notifications)

计时器服务可以通过两种不同的方式管理通知:

  • 仅触发一次的通知
  • 以定义的周期和/或数量重复的通知事件

此行为由通知时传递给定时器的参数定义被添加到通知列表中。 每个通知添加到计时器服务被分配一个唯一的标识号码。 只有一个标识号码分配给通知,无论触发多少次。

10.1.1 TimerNotification类

定义了通知类的特定子类以供定时器使用服务:TimerNotification类。 通知类型包含在实例中
的TimerNotification类是特别的:它是由用户定义的通知被添加到定时器。 所有由定时服务广播的通知都是TimerNotification类的实例。

TimerNotification类具有唯一的通知标识符字段标识触发此通知实例的计时器通知。

10.1.2向定时器添加通知(Adding Notifications to the Timer)

定时服务维护一个已经过期的通知的内部列表请求发送。 通知被添加到这个列表使用Timer类’addNotification方法。 这些方法使用以下参数定时器创建一个TimerNotification对象,然后将其添加到列表中:

  • type - 通知类型字符串。
  • message - 通知的详细消息字符串。
  • userData - 通知的用户数据对象。
  • date - 发生通知的日期。 Timer类包括用于表示以毫秒为单位的持续时间的整型常量,然后可以使用它们创建java.util.Date对象。

除了通知之外,addNotification方法也被重载参数和日期,它可以采用以下可选参数:

  • period - 通知发生之间的时间间隔(以毫秒为单位)。如果此参数为零或为空,则不会启用重复通知。
  • nbOccurences [原文] - 通知发生的总次数。 如果此参数的值为零或未定义(空),并且如果句点为不为零或为空,那么通知将无限期地重复。

如果要插入的通知的日期在当前日期之前,那么addNotification方法的行为就像当前日期已被指定一样。更新正在添加的计时器通知的日期不会生成任何日期通知事件,而不是sendPastNotifications机制定时器启动时应用(请参阅“启动和停止定时器”打开第168页)。

addNotification方法返回新的计时器通知的标识符。该标识符可用于从中检索关于通知的信息定时器或从定时器的通知列表中删除通知。 然而,通知已添加到通知列表后,与其关联参数不能更新。

当发生一次性通知(周期为零或空)时,它将从中删除定时器的通知列表。

10.1.3接收计时器通知(Receiving Timer Notifications)

定时器服务MBean是一个带通知的标准通知广播器类型和时间由通过建立的通知列表定义addNotification方法。给定定时器MBean的所有侦听器都将收到其所有的侦听器计时器通知。配置为监听特定计时器通知的监听器在注册为侦听器时应指定适当的过滤器对象(请参阅“NotificationFilter接口”在第58页)。

当定时器处于活动状态并且定时器通知的日期到期时,定时器服务以给定类型,消息和用户数据广播该通知,以及定时器内的通知标识符。如果有定期通知指定的出现次数,该数字减1。访问定时器通知的出现参数总是返回剩余的访问时发生的次数。

当通知没有重复或已经用完时它会从定时器的通知列表中删除。的方法如果调用,则用于访问通知参数的Timer类将引发异常带有发送和删除的定时器通知的标识符。

10.1.4从定时器167中删除通知(Removing Notifications From the Timer)

计时器通知也可以使用其中一个从通知列表中删除遵循Timer类的方法:

  • removeNotification - 将通知标识符作为参数和从列表中删除相应的通知.
    如果指定了标识符不存在于列表中,此方法抛出InstanceNotFoundException。
  • removeNotifications - 将通知类型作为参数并删除列表中添加了该类型的所有通知。
    如果指定通知类型不对应于列表中的任何通知,此方法抛出一个InstanceNotFoundException异常。
  • removeAllNotifications - 清空计时器的通知列表。 这个方法还会重置通知标识符,这意味着所有现有的标识符对于这个计时器是无效的,并会错误地引用新的通知。

10.2启动和停止定时器(Starting and Stopping the Timer)

定时器服务(由Timer类的一个实例表示)使用激活启动方法并使用停止方法停用。如果通知列表计时器启动时为空,计时器等待添加通知。没有定时器通知在定时器启动之前或停止之后触发。

您可以通过调用定时器来确定定时器是否正在运行方法isActive。如果计时器正在运行,isActive方法返回true。

如果计时器列表中的任何通知都有关联的日期已过当定时器启动时,定时器尝试更新它们。定期的日期通知按其间隔时间递增,直到日期大于当前日期。增量的数量可以受限于其定义的数量的事件。在开始日期和时间之前的一次性日期的通知通知数量有限,无法更新为超过开始日期将从计时器的通知列表中删除。

在定时器启动期间更新或删除通知时,其通知为可以触发或忽略,具体取决于sendPastNotifications属性Timer类的使用:

  • sendPastNotifications = true -所有与之前的日期一次性通知广播开始日期,所有定期通知将以广播方式播出包括那些在开始日期之前应该发生的许多次由于无法在开始日期之后进行更新,因此将其删除。
  • sendPastNotifications = false - 具有开始前日期的通知日期被忽略;如果通知是定期的,它的通知日期会更新,但是没有通知被触发。

将sendPastNotifications标志设置为true会导致泛滥定时器启动时广播通知。 此标志的默认值
是错误的。 将此标志设置为true可确保发生的通知日期定时器停止不会丢失。 用户可以选择在计时器时接收它们即使它们不再符合它们的设定日期,它也会再次启动。

调用在已经启动的Timer上开始,或者在Timer上停止已经停止了,没有效果。 停止后,定时器处于初始状态,并可以在开始时重新启动。

11.关系服务(Relation Service)

作为代理规范的一部分,Java管理扩展(JMX)规范还定义了MBeans之间关系的模型。关系是一个用户定义的,在命名角色中的MBean之间的n元关联。 JMX规范定义用于构建表示关系的对象的类,以及它定义了关系服务,集中了代理中关系的所有操作。

所有关系都由提供有关角色信息的关系类型定义它包含诸如它们的多样性,以及满足这些MBeans的类名角色。通过关系服务,用户可以创建新的类型,然后创建,更新或删除满足这些类型的关系。关系服务也执行查询在所有关系中找到相关的MBean。

关系服务维护关系的一致性,检查所有操作和所有的MBean注销来确保一个关系总是符合它的
关系类型。如果关系不再有效,它将从关系服务中删除,尽管其成员MBean以其他方式继续存在。

11.1关系模型(The Relation Model)

一个关系由指定的角色组成,每个角色的值都是由该角色中的MBeans列表。 该列表必须符合角色信息定义相应角色的MBeans的多重性和类别。 一套一套或更多角色信息定义构成关系类型。 关系类型是a所有关联实例的模板,用于关联表示其角色的MBean。 我们使用术语关系来表示关联现有关系的特定实例MBean根据其定义关系类型中的角色。

11.1.1术语(Terminology)

JMX规范中的关系模型依赖于以下术语。 在这里,我们只定义由术语表示的概念,而不是相应的Java类。

  • role information
    描述关系中的角色之一。角色信息给出了名称的作用,它的多重性表现为一个单一的范围,即班级的名字参与此角色,读写权限和说明字符串。
  • relation type
    关系的元数据,由一组角色信息组成。 它描述了一个关系必须满足的角色,并且它可以作为一个模板创建和维护关系。
  • relation
    当前MBeans之间的关联满足给定的关系类型。 一个关系只能被创建和修改,使其所定义的角色类型总是受到尊重。 关系也可以具有属性和方法在其MBean上运行。
  • role value
    当前满足关系中给定角色的MBeans列表。 角色价值必须始终符合其相应的角色信息。
  • unresolved role
    未解决的角色是对角色非法访问操作的结果,如由其角色信息定义。 而不是由此产生的角色价值
    未解决的角色包含拒绝操作的原因。 例如,用错误的MBean类别设置角色,提供非法列表基数,或试图写只读角色都将返回一个未解决的角色。
  • support classes
    用于表示关系类型和关系实例的内部类。该支持类也暴露在简化关系的用户实现类。 用户的外部实现仍然依赖于关系服务来维护关系模型的一致性。
  • relation service
    一个可以访问和维护所有关系类型的一致性的MBean以及JMX代理中的所有关系实例。 它提供查询操作找到相关的MBeans及其在关系中的作用。 它也是唯一的来源有关关系的通知。

11.1.2关系的例子(Example of a Relation)

在本章中,我们将使用书与书之间关系的例子他们的主人。

为了在JMX规范模型中表示这种关系,我们说图书和所有者是角色。 书籍代表给定MBean的任何数量的拥有书籍类,所有者是另一个MBean类的书籍所有者。 我们可能会定义一个关系键入包含这两个角色并称之为个人图书馆,代表书籍所有权的概念。

与UML相比,下面的图表代表此示例关系建模其相应的关联。
……
FIGURE 11-1 Comparison of the Relation Models

在JMX规范模型中,关系类型是一组静态角色。 关系类型可以在运行时定义,但一旦定义,它们的角色和角色信息不能修改。 给定类型的关系实例定义每个MBean角色并在必要时为其提供操作。

11.1.3保持一致性(Maintaining Consistency)

MBean通过由关系类型定义的关系实例关联关系服务,但是MBean仍然可以通过MBean完全访问
服务器。只有注册的MBean(由它们的对象名称标识)可以是a的成关系。关系服务从不在成员MBean上运行,它只提供他们的对象名称以回应查询。

关系服务阻止无效关系类型的创建,例如,如果角色信息不一致。同样,无法建立无效关系,要么是因为关系类型不受尊重,要么是因为关系类型的对象名称成员MBean在MBean服务器中不存在。角色值的修改也受到相同的一致性检查。

从关系服务中删除关系时,其成员MBean不是通过已移除的实例更长时间的关联,但不受影响。当一个关系类型被删除,该类型的所有现有关系首先被删除。该调用者负责了解删除关系类型的后果。

因为关系仅在已注册的MBean之间定义,所以注销a成员MBean修改关系。关系服务侦听所有MBean服务器通知,用于指示何时取消注册任何关系的成员。然后将相应的MBean从其出现的任何角色值删除。如果新基数的作用与相应的关系类型不一致,该关系从关系服务中删除。

关系服务在修改关系的所有操作之后发送通知实例,创建,更新或删除。此通知提供信息关于修改,例如关系的标识符和新的角色值。该通知还表明该关系是内部还是外部定义(请参阅第174页的“外部关系”)。第171页上的图11-1中介绍的两种模型有所不同。UML协会意味着每本书只能有一个所有者。JMX规范中的关系类型仅对一组角色建模,表明这一点一个关系实例在Books中有一个拥有者MBean和任意数量的MBean角色。JMX规范关系模型只保证MBean满足其要求指定的角色,它不允许用户定义MBean可以有多少关系出现在这里。这意味着关系服务不执行相互关系一致性检查。这些是管理应用程序的责任在创建或修改关系实例时。

如果需要这种一致性水平,管理解决方案的设计者就必须这样做在使用关系服务的对象中实施必要的验证。在我们的例子,设计师需要确保同一本书MBean不是添加到多个个人图书馆关系。一种方法是通过在执行任何操作之前调用关系服务的查询方法。

11.1.4实施(Implementation)

JMX规范定义了其行为实现这一点的Java类关系模型。第170页的“术语”中定义的每个概念都有一个相应的Java类(请参见第171页的图11-2)。随着行为关系服务对象本身,这些类决定了关系服务是如何的用于管理解决方案。

本节介绍关系服务与支持之间的交互类。所有课程的操作和其他细节将进一步介绍部分。异常类是RelationException类的所有子类并只提供一个消息字符串。 API生成的文档其他类的Javadoc工具指出哪些异常是由特定引发的操作。

实际上,角色描述结构是在关系服务之外处理的,并且它们的对象直接由用户实例化(请参阅“角色描述类”第184页)。角色信息对象被分组到数组中以定义关系类型。角色对象和角色列表被实例化传递给角色值的设置者。角色结果由角色值的获取者及其角色列表和未解决的角色返回可以提取列表进行处理。

javax.management.relation
«relation service»
RelationService
RelationServiceMBean
RelationNotification
MBeanServerNotificationFilter
«relation support»
RelationType
RelationTypeSupport
Relation
RelationSupport
RelationSupportMBean
«role description»
RoleInfo
Role
RoleList
RoleUnresolved
RoleUnresolvedList
RoleResult
RoleStatus
«exception superclass»
RelationException
«relation type creation errors»
InvalidRoleInfoException
InvalidRelationTypeException
«relation creation errors»
InvalidRelationServiceException
RelationServiceNotRegistered-Exception
RoleInfoNotFoundException
InvalidRoleValueException
RelationTypeNotFoundException
InvalidRelationIdException
«relation access errors»
RelationNotFoundException
RoleNotFoundException

FIGURE 11-2 Classes of the javax.management.relation package

另一方面,关系类型和关系实例由控制关系服务维护关系模型的一致性。该JMX规范关系模型的实现提供了一个灵活的设计由此关系类型和实例可以是关系的内部或外部服务。

内部关系类型和实例由关系服务创建并且仅可以通过其操作访问。 代表类型和关系的对象内部不能被用户访问。 外部关系类型和实例是在关系服务之外实例化并在其控制下添加的对象。用户可以以任何设计的方式访问这些对象,其中包括作为注册的MBean。

11.1.4.1外部关系类型(External Relation Types)

关系服务维护一个可用于定义的关系类型列表新的关系。关系类型必须在内部创建或外部实例化并在可用于定义关系之前将其添加到关系服务中。

表示外部关系类型的对象必须实现RelationType接口。关系服务依赖于它的方法来访问角色信息每个角色由外部对象定义。请参阅“RelationTypeSupport类”有关用于定义外部关系类型的类的描述,请参阅第181页。

关系类型是不可变的,意味着一旦它们被添加到关系中服务,他们的角色定义不能被修改。如果外部关系类型暴露用于修改角色信息集的方法,它们不应该由其调用在关系服务的控制下添加实例后的用户。该这样做的结果是未定义的,关系服务中的一致性是否定的更长的保证。

使用外部关系类型的好处是角色信息可以例如,在类构造函数中静态定义。这允许预定义的类型
被快速实例化,然后添加到关系服务中。

一旦它被添加到关系服务中,就可以使用外部关系类型创造内部和外部关系。外部关系类型也是以与内部关系类型相同的方式从关系服务中删除相同的后果(请参阅第177页的“RelationService类”)

11.1.4.2对外关系(External Relations)

关系服务还维护它所控制的关系的列表。内部关系是通过关系服务创建的,只能通过其关系服务来访问方法。外部关系是由用户实例化并在其下添加的MBean关系服务的控制。它们必须在MBean服务器中注册然后才能将其添加到关系服务中。他们都可以通过关系服务以及通过MBean服务器。

外部关系对象必须实现定义该关系的Relation接口关系服务用来访问其角色值的方法。外部关系是还负责维护自己的一致性,只允许访问它的角色值由其关系类型描述。最后,外部关系必须告知任何角色值被修改时的关系服务。

关系服务对象公开检查角色信息的方法更新其内部角色值。外部关系对象必须被设计为适当时调用这些。不这样做会导致不一致的关系服务的行为之后未定义。

外部关系的主要好处是能够提供返回的方法有关关系成员的信息,甚至是角色值。因为外部关系也是一个MBean,它可以选择公开这些方法作为属性和操作。

返回到第170页上的“关系示例”,图书所有权关系可以由仅包含角色书籍的一元关系类型表示。关系
将通过外部的所有者MBean的实例来实现关系服务。这个MBean可以有一个属性,如bookCount和诸如买卖之类的操作都适用于当前的成员关系。

有关外部关系的示例,请参见第182页的“RelationSupport类”。

11.2关系服务类(Relation Service Classes)

关系服务在RelationService对象中实现,这是一个标准由RelationServiceMBean接口定义的MBean。 因此可以从管理应用程序远程访问和管理。

……
FIGURE 11-3 Relation Service Classes

关系服务MBean是通知广播者并且是唯一要发送的对象RelationNotification对象。 为了保持一致性,它还会监听MBean通过MBeanServerNotificationFilter对象的服务器通知。

11.2.1 RelationService类

关系服务提供了创建和删除关系类型和方法的方法关系实例以及访问关系中的角色。它也暴露了方法查询关系及其成员以找到相关的MBeans。

有两种方法来定义关系类型:

  • createRelationType -
    根据角色数组创建内部关系类型信息对象;关系类型通过作为a传递的名称来标识参数,并且在所有关系类型名称中必须是唯一的。
  • addRelationType -
    使外部定义的关系类型可用关系服务(请参阅第181页的“RelationType接口”)。定义关系也有两种类似的方法。每一个新的关系触发RelationNotification:
  • createRelation -
    使用给定的角色值列表创建内部关系;该关系由作为参数传递的标识符标识,并且必须是在所有关系标识符中唯一。
  • addRelation -
    将由MBean表示的外部关系放置在关系服务的控制; MBean必须先被实例化 并在MBean服务器中注册。

removeRelationType方法删除内部或外部关系类型。使用removeRelation方法将删除该类型的所有关系(请参阅第171页的“维护一致性”)。

removeRelation方法从关系服务中删除关系,这意味着它不能再被访问。成员MBeans中的角色关系继续存在。当外部关系被删除时,MBean实现它仍然可以在MBean服务器中使用。删除关系触发关系通知。

关系服务提供了访问由其标识的关系类型的方法独特的名称:getRoleInfo和getRoleInfos。

它提供了访问关系及其角色值的方法。所有角色的访问权限受限于关系类型中定义的访问模式和一致性检查,尤其是对于设置角色值:getRelationTypeName,getRole,getRoles,getAllRoles,getReferencedMBeans,setRole和setRoles。设置角色将会触发关系更新通知。

还有确定内部和外部关系的方法:

  • hasRelation - 表示是否定义了给定的关系标识符。
  • isRelation -
    获取对象名称并指示它是否已添加为与服务的外部关系。
  • isRelationMBean - 返回外部定义关系的对象名称。以下查询方法检索给定MBean所涉及的关系:
  • findReferencingRelations -
    检索给定MBean的关系引用。可以通过指定类型来限制搜索范围关系来寻找和/或预期MBean被发现的角色关系。在结果中,关系标识符被映射到角色名称列表MBean被引用(一个MBean可以在相同的几个角色中被引用关系)。
  • findAssociatedMBeans -
    检索与给定MBean关联的MBean关系服务。可以通过指定类型来限制搜索范围关系来寻找和/或预期MBean被发现的角色在关系中。在结果中,相关MBean的对象名称被映射到关系列表两者相关的标识符。

findRelationsOfType方法返回所有的关系标识符
给定关系类型的关系。

为了保持一致性,关系服务监听注销通知来自MBean服务器委托。 当有外部关系时会通知它MBean是未注册的,在这种情况下,关系被删除,或者MBean该关系的成员是未注册的(请参阅“维护一致性”第171页)。 purgeRelations方法将检查所有关系数据的一致性并删除所有不再有效的关系。

每次收到相关的撤销注册通知时,关系服务行为取决于清除标志属性:

  • 如果清除标志为真,则将调用purgeRelations方法自动。
  • 清除标志为false时,不采取任何操作,并且可能会有关系服务

关系服务还公开允许外部关系MBean的方法实现预期的行为,或通知关系服务,以便它可以保持一致性:

  • checkRoleReading和checkRoleWriting - 检查是否可以读取给定的角色并通过比较新值与角色信息进行更新。
  • sendRelationRemovalNotification,sendRoleUpdateNotification和sendRelationCreationNotification 触发给定通知事件。
  • updateRoleMap - 通知关系服务已经有角色值修改,以便它可以更新其内部数据。

11.2.2关系通知类别(RelationNotification Class)

这个类的一个实例被创建并在关系时作为通知广播创建,添加,更新或删除。它定义了两个单独的通知类型这些事件中的每一个都取决于事件是关于内部还是事件外部关系。这个类的静态字段描述了所有的通知类型字符串关系服务可以发送(参见第176页的图11-3)。

此类的方法允许侦听器检索有关事件的信息:

  • getRelationId - 返回受此事件影响的关系的标识符。
  • getRelationTypeName -返回关系的关系类型标识符受此事件影响。
  • getObjectName - 仅当所涉及的关系是一个时才返回对象名称外部定义的MBean。
  • getRoleName,getOldRoleValue,getNewRoleValue - 给予额外的有关角色更新事件的信息。
  • getMBeansToUnregister - 返回MBean的对象名称列表由于关系移除而预期未注册。

11.2.3 MBeanServerNotificationFilter类

这个类被关系服务用来只接收那些通知涉及作为角色成员或外部关系实例的MBean。 它过滤基于它们的对象名称的MBean,确保关系服务将只有接收有关MBeans的注销通知。

其方法允许关系服务在必须添加或更新过滤器时更新过滤器删除关系中的MBean或表示外部关系。

关系服务使用的过滤器实例未公开用于管理关系服务。 这个类在这里被描述,因为它是可用的一部分javax.management.relation包并可以在其他地方重用。

11.3接口和支持类(Interfaces and Support Classes)

外部关系类型和关系实例依赖于在中定义的接口下图,可以选择扩展支持类以方便使用。JMX规范的实现也可以在内部依赖这些类。
……
FIGURE 11-4 Interfaces and Support Classes

11.3.1 RelationType接口(RelationType Interface)

关系类型的任何外部表示都必须实现RelationType界面被关系服务认可。 这个接口的方法公开关系类型的名称及其角色信息(请参阅“RoleInfo类”)。

关系服务调用此接口的方法来访问关系类型名称或角色信息。 因为关系类型是不可变的,所以返回在关系类型用关系注册时,值永远不会改变服务。

实现此接口的对象的实例可以作为外部添加关系类型,使用关系服务的addRelationType方法。提供它的实现是连贯的,它可以通过关系来访问服务方式与内部关系类型相同。 事实上,内部关系类型通常由也实现此接口的对象表示

11.3.2 RelationTypeSupport类

这个类实现了RelationType接口并提供了一个通用的代表任何关系类型的机制。关系类型的名称是作为参数传递给类构造函数。

有两种方法通过一个实例定义一个特定的关系类型RelationTypeSupport类:

  • 其构造函数接受一个RoleInfo对象的数组。
  • addRoleInfo方法一次只能获取一个RoleInfo对象。

在使用此类的实例之后,不能添加角色信息在关系服务中定义一个外部关系类型。

用户也可以扩展这个类来创建自定义关系类型,而不需要重写角色信息访问方法。例如,。的构造函数子类可以确定要传递给超类的RoleInfo对象构造函数。这有效地将一个关系类型定义封装在一个可以的类中被动态下载和实例化。

关系服务的实现通常会实例化RelationTypeSupport类来定义内部关系类型,但这些对象是外部无法访问。

11.3.3关系接口(Relation Interface)

Relation界面描述了一个类的支持的操作预计实例将代表关系。 通过这个接口的方法,实现类公开了访问关系所需的所有功能。

实现Relation接口以表示外部关系的类必须装备为MBean。 该对象必须实例化并注册在它可以被添加到关系服务之前,在MBean服务器中。 那么,它可以通过关系服务或通过任何管理访问它在MBean服务器中公开的接口。

11.3.3.1指定的方法(Specified Methods)

每个关系在关系服务中通过一个唯一的关系标识符进行标识通过getRelationId方法暴露。它返回的字符串必须是在服务注册时所有关系中都是独一无二的。关系服务将拒绝添加具有重复或空标识符的外部关系。

以同样的方式,getRelationTypeName方法必须返回一个有效的关系类型名称已在关系服务中定义。外部关系实例还必须知道它将被控制的关系服务对象:这可以通过getRelationServiceName方法进行验证。这种方法返回一个假定在与MBean相同的MBean服务器中有效的对象名称外部关系实施。

关系服务使用Relation界面的其他方法进行访问一个关系在其控制下的角色。角色值可以被读取或写入单独或批量使用(请参阅第181页的“角色描述类”)。个人无法访问的角色会导致其类指示其性质的异常该错误(请参阅第157页的图11-2中的异常类)。

用于批量角色访问的方法遵循“尽力而为”策略:访问所有指示尝试角色,无法访问的角色不会阻止该操作。那些由于输入错误或由于访问而无法访问的该角色的权利将返回一个未解决的角色对象,指示该角色的性质错误(请参阅“RoleUnresolved类”一节第187页)。

getReferencedMBeans方法返回所有MBean的对象名称列表在关系中引用,每个对象名称映射到其中的角色列表MBean是一个成员。

11.3.3.2保持一致性(Maintaining Consistency)

关系服务委托维护角色一致性的责任关系对象。 通过这种方式,可以在角色执行一致性检查通过外部关系的方法访问。 但是,关系服务必须通知任何角色修改,以便它可以更新其内部数据结构并发送通知。

在访问角色时,无论是获取或设置其值,关系实例都必须验证:

  • 关系类型具有指定角色的相应角色信息。
  • 角色根据其角色信息具有适当的访问权限。
  • 为设置角色提供的角色值与该角色的信息一致关于基数和MBean类。

Relation接口的实现可以依靠checkRoleReading和提供给关系服务MBean的checkRoleWriting方法简化上述验证。

设置角色后,外部关系必须调用updateRoleMap操作关系服务,提供新老角色价值。这允许关系服务来更新其内部数据以保持一致性。

关系服务必须被告知所有新的角色值,以便它可以监听一个取消注册有关任何成员MBean的通知。当一个成员外部关系的MBean从MBean服务器(即关系)中注销服务检查它满足的角色的新基数。

如果基数不再有效并且清除标志为真,则关系服务删除此关系实例(请参阅“RelationService类”(第165页))。如果外部关系依然有效,关系服务称之为外部关系handleMBeanUnregistration方法。

在调用时,此方法将MBean从其引用的角色中移除(因为所有角色成员都必须注册MBean)。关系的保证服务将在必要时调用此方法以释放外部关系听取MBean注销本身。它也允许关系实现定义如何更新相应的角色。例如,取消注册一个给定角色的MBean可以更新其他角色。

在这种情况下,以及在暴露的方法修改角色值的任何其他情况下,实现使用自己的setRole方法或调用适当的关系服务方法,比如updateRoleMap。这是所有实现的责任Relation关系接口也维护其关系实例的一致性作为关系服务的角色价值。

11.3.4 RelationSupport Class

这个类是Relation接口的完整实现,它提供了一个通用关系机制。这个类必须用一个有效的角色列表实例化定义它将表示的关系实例。构造函数也需要一个唯一的关系标识符,以及由该满足的现有关系类型的名称给出角色列表。

事实上,RelationSupport类实现了RelationSupportMBean扩展了Relation界面。这意味着它也是一个标准的MBean管理界面暴露了所有的关系访问方法。因为外部关系必须首先在MBean服务器中注册,外部实例关系支持类可以由远程应用程序管理。

用户还可以扩展RelationSupport类以利用它的优势在开发定制的外部关系时实施。用户也可以选择扩展其MBean接口以公开其他属性或操作访问关系。此自定义仍必须保持角色的一致性访问和角色更新,但它可以使用内置的一致性机制RelationSupport类的方法。

关系服务通常实例化RelationSupport类来定义内部关系实例,但这些对象不能从外部访问。

11.4角色描述类(Role Description Classes)

关系服务访问阅读和写作关系的角色值。 JMX规范定义了用于传递角色值的类参数并将其作为结果接收。 这些类也被外部使用实现关系行为的关系MBean。

……
FIGURE 11-5 Role Description Classes

11.4.1 RoleInfo

角色信息提供角色的元数据描述。它规定:

  • 角色的名称。
  • 角色的多重性,表示为单个闭合间隔可以在该角色中引用的最小和最大数量的MBean。RoleInfo构造函数验证这是一个适当的非空间隔。
  • 所有成员必须是实例的类或接口的名称,如由MBean服务器的isInstanceOf方法确定。
  • 角色访问模式,即角色是可读的还是可写的,或两者兼而有之。

当角色信息被用作新的关系类型的参数时,它是定义角色的信息。当关系类型在关系中声明时服务,对于每个角色,该服务将验证:

  • 角色信息对象不为空。
  • 角色名称在给定关系类型的所有角色中是唯一的。关系服务不保证其他关系类型中具有相同名称的角色是相同的;这是用户的责任。

11.4.2角色类别(Role Class)

Role类的一个实例表示关系中角色的值。 它包含角色名称和引用现有MBean的对象名称列表。

角色值必须始终满足其关系类型的角色信息。 角色名称是将角色值与其定义的角色信息相关联的关键。

Role类用作两个关系的setRole方法的参数服务和关系接口。 它也是使用的列表的组成部分批量设置方法和定义初始角色值。 对于每个角色初始化或更新,关系服务验证:

  • 具有给定名称的角色在关系类型中定义。
  • 引用的MBeans的数量大于或等于最小值基数和小于或等于最大基数。
  • 每个对象名称引用一个已注册的MBean,该MBean是预期的实例类或接口。

11.4.3 RoleList Class

该类扩展java.util.ArrayList以表示一组Role对象。

RoleList类的实例用于定义关系的初始值。 什么时候调用关系服务的createRelation方法,承认a的角色0的基数可以从角色列表中省略。 关系类型的所有其他角色在初始角色列表中必须具有格式良好的角色值。

角色列表对象也被用作两者的setRoles方法的参数关系服务和关系接口。 这些方法仅为其设置角色一个有效的角色值出现在角色列表中。

最后,所有批量访问方法都会返回包含RoleList对象的结果代表成功访问的角色。

11.4.4 RoleUnresolved Class

此类的实例表示对给定的读取或写入访问不成功在关系中的作用。 它仅用于角色访问方法的返回值关系服务或实现Relation关系的对象。

该对象包含:

  • 无法访问的角色的名称
  • 为不成功的写访问提供的值
  • 尝试失败的原因,编码为整数值。 常量解码问题在第185页的图11-5中给出。

11.4.5 RoleUnresolvedList类

该类扩展java.util.ArrayList以表示一组RoleUnresolved对象。 所有批量访问方法都会返回包含RoleUnresolvedList的结果表示无法访问的角色的对象。

11.4.6 RoleResult类

RoleResult类是所有批量访问方法的返回对象关系服务和Relation界面的实现。 角色结果包含角色及其值的列表,以及未解决的角色列表和原因每个都无法访问。

作为getter的结果,角色值包含请求的当前值角色。 未解决的列表包含无法读取的角色,可能是因为角色名称无效或角色不允许阅读。

作为setter的结果,角色值包含那些角色的新值手术成功了。 未解决的列表包含不可能的角色书面,出于任何访问或一致性原因。

11.4.7 RoleStatus类

这个类包含静态字段,给出未解决角色的可能错误代码。错误代码与访问权限或一致性检查相关。该这些字段的名称标识了问题的性质,如图11-5所示第185页。

12.安全(Security)

Java管理扩展(JMX)MBean服务器可能有权访问敏感数据库信息,并可能能够执行敏感操作。 在这种情况下,它是希望控制谁可以访问那些信息以及谁可以执行这些信息操作。 JMX规范建立在标准的Java安全模型上定义控制对MBean服务器及其操作的访问权限。

本章介绍的安全检查仅在存在安全检查时执行安全经理。 也就是说,如果System.getSecurityManager()返回null,那么不执行检查。

12.1权限(Permissions)

假设读者对Java安全模型有一定的了解。一个出色的参考资料是李功的Inside Java™2平台安全性(Addison Wesley,1999年)。文档也可作为Java 2平台标准的一部分在线提供版(J2SE)标准开发工具包(SDK)。

敏感操作需要权限。在执行这样的操作之前,a执行检查以确保主叫方具有所需的权限或权限。

在程序执行过程中的任何一点,都有一组当前的执行线程持有的权限。当这样的线程调用JMX时规范操作,我们说这些是持有的权限。

执行安全检查的操作通过定义所需的权限来实现。如果持有的权限意味着需要的权限,则允许该操作。那是,至少有一个持有许可必须暗示需要的许可。

权限是一个Java对象,它是java.security.Permission的子类。这个类定义了以下方法:
public boolean implies(Permission permission);

持有的持有许可意味着需要所需的许可
held.implies(needed).
例如,要调用MBeanServerFactory.createMBeanServer,需要一个线程许可
MBeanServerPermission(“createMBeanServer”)

以下是一个线程可以容纳的一些权限,意味着需要权限:

  • MBeanServerPermission(“createMBeanServer”)
  • MBeanServerPermission(“createMBeanServer,findMBeanServer”)
  • MBeanServerPermission(“*”)
  • java.security.AllPermission()

不具有任何这些权限的线程,或任何其他权限暗示需要的一个,将无法调用createMBeanServer。 试图这样做会导致SecurityException。
JMX 1.2规范定义了三个权限:

  • MBeanServerPermission
  • MBeanTrustPermission
  • MBeanPermission

这些权限在以下各节中进行介绍。

12.1.1 MBeanServerPermission

MBeanServerPermission控制对类的静态方法的访问javax.management.MBeanServerFactory。 (请参阅“MBean Server Factory”)MBeanServerPermission由单个字符串构成参数,而权限对象的含义取决于这个字符串,如下:

  • MBeanServerPermission(“createMBeanServer”)控制对该对象的访问两个重载的方法MBeanServerFactory.createMBeanServer。持有此权限允许创建一个MBeanServer对象登记在可通过的列表中MBeanServerFactory.findMBeanServer。此权限意味着newMBeanServer权限,因此也保留它允许创建未在该列表中注册的MBeanServer对象。
  • MBeanServerPermission(“newMBeanServer”)控制对这两者的访问重载的方法MBeanServerFactory.newMBeanServer。拿着这个权限允许创建未注册的MBeanServer对象该列表可通过MBeanServerFactory.findMBeanServer访问。
  • MBeanServerPermission(“releaseMBeanServer”)控制对其的访问方法MBeanServerFactory.releaseMBeanServer。拿着这个权限允许从列表中删除MBeanServer对象通过MBeanServerFactory.findMBeanServer。
  • MBeanServerPermission(“findMBeanServer”)控制对其的访问方法MBeanServerFactory.findMBeanServer。持有此权限允许您在MBeanServerFactory的列表中找到MBeanServer对象给定其标识符,并检索列表中的所有MBeanServer对象。

为了方便定义权限,可以使用两个或多个字符串结合在一个逗号分隔的列表中。 由此产生的权限意味着所有的列表中的操作。 因此,举例来说,持有MBeanServerPermission(“newMBeanServer,findMBeanServer”)是相当于同时拥有MBeanServerPermission(“newMBeanServer”)和MBeanServerPermission(“findMBeanServer”)。

持有MBeanServerPermission(“*”)等同于持有所有上面列表中的权限。

12.1.2 MBeanPermission

MBeanPermission控制对MBeanServer对象的方法的访问由MBeanServerFactory.createMBeanServer或MBeanServerFactory.newMBeanServer。 (请参阅第7章“MBean服务器”。)

MBeanPermission使用两个字符串参数构造。 首先参数通常被称为权限的名称,但我们在这里指它作为目标。 第二个参数是权限的行为。

12.1.2.1 MBeanPermission目标(MBeanPermission Target)

MBeanPermission的目标将三条信息分组在一起,每一个都可以省略:

  • The class name。对于需要的权限,这是MBean的类名访问。某些方法不会引用类名称,在这种情况下,该类名称为空。对于持有的许可,这是空的或类名称模式。类名模式可以是一个文字类名,例如javax.management.MBeanServerDelegate或通配符javax.management。。如果类名是空的或者是星号(),那么权限涵盖任何类名。
  • The member。对于需要的许可,这是属性或名称操作被访问。对于不引用的MBeanServer方法
    属性或操作,成员为空。对于持有的权限,这可以是属性或操作的名称被访问,或者是空的或星号(*),覆盖任何属性或操作。
  • The object name。对于需要的权限,这是MBean的ObjectName被访问。 (请参阅第115页上的“ObjectName类”。)用于执行操作不引用单个MBean,它是空的。对于持有的权限,这是可以的MBean或MBean的ObjectName被访问。它可以是一个对象名称模式,它涵盖了所有具有名称的MBean匹配模式;请参阅第117页上的“图案匹配”。它也可以是空的,涵盖所有MBean,而不考虑其名称。如果持有权限中的ObjectName的域部分为空,则不是由默认域取代,因为权限可能适用于几个具有不同域的MBean服务器。

如果全部三个项目都存在,则持有的MBeanPermission只意味着需要的权限比赛。

如果所需的权限具有空类名称,则类名称与该类型无关行动被检查,所以持有的权限将匹配,无论其类名。

如果持有的权限具有空的类名称,则表示该权限涵盖任何类名称,所以无论其类名是什么,所需的权限都会匹配。

同样的规则适用于成员和对象名称。

目标中的三个项目使用以下语法编写为单个字符串:
className#member[objectName]

这三项中的任何一项都可以省略,但至少有一项必须存在。

三个项目中的任何一个都可以是字符“ - ”,表示空项目。空项目与空项目不同。举个例子,一个空的类名权限与“*”相同并表示任何类名。一个空的类名所需的权限被任何类名所隐含。所需的许可从来没有空的类名称,通常拥有的权限永远不会有空类名称。

以下是一些具有其含义的目标示例:

  • com.example.Resource#Name[com.example.main:type=resource]
    这表示对称为MBean的名称的属性或操作的访问其对象名称是com.example.main:type = resource及其类名称是com.example.Resource。
  • com.example.Resource[com.example.main:type=resource]
    com.example.Resource#*[com.example.main:type=resource]
    这两者意味着同样的事情,即访问任何属性或操作具有此对象名称和类名称的MBean。
  • #Name[com.example.main:type=resource]
    *#Name[com.example.main:type=resource]
    这些都意味着同样的事情,即访问属性或操作使用给定的对象名称调用MBean的名称,而不考虑它的类名称。
  • [com.example.main:type=resource]
    这表示使用给定的对象名称访问MBean,而不管它是什么类名称,而不管可以引用哪些属性或操作。
  • [com.example.main:*]
    这表示对具有域的对象名称的任何MBean的访问com.example.main。
  • com.example.Resource#Name
    这表示对任何MBean中名为Name的属性或操作的访问类名为com.example.Resource。

12.1.2.2 MBeanPermission操作(MBeanPermission Actions)

MBeanPermission的操作字符串表示一个或多个方法MBeanServer接口。 并非该接口中的所有方法都是可能的值操作字符串。 完整的清单如下:

  • addNotificationListener
  • getAttribute
  • getClassLoader
  • getClassLoaderFor
  • getClassLoaderRepository
  • getMBeanInfo
  • getObjectInstance
  • instantiate
  • invoke
  • isInstanceOf
  • queryMBeans
  • queryNames
  • registerMBean
  • removeNotificationListener
  • setAttribute
  • unregisterMBean

……

12.1.2.3未经检查的MBean服务器方法(Unchecked MBean Server Methods)

以下MBean服务器方法不受权限检查:

  • isRegistered - 代码总是可以发现一个给定的MBean名称存在。但是,如果没有queryMBeans或queryNames权限,找不到它不知道的MBeans的名称。此操作不受权限检查的原因是它会如果MBean存在但其类名不存在,则生成一个SecurityException由用户的权限覆盖,而它会生成一个InstanceNotFoundException如果MBean不存在,则无论如何类名由用户的权限覆盖。生成一个因此SecurityException将等同于承认MBean确实存在,所以许可检查将毫无用处。尽管可以细化这种情况下的权限检查语义,但是isRegistered方法不足以证明它的正确性。
  • getMBeanCount - 代码总是可以发现有多少MBean。再次,这被认为不足以证明定义权限的正确性
    检查它。
  • getDefaultDomain - 代码始终可以发现MBean服务器的默认值域。

12.1.2.4查询的权限检查(Permission Checking for Queries)

MBeanPermission的queryMBeans和queryNames操作允许控制其中MBean对查询可见。 queryMBeans操作继续如下:

  1. 检查持有的权限以查看它们是否暗示MBeanPermission(“ - # - [ - ]”,“queryMBeans”)如果不是,则抛出SecurityException。如果持有的权限包含任何queryMBeans权限,则这意味着这里显示的权限。只有在没有的情况下才会抛出异常查询MBeans在调用者集合中的权限。没有此检查,如果授予权限的策略被意外配置没有queryMBeans权限,那么所有的查询都会返回一个空集,没有迹象表明这个原因与安全有关。支票这里描述有助于避免这种混淆。
  2. queryMBeans的ObjectName参数,通常是对象名称模式,用于选择查询适用的一组MBean。如参数为空,所有MBean都被选中。
  3. 对于集合中的每个MBean,构造一个MBeanPermission参数是“queryMBeans”,目标参数包含类名称和MBean的对象名称。如果持有的权限不暗示这一点权限,MBean从集合中被删除。
  4. 对于剩余集合中的每个MBean,将QueryExp参数设置为queryMBean 用于决定它是否包含在最终集合中。

queryNames操作的规则与刚才陈述的完全相同,但与由queryNames替换queryMBeans。

有了这些规则,线程拥有的权限将完全控制集合线程可以通过查询看到的MBean。

所持有的权限未涵盖的MBeans是很重要的在执行查询之前从集合中删除。 换句话说,步骤3必须发生在第4步之前。否则,恶意代码可能会执行QueryExp界面将每个MBean保存在某个选定的集合中。

12.1.2.5 getDomains的权限检查(Permission Checking for getDomains)

getDomains权限筛选关于MBean名称的信息对于线程可见,与queryMBeans和queryNames类似。该
线程的持有权限应该暗示:
MBeanPermission(“ - # - [ - ]”,“getDomains”)

否则,MBeanServer.getDomains()方法会抛出一个SecurityException异常。否则,MBean服务器首先获取域的列表如果没有安全检查,并且每个域d都会返回该列表检查所持有的权限意味着:
MBeanPermission(“ - # - [d:x = x]”,“getDomains”)

否则,该域将从列表中消除。

x = x是ObjectName工作方式的工件。一个实现可以使用任何其他关键=值的可能性,而是必须有一个。

定义持有权限时,例如在安全策略文件中,getDomains权限应始终忽略ObjectName或提供在关键属性中只有一个的ObjectName模式,比如“:*”,
“com.example。”或“com.example.visible:*”。

12.1.2.6权限检查getAttributes和setAttributes(Permission Checking for getAttributes andsetAttributes)

用于查询的类似方案用于getAttributes和setAttributes操作。 getAttributes操作按如下进行:

  1. 检查持有的权限以查看它们是否暗示MBeanPermission(“className# - [objectName]”,“getAttribute”)其中className和objectName是MBean的类名和对象名被访问。如果不是,则抛出SecurityException。如果持有的权限允许MBean的任何属性上的getAttribute,则它们将暗示此处显示的许可。只有在getAttribute不允许的情况下MBean中的任何属性都会抛出异常。没有这个检查,如果调用者没有任何属性可用,则为空将返回AttributeList,但没有说明原因与安全有关的任何事情。
  2. 对于赋给getAttributes操作的列表中的每个属性attr,允许MBeanPermission(“className#attr [objectName]”,“getAttribute”)被构造。如果持有的权限不暗示此权限,则该属性从列表中删除。
  3. 然后getAttributes操作对剩余的属性起作用名单。

setAttributes操作的规则与刚才陈述的完全相同,但是将getAttribute(s)替换为setAttribute(s)。

12.1.3 MBeanTrustPermission

此许可表示签署者或代码源中的“信任”。 如果签署者或codesource被授予此权限,因此它被认为是MBeans的可信来源。只有来自可信来源的MBean才能在MBean服务器中注册。

结合MBeanPermission,MBeanTrustPermission可以实现细粒度的控制哪些MBean在MBean服务器中注册。该MBeanPermission的registerMBean操作控制哪些实体可以注册的MBean。 MBeanTrustPermission控制他们可以注册的MBean。

MBeanTrustPermission由单个字符串参数构造而成。 在这JMX规范的版本,参数可以有两个可能的值:

  • “register”
  • “*”

这两个值具有相同的含义,但是如果在将来的JMX版本中说明其他可能性,“*”将涵盖所有这些。

MBeanTrustPermission检查的详细信息如下所示例。

让c是要通过MBean服务器在MBean服务器中注册的MBean的Java类createMBean或registerMBean方法,并且让p成为构建的许可如下:
p = new MBeanTrustPermission(“register”);

然后,为了使createMBean或registerMBean成功,请执行以下操作表达必须是真实的:
c.getProtectionDomain()。暗示(p)的

否则,抛出SecurityException。

12.2策略文件示例(Policy File Examples)

以下是前面介绍的权限的一些示例可以使用标准的Java策略文件语法来授予章节。

最简单的MBean访问策略是授予所有签名者和代码库访问权限的MBean:

grant {
    permission javax.management.MBeanPermission “*”, “*”;
};

这是一个更严格的策略,它授予appl1.jar中的代码许可权获取MBean服务器的类加载器存储库:

grant codeBase “file:${user.dir}${/}appl1.jar” {
    permission javax.management.MBeanPermission ““,
    “getClassLoaderRepository”;
};

这是一个授予appl2.jar代码调用权限的策略来自任何类的MBean的isInstanceOf和getObjectInstance操作,只要它们在域“d1”中注册:

grant codeBase “file:${user.dir}${/}appl2.jar” {
    permission javax.management.MBeanPermission “[d1:*]”,
    “isInstanceOf, getObjectInstance”;
};

这是一个授予appl3.jar中代码查找MBean权限的策略服务器,并调用queryNames操作,但限制返回的设置域“JMImplementation”中的MBean:

grant codeBase “file:${user.dir}${/}appl3.jar” {
    permission javax.management.MBeanServerPermission
    “findMBeanServer”;
    permission javax.management.MBeanPermission
    “JMImplementation:*”, “queryNames”;
};

如果MBean服务器具有其他域中名称的MBean,例如一个MBean注册为“com.example.main:type = user,name = gorey”,他们会从不出现在由appl3.jar中的代码执行的queryNames的结果中。

这是一个授予appl4.jar代码创建权限的策略在任何对象名称下操作类“com.example.Foo”的MBean:

grant codeBase “file:${user.dir}${/}appl4.jar” {
    permission javax.management.MBeanPermission
    “com.example.Foo”, “instantiate, registerMBean”;
    permission javax.management.MBeanPermission
    “com.example.Foo#doIt”,
    “invoke,addNotificationListener,removeNotificationListener”;
};

第一个权限忽略对象名称。 操作或属性名称不是这两项行动所要求的。

然而,第二个权限使用“调用”操作的成员部分忽略它的“add / removeNotificationListener”操作。

这是一个允许MBeans注册的策略,不管它们来自哪里从。 (注册MBeans的线程也必须具有相应的权限MBeanPermissions。)

grant {
    permission javax.management.MBeanTrustPermission “register”;
};

这是一个只信任由“Gorey”签名的MBean的策略:

grant signedBy “Gorey” {
    permission javax.management.MBeanTrustPermission “register”;
};

目录

阅读数 424

Hibernate参考文档

阅读数 4335