由于对于cocoon 2的内容特别的感兴趣,所以跳过了对sax的学习就直接开始了cocoon 2的学习,事实证明这样是不对的,如果你只是想对cocoon 2作一个简单的了解,可以不学sax,但是如果想要深入了解cocoon 2的话,还是应该先学习sax。
首先,关于cocoon入门的资料网上比较少,一个是ibm上提供的三个cocoon的教程,比较不错,另外就是cocoon自己的文档,还有就是必须研究cocoon的源代码了。
一、cocoon是什么?
按照我的理解,cocoon是一个整套的java web开发框架,象struts里面可以实现的功能,在cocoon里面其实都可以实现,但是,我觉得他的侧重点应该是用于在web以各种样式上发布xml内容(Html,pdf,SVG),他提供了内容、逻辑、表现在很大程度上的分离,这一点不是struts这种MVC框架的强项。
至于说有人将cocoon和struts进行比较,我觉得应该是不恰当的,因为两者着重解决的问题是不同的,如果你是一个新闻网站,以发布内容为主,那么你可以考虑cocoon,但是如果你是一个信息管理系统,侧重于事物的处理,那么你可以选择struts以及webwork2等框架。
二、cocoon能干什么?
- 提供静态文件和动态生成的响应
- 使用任意数量的处理将用户请求透明地映射到物理资源
- 执行简单和多级 XSLT 转换
- 将参数动态传递到 XSLT 变换
- 生成各种各样的输出格式,包括 XML、HTML、PNG、JPEG、SVG 和 PDF
以上是ibm教程上的原话,可能只有最后的一条比较容易理解,不过希望您在读完本文以后可以对上述的特性都有一个大概的理解。
三、为什么是cocoon 2?
cocoon1和cocoon2是cocoon的两个不同的版本,其主要的区别是cocoon1是围绕dom接口开发的,这是一种效率较低的api,而cocoon2是围绕sax的。所以在学习cocoon2之前,建议了解sax和xml的相关知识。
四、开始进入cocoon 2的世界,管道模型
安装配置就不说了,网上可以找到相应的说明,而且熟悉java web的人应该可以知道如何安装。
下面就说一说cocoon 2里面最重要的一个概念:管道
一个管道是由一个输入开始,一个输出结束,其中您可以加入任意多的对输入数据的处理模块。所以,在cocoon 2中,一个管道应该是1输入+n处理+1输出。所有这些模块,在cocoon中被称为“components”。
下面是cocoon 2中内置的几个较为常用的模块:
管道输入 — 生成器(如FileGenerator,HTMLGenerator,DirectoryGenerator)和阅读器(常用来读静态文件)
处理步骤 — 转换器(如XSLT 转换器)和操作
管道输出 — 序列化器(如XML,HTML,SVG,PDF序列化器 )
条件的处理 — 匹配器和选择器
cocoon的管道通常至少有生成器和序列化器,也就是我们说的输入和输出。这里面有个感念前面在学习xsl的时候提到过,就是对xml的转换和格式化输出是分开的,在cocoon里面很好的体现了这一点。
五、站点地图
实际上,一个最简单的管道应该是使用匹配器来接受用户请求(URL请求),使用生成器生成xml,使用各种转换器,使用序列化器输出。所有这些,将体现在cocoon的一个配置文件里面sitemap.xmap,就构成了所谓的“站点地图”。
下面是我做测试使用的站点地图:
<?
xml version="1.0" encoding="UTF-8"
?>
<
map:sitemap
xmlns:map
="http://apache.org/cocoon/sitemap/1.0"
>
<
map:components
>(1)
<!--
component declarations
-->
<
map:generators
default
="file"
>
<
map:generator
name
="file"
src
="org.apache.cocoon.generation.FileGenerator"
/>
<
map:generator
name
="xsp"
src
="org.apache.cocoon.generation.ServerPagesGenerator"
/>
<
map:generator
name
="helloWorld"
src
="simpletest.SimpleTest"
/>
</
map:generators
>
<
map:transformers
default
="xslt"
>
<
map:transformer
name
="xslt"
src
="org.apache.cocoon.transformation.TraxTransformer"
/>
</
map:transformers
>
<
map:readers
default
="resource"
>
<
map:reader
name
="resource"
src
="org.apache.cocoon.reading.ResourceReader"
/>
</
map:readers
>
<
map:serializers
default
="html"
>
<
map:serializer
name
="xml"
mime-type
="text/xml"
src
="org.apache.cocoon.serialization.XMLSerializer"
/>
<
map:serializer
name
="html"
mime-type
="text/html"
src
="org.apache.cocoon.serialization.HTMLSerializer"
/>
<
map:serializer
name
="svg2png"
src
="org.apache.cocoon.serialization.SVGSerializer"
mime-type
="image/png"
/>
<
map:serializer
name
="fo2pdf"
src
="org.apache.cocoon.serialization.FOPSerializer"
mime-type
="application/pdf"
/>
</
map:serializers
>
<
map:matchers
default
="wildcard"
>
<
map:matcher
name
="wildcard"
src
="org.apache.cocoon.matching.WildcardURIMatcher"
/>
</
map:matchers
>
<
map:actions
/>
<
map:selectors
/>
</
map:components
>
<
map:pipelines
>
<
map:pipeline
>
<
map:match
pattern
="*.xml"
>(4)
<
map:generate
type
="xsp"
src
="{1}.xsp"
/>
<
map:serialize
type
="xml"
/>
</
map:match
>
<
map:match
pattern
="welcome"
>(2)
<
map:generate
src
="samples.xml"
/>
<
map:transform
src
="simple-samples2html.xsl"
/>
<
map:serialize
type
="html"
/>
</
map:match
>
<
map:match
pattern
="heyThere"
>(3)
<
map:generate
type
="helloWorld"
/>
<
map:serialize
type
="xml"
/>
</
map:match
>
</
map:pipeline
>
</
map:pipelines
>
</
map:sitemap
>
为了节省篇幅,我把后面的东西也放进来,以后会慢慢解释这个文件,先看图中的(1),是所有的组件的定义。
(2)就是一个简单的管道。从他的内容可以看出,首先,他匹配welcome的用户请求,假使是:http://localhost:8089/cocoon-dev/welcome,然后,有一个生成器,使用的是默认的FileGenerator生成器,这个生成器将会读取samples.xml文件,把它作为这个管道的输入。
之后,有一个xsl的转换器,他使用simple-samples2html.xsl来转化生成器生成的xml,最后由一个html的序列化器生成输出。simples.xml和simple-samples2html.xsl的代码就不给出了,熟悉xml&xslt的朋友应该明白。
在浏览器中输入http://localhost:8089/cocoon-dev/welcome后就会得到输出的html页面。当然,如果你将转换器和序列化器的定义修改成:
<map:transform src="transforms/content2rss.xsl"/>
<map:serialize type="xml"/>
或者
<map:transform src="transforms/content2fo.xsl"/>
<map:serialize type="fo2pdf"/>
的话,你将会得到rss和pdf格式的输出。个人认为这应该是cocoon的最主要的功能,这也是我了解cocoon的主要原因。
六、使用cocoon进行开发
在cocoon 2的tutorial里面有这样的一个章节介绍使用cocoon进行web的开发,但是其中只讲到了自定义生成器的部分,个人认为cocoon 2的建议是把查询数据库,逻辑运算,商业逻辑等功能都放在生成器里面、xsp就是一种生成器他的写法类似于jsp,但是不管是自定义的生成器还是xsp,其接口都比较复杂,我觉得并没有struts来的方便,当然这又回到了开始的话题,应用的地方不同,没有好坏之分,这里只是为了研究一下cocoon2的内部结构。
下面是一个自定义的生成器:
package simpletest;
import org.apache.cocoon.generation.AbstractGenerator;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.SAXException;
public
class
SimpleTest extends AbstractGenerator
{
AttributesImpl emptyAttr = new AttributesImpl();
/**//**
* Override the generate() method from AbstractGenerator.
* It simply generates SAX events using SAX methods.
* I haven't done the comparison myself, but this
* has to be faster than parsing them from a string.
*/
public void generate() throws SAXException
{
// the org.xml.sax.ContentHandler is inherited
// through org.apache.cocoon.xml.AbstractXMLProducer
contentHandler.startDocument();
contentHandler.startElement("", "example", "example", emptyAttr);
contentHandler.characters("Hello World!".toCharArray(),0,
"Hello World!".length());
contentHandler.endElement("","example", "example");
contentHandler.endDocument();
}
}
可以看到生成器的接口并不那么简单,这里只写一个generate方法是因为继承了很多的类,他们给你提供了好多方法。
第二点就是我们的generate方法里面写的是sax事件的调用,也就是说,在执行generate方法的时候,contentHandler就好像在读取一个xml文件一样。为了证明这一点,我们有了管道中(3)的配置。运行
http://localhost:8089/cocoon-dev/heyThere之后,我们有了如下结果:
<?
xml version="1.0" encoding="UTF-8"
?>
<
example
>
Hello World!
</
example
>
我们可以理解以上的xml文件就是generate的输出,原因是序列化器是个xml序列化器,他其实并没有对generate的输出作什么操作。
更进一步的说,我们可以这样理解,generate输出一个xml,管道的下一个处理器接受到这个xml以后进行一些操作,生成另外一个xml,以此类推,直到序列化器的出现,把xml转化成了想要的格式。这其实就是cocoon的基本工作原理。
七、实际原理
上面说的工作原理可以通过依次取掉generate后面的处理器,而在最后加装xml的序列化器来验证,但是在cocoon的内部,却没有什么xml被传来传去,那么管道的各个组件之间是怎样协作的呢?在教程上有句话说是通过互相传递sax事件实现的,乍一听来非常难以理解,经过了无数次调试后,终于得知了实情。
我们来看如下的代码:
private
void
connectPipeline(Environment environment,
ArrayList usedTransformers,
XMLSerializer xmlSerializer)
throws ProcessingException
{
XMLProducer prev = this.producer;
XMLConsumer next;
boolean configuredSAXConnector = this.manager.hasComponent(SAXConnector.ROLE);
try {
int cacheableTransformerCount = this.firstNotCacheableTransformerIndex;
Iterator itt = usedTransformers.iterator();
while ( itt.hasNext() ) {
.......................
next = (XMLConsumer) itt.next();
.......................
prev.setConsumer(next);
prev = (XMLProducer) next;
}
........................
}
prev.setConsumer(next);
} catch .............
}
上面是cocoon代码中的一些片断,从中我们可以看出,cocoon把一个管道里面的所有的组件进行一次循环,把前一个组件的XMLConsumer设成后一个组件,也就是后面的组件是前面组件的XML消费者,而对于后一个组件来说,前一个是XMLProducer ,也就是XML生产者。
而且,通过调试得知,每一个组件,不管是什么类型,都会有一个XMLConsumer的属性,另外,还都会有一个contentHandler的属性。ContentHandler这个类是标准的sax接口,从这一点分析,XMLConsumer有可能是cocoon1的残留物,而真正在cocoon2里面其作用的是ContentHandler这个接口的一些实现类。
后面又进行了一些调试,结果却是验证了上面的说法,为了测试做了如下的配置:
<map:match pattern="welcome">
<map:generate type="helloWorld"/>
<map:transform src="simple-samples2html.xsl"/>
<map:transform src="simple-samples2html2.xsl"/>
<map:serialize type="xml"/>
</map:match>
结果是这样的,自定义的generate类的generate()方法中的contentHandler.startDocument();一句的实现代码如下:这段代码在AbstractXMLPipe这个类中
public
void
startDocument()
throws SAXException
{
if (contentHandler != null) contentHandler.startDocument();
}
也就是说,generate的属性contentHandler中还有个contentHandler属性,依次递归调用,后续的测试得出如下的结论:
generate的对象我们称之为g1,
generate的contentHandler属性就是TraxTransformer类的一个对象,也就是
<map:transform src="simple-samples2html.xsl"/>这一句的实现载体,我们暂且称这之对象为t1,
而<map:transform src="simple-samples2html2.xsl"/>的实现载体对象我们暂称之为t2,
<map:serialize type="xml"/>的实现载体是XMLSerializer类的一个对象,我们暂称之为s1
他们之间的关系是g1.contentHandler = t1.contentHandler = t2.contentHandler = s1.contentHandler,而且所有的contentHandler都是AbstractXMLPipe这个类的某一个子类型,也就是说他们的startDocument()等方法的实现都是完全一样的。
这样一来,每一个sax的事件将会在generate.generate()方法中被开始调用,并不断地递归g1,t1,t2.....s1,这就是cocoon管道之间各个组件互相传递数据的真相。
八、其他一些技术的了解:
cocoon中比较有用的一些技术还有xsp和esql,其中xsp是一个类似jsp的组件,他在cocoon中的地位应该是一种generate,用来生成最初的xml,并且,他可以内嵌java代码,就像jsp那样,取得request参数,查询数据库等,如站点地图中的(4)所示,我们可以把这样的一个组件作为管道的起点,这样我们查询或者业务处理的结果就可以通过管道被发布成各种样式了!
下面给出一个xsp的简单例子:
<
xsp:page
language
="java"
xmlns:xsp
="http://apache.org/xsp"
>
<
xsp:structure
>
<
xsp:include
>
java.util.Calendar
</
xsp:include
>
<
xsp:include
>
java.text.*
</
xsp:include
>
</
xsp:structure
>
<
document
>
<
xsp:logic
>
SimpleDateFormat format = new SimpleDateFormat("EEE, MMM d, yyyy");
String timestamp = format.format(
java.util.Calendar.getInstance().getTime()
);
</
xsp:logic
>
<
time
><
xsp:expr
>
timestamp
</
xsp:expr
></
time
>
<
time
><
xsp:expr
>
request
</
xsp:expr
></
time
>
<!--
additional elements
-->
</
document
>
</
xsp:page
>
其中:
<
xsp:logic
>标签和<xsp:expr>标签就类似于jsp中的<%%>和<%=%>
esql是在xsp上使用的一项技术,他应该类似于jsp的一些标签库,实现的功能是操作数据库,直接写在xsp文件中的,这样可以很方便的生成一个由数据库驱动的动态的xml,配合上cocoon的其他组件,也就是说可以很方便的把数据库中的东西发布成各种版本。
下面是他的一些语法定义:
<
xsp:page
language
="java"
xmlns:xsp
="http://apache.org/xsp"
xmlns:esql
="http://apache.org/cocoon/SQL/v2"
>
<
root
>
<
esql:connection
>
<
esql:execute-query
>
<!--
connection
information
-->
<
esql:pool
/>
<!--
SQL query
-->
<
esql:query
/>
<!--
result processing
elements
-->
<
esql:results
/>
<
esql:update-results
/>
<
esql:no-results
/>
<
esql:error-results
/>
</
esql:execute-query
>
</
esql:connection
>
</
root
>
</
xsp:page
>
九、struts&cocoon
目前,已经有人想到了struts和cocoon的整合,用struts来处理逻辑层的事物,用cocoon来实现多种表现形式,操作方法大概提供一个struts的plugin,可以把struts最终的jsp作为cocoon的xml源来使用。
地址:
http://struts.sourceforge.net/struts-cocoon/
不过个人认为这种结合的方式还是比较生硬,记得以前看到过关于strutsX的有关介绍,希望这种事物处理层和表现层同样强大的框架可以早点出来,顺便说一句,cocoon 2项目以前是属于apache xml项目下的,现在已经是一个独立的项目
http://cocoon.apache.org,在这里也祝愿cocoon能够飞的更高,飞得更远。
第一次写那么多,试试发布一下,纯粹是可望志同道合的人交流,各位高手见笑了^_^