Console Logging 函数

来源:互联网 发布:怎样购买已备案域名? 编辑:程序博客网 时间:2024/05/22 14:13

Console Logging 函数

FireBug 为所有 Web 页面提供了一个 console 对象。这个对象有以下函数:

Logging 基础

console.log("message" [,objects]) - 将一个字符串打印到控制台。字符串可以包含任何“String Formatting”小节描述的模式。字符串后面的对象应该用来取代之前字符串中的模式。(译者注:大家用过C里面 printf 吧,效果基本是一样的。)

Logging 等级

通常根据不同的等级来区分Logging的严重程度是很有帮助的。FireBug 提供了4个等级。为了达到视觉分离的效果,这些函数与 log 不同的地方就是它们在被调用的时候会自动包含一个指向代码行数的链接。

console.debug("message" [,objects]) - 记录一个 debug 消息。
console.info("message" [,objects]) - 记录一个信息.
console.warn("message" [,objects]) - 记录一个警告.
console.error("message" [,objects]) - 记录一个错误.

断言

断言是一条确保代码规则的非常好的途径。console 对象包含了一系列各种类型的断言函数,并且允许你编写自己的断言函数。

console.assert(a, "message" [,objects]) - Asserts that an a is true.
console.assertEquals(a, b, "message" [,objects]) - Asserts that a is equal to b.
console.assertNotEquals(a, b, "message" [,objects]) - Asserts that a is not equal to b.
console.assertGreater(a, b, "message" [,objects]) - Asserts that a is greater than b.
console.assertNotGreater(a, b, "message" [,objects]) - Asserts that a is not greater than b.
console.assertLess(a, b, "message" [,objects]) - Asserts that a is less than b.
console.assertNotLess(a, b, "message" [,objects]) - Asserts that a is not less than b.
console.assertContains(a, b, "message" [,objects]) - Asserts that a is in the array b.
console.assertNotContains(a, b, "message" [,objects]) - Asserts that a is not in the array b.
console.assertTrue(a, "message" [,objects]) - Asserts that a is equal to true.
console.assertFalse(a, "message" [,objects]) - Asserts that a is equal to false.
console.assertNull(a, "message" [,objects]) - Asserts that a is equal to null.
console.assertNotNull(a, "message" [,objects]) - Asserts that a is not equal to null.
console.assertUndefined(a, "message" [,objects]) - Asserts that a is equal to undefined.
console.assertNotUndefined(a, "message" [,objects]) - Asserts that a is not equal to undefined.
console.assertInstanceOf(a, b, "message" [,objects]) - Asserts that a is an instance of type b.
console.assertNotInstanceOf(a, b, "message" [,objects]) - Asserts that a is not an instance of type b.
console.assertTypeOf(a, b, "message" [,objects]) - Asserts that the type of a is equal to the string b.
console.assertNotTypeOf(a, b, "message" [,objects]) - Asserts that the type of a is not equal to the string b.

测量(Measurement)

下面的一些函数可以让你方便的测量你的一些代码。

console.trace() - 记录执行点的堆栈信息。
console.time("name") - 根据 name 创建一个唯一的计时器。
console.timeEnd("name") - 根据 name 停止计时器,并且记录消耗的时间,以毫秒为单位。
console.count("name") - 记录该行代码执行的次数。

字符串格式化

所有 console 的 logging 函数都可以通过以下模式格式化字符串:

%s - 将对象格式化为字符串。
%d, %i, %l, %f - 将对象格式化为数字。
%o - 将对象格式化成一个指向 inspector 的超链接。
%1.o, %2.0, etc.. - 将对象格式化成包含自己属性的可交互的表格。
%.o - 将对象格式化成具有自身属性的一个数组。
%x - 将对象格式化成一个可交互的 XML 树形结构。
%1.x, %2.x, etc.. - 将对象格式化成一个可交互的 XML 数型结构,并且展开 n 层节点。

如果你需要一个真实的 % 符号,你可以通过一个转移符号就像这样 "/%"。

命令行函数

内建的命令行函数可以通过以下命令行使用:

$("id") - document.getElementById() 的简写。(译者注:跟 prototype.js 学来的吧?)
$$("css") - 返回一个符合 CSS 选择器的元素数组。
$x("xpath") - 返回一个符合 XPath 选择器的元素数组。
$0 - 返回最近被检查(inspected)的对象。
$1 - 返回最近被检查(inspected)的下一个对象。
$n(5) - 返回最近被检查的第n个对象。
inspect(object) - 将对象显示在 Inspector 中。
dir(object) - 返回一个对象的属性名数组。(译者注:跟 Python 学的?)
clear() - 清除控制台信息。

Posted in JavaScript, Ajax, Web应用 | No Comments »

编写自己的dojo扩展

Posted by Nicholas Ding on 12th 八月 2006

前言

dojo是时下非常流行的javascript框架,它在自己的Wiki上给自己下了一个定义,dojo是一个用JavaScript编写的开源的DHTML工具箱。

dojo很想做一个“大一统”的工具箱,不仅仅是浏览器层面的,野心还是很大的。不过dojo带来了JavaScript编程的一些新想法,其中引入包机制进行动态加载是一个不错的概念。

理解dojo的包机制

其实dojo只需要一些很小的加载代码就可以用来加载它的各种包,它的官方站点上提供的dojo-ajax下载中包含的dojo.js体积还是比较庞大的,因为它将一些常用的包都包含在了js中, 但是很多时候我们并不需要这么多功能,还是按需加载比较好。

幸好在http://download.dojotoolkit.org/这个地址中我们还可以下载到dojo的各个自定义版本,其实包含的组件都是一样的,只不过dojo.js的大小有很大不同,那么,我们就从minimal版本下手。

下载之后会发现minimal版本包含的dojo.js只有18kb,里面仅仅包含了加载机制,非常不错。这样,我们就可以开始编写自己的dojo扩展。

dojo代码结构

解压缩后的目录里面包含src目录,src目录下存放有dojo的各个组件包,我们在这里面新建一个hello目录。

新建一个名为__package__.js文件,很类似Python的模块命名,这个__package__.js定义了在引入这个命名空间的时候默认导入多少类,以及这个命名空间的名字。

我们的目的是做一个dojo.hello.Echo扩展,那么在__package__.js中的代码应该这样:

// kwCompoundRequire 的作用是当你导入整个dojo.hello包的时候需要默认加载多少类// 这些定义就在这个函数里面,common在这里表示默认的加载,这个参数不是固定的// dojo希望自己是一个“大一统”的实现,所以考虑了非浏览器情况,可以有别的,譬如rhinodojo.kwCompoundRequire({common: ["dojo.hello.Echo"]});// 这个定义了包,默认这么写 | 原因嘛,当然是有的,看你的悟性了:-)dojo.provide("dojo.hello.*");

我们指定了默认加载的类Echo,那么我们就去写Echo类,在hello目录中新建Echo.js,代码如下:

// 类名定义,JavaScript写的变扭,其实就是直接定义类名dojo.provide("dojo.hello.Echo")// 类定义部分,非常熟悉的代码dojo.hello.Echo = function() {this.name = "dojo.hello.Echo";this.sayHello = function(greeting) {return greeting;}}

扩展写好了,很简单,接下来就是掉用了,index.html如下。

<html><head><script language="javascript" src="dojo.js"></script><script language="javascript">dojo.require("dojo.hello.*");var echo = new dojo.hello.Echo();document.write(echo.sayHello("Hello World"));</script></head><body></body></html>

注意dojo.require("dojo.hello.*")回去请求两个文件,首先是__package__.js,这样一来就得到了之前在dojo.kwCompoundRequire里面指定的类列表,然后去加载Echo.js。你也可以直 接去加载Echo.js,只需要变成dojo.require("dojo.hello.Echo")

更多内容

这个例子非常简单的介绍了一下dojo的包加载机制,当然这个包中的类并没有引用其它类,dojo还允许在代码中动态加载其它类,当然了,这些都是通过XmlHttp来实现的,因为是同步模式,所以请求的类比较多并且都没有包含在dojo.js中的时候会有页面停顿的现象,这点还是需要注意的。

文中的代码下载:,dojo-hello.tar.gz

一些dojo的资源:

  • dojo 官方站点
  • dojo 手册
  • dojo wiki

Posted in JavaScript, Ajax, Web应用 | No Comments »

cglib 指南

Posted by Nicholas Ding on 6th 六月 2006

cglib,全称是Code Generation Library,它可以用来动态继承Java类或者实现接口,很多知名的开源项目中用到了它,譬如Hibernate,Spring之类用它来实现动态代理。

增强一个已有类

public class MyClass { public void method() {System.out.println("MyClass.method()");}}
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodProxy;import net.sf.cglib.proxy.MethodInterceptor; public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MyClass.class);enhancer.setCallback( new MethodInterceptorImpl() );  MyClass my = (MyClass)enhancer.create(); my.method();} private static class MethodInterceptorImpl implements MethodInterceptor {public Object intercept(Object obj,                                 Method method,                                 Object[] args,                                 MethodProxy proxy) throws Throwable { System.out.println(method); proxy.invokeSuper(obj, args)return null;}}}

执行结果:

public void cglib_test.MyClass.method()MyClass.method()

使用CallbackFilter

public class MyClass { public void method() {System.out.println("MyClass.method()");} public void method2() {System.out.println("MyClass.method2()");}}
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodProxy;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.NoOp;import net.sf.cglib.proxy.Callback;import net.sf.cglib.proxy.CallbackFilter;  public class Main { public static void main(String[] args) { Callback[] callbacks =new Callback[] { new MethodInterceptorImpl(),  NoOp.INSTANCE }; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MyClass.class);enhancer.setCallbacks( callbacks );enhancer.setCallbackFilter( new CallbackFilterImpl() );  MyClass my = (MyClass)enhancer.create(); my.method();my.method2();} private static class CallbackFilterImpl implements CallbackFilter { public int accept(Method method) { if ( method.getName().equals("method2") ) {return 1} else {return 0;}}} private static class MethodInterceptorImpl implements MethodInterceptor {public Object intercept(Object obj,                                 Method method,                                 Object[] args,                                 MethodProxy proxy) throws Throwable { System.out.println(method)return proxy.invokeSuper(obj, args);}}}

执行结果:

public void cglib_test.MyClass.method()MyClass.method()MyClass.method2()

使用Mixin

public interface MyInterfaceA { public void methodA();} public interface  MyInterfaceB {public void methodB();} public class MyInterfaceAImpl implements MyInterfaceA { public void methodA() {System.out.println("MyInterfaceAImpl.methodA()");}} public class MyInterfaceBImpl implements MyInterfaceB { public void methodB() {System.out.println("MyInterfaceBImpl.methodB()");}}
import net.sf.cglib.proxy.Mixin; public class Main { public static void main(String[] args) { Class[] interfaces =new Class[] { MyInterfaceA.class, MyInterfaceB.class };Object[] delegates =new Object[] { new MyInterfaceAImpl(), new MyInterfaceBImpl() };Object obj = Mixin.create(interfaces, delegates);  MyInterfaceA myA = (MyInterfaceA)obj;myA.methodA();  MyInterfaceB myB = (MyInterfaceB)obj;myB.methodB();}}
执行结果:
MyInterfaceAImpl.methodA()MyInterfaceBImpl.methodB()

Posted in Java | 2 Comments »

Don’t repeat the DAO!

Posted by Nicholas Ding on 1st 六月 2006

译者:Nicholas @ Nirvana Studio
原文地址:http://www-128.ibm.com/developerworks/java/library/j-genericdao.html

使用Hibernate和Spring AOP购建一个范型类型安全的DAO
2006年五月12日

在采用了Java 5的范型之后,要实现一个基于范型类型安全的数据访问对象(DAO)就变得切实可行了。在这篇文章里,系统架构师Per Mellqvist展示了一个基于Hibernate的范型DAO实现。然后将介绍如何使用Spring AOP的introduction为一个类增加一个类型安全的接口以便于执行查询。

对于大多数开发者来说,在系统中为每一个DAO编写几乎一样的代码已经成为了一种习惯。同时大家也都认可这种重复就是“代码的味道”,我们中的大多数已经习惯如此。当然也有另外的办法。你可以使用很多ORM工具来避免代码的重复编写。举个例子,用Hibernate,你可以简单的使用session操作直接控制你的持久化领域对象。这种方式的负面影响就是丢失了类型安全。

为什么你的数据访问代码需要一个类型安全的接口?我认为它减少了编程错误,提高了生产率,尤其是在使用现代高级IDE的时候。首先,一个类型安全的接口清晰的制定了哪些领域对象具有持久化功能。其次,它消除了类型转换带来的潜在问题。最后,它平衡了IDE的自动完成功能。使用自动完成功能是最快的方式来记住对于适当的领域类哪些查询是可用的。

在这篇文章里,我将展示给大家如何避免一次次地重复编写DAO代码,但同时还收益于类型安全的接口。事实上,所有内需要编写的是为新的DAO编写一个Hibernate映射文件,一个POJO的Java接口,并且10行Spring配置文件。

DAO实现

DAO模式对于任何Java开发人员来说都是耳熟能详的。这个模式的实现相当多,所以让我们仔细推敲一下我这篇文章里面对于DAO实现的一些假设:

  • 所有系统中的数据库访问都是通过DAO来完成封装
  • 每一个DAO实例对一个主要的领域对象或者实体负责。如果一个领域对象具有独立的生命周期,那么它需要具有自己的DAO。
  • DAO具有CRUD操作
  • DAO可以允许基于criteria方式的查询而不仅仅是通过主键查询。我将这些成为finder方法或者finders。这个finder的返回值通常是DAO所负责的领域对象的集合。

范型DAO接口

范型DAO的基础就是CRUD操作。下面的接口定义了范型DAO的方法:

public interface GenericDao <T, PK extends Serializable> {     /** Persist the newInstance object into database */    PK create(T newInstance)/** Retrieve an object that was previously persisted to the database using     *   the indicated id as primary key     */    T read(PK id)/** Save changes made to a persistent object.  */    void update(T transientObject)/** Remove an object from persistent storage in the database */    void delete(T persistentObject);}

实现这个接口

使用Hibernate实现上面的接口是非常简单的。也就是调用一下Hibernate的方法和增加一些类型转换。Spring负责session和transaction管理。

public class GenericDaoHibernateImpl <T, PK extends Serializable>    implements GenericDao<T, PK>, FinderExecutor {    private Class<T> type;     public GenericDaoHibernateImpl(Class<T> type) {        this.type = type;    }     public PK create(T o) {        return (PK) getSession().save(o);    }     public T read(PK id) {        return (T) getSession().get(type, id);    }     public void update(T o) {        getSession().update(o);    }     public void delete(T o) {        getSession().delete(o);    }     // Not showing implementations of getSession() and setSessionFactory()}

Spring 配置

最后,Spring配置,我创建了一个GenericDaoHibernateImpl的实例。GenericDaoHibernateImpl的构造器必须被告知领域对象的类型,这样DAO实例才能为之负责。这个同样需要Hibernate运行时知道这个对象的类型。下面的代码中,我将领域类Person传递给构造器并且将Hibernate的session工厂作为一个参数用来实例化DAO:

<bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">        <constructor-arg>            <value>genericdaotest.domain.Person</value>        </constructor-arg>        <property name="sessionFactory">            <ref bean="sessionFactory"/>        </property></bean>

可用的范型DAO

我还没有全部完成,但我现在已经有了一个可供作的代码。下面的代码展示了范型DAO如何使用:

public void someMethodCreatingAPerson() {    ...    GenericDao dao = (GenericDao)     beanFactory.getBean("personDao"); // This should normally be injected     Person p = new Person("Per", 90);    dao.create(p);}

这时候,我有一个范型DAO有能力进行类型安全的CRUD操作。同时也有理由编写GenericDaoHibernateImpl的子类来为每个领域对象增加查询功能。但是这篇文章的主旨在于展示如何完成这项功能而不是为每个查询编写明确的代码,然而,我将会使用多个工具来介绍DAO的查询,这就是Spring AOP和Hibernate命名查询。

Spring AOP介绍

你可以使用Spring AOP提供的introduction功能将一个现存的对象包装到一个代理里面来增加新的功能,定义它需要实现的新接口,并且将之前所有不支持的方法委派到一个处理机。在我的DAO实现里面,我用introduction将一定数量的finder方法增加到现存的范型DAO类里面。因为finder方法针对特定的领域对象,所以它们被应用到表明接口的范型DAO中。

<bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/> <bean id="abstractDaoTarget"        class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">        <property name="sessionFactory">            <ref bean="sessionFactory"/>        </property></bean> <bean id="abstractDao"        class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">        <property name="interceptorNames">            <list>                <value>finderIntroductionAdvisor</value>            </list>        </property></bean>

在上面的配置中,我定义了三个Spring bean,第一个bean,FinderIntroductionAdvisor,处理那些introduce到DAO中但是不属于GenericDaoHibernateImpl类的方法。一会我再介绍Advisor bean的详细情况。

第二个bean定义为“abstract”。在Spring中,这个bean可以被其他bean重用但是它自己不会被实例化。不同于抽象属性,bean的定义简单的指出了我需要一个GenericDaoHibernateImpl的实例同时需要一个SessionFactory的引用。注意GenericDaoHibernateImpl类只定义了一个构造器接受领域类作为参数。因为这个bean是抽象的,我可以无限次的重用并且设定合适的领域类。

最后,第三个,也是最有意思的是bean将GenericDaoHibernateImpl的实例包装进了一个代理,给予了它执行finder方法的能力。这个bean定义同样是抽象的并且没有指定任何接口。这个接口不同于任何具体的实例。

扩展通用DAO

每个DAO的接口,都是基于GenericDAO接口的。我需要将为特定的领域类适配接口并且将其扩展包含我的finder方法。

public interface PersonDao extends GenericDao<Person, Long> {    List<Person> findByName(String name);}

上面的代码清晰的展示了通过用户名查找Person对象列表。所需的Java实现类不需要包含任何的更新操作,因为这些已经包含在了通用DAO里。

配置PersonDao

因为Spring配置依赖之前的那些抽象bean,所以它变得很紧凑。我需要指定DAO负责的领域类,并且我需要告诉Spring我这个DAO需要实现的接口。

<bean id="personDao" parent="abstractDao">    <property name="proxyInterfaces">        <value>genericdaotest.dao.PersonDao</value>    </property>    <property name="target">        <bean parent="abstractDaoTarget">            <constructor-arg>                <value>genericdaotest.domain.Person</value>            </constructor-arg>        </bean>    </property></bean>

你可以这样使用:

public void someMethodCreatingAPerson() {    ...    PersonDao dao = (PersonDao)     beanFactory.getBean("personDao"); // This should normally be injected     Person p = new Person("Per", 90);    dao.create(p);     List<Person> result = dao.findByName("Per"); // Runtime exception}

上面的代码是使用类型安全接口PersonDao的一种正确途径,但是DAO的实现并没有完成。当调用findByName()的时候导致了一个运行时异常。这个问题是我还没有findByName()。剩下的工作就是指定查询语句。要完成这个,我使用Hibernate命名查询。

Hibernate命名查询

使用Hibernate,你可以定义任何HQL查询在映射文件里,并且给它一个名字。你可以在之后的代码里面方便的通过名字引用这个查询。这么做的一个优点就是能够在部署的时候调节查询而不需要改变代码。正如你一会将看到的,另一个好处就是实现一个“完整”的DAO而不需要编写任何Java实现代码。

<hibernate-mapping package="genericdaotest.domain">     <class name="Person">         <id name="id">             <generator class="native"/>         </id>         <property name="name" />         <property name="weight" />     </class>      <query name="Person.findByName">         <![CDATA[select p from Person p where p.name = ? ]]>     </query></hibernate-mapping>

上面的代码定义了领域类Person的Hibernate映射文件,有两个属性:name和weight。Person是一个具有上面属性的简单的POJO。这个文件同时包含了一个查询,通过提供的name属性从数据库查找Person实例。Hibernate为命名查询提供了不真实的命名空间功能。为了便于讨论,我将所有的查询名字的前缀变成领域类的的名称。在现实场景中,使用完整的类名,包含包名,是一个更好的主意。

总览

你已经看到了为任何领域对象创建并配置DAO的所需步骤了。这三个简单的步骤就是:

  1. 定义一个接口继承GenericDao并且包含任何所需的finder方法
  2. 在映射文件中为每个领域类的finder方法增加一个命名查询。
  3. 为DAO增加10行Spring配置

可重用的DAO类

Spring advisor和interceptor的功能比较琐碎,事实上他们的工作都引用回了GenericDaoHibernateImpl类。所有带有“find”开头的方法都被传递给DAO的单一方法executeFinder()。

public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {    public FinderIntroductionAdvisor() {        super(new FinderIntroductionInterceptor());    }} public class FinderIntroductionInterceptor implements IntroductionInterceptor {     public Object invoke(MethodInvocation methodInvocation) throws Throwable {         FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis()String methodName = methodInvocation.getMethod().getName();        if (methodName.startsWith("find")) {            Object[] arguments = methodInvocation.getArguments();            return genericDao.executeFinder(methodInvocation.getMethod(), arguments);        } else {            return methodInvocation.proceed();        }    }     public boolean implementsInterface(Class intf) {        return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);    }}

executeFinder() 方法

上面的代码唯一缺的就是executeFinder的实现。这个代码观察被调用的类的名字和方法,并且将他们与Hibernate的查询名相匹配。你可以使用一个FinderNamingStrategy来激活其他方式的命名查询。默认的实现查找一个名为“ClassName.methodName”的查询,ClassName是除包名之外的类名。

public List<T> executeFinder(Method method, final Object[] queryArgs) {     final String queryName = queryNameFromMethod(method);     final Query namedQuery = getSession().getNamedQuery(queryName);     String[] namedParameters = namedQuery.getNamedParameters();     for(int i = 0; i < queryArgs.length; i++) {             Object arg = queryArgs[i];             Type argType =  namedQuery.setParameter(i, arg);      }      return (List<T>) namedQuery.list();} public String queryNameFromMethod(Method finderMethod) {     return type.getSimpleName() + "." + finderMethod.getName();}

总结

在Java 5之前,Java语言并不支持代码同时具有类型安全和范性的特性;你不得不二者选一。在这篇文章里,你可以看到使用Java 5范型支持并且结合Spring和Hibernate(和AOP)一起来提高生产力。一个范型类型安全的DAO类非常容易编写,所有你需要做的就是一个接口,一些命名查询,并且10行Spring配置,并且可以极大的减少错误,同时节省时间。

代码下载: j-genericdao.zip

Posted in Java | No Comments »

jBPM 流程部署文件研究

Posted by Nicholas Ding on 29th 五月 2006

jBPM 为流程定义及其相关文件专门使用了一种打包机制,就是.par文件,似乎JBoss很喜欢这样的形式,之前还有为Hibernate提供的.har包。这个.par被称为Process Archive,故名思义,里面包含了流程需要的所有信息。

其实.par文件就是一个简单的zip格式的压缩包。里面的核心文件是processdefinition.xml这个流程定义,当然用Eclipse jBPM插件制作的流程还含有一个流程图片,可以使用jBPM提供的webapp动态标示当前所执行的流程。除次之外,classes这个目录以内的文件都会被动态加载到内存,因为流程里面定义的Action和Task等的实现类都需要去Classpath找,jBPM会在部署.par包的时候用自己的Class Loader加载进去。(PS:也可以直接放在上层Classpath里面,只要能够加载到就可以)

流程部署详解

如果认为一定要使用Eclipse jBPM插件来部署流程的话,那就错了,jBPM插件从一定程度上简化了jBPM开发,尤其是Deployment功能为大家省了不少事情,但是如果要手工部署,怎么做呢?接下来就要研究一下到底部署这个.par文件的时候做了哪些事情。

首先要让Eclipse jBPM的部署功能有效,那么要确保服务器使用jBPM提供的webapp,并且让起Context位于/jbpm这个位置。例如http://localhost:8080/jbpm,那么在jBPM插件里面写上localhost,端口8080,测试一下连接就可以了。那么我们分析一下webapp,发现原来是org.jbpm.webapp.servlet.UploadServlet这个类在起作用。

看一下UploadServlet的代码,看handleRequest里面的内容,用Commons Fileupload做的文件上传,如果文件小直接加载到内存,文件大会用磁盘的临时空间(Fileupload的文档上有解释)。文件上传完毕,那么就执行doDeployment操作。这个doDeployment才是部署的关键入口。

ZipInputStream zipInputStream = new ZipInputStream(fileItem.getInputStream());
这行代码解释了如何加载.par文件,首先作为Zip格式读取,然后得到jbpmContext来进行流程部署。这个JbpmContext也是采用了ThreadLocal,感觉原理上和Hibernate用的差不多(到这篇文章为止,我在Weblogic还没法成功使用这个得到jbpmContext,还只能用jbpmConfiguration来获取)。
JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext();ProcessDefinition processDefinition = ProcessDefinition.parseParZipInputStream(zipInputStream);jbpmContext.deployProcessDefinition(processDefinition);

这样以来,一个流程就这么简单的部署上去了。当然了,如果不想Upload,还可以使用本地文件系统直接部署,需要提供一个URL就可以了,请参考DeployServlet,还可以使用ant进行部署。这么看来,部署一个流程就变得非常方便了。

部署文件怎么就消失了,到底去了哪里?

在部署完.par流程包之后,仿佛就不再需要这个.par文件了,但是是什么原因能够让服务器不必知道.par的位置而又能每次正常运行这个流程呢?这个问题我诼磨了很久,一个zip文件不可能凭空消失啊,至少他应该存在于引擎可以找到的地方。而这个地方,正是数据库!

看一下jbpm生成的数据库,包含两张比较特别的表jbpm_bytearray和jbpm_byteblock,正是这两张表纯储了.par文件的内容。可以说,他是将zip里面的内容拆开存到了数据库。

mysql> select * from jbpm_bytearray;+-----+------------------------------------------------------+-----------------+| ID_ | NAME_                                                | FILEDEFINITION_ |+-----+------------------------------------------------------+-----------------+|   1 | processimage.jpg                                     |               1 ||   2 | gpd.xml                                              |               1 ||   3 | processimage.jpg                                     |               4 ||   4 | gpd.xml                                              |               4 ||   5 | classes/com/sample/action/MessageActionHandler.class |               4 |+-----+------------------------------------------------------+-----------------+

jbpm_bytearray这张表把.par文件目录存了进去,jbpm_byteblock则是将二进制内容存了进去。可以说如果你的.par文件里面含有Java Bytecode,那么引擎会从数据库读出byte[]数组然后作为类加载,如果你的类存在于引擎可见的Classpath,那么他会从那里面加载。

总结

jBPM在流程的部署上着实下了不少功夫,从流程的部署上可以看到jBPM引擎的一些工作方式,这也有点类似IoC的概念,本身jBPM提供了基于有限状态机的编程模型,这一模型大大的简化了编程难度,同时将流程的定义和实现分离出来,使得可以在流程实现的功能子集定义新的流程。在流程部署上提供了版本机制,即连续部署两个相同的流程会出现版本增量,总是新建高版本的流程,但是低版本的流程在执行过程中不会因为高版本的部署而自动取消,直到运行完毕。

Posted in Java | No Comments »

AJAX 入门

Posted by Nicholas Ding on 17th 五月 2006

AJAX in Action

像其他人一样,当我看到一下RIA应用,例如Google Maps和Google Suggest的时候我都非常惊讶。我希望知道是如何实现的。现在,谜底揭开了,那就是AJAX。这是在我花了一段时间研究AJAX之后才知晓的。这里有一个很好的例子让我们知道AJAX是如何很好的应用在 JavaRSS.com 里面的。

什么是AJAX:
AJAX 是一个架构(architecture)并不是一种技术。AJAX代表异步的JavaScript和XML。
妙语(Punch Line):
延迟加载
问题:
当JavaRSS.com首页加载时,他同时加载了所有条目的介绍(如果你在设置中激活了)。这些介绍只有当你鼠标移动到该条目的上面的时候才显示。

现在的问题是用户不可能是鼠标移过所有的条目,所以预先加载所有的介绍不是个好主意。

解决方案: 使用AJAX,当鼠标移过的时候从服务器动态加载条目的介绍。

这么做可以使初始页的加载大小减小一半甚至更多,这样一来页面加载就更快,就内能得到一个更好的用户体验。

时序图:

AJAX Sequence Diagram

我们首先会在onmouseover事件中调用JavaScript函数getDescription。下面是html代码:

<a href="/" onmouseover="getDescription(3,1)">Java & J2EE News</a>

下面是 getDescription 函数的代码:

var url = 'http://localhost:8080/getDescription.jsp?channelId=' + channelId + '&itemId=' + itemId;    if (window.XMLHttpRequest) {        req = new XMLHttpRequest();    } else if (window.ActiveXObject) {        req = new ActiveXObject("Microsoft.XMLHTTP");    }    req.onreadystatechange = processRequest;    req.open("GET", url, true);    req.send(null);

XMLHttpRequest 对象将用来进行http连接并取回xml文档。我们需要检测一下是否是IE并且创建 XMLHttpRequest 对象。

if (window.XMLHttpRequest) {        req = new XMLHttpRequest();    } else if (window.ActiveXObject) {        req = new ActiveXObject("Microsoft.XMLHTTP");    }

设置回调函数,并且发送"GET"请求至服务器接收xml文档:

req.onreadystatechange = processRequest;    req.open("GET", url, true);    req.send(null);

JSP将根据适当的条目编号创建具有相应介绍的xml文档。

<%String channelId = request.getParameter("channelId");String itemId = request.getParameter("itemId");//String description = new Channel(channelId).getItemDescription(itemId);String description = "This is the description for the channelId: " + channelId + "and itemId: " + itemId; if (description != null) {   response.setContentType("text/xml");   response.setHeader("Cache-Control", "no-cache");   response.getWriter().write("<description>" + description.toString() + "</description>");} else {   //nothing to show   response.setStatus(HttpServletResponse.SC_NO_CONTENT);}%>

检测HTTP请求返回状态码,状态为200,即OK。

function processRequest() {    if (req.readyState == 4) {        if (req.status == 200) {          parseMessages();        } else {  alert ( "Not able to retrieve description" );        }    }}

readyState = 4 的情况下文档被加载。

readyState Status Codes:
  • 0 = uninitialized
  • 1 = loading
  • 2 = loaded
  • 3 = interactive
  • 4 = complete

最后,我们解析XML文档并显示介绍。

问题: 唯一的问题就是我遭遇到的 "&" 字符。 "&" 在XML文档里面不是一个有效字符。所以我需要将他转换成 "&amp;"。

function parseMessages() {response  = req.responseXML.documentElement;itemDescription = response.getElementsByTagName('description')[0].firstChild.data;alert(itemDescription);}

下面是所有的代码:

HTML Code:
<a href="/" onmouseover="getDescription(3,1)">Java & J2EE News<a>
JavaScript Code:
function getDescription(channelId,itemId) {    var url = 'http://localhost:8080/getDescription.jsp?channelId=' + channelId + '&itemId=' + itemId;    if (window.XMLHttpRequest) {        req = new XMLHttpRequest();    } else if (window.ActiveXObject) {        req = new ActiveXObject("Microsoft.XMLHTTP");    }    req.onreadystatechange = processRequest;    req.open("GET", url, true);    req.send(null);} function processRequest() {    if (req.readyState == 4) {        if (req.status == 200) {          parseMessages();        } else {          alert ( "Not able to retrieve description" );}    }} function parseMessages() {response  = req.responseXML.documentElement;itemDescription = response.getElementsByTagName('description')[0].firstChild.data;alert ( itemDescription );}
JSP Code:
<%String channelId = request.getParameter("channelId");String itemId = request.getParameter("itemId");description = "This is the description for the channelId: " + channelId + "and itemId: " + itemId; if (description != null) {   response.setContentType("text/xml");   response.setHeader("Cache-Control", "no-cache");   response.getWriter().write("<description>" + description.toString() + "</description>");} else {   //nothing to show   response.setStatus(HttpServletResponse.SC_NO_CONTENT);}%>

资源:

  • AJAX Java BluePrints Solutions Catalog
  • AJAX in Wikipedia
  • W3C HTTP Status Codes

使用AJAX的Google站点:

  • Gmail
  • Google Suggest
  • Google Maps

关于作者:

  • Jay 具有10年以上的IT工作经验,并且自从Java & J2EE诞生那天起就开始接触他们了。
  • 译者Nicholas@NirvanaStudio

Posted in Ajax | No Comments »

关注 JBossWeb

Posted by Nicholas Ding on 29th 四月 2006

介绍

JBossWeb 是 JBoss 2006 年新产品线中的一个非常重要的部分。他基于 Apache Tomcat 开发而成,但是 JBoss 对 Tomcat 进行了额外的开发。Tomcat 是一个纯 Java 实现的 Web 服务器,想必大家在实际部署的时候不单单是只部署 Tomcat,还需要同时部署 Apache Web Server,通过mod_jk 来整合 Tomcat,这个配置相当繁琐,而且性能未必优越,jk、jk2 的站点似乎很长时间没有再更新了。JBoss 对 Tomcat 的调整力度非常大,JBossWeb 已经不是简单的包含了 Tomcat,而是对 Tomcat 做了本地化,使用了Apache APR,即Apache Portable Runtime 对 Tomcat 底层 API 做了一个本地化实现,下面一副图引子 JBossWeb 的站点,对 JBossWeb 和 Tomcat 做了一个性能比较。
JBossWeb

架构

Tomcat 以内嵌的方式集成到 JBoss 中,基于 JBoss Microkernel 架构,这样以来,在 Tomcat 上就可以使用到更全面的功能,譬如JBoss提供的数据库连接池(感觉比Tomcat配置方便多了),真正的 JNDI 支持,当然还有 JTA 等以来 JNDI 基础设施的服务。
JBossWebArch

JBossWeb 不仅仅支持 JSP 等 Java 技术,同时他的架构还支持其他 Web 技术的集成,譬如 PHP、.NET 两大阵营的正和。到目前为止,JBossWeb 提供了一个与 PHP 集成的例子,通过 JNI 库调用 PHP,可以通过 servlet-mapping 调用 PHP 处理引擎处理 PHP,在此之前,要做到这一点需要配置 Apache 服务器,但现在,JBoss 做到了这一点,而且非常方便。
JBossWebBlock

下面的图介绍了Tomcat Native的基础架构,依仗于 APR 的支持,可以像 Eclipse 一样运行于各种平台,同时又可以提升性能。
TNative URL 重写

URL 重写是 Apache 一个非常重要的部分,mod_rewrite 模块起到了非常大的作用。现在 JBossWeb 同样支持了这个功能,基本配置和 mod_rewrite 几乎一样,而且同样支持全局配置和针对每个Context进行配置。

Posted in Java | 2 Comments »

理解 ANTLR 语法文件

Posted by Nicholas Ding on 26th 四月 2006

理解 ANTLR 语法文件

译者:Nicholas @ NirvanaStudio

原文出处:http://www.placidsystems.com/articles/article-grammarlayout/grammarLayout.htm

你是否被Antlr语法文件的不同部分搞的晕头转向呢,你是否很想知道这些部分的含义呢?我们在这里将从另一个方面来了解它们,这里我们使用Antlr Studio来演示。

不管你信不信,Antlr语法文件在某种程度上和Java源代码很类似。什么,你看不出任何与Java代码相似的地方?好,就让我给你展示......

语法(Grammar)

1

有放在header部分的内容将会出现在Antlr生成的Java代码的头部。站在Java文件的层次上思考,你通常在这个部分里面放置包定义。你还可以把一部分import的内容放在这里。

2这个部分的代码对于文件中的每个语法来说是唯一的。这个部分会被放在类声明之前。以上的例子将只对CalcParser引入ArrayList和MyClass这两个类。

3然后我们开始定义语法,这看起来像是在声明一个类。

4在这个options部分,你可以制定语法生成的一些参数。在Antlr Studio中你可以使用Ctrl+Space看看哪些选项可用。

5Token部分用来制定“假想的”记号,那些通常没有声明在lexer中。这些是在TreeParsers中使用的“假想的”记号。

6另一个行为部分。这个部分的内容被放在类内部。你可以为你的解析器定义一些自定义方法。

规则(RULES)

7

Antlr语法文件中的规则定义对应生成的Java代码中的一个方法定义。

1234正如你所看到的,这里我们可以在规则中做任何事,这些可以通过一个函数完成。我们可以为规则指定参数,上面的例子显示了这一点,甚至可以指定返回值和异常。

5这个options部分允许你指定一些可选参数。

7我们可以指定自定义异常处理机。

Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.

Posted in 编译原理 | No Comments »

ANTLR 介绍

Posted by Nicholas Ding on 17th 四月 2006

ANTLR 介绍

作者: Terence Parr

译者:Nicholas @ NirvanaStudio

原文出处:http://www.cs.usfca.edu/~parrt/course/652/lectures/antlr.html

介绍

自1980年以来我手工编写了很多识别程序(recognizer)和翻译程序(translator)但最终我感到很恶心并且尝试将这个过程自动化:来源于我的座右铭:

"Why program by hand in five days what you can spend five years of your life automating."

手工编写过很多程序之后你就可以发现一些共性,并且这些共性可以合理地格式化并且自动生成。我当时对yacc不是很熟悉但是想要一些东西去代替我原本需要手工 编码的工作。ANTLR就是这个最终的结果(实际上原来它叫做PCCTS)。我现在已经为之工作了十年了。

ANTLR, ANother Tool for Language Recognition, 是一个可以接受含有语法描述的语言描述符并且生成程序能够识别这些语言所产生的句子。作为一个翻译程序的 一部分,你可以给你的语法附上简单的操作符和行为并且告诉ANTLR如何构造AST并且如何输出它们。ANTLR知道如何使用Java,C++,C#或者Python来生成它们。

ANTLR知道如何构造识别程序并且将语法结构应用到三种不同的输入上:(i) 字符流, (ii) 标记(token)流,(iii) 二维树结构。本质上这些对应了词法分析器(Lexer),解析器(Parser)和Tree Walker。用于制定这些语法(Grammar)的句法(Syntax),被称 为meta-language,在所有情况下是独立的。

一旦你适应了ANTLR或者相应的工具,你将会以另一种眼光来看待编程。很多任务期待一种不同于传统编程语言流派的语言解决方案。举个例子,这些课程的笔记就是用TML编写 的,Terence's Markup Language。我讨厌输入HTML所以我用ANTLR编写了一个简单的翻译程序来转换文本成为HTML或者PDF或者其他我讨厌直接编写的东西。

最后,情让我指出ANTLR仅仅是一个工具!它帮你通过自动生成单调乏味的组件来构造程序,但并不试图让你创造一个完整的编译器,举个例子,单行的描述。

在2003年以前,ANTLR的下载量一度达到5000每月。当时ANTLR直接暴露在公共域而且没有一个明确的版权但是附带了完整的源代码。

这些笔记假设你已经熟悉了基本的语言识别和翻译概念。那么现在你就需要熟悉ANTLR的元语言以及如何生成它。之后,我们将把焦点集中在构造复杂的翻译器上。

一个对 ANTLR 句法的简单介绍

了解ANTLR最好的办法就是通过例子。一个简单的计算器经常被用来作为起步教程,并且有一个很好的理由支持这么做:它容易理解且实现简单。还有一些ANTLR例子和教程,但 是在这里我将会用自己的语言来描述一个计算器。首先我们将要做一些东西可以直接计算这些简单的表达式。然后我们将会生成树并且计算这课树得到相同的结果。

当你知道最后需要将输入的字符流断开成为一个个的记号(token),思考表达式的语法结构是一个良好的开端。

直接执行句法

识别程序

让我们编写一个程序来接受一个带有加、减、乘,例如3+4*5-1的表达式,或者带有括号的,用来强行限制计算顺序的如(3+4)*5的表达式。

所有ANTLR语法都是LexerParser或者TreeParser的子类,因此你需要从语法的层次上来思考这些东西,你将会构造一个Parser的子类 。在类声明之后,内需要用EBNF符号来制定规则:

class ExprParser extends Parser;expr:   mexpr ((PLUS|MINUS) mexpr)*    ;      mexpr          :   atom (STAR atom)*    ;    atom:   INT     |   LPAREN expr RPAREN     ;

这个词法分析器遵循了一个相似的模式并且只需要定义一些操作符和空格。将这些词法放入一个文件中,expr.g,是最简单的方法:

 class ExprLexer extends Lexer;options {    k=2; // needed for newline junk    charVocabulary='/u0000'..'/u007F'; // allow ascii}LPAREN: '(' ;RPAREN: ')' ;PLUS  : '+' ;MINUS : '-' ;STAR  : '*' ;INT   : ('0'..'9')+ ;WS    : ( ' '        | '/r' '/n'        | '/n'        | '/t'        )        {$setType(Token.SKIP);}      ;    

要生成一个采用Java解释的语法,用如下方式:

$ java antlr.Tool expr.gANTLR Parser Generator   Version 2.7.2   1989-2003 jGuru.com$ 

ANTLR 生成了什么?

ANTLR生成的识别程序模仿了你需要手工编写的递归的解析器;yacc和它的朋友,另一方面,生成了一个满是整数的表来模拟有限状态机的行为。

ANTLR 将会生成以下文件:

ExprLexer.javaExprParser.javaExprParserTokenTypes.javaExprParserTokenTypes.txt

如果你看下生成的代码,举个例子,ExprParser.java,你将会看到对语法解析文件expr.g中的每个规则生成了一个函数。举个例子,mexpratom的代码应该是这样:

public void mexpr() {  atom();  while ( LA(1)==STAR ) {    match(STAR);    atom();  }}public void atom() {  switch ( LA(1) ) { // switch on lookahead token type    case INT :      match(INT);       break;    case LPAREN :      match(LPAREN);      expr();      match(RPAREN);      break;     default :      // error  }}

注意这些规则引用被翻译成了函数,记号引用被翻译成了match(TOKEN)调用。从一个语法文件构造解析器的唯一难点就是计算lookahead信息。

记号类型这个类定义了记号类型常量,以便于词法分析和语法分析之用:

// $ANTLR 2.7.2: "expr.g" -> "ExprParser.java"$public interface ExprParserTokenTypes {        int EOF = 1;        int NULL_TREE_LOOKAHEAD = 3;        int PLUS = 4;        int MINUS = 5;        int STAR = 6;        int INT = 7;        int LPAREN = 8;        int RPAREN = 9;        int WS = 10;}

测试词法分析和语法分析

要使用生成的解析器,在ExprParser.java中,使用如下main()函数:

import antlr.*;public class Main {    public static void main(String[] args) throws Exception {        ExprLexer lexer = new ExprLexer(System.in);        ExprParser parser = new ExprParser(lexer);        parser.expr();    }}
$ java Main3+(4*5)$ 

或者针对无效输入:

$ java Main3++line 1:3: unexpected token: +$ 

或者

$ java Main3+(4line 1:6: expecting RPAREN, found 'null'$ 

表达式计算

要实际计算表达式,只需要给解析器增加行为:

class ExprParser extends Parser;expr returns [int value=0]{int x;}    :   value=mexpr        ( PLUS x=mexpr  {value += x;}        | MINUS x=mexpr {value -= x;}         )*    ;mexpr returns [int value=0]{int x;}    :   value=atom ( STAR x=atom {value *= x;} )*    ;atom returns [int value=0]    :   i:INT {value=Integer.parseInt(i.getText());}    |   LPAREN value=expr RPAREN    ;

词法分析也是一样,除了增加一个print语句在主函数中:

import antlr.*;public class Main {        public static void main(String[] args) throws Exception {                ExprLexer lexer = new ExprLexer(System.in);                ExprParser parser = new ExprParser(lexer);                int x = parser.expr();                System.out.println(x);        }}

现在,当你运行程序,你会得到一下结果:

$ java Main3+4*523$ java Main(3+4)*535$ 

ANTLR 如何翻译动作

动作通常会被放入生成的解析器的代码中:

像下面的return规则

mexpr returns [int value=0]  : ...  ;

被翻译为

public int mexpr() {  int value=0;  ...  return value;}

如果你增加一个参数,这个参数同样复制到了方法的定义:

mexpr[int x] returns [int value=0]  : ... {value = x;}  ;

生成

public int mexpr(int x) {  int value=0;  ...  value = x;  return value;}

所以,完整的mexpratom翻译规则看起来像下面的代码:

public int mexpr() {  int value=0;  int x; // local variable def from rule mexpr  value = atom();  while ( LA(1)==STAR ) {    match(STAR);    x = atom();    value *= x;  }  return value;}public int atom() {  int value=0;  switch ( LA(1) ) { // switch on lookahead token type    case INT :      Token i = LT(1); // make label i point to next lookahead token object      match(INT);       value=Integer.parseInt(i.getText()); // compute int value of token      break;    case LPAREN :      match(LPAREN);      value = expr(); // return whatever expr() computes      match(RPAREN);      break;     default :      // error  }  return value;}

通过AST中间形式计算结果

现在你看到了一个基本的直接基于句法的翻译/计算描述,里面的语法/句法制定了何时执行动作一个强有力的策略就是构造一个间接的表示形式持有所有或者大多数编码过的输入符号,在数据结构中,包含这些记号的关系。举个例子,输入“3+4”可以通过一个抽象语法树(AST)来表示:

  + / /3   4

针对这种树,内需要一个TreeWalker(由ANTLR从一个树语法中生成)来计算之前的相同值,但是采用了一个不同的方式。

AST的用法变得很清晰,就是当你需要从多次遍历这棵树来指出什么需要计算或者重写,或将树转变为另一种语言的时候就需要用AST。

构造AST

用ANTLR来生成一个有用的AST非常容易。在我们的例子中,打开buildAST选项并且增加一些后缀操作符告诉ANTLR何种记号需要构成子树的根节点。

class ExprParser extends Parser;options {        buildAST=true;}expr:   mexpr ((PLUS^|MINUS^) mexpr)*    ;mexpr    :   atom (STAR^ atom)*    ;atom:   INT    |   LPAREN! expr RPAREN!    ;

然后,词法不需要改变,用来计算树结果的主程序如下:

import antlr.*;import antlr.collections.*;public class Main {       public static void main(String[] args) throws Exception {            ExprLexer lexer = new ExprLexer(System.in);        ExprParser parser = new ExprParser(lexer);        parser.expr();        AST t = parser.getAST();        System.out.println(t.toStringTree());    }    }    
$ java Main3+4 ( + 3 4 )$ java Main3+4*5  ( + 3 ( * 4 5 ) )$ java Main(3+4)*5 ( * ( + 3 4 ) 5 )$ 

AST 解析与计算

通过以上的解析器构造的树非常简单。在TreeParser中一条规则就够了。

class ExprTreeParser extends TreeParser;options {    importVocab=ExprParser;}expr returns [int r=0]{ int a,b; }    :   #(PLUS  a=expr b=expr)  {r = a+b;}    |   #(MINUS a=expr b=expr)  {r = a-b;}       |   #(STAR  a=expr b=expr)  {r = a*b;}    |   i:INT                   {r = (int)Integer.parseInt(i.getText());}    ;

主程序被修改成了使用新的TreeParser来实现计算功能:

import antlr.*;import antlr.collections.*;public class Main {    public static void main(String[] args) throws Exception {            ExprLexer lexer = new ExprLexer(System.in);        ExprParser parser = new ExprParser(lexer);        parser.expr();        AST t = parser.getAST();        System.out.println(t.toStringTree());        ExprTreeParser treeParser = new ExprTreeParser();        int x = treeParser.expr(t);        System.out.println(x);    }    }

现在你得到了树结构以及计算结果。

$ java Main3+4 ( + 3 4 )7$ java Main3+(4*5)+10 ( + ( + 3 ( * 4 5 ) ) 10 )33$ 

Posted in 编译原理 | No Comments »

初识 ANTLR

Posted by Nicholas Ding on 17th 四月 2006

由于并非科班出生,所以对于喜欢计算机程序设计的我来说,尽管通过自己的努力学习了相当多的实用技术,但是让我总觉得遗憾的除了高等数学之外就是汇编和编译原理了。从C到Java的过渡,让我从指针和数据结构中脱离出来,长时间的使用Java让我很少去接触系统底层,开发中几乎不会遇到任何汇编代码,除了能够理解基本的汇编指令之外,对于汇编,我已经没有多少感情了。除了汇编之外、数学和编译原理一直是我最想去学习的,但是自学编译原理让人很难上手,虽然可以大体上理解书上的理论,可是依然感觉要自己亲手写个解释器则是难上加难,直到遇到了ANTLR,我才渐渐开始理解《编译原理》这本书的真正内涵。

在《编译原理》这本书上的介绍非常理论性,对于一个非科班出生人来说很难简单的读懂,他在一个很高的高度上涵盖了编译器(Complier)和解释器(Interpreters)的内容。编译器与解释器的界限是非常模糊的,ANTLR的作者Terence Parr在它的文章An Overview of Language Implementation上给出了一个比较浅显的解释。

Anything that executes natively via machine instructions you can safely say is compiled; everything else is interpreted. A more useful way to describe the difference is that compilers translate source to an intermediate representation (IR) and then translate that to machine code usually by making several passes over the IR. An interpreter on the other hand stops the compilation process before machine code generation at the IR--it "executes" or interprets the IR, emulating an abstract high-level machine architecture.

总体来说,要实现一个Compiler的难度要比Interpreter大的多,虽然经过编译的代码在执行效率上要比动态解释的代码高,但是解释型的语言具有更快的加载速度和扩展性,况且基于虚拟机的动态语言的盛行也证实了解释型语言的强大生命力。
下面主要讨论一个解释器的原理。一般来说,解释器接受一段文本,对文本进行词法分析,经过词法分析之后在进行语法分析,在检测无误后根据语法规则开始解释。
下面看一个最基本的词法分析的例子。这个例子是一个最简单的词法分析器,来匹配文本中的字符串和数字。这个文件采用EBNF描述。例子取自于Terence Parr所写的关于ANTLR的例子。

class SimpleLexer extends Lexeroptions { k=1; filter=true; }ALPHA    : ('a'..'z'|'A'..'Z')+    {System.out.println(“Found alpha: “ + getText());}    ;NUMERIC    : ('0'..'9')+    {System.out.println(“Found numeric: “ + getText());}    ;EXIT    : '.' {System.exit(0);} ;

将这个文件保存为simple.g,然后使用ANTLR生成相应的词法分析器,java antlr.Tool simple.g,并且使用以下代码可以观察结果。

import java.io.*;public class Main {    public static void main(String[] args) {        SimpleLexer simpleLexer = new SimpleLexer(System.in);        while(true) {            try {                simpleLexer.nextToken();            } catch(Exception e) {}        }    }}
如果你输入:This Lexer recognises strings and numbers: hello 22 goodbye 33
那么程序将会输出:
Found alpha: ThisFound alpha: LexerFound alpha: recognisesFound alpha: stringsFound alpha: andFound alpha: numbersFound alpha: helloFound numeric: 22Found alpha: goodbyeFound numeric: 33It ignores everything else: -=+/#Found alpha: ItFound alpha: ignoresFound alpha: everythingFound alpha: else.

这个例子演示了使用ANTLR如何编写一个简单词法分析器,一般来说词法分析是配合语法分析一起使用的,在ANTLR的站点上有很多例子可以参考。 

原创粉丝点击