Nirvana Studio » Java :: 分享知识,传播技术

Archive for the 'Java' Category

为何 Java(以及很多其他编程语言)令人不爽

Posted by ShiningRay on 26th 七月 2006

作者:Ed Watkeys, edw@xmog.com 翻译:ShiningRay

原文地址:http://xmog.com/scrap/show/5
September 8, 2005
Updated December 27, 2005

Java 5.0其中一个最大的新特性是引入了一种迭代集合的新语法,用来替代以前繁琐的格式(如下):

for (Iterator i = c.iterator(); i.hasNext(); ) {
    String s = (String) i.next();
    ...
}

现在,多亏了这种新的优雅的迭代语法,以及泛型的引入,我们可以用下面的代码:

for (String s : c) {
    ...
}

很明显,可以少输入很多字符。但请考虑一个问题:为什么花了10年才引入了这个特性?不过我们先不考虑这个重要的问题,再看看另一种语言,Python。

Python在很多方面都是一个更加适合编程的语言。它的衍化要比Java快得多。例如,Python 2.2引入了生成器(generator)。一个生成器是一个可以产生多个值得函数,在每次调用时都会保存状态。下面是一个简单的例子:

def counter(n):
  while True:
    yield n
    n = n + 1

因为这个函数定义包含了关键词yield,所以Python就可以知道它是一个生成器。可以像下面这样使用counter生成器:

c12 = counter(12)
c12.next()
c12.next()

第一行创建了从12开始计数的生成器的实例。第二行告诉生成器运行到产生(yield)一个值为止。第三行告诉生成器继续运行直到产生了另一个值。该生成器所产生的前两个值分别是整数12和13。

这确实是一个很酷的特性:它让程序员能写出更简单的代码,而不会使生成器变得复杂和容易出错。为何Java不能学习Python呢?

我们也先将第二个问题放一边,思考一下如何用另一种语言来实现Python的生成器,这种语言就是Scheme——世界上有一些自以为是的怪人就用它。Scheme是Lisp的一种方言,它从诞生到现在已经存在了大约30年了。Lisp则已经存在了大约50年了。

下面是我可以完成的对Python中counter生成器模仿最好的Scheme实现:


对资深Lisp程序员多说一句:下面我要演示的子程序要比官方的累加器例子复杂得多,这是因为我是按照了Python生成器的语义来写的。


(define (counter n)
  (letrec ((generator
            (lambda (yield)
              (let counter ((n n))
                (call-with-current-continuation
                 (lambda (continue)
                   (set! generator (lambda (k)
                                     (set! yield k)
                                     (continue n)))
                   (yield n)))
                (counter (+ n 1))))))
    (lambda () (call-with-current-continuation
                (lambda (yield)
                  (generator yield))))))

“我靠!”你可能会有这种反应。确实太复杂了!在写这段代码的最初版本的时候,我说写这个不会太难。然后我发现了一个可能导致死循环的错误,而引发错误不是小概率事件。所以最后我认同了这点:如果只是要写一个能和Python生成器效果一样的函数,还是不要写这样的子过程的比较好。不过,这个可怕的东西在客户端代码使用起来却十分简单:

(define c12 (counter 12))
(c12)
(c12)

第一行定义了c12是子过程counter给一个参数12调用时的结果。第二行和第三行直接调用c12,没有任何参数,就和Python的例子一样,返回了12和13。不过这些都是学院派的,没有哪个疯子会在普通需求下写一个这样的子过程。

写像counter这样的子过程一般会导致手指抽痉、头脑发胀。不过,有意思的是,我们可以跳过这些来写counter,Scheme的生成器要比Python版本的更加容易使用,因为Scheme的返回的是函数,而Python生成器返回的是生成器对象,所以Python生成器需要调用next方法。

(旁白:Python生成器的设计师们本可以这样实现生成器对象:接下来的值通过c12()c12.next()来获取,不过他们并没有这样实现。)

回到Scheme上……在Scheme中写这样的生成器的复杂和容易出错看上去似乎让在Scheme中使用生成器变得不切实际,但实际上并非如此,因为Scheme包含了一个Python和Java都缺乏的特性:扩展语言语法的能力。如果你能够写出Scheme版本的counter,花不了多少功夫就可以创建一个宏(macro)使得这个特性能以一种可以被大家接受的方式使用。下面是我写的宏,可以完成这个任务:

(define-syntax define-generator
  (syntax-rules ()
    ((define-generator (NAME ARG ...) YIELD-PROC E1 E2 ...)
     (define (NAME ARG ...)
       (letrec ((generator
                 (lambda (yield)
                   (let ((YIELD-PROC
                          (lambda v
                            (call-with-current-continuation
                             (lambda (continue)
                               (set! generator (lambda (k)
                                                 (set! yield k)
                                                 (apply continue v)))
                               (apply yield v))))))
                     (let NAME ((ARG ARG) ...)
                       E1 E2 ...)))))
         (lambda () (call-with-current-continuation
                     (lambda (yield)
                       (generator yield)))))))))

一旦有了这个宏,counter生成器的Scheme版本就可以这样定义了:

(define-generator (counter n) yield
  (counter (+ 1 (yield n))))

还不错吧?这个版本唯一让我烦的地方是必须指定yield函数的名称。不过它还是给予程序员一些灵活性,可以根据代码的上下文来给函数起一个最有意义的名称。(其实,资深的Lisp程序员应该知道这个“特性”可以使用一些非hygenic宏来修正,不过这里我们还是坚持标准R5RS Scheme)。

如果你比较一下第一版和第二版的counter,你可能会注意到我在新的define-generator版本中作了一些小手脚:yield函数返回了它产生的值,因此它可以用于对counter的递归调用中。而Python的生成器就不能这样用。

那么为什么Java不能变得更像Python?答案是——其实Java和Python很像:Python的用户也等了将近10年才可以用上生成器。而我在玩了几天之后花了几个小时就在Scheme中加入了对生成器的支持。不过还是有人会说生成器以及最近的其他一些Python特性,如列表包容,都使得Python变得更加容易编写——我当然完全同意这个观点——但是,从根本上来说,Java和Python在这一点上是一样的——都不能修改语言本身。

Java、Python和几乎所有其他非Lisp语言都是让你任由语言设计者摆布。你要等他们实现你需要的语言特性,可能只有列表中的前几个。而且等他们弄个一个新东西给你搔搔痒,谁能确定你喜欢这种结果?

那么为何花了10年才在Java中有了增强迭代语法?这是因为在Java和很多其他编程语言一样,语法是一个大问题。一般用户都不会修改语言的语言。因为这很难完成,只有少数人学习了这种技术才能去做。当需要修改语法时,表达力和清晰的优先级都没有保持向后兼容重要。

在Scheme中,加入语法相对还比较简单,同时还可以根据特定问题的基础来完成,所以无须担心是有要给出一个普遍适用的理想解决方案。这种能根据问题构建语言的能力要胜过对使用很多括号的语言的担心。

链接

Posted in Java, Lisp, Python, Scheme | 7 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 | 4 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 »

关注 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 »

使用 Spring WebFlow Editor

Posted by Nicholas Ding on 4th 四月 2006

使用 WebFlowEditor 开发SWF应用

如果你还不了解Spring Web Flow,请参考Spring Web Flow这篇文章。

如果你已经开始对Spring Web Flow感兴趣了,那么我们开始着手了解如何独自开发SWF应用了。

首先,一个好的开发环境能够节省很多时间,这里我介绍一下如何使用Eclipse + WebFlowEditor开发SWF应用。

安装:

  1. 下载Eclipse,我用的是3.1 M6版本。
  2. 下载Spring Framework和Spring Web Flow
  3. 安装plug-in,首先要装一个Spring IDE,之后要装Eclipse GEF,最后装Spring WebFlowEditor。

安装插件可以通过Help->Software Updates->Find and Install->Search for new festures to install->New Remote Site的办法,添加插件下载页的方式直接通过eclipse下载安装。

Spring IDE: http://springide.org/updatesite/

WebFlowEditor: http://springide.org/updatesite_dev

GET: 通过默认的Eclipse.org update site安装,或者直接从eclipse网站下载安装

配置:

  1. 新建一个Java Project,在Package Explorer选中项目点右键选择Add Spring Project Nature
  2. 我认为是很重要的一点,很多朋友都很疑惑Spring IDE在哪里,其实默认情况下这个View并没有显示出来,我们在Window->Show View->Other里面添加它到开发界面上。其实Spring IDE和Spring WebFlow的最大特点也就是能够以图形的方式直观的给人一个大体的映像,这样一来就能很好的看清Beans之间的联系。
  3. 在Spring Beans和Spring WebFlow里面添加配置信息,主要就是设定applicationContext.xml和xxxFlow.xml(Web Flow 定义),一切配置好后,就可以Show Graph和Show WebFlow Editor的方式显示出图形界面。有图形界面做起来就方便多啦:-)

Spring IDE 视图

图1 - Spring IDE 视图

第一个应用:

这个例子来源于Spring Web Flow发行包内的例子 - fileupload,这是一个很简单的例子,流程也很简单。它采用了Spring MVC,最后我尝试着将它转换成Struts MVC。

在WEB-INF下的upload-flow.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
	"http://www.springframework.org/dtd/spring-webflow.dtd">
 
<webflow id="upload" start-state="selectFile.view">
 
	<view-state id="selectFile.view" view="selectFile.view">
		<transition on="submit" to="bindAndValidate"/>
	</view-state>
 
	<action-state id="bindAndValidate">
		<action bean="upload.process"/>
		<transition on="success" to="confirmation.view"/>
		<transition on="error" to="selectFile.view"/>
	</action-state>
	
	<view-state id="confirmation.view" view="confirmation.view">
		<transition on="back" to="selectFile.view"/>
	</view-state>
	
</webflow>

通过Spring Web Flow Editor生成的流程图如下:

fileupload 流程

图2- fileupload 流程

在这个应用里,所有的view都放在了WEB-INF/jsp里面,每个标单里面必须有这样一个隐藏域:

<INPUT type="hidden" 
    name="_flowExecutionId" 
    value="<%=request.getAttribute("flowExecutionId") %>">

用于SWF判断流程执行位置。

SelectFile.view ==> WEB-INF/jsp/selectFile.view.jsp,在这个View里面还有一个隐藏域:

<INPUT type="hidden" name="_eventId" value="submit">

这个用于表示事件ID,就是transition的on属性,这样标示就能让spring知道执行哪个transitioin了。

与Struts集成:

Spring提供的例子是与Spring MVC集成的,我尝试将它换成了Struts。

1、在struts-config.xml里面加入Spring Plugin,并且定义SWF入口:

<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
 
<struts-config>
 
   <form-beans> 
      <form-bean name="actionForm" type="org.springframework.web.struts.BindingActionForm"/> 
   </form-beans> 
   
	<global-forwards>
		<forward name="confirmation.view" path="/WEB-INF/jsp/confirmation.view.jsp" />
		<forward name="selectFile.view" path="/WEB-INF/jsp/selectFile.view.jsp" />
	</global-forwards>
 
	<action-mappings>
		<action path="/upload" 
			type="org.springframework.web.flow.struts.FlowAction"
			name="actionForm" scope="request" 
			className="org.springframework.web.flow.struts.FlowActionMapping">
			<set-property property="flowId" value="upload" />
		</action>
	</action-mappings>
 
	<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
		<set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml"/>
	</plug-in>
 
</struts-config>

关于view的映射需要使用action forwards,典型的做法是定义成全局的(SWF JavaDocs上介绍)。

定义SWF入口是最重要的,如果不定义,SWF是无法启动的。同时要启动一个Web流程,你需要传递一个flowId给控制器,这里FlowAction包含了一个全局的控制器(详细内容查SWF JavaDocs)。upload这个属性值很重要,他是你的webflow的id号,你可能有多个webflow,用它标示你需要启动的那个。当然还有其他的启动办法,另外的例子上有介绍。

3、编写完整的applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
			"http://www.springframework.org/dtd/spring-beans.dtd">
 
<beans>
 
	<bean id="upload" class="org.springframework.web.flow.config.FlowFactoryBean">
		<property name="flowBuilder">
			<bean class="org.springframework.web.flow.config.XmlFlowBuilder">
				<property name="resource" value="/WEB-INF/upload-flow.xml"/>
			</bean>
		</property>
	</bean>
	
	<bean id="upload.process" 
		class="org.springframework.samples.fileupload.web.flow.ProcessUploadAction">
		<property name="formObjectName" value="file"/>
		<property name="formObjectClass" 
			value="org.springframework.samples.fileupload.web.flow.FileUploadBean"/>
	</bean>
</beans>

4、编写web.xml以加载Struts和Spring:

<?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp">
 
	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>
		<init-param>
			<param-name>debug</param-name>
			<param-value>2</param-value>
		</init-param>
		<init-param>
			<param-name>detail</param-name>
			<param-value>2</param-value>
		</init-param>
		<load-on-startup>2</load-on-startup>