显示标签为“技术”的博文。显示所有博文
显示标签为“技术”的博文。显示所有博文

2010年10月8日星期五

翻墙利器

hoho,终于找到一个翻墙利器
chrome滴,莎猪,记下来,https://chrome.google.com/extensions/detail/kjdehhkgdgjcekacdccoflccmhbkefce?hl=zh-cn
你总有一天要用到滴

2009年4月25日星期六

一个关于 的故事

我们常常会解析html,解析html通常来说有两种方法,其一是直接对html进行解析,其二是将html转换到xml再解析。因为学习成本的关系,越来越倾向于使用后者来实现。第二种方法一个常用的工具就是:neko html parser,他将html转换到xml,又使用了Xerces2进行xml操作。

于是问题开始了,在html中的&符号表示的一些特别意义,在xml中往往没有定义,比如今天我要讲的  他在html中表示non-breaking space,但是你不能用同样的方式在xml中表示,因为xml中&开头表示,可解析的实体,这个实体被DTD预先定义,而  并没有被定义,所有如果用Xerces2来解析出现这样字符的xml文件(当然,这里假设你也没有自己预先定义),会抛出如下异常:
org.xml.sax.SAXParseException: The entity "nbsp" was referenced, but not declared.
一般给出如下的解决方案:使用  这又是什么道理呢?因为这是HTML ISO-8859-1 Reference 定义的规范,如下:
Character Entity Number Entity Name Description
    non-breaking space
neko html parser也这是使用的这样的方案,所以当解析这样一句html的时候:

big black bear bit back the big black bug. 


就bei neko html parser替换成了


big black bear bit back the big black bug. 


如果这样就完了,就不会有今天这篇文章了。
但是替换之后,通过xml解析输出,显示的不是一个空格而是一个 ?,最要人命的地方了是这个?并不是ascii 中的063d。所以当我们通过getTextContent()方法的时候,我们得到的是:
big black bear bit back the big black bug.?

在实际应用中,当然需要出去这个?,于是我们打算使用replaceAll("\\?$", "")来替换掉这个?,但是你错了,你被你的眼睛骗了,这时你会发现根本不起作用你应该是用replaceAll("\240$", ""),240是160的八进制数。

ECLIPSE快捷键设置

C:/Eclipse/workspace/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs

org.eclipse.ui.commands这个属性就是你修改过的快捷键了

2009年4月21日星期二

c与c++ static函数的区别

static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明
一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)
特点: static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.
示例程序一
#include
using namespace std;
void staticLocalVar()
{
static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
cout<<"a="<<a<<endl;
++a;
}
int main()
{
staticLocalVar(); // 第一次调用, 输出a=0
staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
return 0;
}
应用:
利用”记忆性”, 记录函数调用的次数(示例程序一)
利用生存期的”全局性”, 改善”return a pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部变量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
return strBuff;
}

注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ----不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见(影印版)第103-105页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部变量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

二、外部静态变量/函数
在C中 static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:

//file1.cpp

static int varA;
int varB;
extern void funA()
{
……
}

static void funB()
{
……
}

//file2.cpp

extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

三、静态数据成员/成员函数(C++特有)
C+ +重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )
请看示例程序四(<effective>(影印版)第59页)
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { --numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.
另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.

2009年3月5日星期四

IPV4和IPV6在APR中的用法

由于APR中仅仅使用apr_sockaddr_t结构描述套接字地址,因此其余的各类描述信息最终都要
转换为该结构,APR中提供apr_sockaddr_info_get函数实现该功能:
APR_DECLARE(apr_status_t) apr_sockaddr_info_get(apr_sockaddr_t **sa,
const char *hostname,
apr_int32_t family,
apr_port_t port,
apr_int32_t flags,
apr_pool_t *p);
该函数允许从主机名hostname,地址协议族family和端口port创建新的apr_sockaddr_t地址,
并由sa返回。
hostname参数允许是实际的主机名称,或者也可以是字符串类型的IP地址,比如
”127.0.0.1”,甚至可以是NULL,此时默认的地址是”0.0.0.0”。
family的值可以是AF_INET,AF_UNIX等系统定义类型,也可以是APR_UNSPEC类型,此时,地
址协议族由系统决定。
flags参数用以指定Ipv4和Ipv6处理的 优先级,它的取值包括两种:APR_IPV4_ADDR_OK和
APR_IPV6_ADDR_OK。这两个标志并不是在所有的情况下都有效,这可以从函数的实现中看出它的
用法:
{
apr_int32_t masked;
*sa = NULL;
if ((masked = flags & (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK))) {
if (!hostname ||
family != APR_UNSPEC ||
masked == (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK)) {
return APR_EINVAL;u
}
#if !APR_HAVE_IPV6
if (flags & APR_IPV6_ADDR_OK) {
return APR_ENOTIMPL;
}
#endif
}
#if !APR_HAVE_IPV6
if (family == APR_UNSPEC) {
family = APR_INET; v
}
#endif
return find_addresses(sa, hostname, family, port, flags, p); w
}
从实现代码可以看出,函数的内部实际的地址转换过程是由函数find_address完成的。不过在
调用find_address之前,函数进行了相关检查和预处理,这些检查和预处理包括:
1、APR_IPV4_ADDR_OK标记只有在hostname为NULL,同时family 为 APR_UNSPEC的时候才会有
效,而APR_IPV6_ADDR_OK和APR_IPV4_ADDR_OK是相互排斥的,一旦定义了
APR_IPV4_ADDR_OK,就不能使用APR_IPV6_ADDR_OK,反之亦然。只有在hostname为NULL,同时
family 为 APR_UNSPEC并且没有定义APR_IPV4_ADDR_OK的时候APR_IPV6_ADDR_OK才会有效。
2、如果操作系统平台并不支持IPV6,同时并没有限定获取的地址族,那么此时将默认为IPV6。
如果指定必须获取IPV6的地址信息,但系统并不提供支持,此时返回APR_EINVAL。
一般情况下,在IPV4中从主机名到网络地址的解析可以通过gethostbyname()函数完成,不过
该 API不允许调用者指定所需地址类型的任何信息,这意味着它仅返回包含IPV4地址的信息,
对于目前新的IPV6则无能为力。一些平台中为了支持IPV6 地址的解析,提供了新的地址解析
函数getaddrinfo()以及新的地址描述结构struct addrinfo。APR中通过宏HAVE_GETADDRINFO
判断是否支持IPV6地址的解析。目前Window 2000/XP以上的操作系统都能支持新特性。为此APR
中根据系统平台的特性采取不同的方法完成地址解析。
首先我们来看支持IPV6地址解析平台下的实现代码,find_address函数的实现如下:
static apr_status_t find_addresses(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
if (flags & APR_IPV4_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET, port, flags, p);
#if APR_HAVE_IPV6
if (error) {
family = AF_INET6; /* try again */ u
}
else
#endif
return error;
}
#if APR_HAVE_IPV6
else if (flags & APR_IPV6_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET6, port, flags, p);
if (error) { v
family = AF_INET; /* try again */
}
else {
return APR_SUCCESS;
}
}
#endif
return call_resolver(sa, hostname, family, port, flags, p); w
}
从上面的代码可以清晰的看到APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK的含义:对于 前者,函
数内部首先查询对应主机的IPV4地址,只有在IPV4查询失败的时候才会继续查询IPV6地址;
而后者则与之相反,对于给定的主机名称,首先查 询IPV6地址,只有在查询失败的时候才会
查询IPV4。因此APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK决定了查询的优先性, 任何时候一旦
查询成功都不会继续查询另外协议地址,即使被查询主机具有该协议地址。
查询的核心代码封装在内部函数call_resolve中,该函数的参数和apr_sockaddr_info_get函
数的参数完全相同且对应,call_resolve中的宏处理比较的多,因此我们将分开描述:
static apr_status_t call_resolver(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
struct addrinfo hints, *ai, *ai_list;
apr_sockaddr_t *prev_sa;
int error;
char *servname = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_GAI_ADDRCONFIG
if (family == APR_UNSPEC) {
hints.ai_flags = AI_ADDRCONFIG;
}
#endif
在了解上面的代码之前我们首先简要的了解一些getaddrinfo函数的用法,该函数定义如下:
int getaddrinfo(const char *hostname, const char *service, const struct addinfo
*hints,struct addrinfo **result);
hostname是需要进行地址解析的主机名称或者是二进制的地址串(IPV4的点分十进制或者Ipv6
的十六进制数串),service则是一个服务名或者是一个十进制的端口号数串。其中hints是
addfinfo结构,该结构定义如下:
struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME,
AI_NUMERICHOST */
int ai_family; /* PF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
size_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* canonical name for nodename */
struct sockaddr *ai_addr; /* binary address */
struct addrinfo *ai_next; /* next structure in linked list */
};
hints参数可以是一个空置针,也可以是一个指向某个addrinfo结构的指针,调用者在该结构
中填入关于 期望返回的信息类型的暗示,这些暗示将控制内部的转换细节。比如,如果指定的
服务器既支持TCP,也支持UDP,那么调用者可以把hints结构中的 ai_socktype成员设置为
SOCK_DGRAM,使得返回的仅仅是适用于数据报套接口的信息。
hints结构中调用者可以设置的成员包括ai_flags,ai_family,ai_socktype和ai_protocol。
其中,ai_flags成员可用的标志值及含义如下:


ai_family参数指定调用者期待返回的套接口地址结构的类型。它的值包括三 种:
AF_INET,AF_INET6和AF_UNSPEC。如果指定AF_INET,那么函数九不能返回任何IPV6相关的地
址信息;如果仅指定了 AF_INET6,则就不能返回任何IPV4地址信息。AF_UNSPEC则意味着函数
返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某 个主机既有AAAA记录
(IPV6)地址,同时又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A
记录则作为 sockaddr_in结构返回。
if(hostname == NULL) {
#ifdef AI_PASSIVE
hints.ai_flags |= AI_PASSIVE;
#endif
#ifdef OSF1
hostname = family == AF_INET6 ? "::" : "0.0.0.0";
servname = NULL;
#ifdef AI_NUMERICHOST
hints.ai_flags |= AI_NUMERICHOST;
#endif
#else
#ifdef _AIX
if (!port) {
servname = "1";
}
else
#endif /* _AIX */
servname = apr_itoa(p, port);
#endif /* OSF1 */
}
#ifdef HAVE_GAI_ADDRCONFIG
if (error == EAI_BADFLAGS && family == APR_UNSPEC) {
hints.ai_flags = 0;
error = getaddrinfo(hostname, servname, &hints, &ai_list);
}
#endif
if (error) {
#ifndef WIN32
if (error == EAI_SYSTEM) {
return errno;
}
else
#endif
{
#if defined(NEGATIVE_EAI)
error = -error;
#endif
return error + APR_OS_START_EAIERR;
}
}

2009年2月9日星期一

Apache 中内存管理的三种境界

中国文化中几乎所有的数字都被冠以特殊的意义,《易》曰:"道生一,一生二,二生三,三生万物"。由于个人知识与经历的限制,无法完成对于万物的探究,但是三以下的数字数字还是可以追述的。

Apache作为万维网首屈一指的高性能Web服务器,如果能够从科学与哲学的角度进行分析,将会对我们的软件开发者的学习工作工作带来极大的好处.

正如《易》曰"书不尽言,言不尽意",写出来的未必能够表达我说出来的,说出来的未必能够表到我想说出来的。所以如果我不能描绘出我体会到的Apache中内存管理的三种境界,请不要责怪。

APR(Apache Platform Protable) Library 是Apache为了实现跨平台而抽象出来的一套开发库,内存管理作为与系统紧密相关的软件开发的基础之一,在APR中得到了充分的体现。

第一层境界,基于apr_pool的内存管理。apr_poll简单易用,但是只能申请内存而无法释放或返回缓存池,只能到apr_pool_t到清除 (clear)或消毁(destory)才能够继续使用,而且如果根pool没有被消毁之前,内存是无法返回系统的,不够灵活,apr_pool中用了一个全局的根pool,而高级方式的apr_pool可以在第三层境界的基础上直接创建。所以如果Apache中使用了比较大的内存,尤其是在频繁使用的情况下,最好不要用Apache中已近存在的pool结构,而是采用apache中的第二层境界来解决。

第二层境界,基于 apr_bucket_alloc的内存管理。与apr_pool向相比,apr_bucket_alloc,最大的特点是可以将以申请的内存通过释放而返回到内存池,但不返回系统,以后申请内存可以直接从内存池中获取,并且对于小块内存的管理作了优化,接单易用而且灵活,并且 apr_bucket_alloc在apr_util中实现,apache中内存管理的第一层与第三曾境界是在apr中实现的。可笑的是 apr_bucket_alloc可以通过第一层apr_pool来创建,不过在创建的时候使用的是apr_pool中的allocator(第三层结构的指针)。

第三层境界,基于apr_allocator的内存管理。apr_allocator的内存管理更为低级,内存申请返回一个表示内存的结构而不是实现可用的内存指针,可以在此基础上实现诸如小块内存的优化管理,apr_pool与apr_bucket_alloc都是在 apr_allocator的基础上实现的。apr_allocator的基础就是标准的malloc/free函数了。

整理一下,关系如下

malloc/free-->apr_allocator-------->apr_bucket_alloc
\ /
\ /
\ /
apr_pool



第二层境界,基于apr_bucket_alloc的内存管理使用举例

初始化操作

apr_pool_t *pool;
apr_allocator_t* alloc;
apr_bucket_alloc_t* balloc;

apr_allocator_create(&lloc);
apr_pool_create_ex(&pool, NULL, NULL, alloc);
balloc = apr_bucket_alloc_create(pool);

内存申请与释放

char* p= apr_bucket_alloc(size, balloc);
apr_bucket_free(p);



终止化操作:

apr_bucket_alloc_destroy(balloc);
apr_pool_destroy(pool);
apr_allocator_destroy(alloc);

2009年2月7日星期六

Introduction to Buckets and Brigades

Introduction to Buckets and Brigades
BucketBrigade介绍
The Apache 2 Filter Architecture is the major innovation that sets it apart from other webservers, including Apache 1.x, as a uniquely powerful and versatile applications platform. But this power comes at a price: there is a bit of a learning curve to harnessing it. Apart from understanding the architecture itself, the crux of the matter is to get to grips with Buckets and Brigades, the building blocks of a filter.
Apache2过滤器架构的主要革新让它从其它web服务器,包括Apache1.x区别开来,是一个有独特能力和万能的应用程序结构.但是这 个能力带来了代价:为了驾驭它需要一个学习曲线.不考虑理解结构自身,问题的关键是掌握Bucket和Brigade,这是过滤器的构建块.
In this article, we introduce buckets and brigades, taking the reader to the point where you should have a basic working knowledge. In the process, we develop a simple but useful filter module that works by manipulating buckets and brigades directly.
This direct manipulation is the lowest-level API for working with buckets and brigades, and probably the hardest to use. But because it is low level, it serves to demonstrate what's going on. In other articles we will discuss related subjects including debugging, resource management, and alternative ways to work with the data.
在这篇文章中,我们介绍bucket和brigade,帮助读者找到必须具备哪方面的基本知识.在这个过程中,我们开发了一个简单,但是有用的过滤模块,这个模块通过直接操作bucket和brigade工作.
通过最底层的API来直接操作bucket和brigade,可能是最难使用的.但是因为它是低级别的API,它能给我们展示真正发生了什么.在其它文章中我们将会讨论相关的主题,包括调试,资源管理和可供选择的数据处理方式.
Basic Concepts
基本概念
The basic concepts we are dealing with are the bucket and the brigade. Let us first introduce them, before moving on to why and how to use them.
Bucket和brigade是我们要处理的基本概念. 在我们开始介绍为什么使用和怎么使用他们之前,让我们首先介绍他们.
Buckets
A bucket is a container for data. Buckets can contain any type of data. Although the most common case is a block of memory, a bucket may instead contain a file on disc, or even be fed a data stream from a dynamic source such as a separate program. Different bucket types exist to hold different kinds of data and the methods for handling it. In OOP terms, the apr_bucket is an abstract base class from which actual bucket types are derived.
There are several different types of data bucket, as well as metadata buckets. We will describe these at the end of this article.
Bucket是数据的容器,能包含任何类型的数据.尽管通常情况下是一块内存,bucket也能包含硬盘上的文件,甚至来自动态数据源,例如来 自另一个独立的程序.不同的bucket类型保存不同类型的数据和处理这些数据的方法.在OOP项目组中,apr_bucket是一个抽象的基类,真正的 bucket类型能从这个基本派生.
这里有几种不同bucket数据类型,也包括元数据bucket,我们将在这篇文章结束部分讨论.
Brigades
In normal use, there is no such thing as a freestanding bucket: they are contained in bucket brigades. A brigade is a container that may hold any number of buckets in a ring structure. The brigade serves to enable flexible and efficient manipulation of data, and is the unit that gets passed to and from your filter.
在正常使用中,这里没有孤立的bucket:他们被包含在brigade中.brigade是一个容器,包含许多的bucket而组成一个环状结构.brigade用来提供灵活的和有效率的数据操作,也是从你的过滤器中传进和传出的单元.
Motivation
目的
So, why do we need buckets and brigades? Can't we just keep it simple and pass simple blocks of data? Maybe a void* with a length, or a C++ string?
Well, the first part of the answer we've seen already: buckets are more than just data: they are an abstraction that unifies fundamentally different types of data. But even so, how do they justify the additional complexity over simple buffers and ad-hoc use of other data sources in exceptional cases?
因此,我们为什么需要bucket和brigade?我们难道不能让它简单点么,处理一个简单的数据块?可以用void指针加长度或C++字符串?
恩,第一部分的答案我们已经知道:bucket不仅仅是数据,他们是不同基础数据类型数据的统一抽象.尽管如此,他们怎么证明在例外情况下简单缓冲区上的额外复杂和其它数据源的特别使用是正确的?
The second motivation for buckets and brigades is that they enable efficient manipulation of blocks of memory, typical of many filtering applications. We will demonstrate a simple but typical example of this: a filter to display plain text documents prettified as an HTML page, with header and footer supplied by the webmaster in the manner of a fancy directory listing.
Bucket和brigade的第二个目的是它们能够有效率的操作块状内存,这个是许多过滤器典型的需要操作.我们将举一个简单的但是有典型的例子:这个过滤器修饰纯文本文档为HTML页面,这个页面同时被管理员加上页眉和页脚的,展示一个奇特的目录列表.
Now, HTML can of course include blocks of plain text, enclosing them in <pre> to preserve spacing and formatting. So the main task of a text->html filter is to pass the text straight through. But certain special characters need to be escaped. To be safe both with the HTML spec and browsers, we will escape the four characters <, >, &, and " as &lt, etc.
现在,HTML能够包含纯文本,把这些纯文本包含在<pre>中来保持纯文本的原有空格和格式.因此text->html过 滤器主要任务是直接传递文本.但是一些特殊字符需要被处理.为了HTML和浏览器的安全,我们将会处理四个字符<,>,&和” 为&lt等等.
Because the replacement < is longer by three bytes than the original, we cannot just replace the character. If we are using a simple buffer, we either have to extend it with realloc() or equivalent, or copy the whole thing interpolating the replacement. Repeat this a few times and it rapidly gets very inefficient. A better solution is a two-pass scan of the buffer: the first pass simply computes the length of the new buffer, after which we allocate the memory and copy the data with the required replacements. But even that is by no means efficient.
因为<的替代比原有字符长三个字符,我们不能仅仅替换原有字符.如果我们使用简单缓存区,我们可能需要使用realloc()类 函数扩展缓冲区或者拷贝整个字符串来插入替换的字符串.多次这种操作会迅速导致没有效率.一个好的方法是缓冲区的两遍扫描:第一遍扫描简单的计算新缓冲区 的长度,然后我们分配内存,插入替换,拷贝数据,但是甚至这样都不是有效率的.
By using buckets and brigades in place of a simple buffer, we can simply replace the characters in situ, without allocating or copying any big blocks of memory. Provided the number of characters replaced is small in comparison to the total document size, this is much more efficient. In outline:
  1. We encounter a character that needs replacing in the bucket
  2. We split the bucket before and after the character. Now we have three buckets: the character itself, and all data before and after it.
  3. We drop the character, leaving the before and after buckets.
  4. We create a new bucket containing the replacement, and insert it where the character was.
Now instead of moving/copying big blocks of data, we are just manipulating pointers into an existing block. The only actual data to change are the single character removed and the few bytes that replace it.
通过使用bucket和brigade替换简单缓冲区,我们能简单的替换字符,不需要分配或者拷贝任何大块内存.我们这里的替换字符个数相对文档的大小是很少的.这是更有效率的,概括:
1. 我们在bucket中遇到需要替换的字符.
2. 我们在这个字符位置前后把bucket分开,现在我们有三个bucket:字符本身,字符前面数据和字符后面数据.
3. 我们丢掉字符,剩下该字符前和字符后的bucket.
4. 我们创建一个新的bucket来包含替换的字符串,插入到原有字符串的位置.
现在没有了移动和拷贝大块数据,我们仅仅操作现有数据块的指针.真正需要修改的数据是一个单一字符和替换这个字符的少量字节.
A Real example: mod_txt
现实例子:mod_txt
mod_txt is a simple output filter module to display plain text files as HTML (or XHTML) with a header and footer. When a text file is requested, it escapes the text as required for HTML, and displays it between the header and the footer.
It works by direct manipulation of buckets (the lowest-level API), and demonstrates both insertion of file data and substitution of characters, without any allocation of moving of big blocks.
Mod_txt是一个简单的输出过滤器,用来把纯文本显示成HTML(或者XHTML)有着附加的页眉和页脚.当文本文件被请求,它处理纯文本为需要的HTML,在页眉和页脚显示.
直接操作bucket(最低级别的API),展示插入文件数据和字符的替换,没有任何分配和移动大块数据.
Bucket functions
Bucket函数
Firstly we introduce two functions to deal with the data insertions: one for the files, one for the simple entity replacements:
Creating a File bucket requires an open filehandle and a byte range within the file. Since we're transmitting the entire file, we just stat its size to set the byte range. We open it with a shared lock and with sendfile enabled for maximum performance.
首先我们介绍两个函数来处理数据的插入:文件相关,简单字符替换:
创建一个文件bucket需要一个打开的文件句柄和文件偏移区间.既然我们传送整个文件,因此我们仅仅stat它的大小作为文件偏移区间.我们以共享锁和sendfile标志,为了更大的性能,打开文件.
static apr_bucket* txt_file_bucket(request_rec* r, const char* fname) {
apr_file_t* file = NULL ;
apr_finfo_t finfo ;
if ( apr_stat(&finfo, fname, APR_FINFO_SIZE, r->pool) != APR_SUCCESS ) {
return NULL ;
}
if ( apr_file_open(&file, fname, APR_READ|APR_SHARELOCK|APR_SENDFILE_ENABLED,
APR_OS_DEFAULT, r->pool ) != APR_SUCCESS ) {
return NULL ;
}
if ( ! file ) {
return NULL ;
}
return apr_bucket_file_create(file, 0, finfo.size, r->pool,
r->connection->bucket_alloc) ;
}
Creating the simple text replacements, we can just make a bucket of an inline string. The appropriate bucket type for such data is transient:
创建一个简单的文本替换,我们只需要用内嵌的字符串创建bucket.适合这种数据的bucket类型是transient(短暂):
static apr_bucket* txt_esc(char c, apr_bucket_alloc_t* alloc ) {
switch (c) {
case '<': return apr_bucket_transient_create("<", 4, alloc) ;
case '>': return apr_bucket_transient_create(">", 4, alloc) ;
case '&': return apr_bucket_transient_create("&", 5, alloc) ;
case '"': return apr_bucket_transient_create(""", 6, alloc) ;
default: return NULL ; /* shut compilers up */
}
}
Actually this is not the most efficient way to do this. We will discuss alternative formulations of the above below.
事实上,这不是最有效率的方法.我们下面将讨论可选方法
The Filter
过滤器
Now the main filter itself is broadly straightforward, but there are a number of interesting and unexpected points to consider. Since this is a little longer than the above utility functions, we'll comment it inline instead. Note that the Header and Footer file buckets are set in a filter_init function (omitted for brevity).
现在过滤器主要部分还是十分明了的,但是这有一些有兴趣的和异常情况需要考虑.既然下面这些代码比上面函数要长一点,我们将在代码中注释.注意:页眉和页脚文件bucket在filter_init函数中生成(为了简洁省略掉了).
static int txt_filter(ap_filter_t* f, apr_bucket_brigade* bb) {
apr_bucket* b ;
txt_ctxt* ctxt = (txt_ctxt*)f->ctx ;
if ( ctxt == NULL ) {
txt_filter_init(f) ;
ctxt = f->ctx ;
}
Main Loop: This construct is typical for iterating over the incoming data
主循环:这个结构是遍历输入数据的典型方法.
for ( b = APR_BRIGADE_FIRST(bb);
b != APR_BRIGADE_SENTINEL(bb);
b = APR_BUCKET_NEXT(b) ) {
const char* buf ;
size_t bytes ;
As in any filter, we need to check for EOS. When we encounter it,
we insert the footer in front of it. We shouldn't get more than
one EOS, but just in case we do we'll note having inserted the
footer. That means we're being error-tolerant.
在任何一个过滤器中,我们需要检查EOS.当我们遇到它,在它前面插入页脚.我们不会得到一个以上的EOS,但是如果出现的话,我们将会知道已经插入了页脚.这是错误检查.
if ( APR_BUCKET_IS_EOS(b) ) {
/* end of input file - insert footer if any */
if ( ctxt->foot && ! (ctxt->state & TXT_FOOT ) ) {
ctxt->state |= TXT_FOOT ;
APR_BUCKET_INSERT_BEFORE(b, ctxt->foot);
}
The main case is a bucket containing data, We can get it as a simple
buffer with its size in bytes:
主要情况是要检查bucket是否含有数据,我们能得到简单的缓冲区地址和大小,按字节算:
} else if ( apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
== APR_SUCCESS ) {
/* We have a bucket full of text. Just escape it where necessary */
size_t count = 0 ;
const char* p = buf ;
Now we can search for characters that need replacing, and replace them
现在我们能搜索需要替换的字符,然后替换它们
while ( count <>
size_t sz = strcspn(p, "<>&\"") ;
count += sz ;
Here comes the tricky bit: replacing a single character inline.
这里有一个小技巧:在内联中替换单一字符.
if ( count <>
apr_bucket_split(b, sz) ; Split off before buffer
b = APR_BUCKET_NEXT(b) ; Skip over before buffer
APR_BUCKET_INSERT_BEFORE(b, txt_esc(p[sz],
f->r->connection->bucket_alloc)) ;
Insert the replacement
apr_bucket_split(b, 1) ; Split off the char to remove
APR_BUCKET_REMOVE(b) ; ... and remove it
b = APR_BUCKET_NEXT(b) ; Move cursor on to what-remains
so that it stays in sequence with
our main loop
count += 1 ;
p += sz + 1 ;
}
}
}
}
Now we insert the Header if it hasn't already been inserted.
Note:
(a) This has to come after the main loop, to avoid the header itself
getting into the parse.
(b) It works because we can insert a bucket anywhere in the brigade,
and in this case put it at the head.
(c) As with the footer, we save state to avoid inserting it more than once.
如果还没有插入页眉的话,现在插入.
注意:
(a) 这个在主循环以后,避免页眉被处理(译者注:被进行字符替换).
(b) 这个能工作,因为我们能在brigade任何位置插入bucket,这里是插入最前面.
(c) 考虑页脚,我们保存了状态,避免多次插入.
if ( ctxt->head && ! (ctxt->state & TXT_HEAD ) ) {
ctxt->state |= TXT_HEAD ;
APR_BRIGADE_INSERT_HEAD(bb, ctxt->head);
}
Now we've finished manipulating data, we just pass it down the filter chain.
现在我们已经完成数据的处理,我们把brigade传入下一个过滤器.
return ap_pass_brigade(f->next, bb) ;
}
Note that we created a new bucket every time we replaced a character. Couldn't we have prepared four buckets in advance - one for each of the characters to be replaced - and then re-used them whenever the character occurred?
注意:在每次替换字符的时候,我们创建了一个新的bucket.我们不能预先准备四个bucket一一为每一个需要被替换的字符一一然后在需要的时候重用他们?
The problem here is that each bucket is linked to its neighbours. So if we re-use the same bucket, we lose the links, so that the brigade now jumps over any data between the two instances of it. Hence we do need a new bucket every time. That means this technique becomes inefficient when a high proportion of input data has to be changed. We will show alternative techniques for such cases in other articles.
这里的问题是,每一个bucket是它的邻居相邻,我们重用相同的bucket,链表顺序会被打乱,因此brigade现在会跳过中间的一些数 据.为此,我们必须每次都创建新的bucket.这意味着, 当在处理需要被替换的字符占输入数据很高比例的时候,这种技巧是非常没有效率的.我们将会在其它文章中展示可供选择的方案.
Bucket Types
Bucket类型
In the above, we used two data bucket types: file and transient, and the eos metadata bucket type. There are several other bucket types suitable for different kinds of data and metadata.
在上面,我们使用了两种bucket数据类型:文件和transient(短暂),EOS元数据类型.这里有几个其它的bucket数据类型,适合不同类型的数据和元数据.
Simple in-memory buckets
简单内存bucket
When we created transient buckets above, we were inserting a chunk of memory in the output stream. But we noted that this bucket was not the most efficient way to escape a character. The reason for this is that the transient memory has to be copied internally to prevent it going out of scope. We could instead have used memory that's guaranteed never to go out of scope, by replacing
当我们在上面创建短暂bucket,我们在输出流中插入了一块数据.但是我们注意到这种bucket处理起来不是最效率的.因为短暂内存不得不在内部被拷贝,阻止它失效.我们能够使用保证不会失效内存,取代
case '<': return apr_bucket_transient_create("<", 4, alloc) ;
with
static const char* lt = "<" ;
...
case '<': return apr_bucket_immortal_create(lt, 4, alloc) ;
When we create an immortal bucket, we guarantee that the memory won't go out of scope during the lifetime of the bucket, so the APR never needs to copy it internally.
A third variant on the same principle is the pool bucket. This refers to memory allocated on a pool, and will have to be copied internally if and only if the pool is destroyed within the lifetime of the bucket.
当我们创建永久bucket的时候,我们保证在bucket生命周期中内存不会失效,因此APR绝不需要在内部拷贝.
有着相同原理的第三个bucket是pool bucket.涉及在pool上的内存分配,和仅仅如果在bucket的生命周期内pool被销毁,我们不得不进行内部拷贝.
The Heap bucket
bucket
The heap bucket is another form of in-memory bucket. But its usage is rather different from any of the above. We rarely if ever need to create a heap bucket explicitly: rather they are managed internally when we use the stdio-like API to write data to the next filter. This API is discussed in other articles.
堆bucket是内存bucket的另一种.但是使用方式和上面几种十分不同.我们很少,仅仅在明确需要的时候,创建堆bucket:宁愿它们在内部被管理,当我们使用stdio类的API去向下一个过滤器写数据的时候.这些API在其它文章中讨论.
External data buckets
外部数据bucket
The File bucket, as we have already seen, enables us to insert a file (or part) file into the data stream. Although we had to stat it to find its length, we didn't have to read it. If sendfile is enabled, the operating system (through the APR) can optimise sending the file.
The mmap bucket type is similar, and is appropriate to mmaped files. APR may convert file buckets to mmap internally if we (or a later filter) read the data.
Two other more unusual bucket types are the pipe and the socket, which enable us to insert data from an external source via IPC.
文件bucket,我们已经见到,让我们能够插入一个文件或者部分到数据流.尽管我们不得不stat来知道它的长度,但我们不需要读取它的内容.如果允许sendfile标志,操作系统(通过APR)能优化传送文件.
内存映射bucket和内存映射文件是相似的.当我们(接下来的过滤器)读取数据的时候,APR能在内部把文件bucket转成内存映射bucket.
其它两个不常用的bucket类型,管道和套接字,这两个能够让我们从外部数据源,通过IPC插入数据.
Metadata buckets
元数据bucket
In addition to data buckets, there are two metadata types. The EOS bucket is crucial: it must be sent at the end of a data stream, and it serves to signal the end of incoming data. The other metadata type is the rarely-user FLUSH bucket, which may occasionally be required, but is not guaranted to be propagated by every filter.
处理数据bucket,这里有两个元数据bucket.EOS bucket是关键:它必须在数据流结束的时候被发送,也被用来标志输入数据的结束.另一个元数据类型是很少使用的FLUSH bucket,偶尔会需要,但是可以保证不会被每一个过滤器使用.
Note by anonymous, Fri Mar 30 11:56:20 2007
test
Further reading
进一步参考
Cliff Woolley gave a talk on buckets and brigades at ApacheCon 2002. His notes are very readable, and go into more depth than this article.
Cliff Woolley在ApacheCon 2002上给我们一个关于bucket和brigade的讨论.他的注释是非常值得去度的,内容比这篇文章要深入许多.
http://www.cs.virginia.edu/~jcw5q/talks/

2009年2月6日星期五

APR-utils列表

组件名称
文件夹名称
描述
buckets
/srclib/apr-util/buckets
存储段和存储段组
crypto
/srclib/apr-util/crypto
加密和解密
hooks
/srclib/apr-util/hooks
apache挂钩
dbd
/srclib/apr-util/dbd
数据库连接管理
dbm
/srclib/apr-util/dbm
ldap
/srclib/apr-util/ldap
轻量级目录访问协议
strmatch
/srclib/apr-util/strmatch
字符串匹配,包括普通字符串匹配以及正则表达式匹配,正则表达式匹配中使用prec库
uri
/srclib/apr-util/uri
uri操作例程
/srclib/apr-util/xml
xml支持例程,其中使用expat作为xml解析器
xlate
/srclib/apr-util/xlate
i18n 转换库
encoding
/srclib/apr-util/encoding
编码转换库,其中实现了各种编码之间的转换
misc
/srclib/apr-util/misc
大杂烩

2009年1月4日星期日

ANT命令详解

Ant的概念
可能有些读者并不连接什么是Ant以及入可使用它,但只要使用通过Linux系统得读者,应该知道

make这个命令。当编译Linux内核及一些软件的源程序时,经常要用这个命令。Make命令其实就

是一个项目管理工具,而Ant所实现功能与此类似。像make,gnumake和nmake这些编译工具都有

一定的缺陷,但是Ant却克服了这些工具的缺陷。最初Ant开发者在开发跨平台的应用时,用样也

是基于这些缺陷对Ant做了更好的设计。

Ant 与 makefile
Makefile有一些不足之处,比如很多人都会碰到的烦人的Tab问题。最初的Ant开发者多次强调”

只是我在Tab前面加了一个空格,所以我的命令就不能执行”。有一些工具在一定程度上解决了

这个问题,但还是有很多其他的问题。Ant则与一般基于命令的工具有所不同,它是Java类的扩

展。Ant运行需要的XML格式的文件不是Shell命令文件。它是由一个Project组成的,而一个

Project又可分成可多target,target再细分又分成很多task,每一个task都是通过一个实现特

定接口的java类来完成的。

Ant的优点

Ant是Apache软件基金会JAKARTA目录中的一个子项目,它有以下的优点。
跨平台性。Ant是存Java语言编写的,所示具有很好的跨平台性。
操作简单。Ant是由一个内置任务和可选任务组成的。Ant运行时需要一个XML文件(构建文件)。

Ant通过调用target树,就可以执行各种task。每个task实现了特定接口对象。由于Ant构建文件

时XML格式的文件,所以和容易维护和书写,而且结构很清晰。
Ant可以集成到开发环境中。由于Ant的跨平台性和操作简单的特点,它很容易集成到一些开发环

境中去。

Ant 开发

Ant的构建文件
当开始一个新的项目时,首先应该编写Ant构建文件。构建文件定义了构建过程,并被团队开发

中每个人使用。Ant构建文件默认命名为build.xml,也可以取其他的名字。只不过在运行的时候

把这个命名当作参数传给Ant。构建文件可以放在任何的位置。一般做法是放在项目顶层目录中

,这样可以保持项目的简洁和清晰。下面是一个典型的项目层次结构。
(1) src存放文件。
(2) class存放编译后的文件。
(3) lib存放第三方JAR包。
(4) dist存放打包,发布以后的代码。
Ant构建文件是XML文件。每个构建文件定义一个唯一的项目(Project元素)。每个项目下可以定

义很多目标(target元素),这些目标之间可以有依赖关系。当执行这类目标时,需要执行他们所

依赖的目标。
每个目标中可以定义多个任务,目标中还定义了所要执行的任务序列。Ant在构建目标时必须调

用所定义的任务。任务定义了Ant实际执行的命令。Ant中的任务可以为3类。
(1) 核心任务。核心任务是Ant自带的任务。
(2) 可选任务。可选任务实来自第三方的任务,因此需要一个附加的JAR文件。
(3) 用户自定义的任务。用户自定义的任务实用户自己开发的任务。
1.标签
每个构建文件对应一个项目。标签时构建文件的根标签。它可以有多个内在属性,

就如代码中所示,其各个属性的含义分别如下。
(1) default表示默认的运行目标,这个属性是必须的。
(2) basedir表示项目的基准目录。
(3) name表示项目名。
(4) description表示项目的描述。
每个构建文件都对应于一个项目,但是大型项目经常包含大量的子项目,每一个子项目都可以有

自己的构建文件。

2.标签
一个项目标签下可以有一个或多个target标签。一个target标签可以依赖其他的target标签。例

如,有一个target用于编译程序,另一个target用于声称可执行文件。在生成可执行文件之前必

须先编译该文件,因策可执行文件的target依赖于编译程序的target。Target的所有属性如下。
(1).name表示标明,这个属性是必须的。
(2).depends表示依赖的目标。
(3)if表示仅当属性设置时才执行。
(4)unless表示当属性没有设置时才执行。
(5)description表示项目的描述。
Ant的depends属性指定了target的执行顺序。Ant会依照depends属性中target出现顺序依次执行

每个target。在执行之前,首先需要执行它所依赖的target。程序中的名为run的target的

depends属性compile,而名为compile的target的depends属性是prepare,所以这几个target执

行的顺序是prepare->compile->run。
一个target只能被执行一次,即使有多个target依赖于它。如果没有if或unless属性,target总

会被执行。

3.标签
该标签用于创建一个目录,它有一个属性dir用来指定所创建的目录名,其代码如下:

通过以上代码就创建了一个目录,这个目录已经被前面的property标签所指定。

4标签
该标签用来生成一个JAR文件,其属性如下。
(1) destfile表示JAR文件名。
(2) basedir表示被归档的文件名。
(3) includes表示别归档的文件模式。
(4) exchudes表示被排除的文件模式。

5.
该标签用于编译一个或一组java文件,其属性如下。
(1).srcdir表示源程序的目录。
(2).destdir表示class文件的输出目录。
(3).include表示被编译的文件的模式。
(4).excludes表示被排除的文件的模式。
(5).classpath表示所使用的类路径。
(6).debug表示包含的调试信息。
(7).optimize表示是否使用优化。
(8).verbose 表示提供详细的输出信息。
(9).fileonerror表示当碰到错误就自动停止。

6.标签
该标签用来执行编译生成的.class文件,其属性如下。
(1).classname 表示将执行的类名。
(2).jar表示包含该类的JAR文件名。
(3).classpath所表示用到的类路径。
(4).fork表示在一个新的虚拟机中运行该类。
(5).failonerror表示当出现错误时自动停止。
(6).output 表示输出文件。
(7).append表示追加或者覆盖默认文件。

7.标签
该标签用于删除一个文件或一组文件,去属性如下。
(1)/file表示要删除的文件。
(2).dir表示要删除的目录。
(3).includeEmptyDirs 表示指定是否要删除空目录,默认值是删除。
(4).failonerror 表示指定当碰到错误是否停止,默认值是自动停止。
(5).verbose表示指定是否列出所删除的文件,默认值为不列出。

8.标签
该标签用于文件或文件集的拷贝,其属性如下。
(1).file 表示源文件。
(2).tofile 表示目标文件。
(3).todir 表示目标目录。
(4).overwrite 表示指定是否覆盖目标文件,默认值是不覆盖。
(5).includeEmptyDirs 表示制定是否拷贝空目录,默认值为拷贝。
(6).failonerror 表示指定如目标没有发现是否自动停止,默认值是停止。
(7).verbose 表示制定是否显示详细信息,默认值不显示。

Ant的数据类型
在构建文件中为了标识文件或文件组,经常需要使用数据类型。数据类型包含在

org.apache.tool.ant.types包中。下面镜简单介绍构建文件中一些常用的数据类型。

1. argument 类型
由Ant构建文件调用的程序,可以通过元素向其传递命令行参数,如apply,exec和java任

务均可接受嵌套元素,可以为各自的过程调用指定参数。以下是的所有属性。
(1).values 是一个命令参数。如果参数种有空格,但又想将它作为单独一个值,则使用此属性


(2).file表示一个参数的文件名。在构建文件中,此文件名相对于当前的工作目录。
(3).line表示用空格分隔的多个参数列表。
(4).path表示路径。

2.ervironment 类型
由Ant构建文件调用的外部命令或程序,元素制定了哪些环境变量要传递给正在执行的系

统命令,元素可以接受以下属性。
(1).file表示环境变量值得文件名。此文件名要被转换位一个绝对路径。
(2).path表示环境变量的路径。Ant会将它转换为一个本地约定。
(3).value 表示环境变量的一个直接变量。
(4).key 表示环境变量名。
注意 file path 或 value只能取一个。

3.filelist类型
Filelist 是一个支持命名的文件列表的数据类型,包含在一个filelist类型中的文件不一定是

存在的文件。以下是其所有的属性。
(1).dir是用于计算绝对文件名的目录。
(2).files 是用逗号分隔的文件名列表。
(3).refid 是对某处定义的一个的引用。
注意 dir 和 files 都是必要的,除非指定了refid(这种情况下,dir和files都不允许使用)。

4.fileset类型
Fileset 数据类型定义了一组文件,并通常表示为元素。不过,许多ant任务构建成了

隐式的fileset,这说明他们支持所有的fileset属性和嵌套元素。以下为fileset 的属性列表。
(1).dir表示fileset 的基目录。
(2).casesensitive的值如果为false,那么匹配文件名时,fileset不是区分大小写的,其默认

值为true.
(3).defaultexcludes 用来确定是否使用默认的排除模式,默认为true。
(4).excludes 是用逗号分隔的需要派出的文件模式列表。
(5).excludesfile 表示每行包含一个排除模式的文件的文件名。
(6).includes 是用逗号分隔的,需要包含的文件模式列表。
(7).includesfile 表示每行包括一个包含模式的文件名。

5.patternset 类型
Fileset 是对文件的分组,而patternset是对模式的分组,他们是紧密相关的概念。

支持4个属性:includes excludex includexfile 和 excludesfile,与fileset相

同。Patternset 还允许以下嵌套元素:include,exclude,includefile 和 excludesfile.

6.filterset 类型
Filterset定义了一组过滤器,这些过滤器将在文件移动或复制时完成文件的文本替换。
主要属性如下:
(1).begintoken 表示嵌套过滤器所搜索的记号,这是标识其开始的字符串。
(2).endtoken表示嵌套过滤器所搜索的记号这是标识其结束的字符串。
(3).id是过滤器的唯一标志符。
(4).refid是对构建文件中某处定义一个过滤器的引用。

7.Path类型
Path元素用来表示一个类路径,不过它还可以用于表示其他的路径。在用作揖个属性时,路经中

的各项用分号或冒号隔开。在构建的时候,此分隔符将代替当前平台中所有的路径分隔符,其拥

有的属性如下。
(1).location 表示一个文件或目录。Ant在内部将此扩展为一个绝对路径。
(2).refid 是对当前构建文件中某处定义的一个path的引用。
(3).path表示一个文件或路径名列表。

8.mapper类型
Mapper类型定义了一组输入文件和一组输出文件间的关系,其属性如下。
(1).classname 表示实现mapper类的类名。当内置mapper不满足要求时,用于创建定制mapper.
(2).classpath表示查找一个定制mapper时所用的类型路径。
(3).classpathref是对某处定义的一个类路径的引用。
(4).from属性的含义取决于所用的mapper.
(5).to属性的含义取决于所用的mapper.
(6).type属性的取值为identity,flatten glob merge regexp 其中之一,它定义了要是用的

内置mapper的类型。


Ant 的运行
安装好Ant并且配置好路径之后,在命令行中切换到构建文件的目录,输入Ant命令就可以运行

Ant.若没有指定任何参数,Ant会在当前目录下查询build.xml文件。如果找到了就用该文件作为

构建文件。如果使用了 –find 选项,Ant 就会在上级目录中找构建文件,直至到达文件系统得

跟目录。如果构建文件的名字不是build.xml ,则Ant运行的时候就可以使用 –buildfile file

,这里file 指定了要使用的构建文件的名称,示例如下:
Ant
如下说明了表示当前目录的构建文件为build.xml 运行 ant 执行默认的目标。

Ant –buildfile test.xml
使用当前目录下的test.xml 文件运行Ant ,执行默认的目标

2008年9月18日星期四

Informix查询优化

转载于chinaunix一篇"开发优质高效的INFORMIX数据库应用程序",节选部分内容
查询语句(SELECT)的优化
程序设计中的一个著名定律是20%的代码用去了80%的时间,
在数据库应用程序中也同样如此。数据库应用程序的优化通常可分为两个方面:
源代码的优化和SQL语句的优化。源代码的优化在时间成本和风险上代价很高;
另一方面,源代码的优化对数据库系统性能的提升收效有限。
许多程序员认为查询优化是DBMS(数据库管理系统)的任务,
与程序员所编写的SQL语句关系不大,这是错误的。
一个好的查询计划往往可以使程序性能提高数十倍。查询计划是用户所提交的SQL语句的集合,
查询规划是经过优化处理之后所产生的语句集合。
DBMS处理查询计划的过程是这样的:在做完查询语句的词法、语法检查之后,
将语句提交给DBMS的查询优化器,优化器做完代数优化和存取路径的优化之后,
由预编译模块对语句进行处理并生成查询规划,然后在合适的时间提交给系统处理执行,
最后将执行结果返回给用户。虽然现在的数据库产品在查询优化方面已经做得越来越好,
但由用户提交的SQL语句是系统优化的基础,很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效,
因此用户所写语句的优劣至关重要。
1、对查询语句进行优化的理由
下列几方面的原因是我们进行SQL语句优化的理由:
◆ SQL语句是对数据库(数据)进行操作的惟一途径;
◆ SQL语句消耗了70%~90%的数据库资源;
◆ SQL语句独立于程序设计逻辑,相对于对程序源代码的优化,对SQL语句的优化在时间成本和风险上的代价都很低;
◆ SQL语句可以有不同的写法;
◆ SQL语句易学,难精通。
从大多数数据库应用系统的实例来看,查询操作在各种数据库操作中所占据的比重最大,而查询操作所基于的SELECT语句在SQL语句中又是代价最大的语句。
2、查询语句(SELECT)的优化建议
(1)、合理使用索引:where子句中变量顺序应与索引字键顺序相同。
如:create index test_idx on test(bdh, rq, xz)
   索引字键顺序:首先是保单号bdh,其次是日期rq,最后是险种xz,
所以where子句变量顺序应是where bdh<=“P1234”and rq=“06/06/1999”and xz=“DAA”,
不应是where xz=“DAA” and rq=“06/06/1999” and bdh <=“P1234”这样的不按索引字键顺序写法。
(2)、将最具有限制性的条件放在前面,大值在前,小值在后。
   如:where colA<=10000 AND colA>=1 效率高
   where colA>=1 AND colA<=10000 效率低
(3)、避免采用MATCHES和LIKE通配符匹配查询
通配符匹配查询特别耗费时间。即使在条件字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。
例如语句:SELECT * FROM customer WHERE zipcode MATCHES “524*”
可以考虑将它改为SELECT * FROM customer WHERE ZipCode<=“524999” AND ZipCode >=“524000”,
则在执行查询时就会利用索引来查询,显然会大大提高速度。
(4)、避免非开始的子串
例如语句:SELECT * FROM customer WHERE zipcode[2,3] >“24”,在where子句中采用了非开始子串,因而这个语句也不会使用索引。
(5)、避免相关子查询
一个字段的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中的字段值改变之后,
子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,
那么要在子查询中过滤掉尽可能多的行。
例如:将下面的语句
select bdh,bf from TabA
where item IN (select item form TabB where TabB.num=50)
改为:select bdh,bf from TabA, TabB
where TabA.item=TabB.item AND TabB.num=50
(6)、避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,
优化器就避免了排序的步骤。以下是一些影响因素:
◆ 索引中不包括一个或几个待排序的字段;
◆ group by或order by子句中字段的次序与索引的次序不一样;
◆ 排序的字段来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。
如果排序不可避免,那么应当试图简化它,如缩小排序的字段的范围等。
(7)、消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,
一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。
避免这种情况的主要方法就是对连接的字段进行索引。例如,两个表:学生表(学号、姓名、年龄……)
和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,
但某些形式的where子句强迫优化器使用顺序存取。
下面的查询将强迫对orders表执行顺序操作:
SELECT * FROM orders WHERE (cust_num=126 AND order_num>1001) OR order_num=1008
虽然在cust_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。
因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT * FROM orders WHERE cust_num=126 AND order_num>1001
UNION
SELECT * FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
(8)、对于大数据量的求和应避免使用单一的sum命令处理,可采用group by方式与其结合,有时其效率可提高几倍甚至百倍。
(9)、避免会引起磁盘读写的rowid操作。在where子句中或select语句中,用rowid要产生磁盘读写,是一个物理过程,会影响性能。
(10)、使用临时表加速查询
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。
但要注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意不要丢失数据。
五、 其他措施
如何优化一套数据库应用程序,除了以上所述的措施外,还有一些分析并提高效率的措施在实际工作中亦应注意使用。
(1)、利用set explain on语句来分析数据库查找策略。
当发现某一部分INFORMIX语句运行特别慢又找不到原因时,
可在程序中的查询语句之前加入“set explain on”语句,当程序运行时,
在程序运行的当前目录下产生一个“sqexplain.out”文件,
记录了INFORMIX数据库服务器采用何种优化策略来查找数据库。
在该文件中可以发现查找中有无使用索引条件,估计的查找代价等信息。
(2)、数据库在做dbimport后应运行update statistics语句。
当数据库系统用dbimport实用程序完成数据库装载后,应运行update statistics数据库语句保证系统表中统计信息准确,否则将影响数据库优化器的策略和系统运行性能。
例如:select * from test where bdh matches “PC2002*”
test表在bdh字段上建立了索引,但在数据库运行update statistics前,数据库优化查找策略是按顺序查找而不是按索引查找,严重影响了查找速度。
(3)、经常插入和删除的大表应定期运行update statistics high语句。
  对经常插入和删除的大表应定期运行update statistics语句保证系统表中统计信息准确,保证数据库优化器作出正确的优化策略提高系统运行性能。
(4)、对大文件采用load命令装载入库前可先去掉原有的日志方式和去掉索引,等load装载完成后再重建索引和日志,能大幅提高装载效率,避免出错产生。
◆ 去掉原有的日志方式:ontape -S -N dbname
◆ 删除索引:delete index indexname
◆ 用load 装入数据:load from filename insert into tabname
◆ 重建索引:create index to indexname on tabname(colA,colB,……)
◆ 重建日志:ontape -C -B dbname
(5)、针对ESQL/C编程,还应注意程序中的进程的使用
◆INFORMIX数据库中一个事务处理不要跨多个进程。例如:
  begin work;
  ……
  if(fork()==0){ /*子进程处理*/
  ……
  conmit work; /*(或rollback work;)*/
  ……
  }
这种方式,将引起数据库运行效率很低或处理异常。
◆在应用上引入多进程并发处理,充分利用系统资源,能大大提高处理效率。

2008年7月22日星期二

常用数据库JDBC连接写法

1. MySQL(http://www.mysql.com)mm.mysql-2.0.2-bin.jarClass.forName( "org.gjt.mm.mysql.Driver" );cn = DriverManager.getConnection( "jdbc:mysql://MyDbComputerNameOrIP:3306/myDatabaseName", sUsr, sPwd );

2. PostgreSQL(http://www.de.postgresql.org)pgjdbc2.jarClass.forName( "org.postgresql.Driver" );cn = DriverManager.getConnection( "jdbc:postgresql://MyDbComputerNameOrIP/myDatabaseName", sUsr, sPwd );

3. Oracle(http://www.oracle.com/ip/deploy/database/oracle9i/)classes12.zipClass.forName( "oracle.jdbc.driver.OracleDriver" );cn = DriverManager.getConnection( "jdbc:oracle:thin:@MyDbComputerNameOrIP:1521:ORCL", sUsr, sPwd );

4. Sybase(http://jtds.sourceforge.net)jconn2.jarClass.forName( "com.sybase.jdbc2.jdbc.SybDriver" );cn = DriverManager.getConnection( "jdbc:sybase:Tds:MyDbComputerNameOrIP:2638", sUsr, sPwd );//(Default-Username/Password: "dba"/"sql")

5. Microsoft SQLServer(http://jtds.sourceforge.net)Class.forName( "net.sourceforge.jtds.jdbc.Driver" );cn = DriverManager.getConnection( "jdbc:jtds:sqlserver://MyDbComputerNameOrIP:1433/master", sUsr, sPwd );

6. Microsoft SQLServer(http://www.microsoft.com)Class.forName( "com.microsoft.jdbc.sqlserver.SQLServerDriver" );cn = DriverManager.getConnection( "jdbc:microsoft:sqlserver://MyDbComputerNameOrIP:1433;databaseName=master", sUsr, sPwd );

7. ODBCClass.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );Connection cn = DriverManager.getConnection( "jdbc:odbc:" + sDsn, sUsr, sPwd );

8.DB2(新添加)Class.forName("com.ibm.db2.jdbc.net.DB2Driver");String url="jdbc:db2://192.9.200.108:6789/SAMPLE"cn = DriverManager.getConnection( url, sUsr, sPwd );

补充Microsoft SQL Server series (6.5, 7.x and 2000) and Sybase 10JDBC Name: jTDSURL: http://jtds.sourceforge.net/Version: 0.5.1Download URL: http://sourceforge.net/project/showfiles.php?group_id=33291

语法:Class.forName("net.sourceforge.jtds.jdbc.Driver ");

Connection con = DriverManager.getConnection("jdbc:jtds:sqlserver://host:port/database","user","password");

orConnection con = DriverManager.getConnection("jdbc:jtds:sybase://host:port/database","user","password");

PostgresqlJDBC Name: PostgreSQL JDBCURL: http://jdbc.postgresql.org/Version: 7.3.3 build 110

Download URL: http://jdbc.postgresql.org/download.html

语法:Class.forName("org.postgresql.Driver");

Connection con=DriverManager.getConnection("jdbc:postgresql://host:port/database","user","password");

IBM AS400主机在用的JDBC语法有装V4R4以上版本的Client Access Express可以在C:\Program Files\IBM\Client Access\jt400\lib找到 driver 档案 jt400.zip,并更改扩展名成为 jt400.jar

语法java.sql.DriverManager.registerDriver (new com.ibm.as400.access.AS400JDBCDriver ());

Class.forName("com.ibm.as400.access.AS400JDBCConnection");con = DriverManager.getConnection("jdbc:as400://IP","user","password");informixClass.forName("com.informix.jdbc.IfxDriver").newInstance();

String url = "jdbc:informix-sqli://123.45.67.89:1533/testDB:INFORMIXSERVER=myserver; user=testuser;password=testpassword";

Lib:jdbcdrv.zip

Class.forName( "com.sybase.jdbc.SybDriver" )url="jdbc:sybase:

Tds:127.0.0.1:2638/asademo";

SybConnection connection= (SybConnection)DriverManager.getConnection(url,"dba","sql");

补充两个SAP DBClass.forName ("com.sap.dbtech.jdbc.DriverSapDB");

java.sql.Connection connection = java.sql.DriverManager.getConnection ( "jdbc:sapdb://" + host + "/" + database_name,user_name, password)InterBaseString url = "jdbc:interbase://localhost/e:/testbed/database/employee.gdb";

Class.forName("interbase.interclient.Driver");//Driver d = new interbase.interclient.Driver (); /* this will also work if you do not want the line above */Connection conn = DriverManager.getConnection( url, "sysdba", "masterkey" );

HSqlDBurl: http://hsqldb.sourceforge.net/

driver: org.hsqldb.jdbcDriver连接方式有4种,

分别为:

con-str(内存): jdbc:hsqldb.con-str

(本地): jdbc:hsqldb:/path/to/the/db/dircon-str

(http): jdbc:hsqldb:http://dbsrvcon-str

(hsql): jdbc:hsqldb:hsql://dbsrv

2008年7月15日星期二

内部类

本文将详细的解释内部类 希望和大家交流. 简单的内部类定义形如这样:
class A{ class B{}} 这样的类被称为内部类,又被称为内隐类. 从简单到深入一步一步的分析内部类的特点。
class OuterClass{
static class A{//静态内部类
public A( ){
System.out.println("Test$A !");
}
}
class B{//非静态内部类
public B(){
System.out.println("Test$B !");
}
}
public void disp( )
{
final int a=10; int b;
class C //成员函数中的局部内部类
{ public C( )
{ System.out.println(“in class C a="+a);
//System.out.println(b);
}
}
C c=new C( );
}
}public class Test extends OuterClass
{
public static void main(String args[])
{ OuterClass.A a=new OuterClass.A(); //建立静态内部类对象

B b=new OuterClass( ).new B();

//建立非静态内部类的对象
//注意这个OuterClass().new B();相当于生成一个外部类的对象,然后在利用外部类对象生成内部类对象


OuterClass t=new OuterClass( );
t.disp( );
//通过外部对象调用一个对象方法的形式,新建立了对象C.
}
}

注意在上面的b在运行时会为0,因为是类属性.

class OuterClass
{
static class A { } //静态内部类
class B { } //非静态内部类
public void disp( )
{
class C{ } //局部内部类
}
}

编译后的将产生下面的一些类文件:

OuterClass.class
OuterClass$A.class
OutClass$B.class
OuterClass$1$C.class
记住以下几句话:

1,一个内部类的对象能够访问创建它的外部类对象的所有属性及方法(包括私有部分)。
//可以闭上眼镜,把这个内部类等同于一个类的一个方法,当然就可以访问这个外部类的
//所有方法和属性,私有方法和属性是属于外部类的,当然也就等同于内部类的.

2,对于同一个包中的其它类来说,内部类能够隐藏起来。(将内部类用private修饰即可)
//只有在内部类中,才能定义一个为private类型的class,因为这时编译器已经把这个类看作这个类的成员了,但是在一般使用时,就是所谓的”顶级类时”,不能使用private,只能是public 或者是friendly. 如果要是想保证一个类不产生任何的对象,请在构造函数中,把构造函数声明成private.
3, 内部类可定义在方法中,称为局部内部类,但它只能使用方法中的final常量。
// 定义在一个方法内的类,又被成为局部内部类,这个类只能使用在方法中的final常量,注意,这个常量是在一个方法中的,那么能否使用一个类中的常量呢?
当然是可以的,因为类中的常量在在一个方法中是可见的.
//
如果把一个类写在了一个if中,比如这样:
class A{
int a = 10;
if(a!=10){
class B{
B(){
System.out.println(a);
}
}
}
}
在编译后会有几个错误呢?
首先那个a没有被定义为final,你有一次上了圈套. 类B同样会被生成出来,只是离开了if域就失效了.
4,内部类可以被定义为抽象类
// abstract 类同样可以在内部类中
5, 非静态内部类不能声明本类的static成员
//只有一个静态的内部类,才可以声明一个static成员,
class A{
static class B{

//如果这里不是一个static类,是不可以被声明这个gg方法的.
static void gg(){
int a = 100;
System.out.println(a);
}
}
}

class aa{
public static void main(String args[]){

A.B hh = new A.B();
hh.gg();


}
}
使用内部类可以非常方便的编写事件驱动程序
这个在写事件驱动时,会有很好的解释.
匿名内部类
在某些情况下,我们只需要内部类的一个对象,那么我们就没有必要给内部类命名,没有名字的内部类我们称为匿名内部类
public class A extends Applet
{ public void init( )
{ addMouseListener( new B( ) );
}
class B extends MouseAdapter
{ public void mousePressed(MouseEvent me)
{ showStatus("Mouse Pressed."); }
}
}
用匿名内隐类修改:

import java.applet.*;
import java.awt.event.*;
import java.awt.*;
public class AnonymousInnerClassDemo extends Applet
{ public void init( )
{ addMouseListener( new MouseAdapter( )
{ public void mousePressed(MouseEvent me)
{ showStatus("Mouse Pressed"); } } );
}
}
下面是一个think in java里的例子
public class Pr{

public Concents cont(){
return new Concents(){

private int i= 11;
public int value (){return i;}
};//这里是有一个逗号
}
}
这个Contents是通过默认的构造函数产生的,匿名类的产生也就是一个新类向上转型到父类的过程.
那么如果一个父类没有一个默认的构造函数,应该什么办呢? 那就只有调用一个super()了.
class noname{

Warpping wrap(int x){

return new Warpping(x){

public int value(){

return super.value()*47;
}
};
}
public static void main(String s[]){

noname p = new noname();
Warpping w = p.wrap(10);
}
}

如果在匿名内部类中用到了外部对象 , 就必须保证这个外部对象是final的 。public class PP{

public DD dest(final String dest, final float price){

return new DD(){

private int cost;
{
cost = Math.round(price);
if(cost>100)
System.out.println("Over budget");
}
};
}
publc static void main(String []arg){

PP h = new PP();
DD d = h.dest("haha",11.111);

}
}

2008年7月14日星期一

seam-gen

Seam Gen是什么
Seam Gen(也叫seam)用来生成seam框剪使用的代码,seam.bat(Windows)和seam(Linux/Unix)使用Ant来生成Seam工程和源代码,使用Seam之前必须先安装Ant 1.6(或者更新版本)和支持EJB3的JBoss Application Server(推荐使用4.2.0GA之后的版本。)

seam.bat或者seam命令位于Seam框架的根目录下。

Seam 工程创建和开发命令
setup
使用示例: seam.bat setup
运行向导,设置seam-gen/build.properties文件中的属性,这个命令设置了项目的工作目录,JBoss目录等。另外这个命令也设置了一些其他的关于代码生成的属性,例如model,action和test使用的package,数据库连接等信息。
另外直接编辑seam-gen/build.properties可以达到相同的效果。

create-project
使用示例:seam.bat create-project
根据seam-gen/build.properties文件中的内容,创建工程,包括依赖的类库,ant build脚本,和两个配置文件(开发用和发布用),这个命令也会产生Eclipse和Netbeans所需要的工程文件。使用这个命令建立的工程可以被Eclipse或者Netbeans轻松的引入。

update-project
使用示例:seam.bat update-project
更新项目的类库。

delete-project
使用示例:seam.bat delete-project
删除项目目录,也从JBoss中取消部署,注意这个命令一旦被执行就没有办法回退。

deploy
使用示例:seam.bat deploy
将项目(打包的EAR或者WAR)和数据源部署到JBoss 服务器上。

undeploy
使用示例:seam.bat undeploy
将项目(打包的EAR或者WAR)和数据库从JBoss服务器上删除。

explode
使用示例:seam.bat explode
部署项目(以展开目录的形式,与打包的EAR和WAR对应)和数据源到JBoss服务器上。

restart
使用示例:seam.bat restart
重新启动已经部署的项目(以展开目录的形式)

unexplode
使用示例:seam.bat unexplode
与explode对应,删除服务器上部署的项目(以展开目录的形式)和数据源。

Seam 代码生成命令

new-action
使用示例:seam.bat new-action
创建一个新的java接口和SLSB(Stateless Session Beam),并且连带Seam和EJB3的标注(Annotation)

new-form
使用示例:seam.bat new-form
建立一个Java接口,和SFSB(Stateful Session Bean),并且连带Seam和EJB3的标注(Annotation)。并且建立XHTML的页面,和能够模拟JSF请求的TestNG的测试代码。

new-conversation
使用示例:seam.bat new-conversation
建立一个Java接口和SFSB,并且连带Seam和EJB3的标注(Annotation)。并且添加带有@Begin和@End的方法框架。
new-entity
使用示例:seam new-entity
建立一个带有Seam和EJB3标注的Entity Beam。
generate-entities
使用示例:seam.bat generate-entities
从已有的数据库Schema生成JPA兼容的Entity类。这个命令使用Hibernate的逆向工程工具生成JPA Entity类,Seam EntityHome和 EntityQuery,JavaBean,和Facelets试图(查看,查找,编辑)。

2008年7月8日星期二

SVN主要命令

<Location /svn>
DAV svn
SVNParentPath e:svn
AuthType Basic
AuthName "Subversion repositories"
AuthUserFile passwd
AuthzSVNAccessFile svnaccessfile
Require valid-user
</Location>



svnadmin create --fs-type fsfs TestRepository


binhtpasswd -c passwd <username>


[groups]
developers = user1,user2,user3,user4
docs = user5,user6,user7
#to allow everyone read access
[/]
* = r
#allow all developers complete access
@developers = rw
#give the doc people write access to the docs folder
[/project/trunk/doc]
@docs = rw

2008年3月25日星期二

Seam学习笔记---Seam的组件

Seam组件

无状态Session Bean


无状态Session Bean组件无法在多次调用之间保持状态。因此,它们通常在不同的Seam上下文中,操作其他组件的状态。他们可以作为JSF的action listener,但是不能为JSF组件的显示提供属性。

无状态Session Bean总是生活在无状态上下文中。

无状态Session Bean是Seam组件中最没趣的了。

Seam无状态Session Bean组件可以使用 Component.getInstance() 或者 @In(create=true) 实例化。它们不能直接使用JNDI或者 new 操作实例化。

有状态Session Bean


有状态Session Bean不仅可以在bean的多次调用之间保持状态,而且在多次请求之间也可以保持状态。 不由数据库保存的状态通常应该由有状态Session Bean保持。这是Seam和其他web框架之间的一个显著的不同点。 其他框架把当前会话的信息直接保存在 HttpSession 中,而在Seam中你应该把它们保存在有状态Session Bean的实例中,该实例被绑定到会话上下文。这可以让Seam来替你管理状态的声明周期,并且保证在多个不同的并发会话中没有状态冲突。

有状态Session Bean经常被作为JSF action listener使用,也可以作为JSF显示或者form提交的后台bean,提供属性供组件访问。

默认情况下,有状态Session Bean会被绑定到Conversation Context。它们绝不会绑定到page或stateless context。

对Session范围的有状态Session Bean的并发请求,会被Seam按顺序串行处理。

Seam有状态Session Bean组件可以使用 Component.getInstance() 或者 @In(create=true) 实例化。它们不能直接使用JNDI或者 new 操作实例化。

实体Bean


实体Bean可以被绑定到上下文变量,起到Seam组件的作用。因为Entity除了上下文标识之外,还有持久标识,Entity实体通常明确的由Java Code绑定,而非由Seam隐性初始化。

Entity Bean实体不支持双向注入或者上下文分界。对Entity Bean的调用也不会触发验证。

Entity Bean通常不作为JSF的action listener使用,但经常作为JSF组件用于显示或者form提交的后台bean,提供属性功能。 特别是,当Entity作为后台Bean的时候,它会和一个无状态Session Bean扮演的action listener联用,来实现CRUD之类的功能。

默认情况下,Entity Bean被绑定到Conversation Context。他们永远不能被绑定到无状态Context。

注意,在集群环境中,把Entity Bean直接绑定到Conversation或者Session范围的Seam上下文变量,与在有状态Session Bean中保持一个对Entity Bean的引用相比,性能比较差。因此,并非所有的Seam应用程序都会把Entity Bean定义为Seam组件。

Seam实体Bean组建可以使用 Component.getInstance()@In(create=true) 或者直接使用 new 操作来实例化。

组件名字


所有Seam组件都需要名字。我们可以通过 @Name 注解来命名组件:

@Name("loginAction") @Stateless public class LoginAction implements Login {     ... }

这个名字是 seam component name,和EJB规范定义的任何其他名字都没有关系。 但是,Seam组件名字就作为JSF管理的Bean Name使用,因此,可以理解为这两个概念是等同的。

@Name 不是定义组件名称的唯一方式,但是我们总得要在 某个地方 来指定名字。 否则,Seam 注解的其他部分就无法工作。

就如同在JSF中,Seam组件实例绑定成上下文变量时,其名字通常和组件名相同。 因此,例如我们可以通过 Contexts.getStatelessContext().get("loginAction") 来访问 LoginAction。 特别是,不管Seam自己何时初始化一个组件,它将这个新实例以组件的名字绑定成一个变量。 但是,又和JSF一样,应用程序也可以把组件绑定成其他的上下文变量,只需通过API编程调用。 例如,当前登录的用户(User)可以被绑定成为Session上下文中的 currentUser 变量,而同时,另一个用作某种管理功能的用户则被绑定成对话上下文的 user 变量。

对非常大型的应用程序,经常使用全限定名;内置的Seam组件就是这样。

@Name("com.jboss.myapp.loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login {     ... }

我们可以在Java代码和JSF表达式语言中使用全限定的组件名称。

<h:commandButton type="submit" value="Login"                  action="#{com.jboss.myapp.loginAction.login}"/>

这很啰嗦,Seam也提供了把全限定名简写的办法。在 components.xml 文件中加入类似这样的一行:

<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

所有的Seam内置组件都有全限定名,但大多数都在Seam jar文件的 components.xml 中简写为简单的名字。

Seam学习笔记---Seam的上下文

基本的Seam上下文

基本的Seam上下文有:

  • Stateless context

  • Event (or request) context

  • Page context

  • Conversation context

  • Session context

  • Business process context

  • Application context

Stateless context(无状态上下文)


那些确实没有状态的组件(主要是无状态Session Bean)总是运行在无状态上下文中(实际上就是上下文无关)。 无状态组件没什么太大的意思,也有争议认为它们不十分面向对象。但不管怎么样,它们还是很重要,并且通常很有用。

Event context(事件上下文)


事件上下文是“最窄”的有状态上下文,是Web Request 上下文的泛化,用以包含其他种类的事件。 然而,与JSF请求的生命周期相关联的事件上下文是事件上下文最重要的实例,并且也是你最常打交道的。 与事件上下文相关联的组件在请求结束时被销毁,但是它们的状态至少在请求的生命周期中是存在并且是定义良好的。

Page context(页面上下文)


页面上下文允许你将状态与一个渲染页面的实例相关联。 你可以在Event Listener中初始化状态,或者在实际渲染页面的时候初始化状态,任何源于该页面的事件都可以访问到这些状态。 这在支持像可点击列表这种的功能时特别有用,列表的内容通过服务器端的数据变化产生。 实际上状态被序列化到了客户端,因此在多窗口操作或者回退按钮的时候,这种结构是非常健壮的。

Conversation context(业务会话上下文)

业务会话上下文是Seam中最核心的概念conversation(业务会话)是从用户的视角看待的一个工作单元。 它可能跨越与用户交互的多个Servlet、多个请求,和多个数据库事务。但是对用户来说,一个业务会话解决一个单一的问题。 例如说:“预订酒店”,“批准合同”,“创建订单”都是业务会话。 你可以将业务会话理解成对一个“use case(用例)”或“user story(用户故事)”的实现,但这种联系并非是必须的。

业务会话保存关于“在此窗口中,用户正在干什么”的状态。在任何时间,一个用户可能同时位于多个业务会话活动中,一般是在几个不同窗口中。 业务会话上下文让我们可以确保不同业务会话的状态不会互相干扰,不会导致Bug。

你可能要花上一点时间才能习惯以这一业务会话的观点来思考你的应用程序。 但一旦你习惯于它,你会喜欢上这个术语,并且再不会不用业务会话来思考了!

一些业务会话仅存在在一次请求中。跨域多个请求的业务会话必须通过Seam提供的annotation注解来划分。

一些业务会话同时也是tasks(任务)。任务是一种业务会话,它特指一个长时间运行的业务过程,当正确完成后,可能会触发一个业务流程状态的转换。Seam为任务划分提供了专门的annocation注解。

业务对话可以是nested(嵌套)的,一个业务对话嵌套“在”一个更大的业务对话中。这是一项高级特性。

通常,业务对话状态实际上由Seam保存在Servlet Session 中,跨越请求。Seam实现了可配置的 conversation timeout,可以自动销毁不活动的业务会话,这就可以确保,如果用户取消对话,用户的登录Session中保存的状态不会无限增长。

对于在一个长时间运行的业务会话中所产生的并发请求,Seam按顺序执行。

除此之外,Seam也可以配置成把对话状态保存在客户端浏览器中。

Session context(Session上下文)


Session上下文保存与用户登录session相关联的状态。虽然当需要在多个业务会话中交换状态的时候这很有用,但我们一般不建议使用Session 上下文保存组件,除非是保存有关登录用户的全局信息。

在JSR-168 Portal环境下,Session上下文代表Portlet上下文。

Business process context (业务流程上下文)


业务流程上下文保存了长时间运行的业务流程相关的状态。这种状态由BPM引擎(jBPM)管理和持久化。 业务流程跨越多个用户的交互,因此状态在多个用户之间通过良好定义的方式共享。 当前的任务决定当前的业务流程实例,业务流程的生命周期通过外置的 process definition language(流程定义语言) 来定义,因此没有特别的annotation注解用于划分业务流程。

Application context(应用上下文)


Application上下文就是Servlet规范中的Servlet上下文。应用程序上下文在保存静态信息方面有用,例如配置数据,引用数据或者元模型。 例如,Seam把自己的配置和元模型保存在应用程序上下文中。

Context variables(上下文变量)


上下文定义了命名空间,一组 context variables(上下文变量)。 这些工作很类似Servlet规范中对Session或Request attributes的定义。 你可以绑定任何你喜欢的值到Context Variable,但通常我们会绑定Seam组件实例到Context Variables。

因此,在上下文中,组件实例是通过上下文变量名字来辨别的(通常是这样,但并非绝对,就和组件名称一样)。 你可以通过程序在特定范围内访问被命名的组件实例,这是通过 Contexts 类进行的,它提供了对 Context 接口的几个线程绑定的实例的访问:

User user = (User) Contexts.getSessionContext().get("user");

你也可以通过名字来设置或修改变量值:

Contexts.getSessionContext().set("user", user);

但通常,我们通过注射(injection)来从上下文中获得组件,并且通过反向注射(outjection)把组件实例返回上下文。

2008年3月20日星期四

免得老是忘记-toString

为了方便Log4J等方式的调试,显示一个类的实例,通常需要做如下方式的输出:
log.trace(myClassInstance);
此时需要MyClass实现重载 toString()方法,利用Jakarta Common Lang可以很容易实现toString方法,由ToStringBuilder类完成对一个类的细节的显示,参考toString方法的实现如下:

import org.apache.commons.lang.builder.ToStringBuilder;

public String toString() { return ToStringBuilder.reflectionToString(this); }relectionToString()

将利用Java Refelection机制显示类实例的所有属性的信息.

2008年3月10日星期一

Cognos报表展示的参数传递

如何调用已发布到Enterprise server的ppx报表(参数传递)

Cognos的内容一般作为网页中某一特定的帧来展现。我们可以在外部统一的用户交互界面中收集用户递交的查询条件(一般用JSP实现),在检查完用户输入的有效性之后,把这些参数按照Cognos约定的标准,以POST的方式传到某一帧中的Cognos的网管。Cognos网管就会把用户查询的结果返回到该帧中。

下面是一个接口调用的实例:
假设服务器名称为:servername
ppx报表发布到enterprise server之后在enterprise server的根目录下,名称为:testreport
如果我们要对这个报表进行访问,可通过如下url对报表进行调用:http://test/cognos/ cgi-bin/ppdscgi.exe?DC=R&E=%2Ftestreport
如果用户要求访问的是一个可动态分析的cube,那么相应的url为
http://test/cognos/ cgi-bin/ppdscgi.exe?DC=Q&E=%2Fcubename

其中:%2F是一个URL使用的转意符,它的原型是符号“\”。

如果报表或Cube是发布于一个文件夹test中的,那么相应的url为:

http://test/cognos/ cgi-bin/ppdscgi.exe?DC=R&E=%2Ftest%2Ftestreport



通过以上的接口可以访问到任意发布到Powerplay Enterprise Server的报表或Cube。如果要向报表或Cube传递过滤条件,可采用下面的调用标准。

例如在Enterprise Server发布有报表ICBC,该报表开放了四个传参接口(Years,Products,Locations,Channles)。用户可以选择向其中的某几个接口传参。

如果选择“Products”为“Outdoor Products”则调用

http://test/cognos/cgi-bin/ppdscgi.exe?DC=R&E=%2Ficbc&DM=Products&FC=0%09Outdoor%20Products&ZZ=X

其中&DM=Products //表示要过滤维度Products

&FC=0%09Outdoor%20Products //表示维度Products的过滤值



如果是过滤两个维度,例如过滤“Products”和“Locations”则调用

http://info/cognos/cgi-bin/ppdscgi.exe?DC=R&E=%2Ficbc&DM=Products%09Locations&FC=0%09Outdoor%20Products%091%09Europe&ZZ=X

其中&DM=Products%09Locations //维度间用%09分开

&FC=0%09Outdoor%20Products%091%09Europe //维度值从0开始标号



如果是过滤三个维度,例如过滤“Products”和“Locations”及“Channels”则调用

http://info/cognos/cgi-bin/ppdscgi.exe?DC=R&E=%2Ficbc2&DM=Products%09Locations%09Channels&FC=0%09Environmental%20Line%091%09Europe%092%09Independent&ZZ=X

其中&DM=Products%09Locations%09Channels //维度间用%09分开

&FC=0%09Outdoor%20Products%091%09Europe%092%09Independent

//维度值从0开始标号



在每条Url后添加&ZZ=X,表示参数传递结束。



注意:维度过滤值必须用该维度节点的code

2008年3月5日星期三

Operational Data Storage

ODS是一个面向主题的、集成的、可变的、当前的细节数据集合,用于支持企业对于即时性的、操作性的、集成的全体信息的需 求。常常被作为数据仓库的过渡,也是数据仓库项目的可选项之一。

根据Bill.Inmon的定义,“数据仓库是面向主题的、集成的、稳定的、随时间变化的,主要用于决策支持的数据库系统”

ODS是一个面向主题的、集成的、可变的、当前的细节数据集合,用于支持企业对于即时性的、操作性的、集成的全体信息的需 求。常常被作为数据仓库的过渡,也是数据仓库项目的可选项之一。

在Kimball的<<数据仓库生命周期工具集The Data WareHouse Liftcycle Toolkit>>,他是这样定义的

1. 是操作型系统中的集成,用于当前,历史以及其它细节查询(业务系统的一部分)

2. 为决策支持提供当前细节数据(数据仓库的一部分)

因此操作数据存储(ODS) 是用于支持企业日常的全局应用的数据集合,ODS的数据具有面向主题、集成的、可变的和数据是当前的或是接近当前的4个基本特征。同样也可以看出ODS是介于DB和DW 之间的一种数据存储技术,和原来面向应用的分散的DB相比,ODS中的数据组织方式和数据仓库(DW)一样也是面向主题的和集成的,所以对进入ODS的数 据也象进入数据仓库的数据一样进行集成处理。另外ODS只是存放当前或接近当前的数据,如果需要的话还可以对ODS中的数据进行增、删和更新等操 作,虽然DW中的数据也是面向主题和集成的,但这些数据一般不进行修改,所以ODS和DW的区别主要体现数据的可变性、当前性、稳定性、汇总度上。

由于ODS仍然存储在普通的关系数据库中,出于性能、存储和备份恢复等数据库的角度以及对源数据库的性能影响角度,个人不建议ODS保存相当长周期的数据,同样ODS中的数据也尽量不做转换,而是原封不动地与业务数据库保持一致。即ODS只是业务数据库的一个备份或者映像,目的是为了使数据仓库的处理和决策支持要求与OLTP系统相隔离,减少决策支持要求对OLTP系统的影响。

为什么需要有一个ODS系统呢?一般在带有ODS的系统体系结构中,ODS都具备如下几个作用:

1) 在业务系统和数据仓库之间形成一个隔离层。

一 般的数据仓库应用系统都具有非常复杂的数据来源,这些数据存放在不同的地理位置、不同的数据库、不同的应用之中,从这些业务系统对数据进行抽取并不是一件 容易的事。因此,ODS用于存放从业务系统直接抽取出来的数据,这些数据从数据结构、数据之间的逻辑关系上都与业务系统基本保持一致,因此在抽取过程中极 大降低了数据转化的复杂性,而主要关注数据抽取的接口、数据量大小、抽取方式等方面的问题。

2) 转移一部分业务系统细节查询的功能

在 数据仓库建立之前,大量的报表、分析是由业务系统直接支持的,在一些比较复杂的报表生成过程中,对业务系统的运行产生相当大的压力。ODS的数据从粒度、 组织方式等各个方面都保持了与业务系统的一致,那么原来由业务系统产生的报表、细节数据的查询自然能够从ODS中进行,从而降低业务系统的查询压力。

3) 完成数据仓库中不能完成的一些功能。

一 般来说,带有ODS的数据仓库体系结构中,DW层所存储的数据都是进行汇总过的数据和运营指标,并不存储每笔交易产生的细节数据,但是在某些特殊的应用中,可能需要 对交易细节数据进行查询,这时就需要把细节数据查询的功能转移到ODS来完成,而且ODS的数据模型按照面向主题的方式进行存储,可以方便地支持多维分析 等查询功能。即数据仓库从宏观角度满足企业的决策支持要求,而ODS层则从微观角度反映细节交易数据或者低粒度的数据查询要求。

在一个没有ODS层的数据仓库应用系统体系结构中,数据仓库中存储的数据粒度是根据需要而确定的,但一般来说,最为细节的业务数据也是需要保留的,实际上 也就相当于ODS,但与ODS所不同的是,这时的细节数据不是“当前、不断变化的”数据,而是“历史的,不再变化的”数据。这样的数据仓库的存储压力和性能压力都是比较大的,因此对数据仓库的物理设计和逻辑设计提出了更高的要求。

2008年2月28日星期四

Cognos小技巧1

1,机构中数据分级控制,
case
left (#"'"+$account.personalInfo.businessPhone+"'"# ,1)
when '0' then [ocrm_person].[RPTORGDIMEN].[CENTRE]
when '1' then [ocrm_person].[RPTORGDIMEN].[PROVINCE]
when '2' then [ocrm_person].[RPTORGDIMEN].[CITY]
when '3' then [ocrm_person].[RPTORGDIMEN].[BRANCHCODE]
when '4' then [ocrm_person].[RPTORGDIMEN].[NODE]
else #"'"+$account.personalInfo.email+"'"#
end = #"'"+$account.personalInfo.email+"'"#

2,Cognos外部链接登录格式
http://192.168.5.83:9300/p2pd/servlet/dispatch?CAMNamespace=default&CAMUsername=administrator&CAMPassword=123456