seasun  
在不断模仿、思考、总结中一步一步进步!
公告
  •     我的blog中的部分资源是来自于网络上,如果您认为侵犯了您的权利,请及时联系我我会尽快删除!E-MAIL:shiwenfeng@aliyun.com和QQ:281340916,欢迎交流。

日历
<2009年11月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

导航

常用链接

随笔分类

good blog author

积分与排名

  • 积分 - 80677
  • 排名 - 698

最新评论

阅读排行榜

 

2009年11月21日

一、首先,模块的组织更加的细致,从那么多的jar分包就看的出来:

 

Spring的构建系统以及依赖管理使用的是Apache Ivy,从源码包看出,也使用了Maven。

Maven确实是个好东西,好处不再多言,以后希望能进一步用好它。

二、新特性如下:

Spring Expression Language (Spring表达式语言)

IoC enhancements/Java based bean metadata (Ioc增强/基于Java的bean元数据)

General-purpose type conversion system and UI field formatting system (通用类型转换系统和UI字段格式化系统)

Object to XML mapping functionality (OXM) moved from Spring Web Services project (对象到XML映射功能从Spring Web Services项目移出)

Comprehensive REST support (广泛的REST支持)

@MVC additions (@MVC增强)

Declarative model validation (声明式模型验证)

Early support for Java EE 6 (提前对Java EE6提供支持)

Embedded database support (嵌入式数据库的支持)

三、针对Java 5的核心API升级

1、BeanFactory接口尽可能返回明确的bean实例,例如:

T getBean(String name, Class requiredType)

Map getBeansOfType(Class type)

Spring3对泛型的支持,又进了一步。个人建议泛型应该多用,有百利而无一害!

2、Spring的TaskExecutor接口现在继承自java.util.concurrent.Executor:

扩展的子接口AsyncTaskExecutor支持标准的具有返回结果Futures的Callables。

任务计划,个人还是更喜欢Quartz。

3、新的基于Java5的API和SPI转换器

无状态的ConversionService 和 Converters

取代标准的JDK PropertyEditors

类型化的ApplicationListener,这是一个实现“观察者设计模式”使用的事件监听器。

基于事件的编程模式,好处多多,在项目中应该考虑使用,基于事件、状态迁移的设计思路,有助于理清软件流程,和减少项目的耦合度。

四、Spring表达式语言

Spring表达式语言是一种从语法上和统一表达式语言(Unified EL)相类似的语言,但提供更多的重要功能。它可以在基于XML配置文件和基于注解的bean配置中使用,并作为基础为跨Spring portfolio平台使用表达式语言提供支持。

接下来,是一个表达式语言如何用于配置一个数据库安装中的属性的示例:

<bean class="mycompany.RewardsTestDatabase">
    <property name="databaseName"
        value="#{systemProperties.databaseName}"/>
    <property name="keyGenerator"
        value="#{strategyBean.databaseKeyGenerator}"/>
</bean>
如果你更愿意使用注解来配置你的组件,那么这种功能同样可用:

@Repository public class RewardsTestDatabase {
      @Value("#{systemProperties.databaseName}")
      public void setDatabaseName(String dbName) { … }
     
      @Value("#{strategyBean.databaseKeyGenerator}")
      public voidsetKeyGenerator(KeyGenerator kg) { … }
}

又多一种表达式语言,造轮子的运动还在继续中!

五、基于Java的bean元数据

JavaConfig项目中的一些核心特性已经集成到了Spring中来,这意味着如下这些特性现在已经可用了:

@Configuration

@Bean

@DependsOn

@Primary

@Lazy

@Import

@Value

又来一堆的注解,无语了,感觉还是配置文件方便!:(

这儿有一个例子,关于一个Java类如何使用新的JavaConfig特性提供基础的配置信息:

package org.example.config;

@Configuration
public class AppConfig {
    private @Value("#{jdbcProperties.url}") String jdbcUrl;
    private @Value("#{jdbcProperties.username}") String username;
    private @Value("#{jdbcProperties.password}") String password;

    @Bean
    public FooService fooService() {
        return new FooServiceImpl(fooRepository());
    }

    @Bean
    public FooRepository fooRepository() {
        return new HibernateFooRepository(sessionFactory());
    }

    @Bean
    public SessionFactory sessionFactory() {
        // wire up a session factory
        AnnotationSessionFactoryBean asFactoryBean =
            new AnnotationSessionFactoryBean();
        asFactoryBean.setDataSource(dataSource());
        // additional config
        return asFactoryBean.getObject();
    }

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(jdbcUrl, username, password);
    }
}为了让这段代码开始生效,我们需要添加如下组件扫描入口到最小化的应用程序上下文配置文件中:

<context:component-scan base-package="org.example.config"/>
<util:properties id="jdbcProperties" location="classpath:org/example/config/jdbc.properties"/>

六、在组件中定义bean的元数据

感觉Spring提供了越来越多的注解、元数据,复杂性已经超出了当初带来的方便本身!

七、通用类型转换系统和UI字段格式化系统

Spring3加入了一个通用的类型转换系统,目前它被SpEL用作类型转换,并且可能被一个Spring容器使用,用于当绑定bean的属性值的时候进行类型转换。

另外,还增加了一个UI字段格式化系统,它提供了更简单的使用并且更强大的功能以替代UI环境下的JavaBean的PropertyEditors,例如在SpringMVC中。

这个特性要好好研究下,通用类型转换系统如果果如所言的话,带来的好处还是很多的。

八、数据层

对象到XML的映射功能已经从Spring Web Services项目移到了Spring框架核心中。它位于org.springframework.oxm包中。

OXM?研究下!时间真不够!

九、Web层

在Web层最激动人心的新特性莫过于新增的对构件REST风格的web服务和web应用的支持!另外,还新增加了一些任何web应用都可以使用的新的注解。

服务端对于REST风格的支持,是通过扩展既有的注解驱动的MVC web框架实现的。

客户端的支持则是RestTemplate类提供的。

无论服务端还是客户端REST功能,都是使用HttpConverter来简化对HTTP请求和应答过程中的对象到表现层的转换过程。

MarshallingHttpMessageConverter使用了上面提到的“对象到XML的映射机制”。

十、@MVC增强

新增了诸如@CookieValue 和 @RequestHeaders这样的注解等。

十一、声明式模型验证

支持JSR 303,使用Hibernate Validator作为实现。

十二、提前对Java EE6提供支持

提供了使用@Async注解对于异步方法调用的支持(或者EJB 3.1里的 @Asynchronous)

另外,新增对JSR 303, JSF 2.0, JPA 2.0等的支持。

十三、嵌入式数据库的支持

对于嵌入式的Java数据库引擎提供了广泛而方便的支持,诸如HSQL, H2, 以及Derby等。

这是不是代表一种潮流呢?数据库向越来越小型化发展,甚至小型化到嵌入式了,我认为这在桌面级应用上还是很有市场的。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/abigfrog/archive/2009/10/30/4748685.aspx

posted @ 2010-01-25 17:26 shiwf 阅读(4677) | 评论 (0)编辑 收藏
 
请查看此blog: http://conkeyn.javaeye.com/category/35770

HQL: Hibernate查询语言

Hibernate配备了一种非常强大的查询语言,这种语言看上去很像SQL。但是不要被语法结构上的相似所迷惑,HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态 和关联之类的概念。

1.大小写敏感性问题

除了Java类与属性的名称外,查询语句对大小写并不敏感。 所以 SeLeCT sELEct 以及 SELECT 是相同的,但是 org.hibernate.eg.FOO 并不等价于 org.hibernate.eg.Foo 并且 foo.barSet 也不等价于 foo.BARSET

本手册中的HQL关键字将使用小写字母. 很多用户发现使用完全大写的关键字会使查询语句 的可读性更强, 但我们发现,当把查询语句嵌入到Java语句中的时候使用大写关键字比较难看。

2.from子句

Hibernate中最简单的查询语句的形式如下:

from eg.Cat

该子句简单的返回eg.Cat 类的所有实例。通常我们不需要使用类的全限定名, 因为 auto-import (自动引入) 是缺省的情况。所以我们几乎只使用如下的简单写法:

from Cat

大多数情况下, 你需要指定一个别名 , 原因是你可能需要 在查询语句的其它部分引用到Cat

from Cat as cat

这个语句把别名cat 指定给类Cat 的实例, 这样我们就可以在随后的查询中使用此别名了。关键字as 是可选的,我们也可以这样写:

from Cat cat

子句中可以同时出现多个类, 其查询结果是产生一个笛卡儿积或产生跨表的连接。

from Formula, Parameter

from Formula as form, Parameter as param

查询语句中别名的开头部分小写被认为是实践中的好习惯, 这样做与Java变量的命名标准保持了一致 (比如,domesticCat )。

3.关联(Association)与连接(Join)

我们也可以为相关联的实体甚至是对一个集合中的全部元素指定一个别名, 这时要使用关键字join

from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten

from Cat as cat left join cat.mate.kittens as kittens

from Formula form full join form.parameter param

受支持的连接类型是从ANSI SQL中借鉴来的。

  • inner join (内连接)

  • left outer join (左外连接)

  • right outer join (右外连接)

  • full join (全连接,并不常用)

语句inner join , left outer join 以及 right outer join 可以简写。

from Cat as cat
join cat.mate as mate
left join cat.kittens as kitten

还有,一个"fetch"连接允许仅仅使用一个选择语句就将相关联的对象或一组值的集合随着他们的父对象的初始化而被初始化,这种方法在使用到集合的情况下尤其有用,对于关联和集合来说,它有效的代替了映射文件中的外联接与延迟声明(lazy declarations). 查看 第20.1节 “ 抓取策略(Fetching strategies) ” 以获得等多的信息。

from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens

一个fetch连接通常不需要被指定别名, 因为相关联的对象不应当被用在 where 子句 (或其它任何子句)中。同时,相关联的对象并不在查询的结果中直接返回,但可以通过他们的父对象来访问到他们。

注意fetch 构造变量在使用了scroll() iterate() 函数的查询中是不能使用的。最后注意,使用full join fetch right join fetch 是没有意义的。

如果你使用属性级别的延迟获取(lazy fetching)(这是通过重新编写字节码实现的),可以使用 fetch all properties 来强制Hibernate立即取得那些原本需要延迟加载的属性(在第一个查询中)。

from Document fetch all properties order by name

from Document doc fetch all properties where lower(doc.name) like '%cats%'

4.select子句

select 子句选择将哪些对象与属性返回到查询结果集中. 考虑如下情况:

select mate
from Cat as cat
inner join cat.mate as mate

该语句将选择mate s of other Cat s。(其他猫的配偶) 实际上, 你可以更简洁的用以下的查询语句表达相同的含义:

select cat.mate from Cat cat

查询语句可以返回值为任何类型的属性,包括返回类型为某种组件(Component)的属性:

select cat.name from DomesticCat cat
where cat.name like 'fri%'

select cust.name.firstName from Customer as cust

查询语句可以返回多个对象和(或)属性,存放在 Object[] 队列中,

select mother, offspr, mate.name
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr

或存放在一个List 对象中,

select new list(mother, offspr, mate.name)
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr

也可能直接返回一个实际的类型安全的Java对象,

select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr

假设类Family 有一个合适的构造函数.

你可以使用关键字as 给“被选择了的表达式”指派别名:

select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n
from Cat cat

这种做法在与子句select new map 一起使用时最有用:

select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
from Cat cat

该查询返回了一个Map 的对象,内容是别名与被选择的值组成的名-值映射。

5.聚集函数

HQL查询甚至可以返回作用于属性之上的聚集函数的计算结果:

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from Cat cat

受支持的聚集函数如下:

  • avg(...), sum(...), min(...), max(...)

  • count(*)

  • count(...), count(distinct ...), count(all...)

你可以在选择子句中使用数学操作符、连接以及经过验证的SQL函数:

select cat.weight + sum(kitten.weight)
from Cat cat
join cat.kittens kitten
group by cat.id, cat.weight

select firstName||' '||initial||' '||upper(lastName) from Person

关键字distinct all 也可以使用,它们具有与SQL相同的语义.

select distinct cat.name from Cat cat
select count(distinct cat.name), count(cat) from Cat cat

6.多态查询

一个如下的查询语句:

from Cat as cat

不仅返回Cat 类的实例, 也同时返回子类 DomesticCat 的实例. Hibernate 可以在from 子句中指定任何 Java 类或接口. 查询会返回继承了该类的所有持久化子类的实例或返回声明了该接口的所有持久化类的实例。下面的查询语句返回所有的被持久化的对象:

from java.lang.Object o

接口Named 可能被各种各样的持久化类声明:

from Named n, Named m where n.name = m.name

注意,最后的两个查询将需要超过一个的SQL SELECT .这表明order by 子句 没有对整个结果集进行正确的排序. (这也说明你不能对这样的查询使用Query.scroll() 方法.)

7.where子句

where 子句允许你将返回的实例列表的范围缩小. 如果没有指定别名,你可以使用属性名来直接引用属性:

from Cat where name='Fritz'

如果指派了别名,需要使用完整的属性名:

from Cat as cat where cat.name='Fritz'

返回名为(属性name等于)'Fritz'的Cat 类的实例。

select foo
from Foo foo, Bar bar
where foo.startDate = bar.date

将返回所有满足下面条件的Foo 类的实例:存在如下的bar 的一个实例,其date 属性等于 Foo startDate 属性。 复合路径表达式使得where 子句非常的强大,考虑如下情况:

from Cat cat where cat.mate.name is not null

该查询将被翻译成为一个含有表连接(内连接)的SQL查询。如果你打算写像这样的查询语句

from Foo foo
where foo.bar.baz.customer.address.city is not null

在SQL中,你为达此目的将需要进行一个四表连接的查询。

= 运算符不仅可以被用来比较属性的值,也可以用来比较实例:

from Cat cat, Cat rival where cat.mate = rival.mate

select cat, mate
from Cat cat, Cat mate
where cat.mate = mate

特殊属性(小写)id 可以用来表示一个对象的唯一的标识符。(你也可以使用该对象的属性名。)

from Cat as cat where cat.id = 123
from Cat as cat where cat.mate.id = 69

第二个查询是有效的。此时不需要进行表连接!

同样也可以使用复合标识符。比如Person 类有一个复合标识符,它由country 属性 与medicareNumber 属性组成。

from bank.Person person
where person.id.country = 'AU'
and person.id.medicareNumber = 123456

from bank.Account account
where account.owner.id.country = 'AU'
and account.owner.id.medicareNumber = 123456

第二个查询也不需要进行表连接。

同样的,特殊属性class 在进行多态持久化的情况下被用来存取一个实例的鉴别值(discriminator value)。 一个嵌入到where子句中的Java类的名字将被转换为该类的鉴别值。

from Cat cat where cat.class = DomesticCat

你也可以声明一个属性的类型是组件或者复合用户类型(以及由组件构成的组件等等)。永远不要尝试使用以组件类型来结尾的路径表达式(path-expression_r)(与此相反,你应当使用组件的一个属性来结尾)。 举例来说,如果store.owner 含有一个包含了组件的实体address

store.owner.address.city    // 正确
store.owner.address         // 错误!

一个“任意”类型有两个特殊的属性id class , 来允许我们按照下面的方式表达一个连接(AuditLog.item 是一个属性,该属性被映射为<any> )。

from AuditLog log, Payment payment
where log.item.class = 'Payment' and log.item.id = payment.id

注意,在上面的查询与句中,log.item.class payment.class 将涉及到完全不同的数据库中的列。

8.表达式

where 子句中允许使用的表达式包括大多数你可以在SQL使用的表达式种类:

  • 数学运算符+, -, *, /

  • 二进制比较运算符=, >=, <=, <>, !=, like

  • 逻辑运算符and, or, not

  • in , not in , between , is null , is not null , is empty , is not empty , member of and not member of

  • "简单的" case, case ... when ... then ... else ... end ,和 "搜索" case, case when ... then ... else ... end

  • 字符串连接符...||... or concat(...,...)

  • current_date() , current_time() , current_timestamp()

  • second(...) , minute(...) , hour(...) , day(...) , month(...) , year(...) ,

  • EJB-QL 3.0定义的任何函数或操作:substring(), trim(), lower(), upper(), length(), locate(), abs(), sqrt(), bit_length()

  • coalesce() nullif()

  • cast(... as ...) , 其第二个参数是某Hibernate类型的名字,以及extract(... from ...) ,只要ANSI cast() extract() 被底层数据库支持

  • 任何数据库支持的SQL标量函数,比如sign() , trunc() , rtrim() , sin()

  • JDBC参数传入 ?

  • 命名参数:name , :start_date , :x1

  • SQL 直接常量 'foo' , 69 , '1970-01-01 10:00:01.0'

  • Java public static final 类型的常量 eg.Color.TABBY

关键字in between 可按如下方法使用:

from DomesticCat cat where cat.name between 'A' and 'B'

from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

而且否定的格式也可以如下书写:

from DomesticCat cat where cat.name not between 'A' and 'B'

from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

同样, 子句is null is not null 可以被用来测试空值(null).

在Hibernate配置文件中声明HQL“查询替代(query substitutions)”之后,布尔表达式(Booleans)可以在其他表达式中轻松的使用:

<property name="hibernate.query.substitutions">true 1, false 0</property>

系统将该HQL转换为SQL语句时,该设置表明将用字符 1 0 来 取代关键字true false :

from Cat cat where cat.alive = true

你可以用特殊属性size , 或是特殊函数size() 测试一个集合的大小。

from Cat cat where cat.kittens.size > 0

from Cat cat where size(cat.kittens) > 0

对于索引了(有序)的集合,你可以使用minindex maxindex 函数来引用到最小与最大的索引序数。同理,你可以使用minelement maxelement 函数来引用到一个基本数据类型的集合中最小与最大的元素。

from Calendar cal where maxelement(cal.holidays) > current date

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

在传递一个集合的索引集或者是元素集(elements indices 函数) 或者传递一个子查询的结果的时候,可以使用SQL函数any, some, all, exists, in

select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)

select p from NameList list, Person p
where p.name = some elements(list.names)

from Cat cat where exists elements(cat.kittens)

from Player p where 3 > all elements(p.scores)

from Show show where 'fizard' in indices(show.acts)

注意,在Hibernate3种,这些结构变量- size , elements , indices , minindex , maxindex , minelement , maxelement - 只能在where子句中使用。

一个被索引过的(有序的)集合的元素(arrays, lists, maps)可以在其他索引中被引用(只能在where子句中):

from Order order where order.items[0].id = 1234

select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
and person.nationality.calendar = calendar

select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11

select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11

[] 中的表达式甚至可以是一个算数表达式。

select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item

对于一个一对多的关联(one-to-many association)或是值的集合中的元素, HQL也提供内建的index() 函数,

select item, index(item) from Order order
join order.items item
where index(item) < 5

如果底层数据库支持标量的SQL函数,它们也可以被使用

from DomesticCat cat where upper(cat.name) like 'FRI%'

如果你还不能对所有的这些深信不疑,想想下面的查询。如果使用SQL,语句长度会增长多少,可读性会下降多少:

select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = 'widget'
and store.location.name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.currentOrder.lineItems)

提示: 会像如下的语句

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = 'widget'
AND store.loc_id = loc.id
AND loc.name IN ( 'Melbourne', 'Sydney' )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)

9.order by子句

查询返回的列表(list)可以按照一个返回的类或组件(components)中的任何属性(property)进行排序:

from DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate

可选的asc desc 关键字指明了按照升序或降序进行排序.

10.group by子句

一个返回聚集值(aggregate values)的查询可以按照一个返回的类或组件(components)中的任何属性(property)进行分组:

select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color

select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id

having 子句在这里也允许使用.

select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

如果底层的数据库支持的话(例如不能在MySQL中使用),SQL的一般函数与聚集函数也可以出现 在having order by 子句中。

select cat
from Cat cat
join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc

注意group by 子句与 order by 子句中都不能包含算术表达式(arithmetic expression_rs).

posted @ 2010-01-24 13:55 shiwf 阅读(2048) | 评论 (0)编辑 收藏
 

DWR 3.0 上传文件

第一步:需要文件包,其实就是dwr 3.0中例子所需要的包, dwr.jar 、 commons-fileupload-1.2.jar 、 commons-io-1.3.1.jar

 

 

 

第二步:编辑web.xml,添加dwr-invoke

Xml代码 复制代码
  1. <servlet>  
  2.     <display-name>DWR Sevlet</display-name>  
  3.     <servlet-name>dwr-invoker</servlet-name>  
  4.     <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>  
  5.     <init-param>  
  6.         <description>是否打开调试功能</description>  
  7.         <param-name>debug</param-name>  
  8.         <param-value>true</param-value>  
  9.     </init-param>  
  10.     <init-param>  
  11.         <description>日志级别有效值为: FATAL, ERROR, WARN (the default), INFO and DEBUG.</description>  
  12.         <param-name>logLevel</param-name>  
  13.         <param-value>DEBUG</param-value>  
  14.     </init-param>  
  15.     <init-param>  
  16.         <description>是否激活反向Ajax</description>  
  17.         <param-name>activeReverseAjaxEnabled</param-name>  
  18.         <param-value>true</param-value>  
  19.     </init-param>  
  20.     <init-param>     
  21.          <description>在WEB启动时是否创建范围为application的creator</description>     
  22.          <param-name>initApplicationScopeCreatorsAtStartup</param-name>     
  23.          <param-value>true</param-value>     
  24.     </init-param>     
  25.     <init-param>  
  26.         <description>在WEB启动时是否创建范围为application的creator</description>     
  27.         <param-name>preferDataUrlSchema</param-name>  
  28.         <param-value>false</param-value>  
  29.     </init-param>  
  30.         <load-on-startup>1</load-on-startup>     
  31.        
  32. </servlet>  
  33. <servlet-mapping>  
  34.     <servlet-name>dwr-invoker</servlet-name>  
  35.     <url-pattern>/dwr/*</url-pattern>  
  36. </servlet-mapping>  

 第三步:创建上传类FileUpload.java,编辑代码,内容如下:

Java代码 复制代码
  1. package learn.dwr.upload_download;   
  2.   
  3. import java.awt.Color;   
  4. import java.awt.Font;   
  5. import java.awt.Graphics2D;   
  6. import java.awt.geom.AffineTransform;   
  7. import java.awt.image.AffineTransformOp;   
  8. import java.awt.image.BufferedImage;   
  9. import java.io.File;   
  10. import java.io.FileOutputStream;   
  11. import java.io.InputStream;   
  12. import org.directwebremoting.WebContext;   
  13. import org.directwebremoting.WebContextFactory;   
  14.   
  15. /**  
  16.  * title: 文件上传  
  17.  * @author Administrator  
  18.  * @时间 2009-11-22:上午11:40:22  
  19.  */  
  20. public class FileUpload {   
  21.   
  22.     /**  
  23.      * @param uploadImage 圖片文件流  
  24.      * @param uploadFile 需要用简单的文本文件,如:.txt文件,不然上传会出乱码  
  25.      * @param color  
  26.      * @return  
  27.      */  
  28.     public BufferedImage uploadFiles(BufferedImage uploadImage,   
  29.             String uploadFile, String color) {   
  30.         // uploadImage = scaleToSize(uploadImage);   
  31.         // uploadImage =grafitiTextOnImage(uploadImage, uploadFile, color);   
  32.         return uploadImage;   
  33.     }   
  34.   
  35.     /**  
  36.      * 文件上传时使用InputStream类进行接收,在DWR官方例中是使用String类接收简单内容  
  37.      *   
  38.      * @param uploadFile  
  39.      * @return  
  40.      */  
  41.     public String uploadFile(InputStream uploadFile, String filename)   
  42.             throws Exception {   
  43.         WebContext webContext = WebContextFactory.get();   
  44.         String realtivepath = webContext.getContextPath() + "/upload/";   
  45.         String saveurl = webContext.getHttpServletRequest().getSession()   
  46.                 .getServletContext().getRealPath("/upload");   
  47.         File file = new File(saveurl + "/" + filename);   
  48.         // if (!file.exists()) {   
  49.         // file.mkdirs();   
  50.         // }   
  51.         int available = uploadFile.available();   
  52.         byte[] b = new byte[available];   
  53.         FileOutputStream foutput = new FileOutputStream(file);   
  54.         uploadFile.read(b);   
  55.         foutput.write(b);   
  56.         foutput.flush();   
  57.         foutput.close();   
  58.         uploadFile.close();   
  59.         return realtivepath + filename;   
  60.     }   
  61.   
  62.     private BufferedImage scaleToSize(BufferedImage uploadImage) {   
  63.         AffineTransform atx = new AffineTransform();   
  64.         atx   
  65.                 .scale(200d / uploadImage.getWidth(), 200d / uploadImage   
  66.                         .getHeight());   
  67.         AffineTransformOp atfOp = new AffineTransformOp(atx,   
  68.                 AffineTransformOp.TYPE_BILINEAR);   
  69.         uploadImage = atfOp.filter(uploadImage, null);   
  70.         return uploadImage;   
  71.     }   
  72.   
  73.     private BufferedImage grafitiTextOnImage(BufferedImage uploadImage,   
  74.             String uploadFile, String color) {   
  75.         if (uploadFile.length() < 200) {   
  76.             uploadFile += uploadFile + " ";   
  77.         }   
  78.         Graphics2D g2d = uploadImage.createGraphics();   
  79.         for (int row = 0; row < 10; row++) {   
  80.             String output = "";   
  81.             if (uploadFile.length() > (row + 1) * 20) {   
  82.                 output += uploadFile.substring(row * 20, (row + 1) * 20);   
  83.             } else {   
  84.                 output = uploadFile.substring(row * 20);   
  85.             }   
  86.             g2d.setFont(new Font("SansSerif", Font.BOLD, 16));   
  87.             g2d.setColor(Color.blue);   
  88.             g2d.drawString(output, 5, (row + 1) * 20);   
  89.         }   
  90.         return uploadImage;   
  91.     }   
  92. }  

 第四步:添加到dwr.xml

Java代码 复制代码
  1. <create creator="new">   
  2.     <param name="class" value="learn.dwr.upload_download.FileUpload" />   
  3. </create>  

 第五步:添加前台html代码

Html代码 复制代码
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  5. <title>二进制文件处理,文件上传</title>  
  6. <script type='text/javascript' src='/learnajax/dwr/interface/FileUpload.js'></script>  
  7. <script type='text/javascript' src='/learnajax/dwr/engine.js'></script>  
  8. <script type='text/javascript' src='/learnajax/dwr/util.js'></script>  
  9. <script type='text/javascript' >  
  10. function uploadFiles(){   
  11.     var uploadImage = dwr.util.getValue("uploadImage");   
  12.      FileUpload.uploadFiles(uploadImage, "", "", function(imageURL) {   
  13.         alert(imageURL);   
  14.         dwr.util.setValue('image', imageURL);   
  15.   });   
  16.   
  17. }   
  18. function uploadFile(){   
  19.     var uploadFile = dwr.util.getValue("uploadFile");   
  20.     //var uploadFile =document.getElementById("uploadFile").value;   
  21.     var uploadFileuploadFile_temp = uploadFile.value.replace("\\","/");   
  22.     var filenames = uploadFile.value.split("/");   
  23.     var filename = filenames[filenames.length-1];   
  24.     //var eextension = e[e.length-1];   
  25.     FileUpload.uploadFile(uploadFile,filename,function(data){   
  26.         var file_adocument.getElementById("file_a");   
  27.         file_a.href=data;   
  28.         file_a.innerHTML=data;   
  29.         document.getElementById("filediv").style.display="";   
  30.     });   
  31. }   
  32.        
  33. </script>  
  34. </head>  
  35.   
  36. <body>  
  37. <table border="1" cellpadding="3" width="50%">  
  38.     <tr>  
  39.         <td>Image</td>  
  40.         <td><input type="file" id="uploadImage" /></td>  
  41.         <td><input type="button" onclick="uploadFiles()" value="upload"/><div id="image.container">&nbsp;</div></td>  
  42.     </tr>  
  43.     <tr>  
  44.         <td>File</td>  
  45.         <td><input type="file" id="uploadFile" /></td>  
  46.         <td><input type="button" onclick="uploadFile()" value="upload"/><div id="file.container">&nbsp;</div></td>  
  47.     </tr>  
  48.     <tr>  
  49.         <td colspan="3"></td>  
  50.     </tr>  
  51. </table>  
  52. <img id="image" src="javascript:void(0);"/>  
  53. <div id="filediv" style="display:none;">  
  54. <a href="" id="file_a">上传的文件</a>  
  55. </div>  
  56. </body>  
  57. </html>  
 

添加进度条么,就需要用reverse ajax 进行配合使用了。

posted @ 2010-01-24 13:42 shiwf 阅读(4349) | 评论 (1)编辑 收藏
 

本文转自:http://www.blogjava.net/xylz/

DWR作为Ajax远程调用的服务端得到了很多程序员的追捧,在DWR的2.x版本中已经集成了Guice的插件。

老套了,我们还是定义一个HelloWorld的服务吧,哎,就喜欢HelloWorld,不怕被别人骂! 

1 public interface HelloWorld {
2 
3     String sayHello();
4 
5     Date getSystemDate();
6 }
7 

然后写一个简单的实现吧。 

 1 public class HelloWorldImpl implements HelloWorld {
 2 
 3     @Override
 4     public Date getSystemDate() {
 5         return new Date();
 6     }
 7 
 8     @Override
 9     public String sayHello() {
10         return "Hello, guice";
11     }
12 }
13 

然后是与dwr有关的东西了,我们写一个dwr的listener来注入我们的模块。 

 1 package cn.imxylz.study.guice.web.dwr;
 2 
 3 import org.directwebremoting.guice.DwrGuiceServletContextListener;
 4 
 5 /**
 6  * @author xylz (www.imxylz.cn)
 7  * @version $Rev: 105 $
 8  */
 9 public class MyDwrGuiceServletContextListener extends DwrGuiceServletContextListener{
10 
11     @Override
12     protected void configure() {
13         bindRemotedAs("helloworld", HelloWorld.class).to(HelloWorldImpl.class).asEagerSingleton();
14     }
15 }
16 

这里使用bindRemotedAs来将我们的服务开放出来供dwr远程调用。

剩下的就是修改web.xml,需要配置一个dwr的Servlet并且将我们的listener加入其中。看看全部的内容。 

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 4     version="2.5">
 5 
 6     <display-name>guice-dwr</display-name>
 7     <description>xylz study project - guice</description>
 8 
 9     <listener>
10         <listener-class>cn.imxylz.study.guice.web.dwr.MyDwrGuiceServletContextListener
11         </listener-class>
12     </listener>
13     <servlet>
14         <servlet-name>dwr-invoker</servlet-name>
15         <servlet-class>org.directwebremoting.guice.DwrGuiceServlet</servlet-class>
16         <init-param>
17           <param-name>debug</param-name>
18           <param-value>true</param-value>
19         </init-param>
20     </servlet>
21     <servlet-mapping>
22         <servlet-name>dwr-invoker</servlet-name>
23         <url-pattern>/dwr/*</url-pattern>
24     </servlet-mapping>
25 
26 </web-app>
27 

非常简单,也非常简洁,其中DwrGuiceServlet的debug参数只是为了调试方便才开放的,实际中就不用写了。

好了,看看我们的效果。

 1 <html>
 2 <head><title>dwr - test (www.imxylz.cn) </title>
 3   <script type='text/javascript' src='/guice-dwr/dwr/interface/helloworld.js'></script>
 4   <script type='text/javascript' src='/guice-dwr/dwr/engine.js'></script>
 5   <script type='text/javascript' src='/guice-dwr/dwr/util.js'></script>
 6   <script type='text/javascript'>
 7     var showHello = function(data){
 8         dwr.util.setValue('result',dwr.util.toDescriptiveString(data,1));   
 9     }
10     var getSystemDate = function(data){
11         dwr.util.setValue('systime',dwr.util.toDescriptiveString(data,2));   
12     }
13   </script>
14   <style type='text/css'>
15     input.button { border: 1px outset; margin: 0px; padding: 0px; }
16     span { background: #ffffdd; white-space: pre; padding-left:20px;}
17   </style>
18 </head>
19 <body onload='dwr.util.useLoadingMessage()'>
20     <p>
21     <h2>Guice and DWR</h2>
22         <input class='button' type='button' value="Call HelloWorld 'sayHello' service" onclick="helloworld.sayHello(showHello)" />
23         <span id='result' ></span>
24     </p>
25     <p>
26         <input class='button' type='button' value="Call HelloWorld 'getSystemDate' service" onclick="helloworld.getSystemDate(getSystemDate)" />
27         <span id='systime' ></span>
28     </P>
29 </body>
30 </html>

我们通过两个按钮来获取我们的远程调用的结果。

posted @ 2010-01-06 18:46 shiwf 阅读(442) | 评论 (0)编辑 收藏
 
     摘要: Guice真的无法享受企业级组件吗,JavaEye里炮轰Guice的占绝大多数。但如果Guice能整合Spring,那么我们似乎可以做很多有意义的事了。那么开始Spring整合之旅吧。不过crazybob在整合方面极不配合,就给了我们一个单元测试类,然后让我们自力更生。好在Guice本身足够简单。   首先还是来一个最简单无聊的HelloWorld整合吧。   Hell...  阅读全文
posted @ 2010-01-06 16:39 shiwf 阅读(2637) | 评论 (0)编辑 收藏
 

Guice可真轻啊,所需的3Jar包才不到600k。但缺点就是必须JDK1.5以上,像我们公司有几十个大大小小的Java项目,没有一个是1.5的,有点感慨啊。废话少说

 先建立一个service:

 

IHelloService.java

Java代码
  1. package com.leo.service;  
  2.   
  3. import com.google.inject.ImplementedBy;  
  4. import com.leo.service.impl.HelloServiceImpl;  
  5.   
  6. /* 
  7.  * 采用annotation进行接口与实现类之间的绑定 
  8.  * 注意:接口与实现类之间绑定是必须的,如果只是单独一个类,没有接口, 
  9.  * 那么Guice会隐式的自动帮你注入。并且接口此是不应该声明注入域范围的, 
  10.  * 应该在其实现地方声明 
  11.  * 
  12.  */  
  13. @ImplementedBy(HelloServiceImpl.class)  
  14. public interface IHelloService {  
  15.     public String sayHello(String str);  
  16. }  

 

 

再来一个简单的实现:

 

HelloServiceImpl.java

Java代码
  1. package com.leo.service.impl;  
  2.   
  3. import com.google.inject.Singleton;  
  4. import com.leo.service.IHelloService;  
  5.   
  6. /* 
  7.  * 这里如果默认不用annotation标注其作用域,或在自定义的module也不指定的话 
  8.  * 默认的创建对象的方式是类似于Spring的prototype,在此处因为仅仅是一个stateless service 
  9.  * 我们用@Singleton来标注它,更多的作用域可看Guice文档 
  10.  *  
  11.  * 注意:与标注@Singleton等效的工作也可以在自定义的module里来实现 
  12.  */  
  13.   
  14. @Singleton  
  15. public class HelloServiceImpl implements IHelloService {  
  16.   
  17.     public String sayHello(String str) {  
  18.         return new StringBuilder("Hello " + str + " !").toString();  
  19.     }  
  20.   
  21. }  

 

 

Struts2的配置相信大家都会了,这里需要注意的是Struts2的工厂已经变了,默认是Spring现在我们要改成Guice,请看:

 

struts.properties

Java代码
  1. struts.objectFactory = guice  
  2. #如果自已想实现Module接口,则下面注释请去掉  
  3. #guice.module=com.leo.module.MyModule  
  4. struts.action.extension=  

 

 

再来看看调用代码,稍微比Spring简洁了些:

 

HelloAction.java

Java代码
  1. package com.leo.action;  
  2.   
  3. import com.google.inject.Inject;  
  4. import com.leo.service.IHelloService;  
  5. import com.opensymphony.xwork2.ActionSupport;  
  6.   
  7. public class HelloAction extends ActionSupport {  
  8.   
  9.     private static final long serialVersionUID = -338076402728419581L;  
  10.   
  11.     /* 
  12.      * 通过field字段进行注入,除此之外,还有construct, method注入均可 
  13.      */  
  14.     @Inject  
  15.     private IHelloService helloService;  
  16.   
  17.     private String message;  
  18.   
  19.     public String getMessage() {  
  20.         return message;  
  21.     }  
  22.   
  23.     public void setMessage(String message) {  
  24.         this.message = message;  
  25.     }  
  26.   
  27.     public String execute() {  
  28.         message = helloService.sayHello("leo");  
  29.         return SUCCESS;  
  30.     }  
  31.   
  32. }  

 

 

struts.xml配置也是非常简单:

 

struts.xml

Xml代码
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE struts PUBLIC  
  3.     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
  4.     "http://struts.apache.org/dtds/struts-2.0.dtd">  
  5.   
  6. <struts>  
  7.     <package name="default" extends="struts-default">  
  8.         <action name="hello" class="com.leo.action.HelloAction">  
  9.             <result>index.jsp</result>  
  10.         </action>  
  11.     </package>  
  12. </struts>  

 

 

到这里,算是大功告成了,Guice文档在与Struts2整合部分例子有误,而且郁闷的是,竟然连Guice的Filter需要在web.xml配置都没有说,我把配好的web.xml弄出来给大家看看

 

web.xml

Xml代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   
  5.     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  6.   
  7.     <filter>  
  8.         <filter-name>guice</filter-name>  
  9.         <filter-class>  
  10.             com.google.inject.servlet.GuiceFilter  
  11.         </filter-class>  
  12.     </filter>  
  13.   
  14.     <filter>  
  15.         <filter-name>struts</filter-name>  
  16.         <filter-class>  
  17.             org.apache.struts2.dispatcher.FilterDispatcher  
  18.         </filter-class>  
  19.     </filter>  
  20.   
  21.     <filter-mapping>  
  22.         <filter-name>guice</filter-name>  
  23.         <url-pattern>/*</url-pattern>  
  24.     </filter-mapping>  
  25.   
  26.     <filter-mapping>  
  27.         <filter-name>struts</filter-name>  
  28.         <url-pattern>/*</url-pattern>  
  29.     </filter-mapping>  
  30.   
  31.     <welcome-file-list>  
  32.         <welcome-file>index.jsp</welcome-file>  
  33.     </welcome-file-list>  
  34. </web-app>  

 

 

可以布署,运行了,输入http://localhost:8080/struts2_guice/hello 就可以看到结果了。

 

如果你觉得Annotation太麻烦,或不喜欢,也可以尝试自己实现Guice的Module,以下是一个简单的实现:

MyModule.java

Java代码
  1. package com.leo.module;  
  2.   
  3. import com.google.inject.Binder;  
  4. import com.google.inject.Module;  
  5. import com.google.inject.Scopes;  
  6. import com.leo.service.IHelloService;  
  7. import com.leo.service.impl.HelloServiceImpl;  
  8.   
  9. /* 
  10.  * 如果你觉得Annotation有种支离破碎的感觉,别急,Guice还为你提供一种统一 
  11.  * 注入管理的自定义实现。在本例中,先前的IHelloService, HelloServiceImpl 
  12.  * 你现在可以完全将所有的Annotation去掉,然后实现Module接口的唯一一个方法 
  13.  * 实现如下 
  14.  */  
  15. public class MyModule implements Module {  
  16.   
  17.     public void configure(Binder binder) {  
  18.   
  19.         /* 
  20.          * 将接口IHelloService 与其实现HelloServiceImpl 绑定在一起 并且作用域为Scopes.SINGLETON 
  21.          * 在这里有多种配置方法,但因为是入门实例,不想说的太复杂。其中如果不配置作用域,默认就是类似于Spring 
  22.          * 的Scope="prototype" 
  23.          */  
  24.         binder.bind(IHelloService.class).to(HelloServiceImpl.class).in(  
  25.                 Scopes.SINGLETON);  
  26.     }  
  27.   
  28. }  

 

 

运行效果完全一模一样,因此团队开发如果统一风格的话Guice确实能快速不少。但目前Guice仅仅只是一个IoC,远远没有Spring所涉及的那么广,但又正如Rod Johnson反复在其《J2EE without EJB》里强调:架构要永远 simplest, simplest 再 simplest,因此你觉得够用,就是最好的。

 

总的来说,开发,运行的速度似乎又快了不少,但Guice真的能不能扛起其所说的下一代IoC容器,我们拭目以待吧。


posted @ 2010-01-06 16:16 shiwf 阅读(647) | 评论 (0)编辑 收藏
 

The Example Document and Beans

In this example, we will unmarshall the same XML document that we used in the previous article:

<?xml version="1.0"?>
<catalog library="somewhere">
<book>
<author>Author 1</author>
<title>Title 1</title>
</book>
<book>
<author>Author 2</author>
<title>His One Book</title>
</book>
<magazine>
<name>Mag Title 1</name>
<article page="5">
<headline>Some Headline</headline>
</article>
<article page="9">
<headline>Another Headline</headline>
</article>
</magazine>
<book>
<author>Author 2</author>
<title>His Other Book</title>
</book>
<magazine>
<name>Mag Title 2</name>
<article page="17">
<headline>Second Headline</headline>
</article>
</magazine>
</catalog>

The bean classes are also the same, except for one important change: In the previous article, I had declared these classes to have package scope -- primarily so that I could define all of them in the same source file! Using the Digester framework, this is no longer possible; the classes need to be declared as public (as is required for classes conforming to the JavaBeans specification):

import java.util.Vector;
public class Catalog {
private Vector books;
private Vector magazines;
public Catalog() {
books = new Vector();
magazines = new Vector();
}
public void addBook( Book rhs ) {
books.addElement( rhs );
}
public void addMagazine( Magazine rhs ) {
magazines.addElement( rhs );
}
public String toString() {
String newline = System.getProperty( "line.separator" );
StringBuffer buf = new StringBuffer();
buf.append( "--- Books ---" ).append( newline );
for( int i=0; i<books.size(); i++ ){
buf.append( books.elementAt(i) ).append( newline );
}
buf.append( "--- Magazines ---" ).append( newline );
for( int i=0; i<magazines.size(); i++ ){
buf.append( magazines.elementAt(i) ).append( newline );
}
return buf.toString();
}
}

public class Book {
private String author;
private String title;
public Book() {}
public void setAuthor( String rhs ) { author = rhs; }
public void setTitle(  String rhs ) { title  = rhs; }
public String toString() {
return "Book: Author='" + author + "' Title='" + title + "'";
}
}

import java.util.Vector;
public class Magazine {
private String name;
private Vector articles;
public Magazine() {
articles = new Vector();
}
public void setName( String rhs ) { name = rhs; }
public void addArticle( Article a ) {
articles.addElement( a );
}
public String toString() {
StringBuffer buf = new StringBuffer( "Magazine: Name='" + name + "' ");
for( int i=0; i<articles.size(); i++ ){
buf.append( articles.elementAt(i).toString() );
}
return buf.toString();
}
}

public class Article {
private String headline;
private String page;
public Article() {}
public void setHeadline( String rhs ) { headline = rhs; }
public void setPage(     String rhs ) { page     = rhs; }
public String toString() {
return "Article: Headline='" + headline + "' on page='" + page + "' ";
}
}












import org.apache.commons.digester.*;
import java.io.*;
import java.util.*;
public class DigesterDriver {
public static void main( String[] args ) {
try {
Digester digester = new Digester();
digester.setValidating( false );
digester.addObjectCreate( "catalog", Catalog.class );
digester.addObjectCreate( "catalog/book", Book.class );
digester.addBeanPropertySetter( "catalog/book/author", "author" );
digester.addBeanPropertySetter( "catalog/book/title", "title" );
digester.addSetNext( "catalog/book", "addBook" );
digester.addObjectCreate( "catalog/magazine", Magazine.class );
digester.addBeanPropertySetter( "catalog/magazine/name", "name" );
digester.addObjectCreate( "catalog/magazine/article", Article.class );
digester.addSetProperties( "catalog/magazine/article", "page", "page" );
digester.addBeanPropertySetter( "catalog/magazine/article/headline" );
digester.addSetNext( "catalog/magazine/article", "addArticle" );
digester.addSetNext( "catalog/magazine", "addMagazine" );
File input = new File( args[0] );
Catalog c = (Catalog)digester.parse( input );
System.out.println( c.toString() );
} catch( Exception exc ) {
exc.printStackTrace();
}
}
}

After instantiating the Digester, we specify that it should not validate the XML document against a DTD -- because we did not define one for our simple Catalog document. Then we specify the patterns and the associated rules: the ObjectCreateRule creates an instance of the specified class and pushes it onto the parse stack. The SetPropertiesRule sets a bean property to the value of an XML attribute of the current element -- the first argument to the rule is the name of the attribute, the second, the name of the property.

Whereas SetPropertiesRule takes the value from an attribute, BeanPropertySetterRule takes the value from the raw character data nested inside of the current element. It is not necessary to specify the name of the property to set when using BeanPropertySetterRule: it defaults to the name of the current XML element. In the example above, this default is being used in the rule definition matching the catalog/magazine/article/headline pattern. Finally, the SetNextRule pops the object on top of the parse stack and passes it to the named method on the object below it -- it is commonly used to insert a finished bean into its parent.

Note that it is possible to register several rules for the same pattern. If this occurs, the rules are executed in the order in which they are added to the Digester -- for instance, to deal with the <article> element, found at catalog/magazine/article, we first create the appropriate article bean, then set the page property, and finally pop the completed article bean and insert it into its magazine parent.

Invoking Arbitrary Functions

It is not only possible to set bean properties, but to invoke arbitrary methods on objects in the stack. This is accomplished using the CallMethodRule to specify the method name and, optionally, the number and type of arguments passed to it. Subsequent specifications of the CallParamRule define the parameter values to be passed to the invoked functions. The values can be taken either from named attributes of the current XML element, or from the raw character data contained by the current element. For instance, rather than using the BeanPropertySetterRule in the DigesterDriver implementation above, we could have achieved the same effect by calling the property setter explicitly, and passing the data as parameter:

   digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );
digester.addCallParam( "catalog/book/author", 0 );

The first line gives the name of the method to call (setAuthor()), and the expected number of parameters (1). The second line says to take the value of the function parameter from the character data contained in the <author> element and pass it as first element in the array of arguments (i.e., the array element with index 0). Had we also specified an attribute name (e.g., digester.addCallParam( "catalog/book/author", 0, "author" );), the value would have been taken from the respective attribute of the current element instead.

One important caveat: confusingly, digester.addCallMethod( "pattern", "methodName", 0 ); does not specify a call to a method taking no arguments -- instead, it specifies a call to a method taking one argument, the value of which is taken from the character data of the current XML element! We therefore have yet another way to implement a replacement for BeanPropertySetterRule:

   digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );

 

To call a method that truly takes no parameters, use digester.addCallMethod( "pattern", "methodName" );.




Summary of Standard Rules



Below are brief descriptions of all of the standard rules.

Creational

  • ObjectCreateRule: Creates an object of the specified class using its default constructor and pushes it onto the stack; it is popped when the element completes. The class to instantiate can be given through a class object or the fully-qualified class name.

  • FactoryCreateRule: Creates an object using a specified factory class and pushes it onto the stack. This can be useful for classes that do not provide a default constructor. The factory class must implement the org.apache.commons.digester.ObjectCreationFactory interface.

Property Setters

  • SetPropertiesRule: Sets one or several named properties in the top-level bean using the values of named XML element attributes. Attribute names and property names are passed to this rule in String[] arrays. (Typically used to handle XML constructs like <article page="10">.)

  • BeanPropertySetterRule: Sets a named property on the top-level bean to the character data enclosed by the current XML element. (Example: <page>10</page>.)

  • SetPropertyRule: Sets a property on the top-level bean. Both the property name, as well as the value to which this property will be set, are given as attributes to the current XML element. (Example: <article key="page" value="10" />.)

Parent/Child Management

  • SetNextRule: Pops the object on top of the stack and passes it to a named method on the object immediately below. Typically used to insert a completed bean into its parent.

  • SetTopRule: Passes the second-to-top object on the stack to the top-level object. This is useful if the child object exposes a setParent method, rather than the other way around.

  • SetRootRule: Calls a method on the object at the bottom of the stack, passing the object on top of the stack as argument.

Arbitrary Method Calls

  • CallMethodRule: Calls an arbitrary named method on the top-level bean. The method may take an arbitrary set of parameters. The values of the parameters are given by subsequent applications of the CallParamRule.

  • CallParamRule: Represents the value of a method parameter. The value of the parameter is either taken from a named XML element attribute, or from the raw character data enclosed by the current element. This rule requires that its position on the parameter list is specified by an integer index.

Specifying Rules in XML: Using the xmlrules Package

Related Reading

Programming Jakarta Struts
By Chuck Cavaness

So far, we have specified the patterns and rules programmatically at compile time. While conceptually simple and straightforward, this feels a bit odd: the entire framework is about recognizing and handling structure and data at run time, but here we go fixing the behavior at compile time! Large numbers of fixed strings in source code typically indicate that something is being configured (rather than programmed), which could be (and probably should be) done at run time instead.

The org.apache.commons.digester.xmlrules package addresses this issue. It provides the DigesterLoader class, which reads the pattern/rule-pairs from an XML document and returns a digester already configured accordingly. The XML document configuring the Digester must comply with the digester-rules.dtd, which is part of the xmlrules package.

Below is the contents of the configuration file (named rules.xml) for the example application. I want to point out several things here.

Patterns can be specified in two different ways: either as attributes to each XML element representing a rule, or using the <pattern> element. The pattern defined by the latter is valid for all contained rule elements. Both ways can be mixed, and <pattern> elements can be nested -- in either case, the pattern defined by the child element is appended to the pattern defined in the enclosing <pattern> element.

The <alias> element is used with the <set-properties-rule> to map an XML attribute to a bean property.

Finally, using the current release of the Digester package, it is not possible to specify the BeanPropertySetterRule in the configuration file. Instead, we are using the CallMethodRule to achieve the same effect, as explained above.

<?xml version="1.0"?>
<digester-rules>
<object-create-rule pattern="catalog" classname="Catalog" />
<set-properties-rule pattern="catalog" >
<alias attr-name="library" prop-name="library" />
</set-properties-rule>
<pattern value="catalog/book">
<object-create-rule classname="Book" />
<call-method-rule pattern="author" methodname="setAuthor"
paramcount="0" />
<call-method-rule pattern="title" methodname="setTitle"
paramcount="0" />
<set-next-rule methodname="addBook" />
</pattern>
<pattern value="catalog/magazine">
<object-create-rule classname="Magazine" />
<call-method-rule pattern="name" methodname="setName" paramcount="0" />
<pattern value="article">
<object-create-rule classname="Article" />
<set-properties-rule>
<alias attr-name="page" prop-name="page" />
</set-properties-rule>
<call-method-rule pattern="headline" methodname="setHeadline"
paramcount="0" />
<set-next-rule methodname="addArticle" />
</pattern>
<set-next-rule methodname="addMagazine" />
</pattern>
</digester-rules>

Since all the actual work has now been delegated to the Digester and DigesterLoader classes, the driver class itself becomes trivially simple. To run it, specify the catalog document as the first command line argument, and the rules.xml file as the second. (Confusingly, the DigesterLoader will not read the rules.xml file from a File or an org.xml.sax.InputSource, but requires a URL -- the File reference in the code below is therefore transformed into an equivalent URL.)

import org.apache.commons.digester.*;
import org.apache.commons.digester.xmlrules.*;
import java.io.*;
import java.util.*;
public class XmlRulesDriver {
public static void main( String[] args ) {
try {
File input = new File( args[0] );
File rules = new File( args[1] );
Digester digester = DigesterLoader.createDigester( rules.toURL() );
Catalog catalog = (Catalog)digester.parse( input );
System.out.println( catalog.toString() );
} catch( Exception exc ) {
exc.printStackTrace();
}
}
}

Conclusion

This concludes our brief overview of the Jakarta Commons Digester package. Of course, there is more. One topic ignored in this introduction are XML namespaces: Digester allows you to specify rules that only act on elements defined within a certain namespace.

We mentioned briefly the possibility of developing custom rules, by extending the Rule class. The Digester class exposes the customary push(), peek(), and pop() methods, giving the individual developer freedom to manipulate the parse stack directly.

Lastly, note that there is an additional package providing a Digester implementation which deals with RSS (Rich-Site-Summary)-formatted newsfeeds. The Javadoc tells the full story.

References

Philipp K. Janert is a software project consultant, server programmer, and architect.



posted @ 2009-12-30 11:29 shiwf 阅读(457) | 评论 (0)编辑 收藏
 

Beanutils用了魔术般的反射技术,实现了很多夸张有用的功能,都是C/C++时代不敢想的。无论谁的项目,始终一天都会用得上它。我算是后知后觉了,第一回看到它的时候居然错过。

1.属性的动态getter,setter

在这框架满天飞的年代,不能事事都保证执行getter,setter函数了,有时候属性是要需要根据名字动态取得的,就像这样:  
BeanUtils.getProperty(myBean,"code");
而BeanUtils更强的功能是直接访问内嵌对象的属性,只要使用点号分隔。
BeanUtils.getProperty(orderBean, "address.city");
相比之下其他类库的BeanUtils通常都很简单,不能访问内嵌的对象,所以经常要用Commons BeanUtils替换它们。
BeanUtils还支持List和Map类型的属性。如下面的语法即可取得顾客列表中第一个顾客的名字
BeanUtils.getProperty(orderBean, "customers[1].name");
其中BeanUtils会使用ConvertUtils类把字符串转为Bean属性的真正类型,方便从HttpServletRequest等对象中提取bean,或者把bean输出到页面。
而PropertyUtils就会原色的保留Bean原来的类型。

2.beanCompartor 动态排序

还是通过反射,动态设定Bean按照哪个属性来排序,而不再需要在bean的Compare接口进行复杂的条件判断。
List peoples = ...; // Person对象的列表Collections.sort(peoples, new BeanComparator("age"));

如果要支持多个属性的复合排序,如"Order By lastName,firstName"

ArrayList sortFields = new ArrayList();sortFields.add(new BeanComparator("lastName"));
sortFields.add(new BeanComparator("firstName"));
ComparatorChain multiSort = new ComparatorChain(sortFields);
Collections.sort(rows,multiSort);

其中ComparatorChain属于jakata commons-collections包。
如果age属性不是普通类型,构造函数需要再传入一个comparator对象为age变量排序。
另外, BeanCompartor本身的ComparebleComparator, 遇到属性为null就会抛出异常, 也不能设定升序还是降序。
这个时候又要借助commons-collections包的ComparatorUtils.

   Comparator mycmp = ComparableComparator.getInstance();
   mycmp = ComparatorUtils.nullLowComparator(mycmp);  //允许null
   mycmp = ComparatorUtils.reversedComparator(mycmp); //逆序
   Comparator cmp = new BeanComparator(sortColumn, mycmp);

3.Converter 把Request或ResultSet中的字符串绑定到对象的属性

   经常要从request,resultSet等对象取出值来赋入bean中,下面的代码谁都写腻了,如果不用MVC框架的绑定功能的话。

   String a = request.getParameter("a");   bean.setA(a);   String b = ....

不妨写一个Binder:

     MyBean bean = ...;    HashMap map = new HashMap();    Enumeration names = request.getParameterNames();    while (names.hasMoreElements())    {      String name = (String) names.nextElement();      map.put(name, request.getParameterValues(name));    }    BeanUtils.populate(bean, map);

    其中BeanUtils的populate方法或者getProperty,setProperty方法其实都会调用convert进行转换。
    但Converter只支持一些基本的类型,甚至连java.util.Date类型也不支持。而且它比较笨的一个地方是当遇到不认识的类型时,居然会抛出异常来。
    对于Date类型,我参考它的sqldate类型实现了一个Converter,而且添加了一个设置日期格式的函数。
要把这个Converter注册,需要如下语句:

    ConvertUtilsBean convertUtils = new ConvertUtilsBean();
   DateConverter dateConverter = new DateConverter();
   convertUtils.register(dateConverter,Date.class);



//因为要注册converter,所以不能再使用BeanUtils的静态方法了,必须创建BeanUtilsBean实例
BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean());
beanUtils.setProperty(bean, name, value);
4 其他功能
4.1 PropertyUtils,当属性为Collection,Map时的动态读取:
 
Collection: 提供index
   BeanUtils.getIndexedProperty(orderBean,"items",1);
或者
  BeanUtils.getIndexedProperty(orderBean,"items[1]");

Map: 提供Key Value
  BeanUtils.getMappedProperty(orderBean, "items","111");//key-value goods_no=111
或者
  BeanUtils.getMappedProperty(orderBean, "items(111)")
 
4.2 PropertyUtils,获取属性的Class类型
     public static Class getPropertyType(Object bean, String name)
 
4.3 ConstructorUtils,动态创建对象
      public static Object invokeConstructor(Class klass, Object arg)
4.4 MethodUtils,动态调用方法
    MethodUtils.invokeMethod(bean, methodName, parameter);
4.5 动态Bean 用DynaBean减除不必要的VO和FormBean 
posted @ 2009-12-30 11:24 shiwf 阅读(43621) | 评论 (4)编辑 收藏
 

1.BeanUtils基本用法:

java 代码
  1. package com.beanutil;   
  2.   
  3. import java.util.Map;   
  4.   
  5. public class User {   
  6.   
  7.     private Integer id;   
  8.     private Map map;   
  9.     private String username;   
  10.     public Integer getId() {   
  11.         return id;   
  12.     }   
  13.     public void setId(Integer id) {   
  14.         this.id = id;   
  15.     }   
  16.     public Map getMap() {   
  17.         return map;   
  18.     }   
  19.     public void setMap(Map map) {   
  20.         this.map = map;   
  21.     }   
  22.     public String getUsername() {   
  23.         return username;   
  24.     }   
  25.     public void setUsername(String username) {   
  26.         this.username = username;   
  27.     }   
  28.        
  29.        
  30. }  
java 代码
  1. public class Order {   
  2.     private User user;   
  3.     private Integer id;   
  4.     private String desc;   
  5.     public String getDesc() {   
  6.         return desc;   
  7.     }   
  8.     public void setDesc(String desc) {   
  9.         this.desc = desc;   
  10.     }   
  11.     public Integer getId() {   
  12.         return id;   
  13.     }   
  14.     public void setId(Integer id) {   
  15.         this.id = id;   
  16.     }   
  17.     public User getUser() {   
  18.         return user;   
  19.     }   
  20.     public void setUser(User user) {   
  21.         this.user = user;   
  22.     }   
  23.        
  24.   
  25. }  

 

java 代码
  1.   
  2. import java.util.HashMap;   
  3. import java.util.Map;   
  4.   
  5. import org.apache.commons.beanutils.BeanUtils;   
  6.   
  7. public class Test {   
  8.        
  9.     private User user = new User();   
  10.     private Order order1 = new Order();   
  11.     private Order order2 = new Order();   
  12.     private Order order3 = new Order();   
  13.     private Map map = new HashMap();   
  14.     private User user1 = new User();   
  15.   
  16.     public Test(){   
  17.         init();   
  18.     }   
  19.     public static void main(String[] args) throws Exception{   
  20.         Test test = new Test();   
  21.         //输出某个对象的某个属性   
  22.         System.out.println(BeanUtils.getProperty(test.user, "username"));   
  23.            
  24.         //输出某个对象的内嵌属性,只要使用点号分隔   
  25.         System.out.println(BeanUtils.getProperty(test.order1, "user.username"));   
  26.            
  27.         //BeanUtils还支持List和Map类型的属性,对于Map类型,则需要以"属性名(key值)"的   
  28.         //对于Indexed,则为"属性名[索引值]",注意对于ArrayList和数组都可以用一样的方式进行操作   
  29.         System.out.println(BeanUtils.getProperty(test.user1, "map(order2).desc"));   
  30.   
  31.         //拷贝对象的属性值   
  32.         User tempUser = new User();   
  33.         BeanUtils.copyProperties(tempUser, test.user1);   
  34.            
  35.         System.out.println(tempUser.getUsername());   
  36.         System.out.println(tempUser.getId());   
  37.            
  38.            
  39.            
  40.            
  41.     }   
  42.        
  43.     //初始化   
  44.     public void init(){   
  45.            
  46.            
  47.         user.setId(0);   
  48.         user.setUsername("zhangshan");   
  49.            
  50.            
  51.         order1.setId(1);   
  52.         order1.setDesc("order1");   
  53.         order1.setUser(user);   
  54.            
  55.            
  56.            
  57.         order2.setId(2);   
  58.         order2.setDesc("order2");   
  59.         order2.setUser(user);   
  60.            
  61.            
  62.         order3.setId(3);   
  63.         order3.setDesc("order3");   
  64.         order3.setUser(user);   
  65.            
  66.            
  67.         map.put("order1", order1);   
  68.         map.put("order2", order2);   
  69.         map.put("order3", order3);   
  70.            
  71.            
  72.         user1.setId(1);   
  73.         user1.setUsername("lisi");   
  74.         user1.setMap(map);   
  75.            
  76.            
  77.     }   
  78. }   

 

输出结果为:

zhangshan
zhangshan
order2
lisi
1

 

2. BeanCompartor 动态排序

A:动态设定Bean按照哪个属性来排序,而不再需要再实现bean的Compare接口进行复杂的条件判断

java 代码
  1. //动态设定Bean按照哪个属性来排序,而不再需要再实现bean的Compare接口进行复杂的条件判断   
  2.         List<order></order> list = new ArrayList<order></order>();   
  3.            
  4.         list.add(test.order2);   
  5.         list.add(test.order1);   
  6.         list.add(test.order3);   
  7.            
  8.         //未排序前   
  9.         for(Order order : list){   
  10.             System.out.println(order.getId());   
  11.         }   
  12.         //排序后   
  13.         Collections.sort(list, new BeanComparator("id"));   
  14.         for(Order order : list){   
  15.             System.out.println(order.getId());   
  16.         }  

 

B:支持多个属性的复合排序

java 代码
  1. //支持多个属性的复合排序   
  2.         List <beancomparator></beancomparator> sortFields = new ArrayList<beancomparator></beancomparator>();    
  3.         sortFields.add(new BeanComparator("id"));   
  4.         sortFields.add(new BeanComparator("desc"));   
  5.         ComparatorChain multiSort = new ComparatorChain(sortFields);   
  6.         Collections.sort(list, multiSort);   
  7.            
  8.         for(Order order : list){   
  9.             System.out.println(order.getId());   
  10.         }  

 

C:使用ComparatorUtils进一步指定排序条件

上面的排序遇到属性为null就会抛出异常, 也不能设定升序还是降序。
  不过,可以借助commons-collections包的ComparatorUtils
  BeanComparator,ComparableComparator和ComparatorChain都是实现了Comparator这个接口

java 代码
  1. //上面的排序遇到属性为null就会抛出异常, 也不能设定升序还是降序。   
  2.         //不过,可以借助commons-collections包的ComparatorUtils   
  3.         //BeanComparator,ComparableComparator和ComparatorChain都是实现了Comparator这个接口   
  4.         Comparator mycmp = ComparableComparator.getInstance();      
  5.         mycmp = ComparatorUtils.nullLowComparator(mycmp);  //允许null      
  6.         mycmp = ComparatorUtils.reversedComparator(mycmp); //逆序      
  7.         Comparator cmp = new BeanComparator("id", mycmp);   
  8.         Collections.sort(list, cmp);   
  9.         for(Order order : list){   
  10.             System.out.println(order.getId());   
  11.         }  
posted @ 2009-12-30 11:20 shiwf 阅读(914) | 评论 (0)编辑 收藏
 
     摘要:  CXF 最新版本:2.2.2开源服务框架,可以通过API,如JAX-WS,构建和开发服务。服务可以使多种协议的,例如SOAP, XML/HTTP, RESTful HTTP,  CORBA,并可以工作与多种传输协议之上,如HTTP,JMS,JBI。 主要特性 ·支持Webservice标准:包括SOAP, the Basic Profile, WSDL, ...  阅读全文
posted @ 2009-12-14 21:24 shiwf 阅读(2539) | 评论 (0)编辑 收藏
 

JFreeChart类:
void setAntiAlias(boolean flag)    字体模糊边界
void setBackgroundImage(Image image)   背景图片
void setBackgroundImageAlignment(int alignment)  背景图片对齐方式(参数常量在org.jfree.ui.Align类中定义)
void setBackgroundImageAlpha(float alpha)  背景图片透明度(0.0~1.0)
void setBackgroundPaint(Paint paint)   背景色
void setBorderPaint(Paint paint)   边界线条颜色
void setBorderStroke(Stroke stroke)   边界线条笔触
void setBorderVisible(boolean visible)   边界线条是否可见

--------------------------------------------------------------------------------------

TextTitle类:
void setFont(Font font)     标题字体
void setPaint(Paint paint)    标题字体颜色
void setText(String text)    标题内容

-----------------------------------------------------------------------------------

StandardLegend(Legend)类:
void setBackgroundPaint(Paint paint)   图示背景色
void setTitle(String title)    图示标题内容
void setTitleFont(Font font)    图示标题字体
void setBoundingBoxArcWidth(int arcWidth)  图示边界圆角宽
void setBoundingBoxArcHeight(int arcHeight)  图示边界圆角高
void setOutlinePaint(Paint paint)   图示边界线条颜色
void setOutlineStroke(Stroke stroke)   图示边界线条笔触
void setDisplaySeriesLines(boolean flag)  图示项是否显示横线(折线图有效)
void setDisplaySeriesShapes(boolean flag)  图示项是否显示形状(折线图有效)
void setItemFont(Font font)    图示项字体
void setItemPaint(Paint paint)    图示项字体颜色
void setAnchor(int anchor)    图示在图表中的显示位置(参数常量在Legend类中定义)

-------------------------------------------------------------------------------------------

Axis类:
void setVisible(boolean flag)    坐标轴是否可见
void setAxisLinePaint(Paint paint)   坐标轴线条颜色(3D轴无效)
void setAxisLineStroke(Stroke stroke)   坐标轴线条笔触(3D轴无效)
void setAxisLineVisible(boolean visible)  坐标轴线条是否可见(3D轴无效)
void setFixedDimension(double dimension)  (用于复合表中对多坐标轴的设置)
void setLabel(String label)    坐标轴标题
void setLabelFont(Font font)    坐标轴标题字体
void setLabelPaint(Paint paint)    坐标轴标题颜色
void setLabelAngle(double angle)`   坐标轴标题旋转角度(纵坐标可以旋转)
void setTickLabelFont(Font font)   坐标轴标尺值字体
void setTickLabelPaint(Paint paint)   坐标轴标尺值颜色
void setTickLabelsVisible(boolean flag)   坐标轴标尺值是否显示
void setTickMarkPaint(Paint paint)   坐标轴标尺颜色
void setTickMarkStroke(Stroke stroke)   坐标轴标尺笔触
void setTickMarksVisible(boolean flag)   坐标轴标尺是否显示

--------------------------------------------------------------------------

ValueAxis(Axis)类:

void setAutoRange(boolean auto)    自动设置数据轴数据范围
void setAutoRangeMinimumSize(double size)  自动设置数据轴数据范围时数据范围的最小跨度
void setAutoTickUnitSelection(boolean flag)  数据轴的数据标签是否自动确定(默认为true)
void setFixedAutoRange(double length)   数据轴固定数据范围(设置100的话就是显示MAXVALUE到MAXVALUE-100那段数据范围)
void setInverted(boolean flag)    数据轴是否反向(默认为false)
void setLowerMargin(double margin)   数据轴下(左)边距
void setUpperMargin(double margin)   数据轴上(右)边距
void setLowerBound(double min)    数据轴上的显示最小值
void setUpperBound(double max)    数据轴上的显示最大值
void setPositiveArrowVisible(boolean visible)  是否显示正向箭头(3D轴无效)
void setNegativeArrowVisible(boolean visible)  是否显示反向箭头(3D轴无效)
void setVerticalTickLabels(boolean flag)  数据轴数据标签是否旋转到垂直
void setStandardTickUnits(TickUnitSource source) 数据轴的数据标签(可以只显示整数标签,需要将AutoTickUnitSelection设false)

---------------------------------------------------------------------------
NumberAxis(ValueAxis)类:

void setAutoRangeIncludesZero(boolean flag)  是否强制在自动选择的数据范围中包含0
void setAutoRangeStickyZero(boolean flag)  是否强制在整个数据轴中包含0,即使0不在数据范围中
void setNumberFormatOverride(NumberFormat formatter) 数据轴数据标签的显示格式
void setTickUnit(NumberTickUnit unit)   数据轴的数据标签(需要将AutoTickUnitSelection设false)

DateAxis(ValueAxis)类:
void setMaximumDate(Date maximumDate)   日期轴上的最小日期
void setMinimumDate(Date minimumDate)   日期轴上的最大日期
void setRange(Date lower,Date upper)   日期轴范围
void setDateFormatOverride(DateFormat formatter) 日期轴日期标签的显示格式
void setTickUnit(DateTickUnit unit)   日期轴的日期标签(需要将AutoTickUnitSelection设false)
void setTickMarkPosition(DateTickMarkPosition position) 日期标签位置(参数常量在org.jfree.chart.axis.DateTickMarkPosition类中定义)

CategoryAxis(Axis)类:
void setCategoryMargin(double margin)   分类轴边距
void setLowerMargin(double margin)   分类轴下(左)边距
void setUpperMargin(double margin)   分类轴上(右)边距
void setVerticalCategoryLabels(boolean flag)  分类轴标题是否旋转到垂直
void setMaxCategoryLabelWidthRatio(float ratio)  分类轴分类标签的最大宽度

-----------------------------------------------------------------------------------------------------------

Plot类:
void setBackgroundImage(Image image)   数据区的背景图片
void setBackgroundImageAlignment(int alignment)  数据区的背景图片对齐方式(参数常量在org.jfree.ui.Align类中定义)
void setBackgroundPaint(Paint paint)   数据区的背景图片背景色
void setBackgroundAlpha(float alpha)   数据区的背景透明度(0.0~1.0)
void setForegroundAlpha(float alpha)   数据区的前景透明度(0.0~1.0)
void setDataAreaRatio(double ratio)   数据区占整个图表区的百分比
void setOutLinePaint(Paint paint)   数据区的边界线条颜色
void setOutLineStroke(Stroke stroke)   数据区的边界线条笔触
void setNoDataMessage(String message)   没有数据时显示的消息
void setNoDataMessageFont(Font font)   没有数据时显示的消息字体
void setNoDataMessagePaint(Paint paint)   没有数据时显示的消息颜色

CategoryPlot(Plot)类:
void setDataset(CategoryDataset dataset)  数据区的2维数据表
void setColumnRenderingOrder(SortOrder order)  数据分类的排序方式
void setAxisOffset(Spacer offset)   坐标轴到数据区的间距
void setOrientation(PlotOrientation orientation) 数据区的方向(PlotOrientation.HORIZONTAL或PlotOrientation.VERTICAL)
void setDomainAxis(CategoryAxis axis)   数据区的分类轴
void setDomainAxisLocation(AxisLocation location) 分类轴的位置(参数常量在org.jfree.chart.axis.AxisLocation类中定义)
void setDomainGridlinesVisible(boolean visible)  分类轴网格是否可见
void setDomainGridlinePaint(Paint paint)  分类轴网格线条颜色
void setDomainGridlineStroke(Stroke stroke)  分类轴网格线条笔触
void setRangeAxis(ValueAxis axis)   数据区的数据轴
void setRangeAxisLocation(AxisLocation location) 数据轴的位置(参数常量在org.jfree.chart.axis.AxisLocation类中定义)
void setRangeGridlinesVisible(boolean visible)  数据轴网格是否可见
void setRangeGridlinePaint(Paint paint)   数据轴网格线条颜色
void setRangeGridlineStroke(Stroke stroke)  数据轴网格线条笔触
void setRenderer(CategoryItemRenderer renderer)  数据区的表示者(详见Renderer组)
void addAnnotation(CategoryAnnotation annotation) 给数据区加一个注释
void addRangeMarker(Marker marker,Layer layer)  给数据区加一个数值范围区域

PiePlot(Plot)类:
void setDataset(PieDataset dataset)   数据区的1维数据表
void setIgnoreNullValues(boolean flag)   忽略无值的分类
void setCircular(boolean flag)    饼图是否一定是正圆
void setStartAngle(double angle)   饼图的初始角度
void setDirection(Rotation direction)   饼图的旋转方向
void setExplodePercent(int section,double percent) 抽取的那块(1维数据表的分类下标)以及抽取出来的距离(0.0~1.0),3D饼图无效
void setLabelBackgroundPaint(Paint paint)  分类标签的底色
void setLabelFont(Font font)    分类标签的字体
void setLabelPaint(Paint paint)    分类标签的字体颜色
void setLabelLinkMargin(double margin)   分类标签与图的连接线边距
void setLabelLinkPaint(Paint paint)   分类标签与图的连接线颜色
void setLabelLinkStroke(Stroke stroke)   分类标签与图的连接线笔触
void setLabelOutlinePaint(Paint paint)   分类标签边框颜色
void setLabelOutlineStroke(Paint paint)   分类标签边框笔触
void setLabelShadowPaint(Paint paint)   分类标签阴影颜色
void setMaximumLabelWidth(double width)   分类标签的最大长度(0.0~1.0)
void setPieIndex(int index)    饼图的索引(复合饼图中用到)
void setSectionOutlinePaint(int section,Paint paint) 指定分类饼的边框颜色
void setSectionOutlineStroke(int section,Stroke stroke) 指定分类饼的边框笔触
void setSectionPaint(int section,Paint paint)  指定分类饼的颜色
void setShadowPaint(Paint paint)   饼图的阴影颜色
void setShadowXOffset(double offset)   饼图的阴影相对图的水平偏移
void setShadowYOffset(double offset)   饼图的阴影相对图的垂直偏移
void setLabelGenerator(PieSectionLabelGenerator generator) 分类标签的格式,设置成null则整个标签包括连接线都不显示
void setToolTipGenerator(PieToolTipGenerator generator)  MAP中鼠标移上的显示格式
void setURLGenerator(PieURLGenerator generator)   MAP中钻取链接格式

PiePlot3D(PiePlot)类:
void setDepthFactor(double factor)   3D饼图的Z轴高度(0.0~1.0)

MultiplePiePlot(Plot)类:
void setLimit(double limit)    每个饼图之间的数据关联(详细比较复杂)  
void setPieChart(JFreeChart pieChart)   每个饼图的显示方式(见JFreeChart类个PiePlot类)

-----------------------------------------------------------------------------------------------------------

AbstractRenderer类:
void setItemLabelAnchorOffset(double offset)     数据标签的与数据点的偏移
void setItemLabelsVisible(boolean visible)     数据标签是否可见
void setItemLabelFont(Font font)      数据标签的字体
void setItemLabelPaint(Paint paint)      数据标签的字体颜色
void setItemLabelPosition(ItemLabelPosition position)    数据标签位置
void setPositiveItemLabelPosition(ItemLabelPosition position)   正数标签位置
void setNegativeItemLabelPosition(ItemLabelPosition position)   负数标签位置
void setOutLinePaint(Paint paint)      图形边框的线条颜色
void setOutLineStroke(Stroke stroke)      图形边框的线条笔触
void setPaint(Paint paint)       所有分类图形的颜色
void setShape(Shape shape)       所有分类图形的形状(如折线图的点)
void setStroke(Stroke stroke)       所有分类图形的笔触(如折线图的线)
void setSeriesItemLabelsVisible(int series,boolean visible)   指定分类的数据标签是否可见
void setSeriesItemLabelFont(int series,Font font)    指定分类的数据标签的字体
void setSeriesItemLabelPaint(int series,Paint paint)    指定分类的数据标签的字体颜色
void setSeriesItemLabelPosition(int series,ItemLabelPosition position)  数据标签位置
void setSeriesPositiveItemLabelPosition(int series,ItemLabelPosition position) 正数标签位置
void setSeriesNegativeItemLabelPosition(int series,ItemLabelPosition position) 负数标签位置
void setSeriesOutLinePaint(int series,Paint paint)    指定分类的图形边框的线条颜色
void setSeriesOutLineStroke(int series,Stroke stroke)    指定分类的图形边框的线条笔触
void setSeriesPaint(int series,Paint paint)     指定分类图形的颜色
void setSeriesShape(int series,Shape shape)     指定分类图形的形状(如折线图的点)
void setSeriesStroke(int series,Stroke stroke)     指定分类图形的笔触(如折线图的线)

AbstractCategoryItemRenderer(AbstractRenderer)类:
void setLabelGenerator(CategoryLabelGenerator generator)   数据标签的格式
void setToolTipGenerator(CategoryToolTipGenerator generator)   MAP中鼠标移上的显示格式
void setItemURLGenerator(CategoryURLGenerator generator)   MAP中钻取链接格式
void setSeriesLabelGenerator(int series,CategoryLabelGenerator generator) 指定分类的数据标签的格式
void setSeriesToolTipGenerator(int series,CategoryToolTipGenerator generator) 指定分类的MAP中鼠标移上的显示格式
void setSeriesItemURLGenerator(int series,CategoryURLGenerator generator) 指定分类的MAP中钻取链接格式

BarRenderer(AbstractCategoryItemRenderer)类:
void setDrawBarOutline(boolean draw)      是否画图形边框
void setItemMargin(double percent)      每个BAR之间的间隔
void setMaxBarWidth(double percent)      每个BAR的最大宽度
void setMinimumBarLength(double min)      最短的BAR长度,避免数值太小而显示不出
void setPositiveItemLabelPositionFallback(ItemLabelPosition position)  无法在BAR中显示的正数标签位置
void setNegativeItemLabelPositionFallback(ItemLabelPosition position)  无法在BAR中显示的负数标签位置

BarRenderer3D(BarRenderer)类:
void setWallPaint(Paint paint)    3D坐标轴的墙体颜色

StackedBarRenderer(BarRenderer)类:
没有特殊的设置

StackedBarRenderer3D(BarRenderer3D)类:
没有特殊的设置

GroupedStackedBarRenderer(StackedBarRenderer)类:
void setSeriesToGroupMap(KeyToGroupMap map)  将分类自由的映射成若干个组(KeyToGroupMap.mapKeyToGroup(series,group))

LayeredBarRenderer(BarRenderer)类:
void setSeriesBarWidth(int series,double width)  设定每个分类的宽度(注意设置不要使某分类被覆盖)

WaterfallBarRenderer(BarRenderer)类:
void setFirstBarPaint(Paint paint)   第一个柱图的颜色
void setLastBarPaint(Paint paint)   最后一个柱图的颜色
void setPositiveBarPaint(Paint paint)   正值柱图的颜色
void setNegativeBarPaint(Paint paint)   负值柱图的颜色

IntervalBarRenderer(BarRenderer)类:
需要传IntervalCategoryDataset作为数据源

GanttBarRenderer(IntervalBarRenderer)类:
void setCompletePaint(Paint paint)   完成进度颜色
void setIncompletePaint(Paint paint)   未完成进度颜色
void setStartPercent(double percent)   设置进度条在整条中的起始位置(0.0~1.0)
void setEndPercent(double percent)   设置进度条在整条中的结束位置(0.0~1.0)

StatisticBarRenderer(BarRenderer)类:
需要传StatisticCategoryDataset作为数据源

LineAndShapeRenderer(AbstractCategoryItemRenderer)类:
void setDrawLines(boolean draw)    是否折线的数据点之间用线连
void setDrawShapes(boolean draw)   是否折线的数据点根据分类使用不同的形状
void setShapesFilled(boolean filled)   所有分类是否填充数据点图形
void setSeriesShapesFilled(int series,boolean filled) 指定分类是否填充数据点图形
void setUseFillPaintForShapeOutline(boolean use) 指定是否填充数据点的Paint也被用于画数据点形状的边框

LevelRenderer(AbstractCategoryItemRenderer)类:
void setItemMargin(double percent)   每个分类之间的间隔
void setMaxItemWidth(double percent)   每个分类的最大宽度

CategoryStepRenderer(AbstractCategoryItemRenderer)类:
void setStagger(boolean shouldStagger)   不同分类的图是否交错

MinMaxCategoryRenderer(AbstractCategoryItemRenderer)类:
void setDrawLines(boolean drawLines)   是否在每个分类线间画连接线
void setGroupPaint(Paint groupPaint)   一组图形连接线的颜色
void setGroupStroke(Stroke groupStroke)   一组图形连接线的笔触
void setMaxIcon(Icon maxIcon)    最大值的ICON
void setMinIcon(Icon minIcon)    最小值的ICON
void setObjectIcon(Icon objectIcon)   所有值的ICON

AreaRender(AbstractCategoryItemRenderer)类:
没有特殊的设置

StackedAreaRender(AreaRender)类:
没有特殊的设置

posted @ 2009-12-11 16:44 shiwf 阅读(324) | 评论 (0)编辑 收藏
 
        要运行sample下的例子,首先你要安装ant,并设置好环境变量 。然后到dos方式下,到某一个sample的目录,运行 ant view 则会展现报表

1. alterdesign
该例子演示了报表编译后,在报表展现的时候如何动态的控制其中的元素比如让某一个矩形变色或其他

2. antcompile
演示如何让 ant 来编译

3. chart
演示了如何在报表中添加图像,JasperReport是用Scriptlet的方式往报表中添加图像,而Scriptlet是调用也是开源的jfreechart的Api来生成图形,去jfreechart看一下,该工具能的图形生成能力也很强

4. datasource

演示了如何在报表中使用各种数据源,能够使用beanarray beancollection,也可以用自定义的数据源,只要继承了JRDataSource的
两个接口,这个能给用户提供非常大的灵活性,报表的数据不用局限于一条Sql语句,也可以使用存储过程,对生成报表中的数据也可以排序,二 次检索,等等

5. fonts
各种字体的演示

6. horizontal
演示了水平分栏的报表,演示报表中分了三栏,其中还用到了textFieldExpression,就像if语句的效果来选择输出的内容

7. hyperlink
演示了各种样式的链接

8. images
演示了如何在报表中加入图像以及图像的显示方式

9. jasper
演示了分组分栏的报表,演示中用了2次group

10. jcharts
演示了调用另一个开源的API jcharts来往报表中加入分析图形,原理同上chart,如果jfreechart都还不能满足你分析图形的要求,那到jcharts里找找看吧,说不定有

11. landscape
演示横向的报表

12. nopagebreak
演示比如在IE中不分页的方式打印出报表的内容,通过这个演示也可以了解报表输出如何配置参数

13. noreport
演示了如何直接通过java程序生成JasperPrint对象来输出

14. noxmldesign
演示了如何直接通过java程序生成JasperDesign对象来动态的生成报表,根据这个例子,用户可以作出自定义选列的报表,当然比较麻烦,而且肯定自己要补充他的API库(JasperReport真是强大啊,呵呵)

15. pdfencrypt

演示了pdf的输出方式,可以给pdf文件加密码,其实就是pdf输出方式的参数配置,具体有那些参数可配置,去看看API吧

16. printservice
演示了如何直接打印报表

17. query

演示了如何让查询的sql动态起来,比如你可以通过一个Jsp页面传报表的sql的where条件,order条件,甚至整个sql语句

18. rotation

演示了文字纵向显示的报表

19. scriptlet
演示了如何继承JRDefaultScriptlet,并加入自己的处理语句,这个功能可是很强大的哦,看看这些接口
beforeReportInit()
afterReportInit()
beforePageInit()
afterPageInit()
beforeColumnInit()
afterColumnInit()
beforeGroupInit(String groupName)
afterGroupInit(String groupName)
看看这些名字就知道你能完成那些功能,比如显示一列数据后,马上跟上该列数据的分析图形,当然你也可以加上自己的方法并在报表中调用

20. shapes
演示了JasperReport中自带的图形,及能配置的参数当然你也能继承或者覆写JasperReport中的Api生成你要的图形,

21. stretch
演示了如何处理报表中数据拉伸以及带来周围的线及框的拉伸,你能了解到虽然黑框式表格不是JasperReport中缺省的展现方式,
但在JasperReport中不难实现

22. subreport

演示了子报表,还告诉你一个报表中可以有n个子报表,子报表中还可以嵌套子报表

23. tableofcontents
演示了如何生成一个有目录的,复杂的报表

24. unicode
演示了各种 字符编码

25. webapp

演示了如何把报表放到一个JavaWeb项目中,可以用Jsp Servlet applet

posted @ 2009-12-08 14:17 shiwf 阅读(611) | 评论 (0)编辑 收藏
 
JasperReports 是什么
JasperReports是一个面向开发人员设计的开源Java类库, 通过它可以为Java应用程序增加报表功能。由于 JasperReports 不是独立的工具,所以不能对它进行独立安装。而是要通过应用程序的 CLASSPATH 来包含其类库,从而把它嵌入到 Java应用程序中。JasperReports 是一个 Java类库,也就是说它不是为最终用户准备的。它的目标用户是那些需要为应用程序添加报表功能的Java
开发人员。

JasperReports采用 Lesser GNU Public Library (LGPL)许可协议,所以开放源代码的或不开放源代码的应用程序都可以使用它。通过链接来使用JasperReports 类库的应用程序不需要开放源代码,而需要对现有JasperReports 源代码进行修改的,那么所修改的内容必须也遵循 LGPL 进行发布。更详细的说明可参考 http://www.gnu.org/copyleft/lesser.html。尽管 JasperReports 主要用于通过 Servlet API 来为基于 Web 的应用程序增加报表功能,但它并不是完全依赖于 Servlet API或任何 Java EE类库。因此,它并不仅限于 Web 应用程序。用 JasperReports 来建立独立的桌面程序或命令行程序来生成报表的开发从未停止过。可是,话说又回来,JasperReports除了是一个 Java类库之外,什么都不是。它做的事情只是通过提供 API来为各种Java应用程序增加生成报表的功能。
JasperReports需要 Java Development Kit (JDK) 1.4或更新的版本来进行编译,以便和 JasperReports 的 Java 类库一同工作。同时还需要 Java Runtime Environment (JRE) 1.3或更新的版本来运行这些应用程序。早期版本的 JasperReports 需要 JDK 来运行 JasperReports 应用程序 (严格地讲,JasperReports 需要 tools.jar 被设置在 CLASSPATH 环境变量中,JDK包含了 tools.jar,而 JRE 中没有)。然而,从 0.6.4 版以后,JasperReports把 Eclipse Java Development Tools (JDT)编译器捆绑在一起,因此不再需
要 JDK 来运行部署后的应用程序。本书的例子是用 JDK1.6 开发的,但它们在JasperReports支持的任何其它 JDK 或 JRE上也应该能够顺利地编译和运行。 

JasperReports 的特点
JasperReports 除了以文本数据方式生成报表外,还可以生成包含图片、图表和图形的专业报表。JasperReports的主要特点包括: 
•  灵活的报表排版
•  多样的数据表现方式
•  多样的数据提供方式
•  支持从多种数据源接收数据
•  能够生成水印
•  能够生成子报表
此外,它还可以用许多种格式来输出报表。下面的各小节将对这些特点做简要介绍。

类库依赖
JasperReports借用了其它的开源Java类库来实现其部分功能,其中包括:
iText: 一个用于生成和处理 PDF的类库。另外,它还可以生成和处理 RTF、XML和 HTML文档。JasperReports用它来导出 PDF和 RTF 格式的报表。要获得有关 iText 的详细介绍,可以访问 http://www.lowagie.com/iText/。

JFreeChart: 一个 Java类库,可用于生成各种图表,包括:饼图、条形图、线形图、区域图、等等。JasperReports通过 JFreeChart 来实现其图表功能。有关 JFreeChart 的详细介绍可以查阅http://www.jfree.org/jfreechart/。
 
Apache POI: 一个Java类库, 用于创建和处理各种建立在Microsoft的OLE2混合文档格式基础上的Microsoft Office格式的文档。 JasperReports通过POI来导出XLS 格式的报表,更多的 Jakarta POI有关介绍可查阅http://poi.apache.org/

JAXP: 用于解析和转换XML文档的 Java API,JasperReports用它来解析XML文件。JAXP 包含在 Java SE 5.0中。如果使用更早版本的Java SE,也可以要独立地下载它。有关 JAXP的详细介绍可以查阅https://jaxp.dev.java.net/

Apache Commons: 一套 Java类库,提供了大量的可重用组件。JasperReports使用了其中的 Commons Digester、BeanUtils、Logging组件来辅助JAXP 解析XML。关于 Apache Commons的详细介绍可查阅http://commons.apache.org/

典型的开发流程
下面的图形给出了用 JasperReports创建报表的典型开发流程: 

用 JasperReports进行开发时,第一步要创建报表模板,它是一个 XML文件。它可以通过手工编码来完成,也可以用图形化的报表设计软件完成。虽然JasperReports的报表模板是 XML文件,但其文件名却用.jrxml 来作为扩展名。JasperReports XML模板通常就是指 JRXML文件,本书中也使用这一术语。



posted @ 2009-12-08 13:58 shiwf 阅读(2165) | 评论 (0)编辑 收藏
 
     摘要:     1     前言 1 2     阅读本篇的基础准备 2 2.1      概念的基础 2.2      环境的基础 3     什么是...  阅读全文
posted @ 2009-12-04 12:47 shiwf 阅读(841) | 评论 (2)编辑 收藏
 

jbpm概念
1 : process definition(流程定义):
         工作流的流程的完整定义,包括节点和节点之间的走向等关键信息。通常以xml格式提供。一个具体的系统往往是由许多个流程组成的。
2 : process instance(流程实例):
         每个process defination生成的业务层的实例。当process instance创建以后,代表流程的执行路径,并被定义到开始节点。
3  : token(令牌):
         表示了一个执行的路径,它是运行时产生的。当实例建立以后,令牌也就产生了。
4 : node:
         表示流程中的一个节点。
5 : transition:
         关联两个节点,用于表示节点的走向
6 : signal:
         让一个token执行下一步。process instance也有signal,当用process instance的signal时,其实就是运行process instance根令牌(root token)的signal.   当token进入到一个node时,node会被执行,并产生一些事件,比如进入、离开节点等,这也是执行业务逻辑的地方。事件由action来表示。
7 : 事件Event 
        Event反映的是流程执行中的各个时刻。在流程执行中JBPM引擎会在计算下一个状态的时候触发各种事件。一个事件通常和流程定义中的一个元素相关联,比如流程定义本身,节点或者转移。大部分的元素能够触发不同类型的事件,比如一个节点可以触发节点进入事件,节点离开事件。事件其实是和动作连接在一起的。每个事件维护一个动作列表。当JBPM引擎触发一个事件的时候,该事件维护的动作列表中的动作将被执行。
 

事件类型
在JBPM中事件类型是写死在事件类中的,共有16种:
EVENTTYPE_TRANSITION = "transition"; // 转移
EVENTTYPE_BEFORE_SIGNAL = "before-signal"; // 发信号前
EVENTTYPE_AFTER_SIGNAL = "after-signal"; // 发信号后
EVENTTYPE_PROCESS_START = "process-start"; // 处理开始状态
EVENTTYPE_PROCESS_END = "process-end"; // 处理结束状态
EVENTTYPE_NODE_ENTER = "node-enter"; // 进入节点
EVENTTYPE_NODE_LEAVE = "node-leave"; // 离开节点
EVENTTYPE_SUPERSTATE_ENTER = "superstate-enter"; // 进入超级状态
EVENTTYPE_SUPERSTATE_LEAVE = "superstate-leave"; // 离开超级状态
EVENTTYPE_SUBPROCESS_CREATED = "subprocess-created"; // 子流程创建
EVENTTYPE_SUBPROCESS_END = "subprocess-end"; // 子流程结束
EVENTTYPE_TASK_CREATE = "task-create"; // 任务创建
EVENTTYPE_TASK_ASSIGN = "task-assign"; // 任务分派
EVENTTYPE_TASK_START = "task-start"; // 任务启动
EVENTTYPE_TASK_END = "task-end"; // 任务结束
EVENTTYPE_TIMER = "timer"; // 定时器

常用API
ProcessInstance是ProcessDefinition的一个执行实例,想象一下对于订票流程,每个客户的订票动作都会根据订票流程定义而创建一个流程实例,也就是执行实例ProcessInstance.当一个ProcessInstance被创建后,负责执行主路径的token也被创建,这个token就是根token(root token),根token此时位于流程定义的开始状态start state.

创建执行实例很简单有2种方式 :
1 : 通过 ProcessDefinition 类的 createProcessInstance() 方法

//得到 processDefinition 
ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("processdefinition.xml");
//通过 processDefinition 创建 出 processInstance
ProcessInstance  processInstance = processDefinition.createProcessInstance();

2 :通过 ProcessInstance 类的  构造函数
//得到 jbpmContext
JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();

//得到 processDefinition
ProcessDefinition processDefinition = jbpmContext.getGraphSession().findLatestProcessDefinition("baoxiao");

//得到 processInstance
 ProcessInstance processInstance = new ProcessInstance(processDefinition);

 

posted @ 2009-12-04 12:42 shiwf 阅读(629) | 评论 (0)编辑 收藏
 

本文转载:http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/

EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,它能利用对接口或类的模拟来辅助单元测试。本文将对 EasyMock 的功能和原理进行介绍,并通过示例来说明如何使用 EasyMock 进行单元测试。

Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。

编写自定义的 Mock 对象需要额外的编码工作,同时也可能引入错误。EasyMock 提供了根据指定接口动态构建 Mock 对象的方法,避免了手工编写 Mock 对象。本文将向您展示如何使用 EasyMock 进行单元测试,并对 EasyMock 的原理进行分析。

1.Mock 对象与 EasyMock 简介

单元测试与 Mock 方法

单元测试是对应用中的某一个模块的功能进行验证。在单元测试中,我们常遇到的问题是应用中其它的协同模块尚未开发完成,或者被测试模块需要和一些不容易构造、比较复杂的对象进行交互。另外,由于不能肯定其它模块的正确性,我们也无法确定测试中发现的问题是由哪个模块引起的。

Mock 对象能够模拟其它协同模块的行为,被测试模块通过与 Mock 对象协作,可以获得一个孤立的测试环境。此外,使用 Mock 对象还可以模拟在应用中不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)和比较复杂的对象(如 JDBC 中的 ResultSet 对象),从而使测试顺利进行。

EasyMock 简介

手动的构造 Mock 对象会给开发人员带来额外的编码量,而且这些为创建 Mock 对象而编写的代码很有可能引入错误。目前,有许多开源项目对动态构建 Mock 对象提供了支持,这些项目能够根据现有的接口或类动态生成,这样不仅能避免额外的编码工作,同时也降低了引入错误的可能。

EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。

安装 EasyMock

EasyMock 是采用 MIT license 的一个开源项目,您可以在 Sourceforge 上下载到相关的 zip 文件。目前您可以下载的 EasyMock 最新版本是2.3,它需要运行在 Java 5.0 平台上。如果您的应用运行在 Java 1.3 或 1.4 平台上,您可以选择 EasyMock1.2。在解压缩 zip 包后,您可以找到 easymock.jar 这个文件。如果您使用 Eclipse 作为 IDE,把 easymock.jar 添加到项目的 Libraries 里就可以使用了(如下图所示)。此外,由于我们的测试用例运行在 JUnit 环境中,因此您还需要 JUnit.jar(版本3.8.1以上)。


图1:Eclipse 项目中的 Libraries
Eclipse 项目中的 Libraries 

2.使用 EasyMock 进行单元测试

通过 EasyMock,我们可以为指定的接口动态的创建 Mock 对象,并利用 Mock 对象来模拟协同模块或是领域对象,从而使单元测试顺利进行。这个过程大致可以划分为以下几个步骤:

  • 使用 EasyMock 生成 Mock 对象;
  • 设定 Mock 对象的预期行为和输出;
  • 将 Mock 对象切换到 Replay 状态;
  • 调用 Mock 对象方法进行单元测试;
  • 对 Mock 对象的行为进行验证。

接下来,我们将对以上的几个步骤逐一进行说明。除了以上的基本步骤外,EasyMock 还对特殊的 Mock 对象类型、特定的参数匹配方式等功能提供了支持,我们将在之后的章节中进行说明。

使用 EasyMock 生成 Mock 对象

根据指定的接口或类,EasyMock 能够动态的创建 Mock 对象(EasyMock 默认只支持为接口生成 Mock 对象,如果需要为类生成 Mock 对象,在 EasyMock 的主页上有扩展包可以实现此功能),我们以 ResultSet 接口为例说明EasyMock的功能。java.sql.ResultSet 是每一个 Java 开发人员都非常熟悉的接口:


清单1:ResultSet 接口
            public interface java.sql.ResultSet {
            ......
            public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;
            public abstract double getDouble(int arg0) throws java.sql.SQLException;
            ......
            }
            

通常,构建一个真实的 RecordSet 对象需要经过一个复杂的过程:在开发过程中,开发人员通常会编写一个 DBUtility 类来获取数据库连接 Connection,并利用 Connection 创建一个 Statement。执行一个 Statement 可以获取到一个或多个 ResultSet 对象。这样的构造过程复杂并且依赖于数据库的正确运行。数据库或是数据库交互模块出现问题,都会影响单元测试的结果。

我们可以使用 EasyMock 动态构建 ResultSet 接口的 Mock 对象来解决这个问题。一些简单的测试用例只需要一个 Mock 对象,这时,我们可以用以下的方法来创建 Mock 对象:
ResultSet mockResultSet = createMock(ResultSet.class);
            

其中 createMockorg.easymock.EasyMock 类所提供的静态方法,你可以通过 static import 将其引入(注:static import 是 java 5.0 所提供的新特性)。

如果需要在相对复杂的测试用例中使用多个 Mock 对象,EasyMock 提供了另外一种生成和管理 Mock 对象的机制:
IMocksControl control = EasyMock.createControl();
            java.sql.Connection mockConnection = control.createMock(Connection.class);
            java.sql.Statement mockStatement = control.createMock(Statement.class);
            java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);
            

EasyMock 类的 createControl 方法能创建一个接口 IMocksControl 的对象,该对象能创建并管理多个 Mock 对象。如果需要在测试中使用多个 Mock 对象,我们推荐您使用这一机制,因为它在多个 Mock 对象的管理上提供了相对便捷的方法。

如果您要模拟的是一个具体类而非接口,那么您需要下载扩展包 EasyMock Class Extension 2.2.2。在对具体类进行模拟时,您只要用 org.easymock.classextension.EasyMock 类中的静态方法代替 org.easymock.EasyMock 类中的静态方法即可。

设定 Mock 对象的预期行为和输出

在一个完整的测试过程中,一个 Mock 对象将会经历两个状态:Record 状态和 Replay 状态。Mock 对象一经创建,它的状态就被置为 Record。在 Record 状态,用户可以设定 Mock 对象的预期行为和输出,这些对象行为被录制下来,保存在 Mock 对象中。

添加 Mock 对象行为的过程通常可以分为以下3步:

  • 对 Mock 对象的特定方法作出调用;
  • 通过 org.easymock.EasyMock 提供的静态方法 expectLastCall 获取上一次方法调用所对应的 IExpectationSetters 实例;
  • 通过 IExpectationSetters 实例设定 Mock 对象的预期输出。

 

设定预期返回值

Mock 对象的行为可以简单的理解为 Mock 对象方法的调用和方法调用所产生的输出。在 EasyMock 2.3 中,对 Mock 对象行为的添加和设置是通过接口 IExpectationSetters 来实现的。Mock 对象方法的调用可能产生两种类型的输出:(1)产生返回值;(2)抛出异常。接口 IExpectationSetters 提供了多种设定预期输出的方法,其中和设定返回值相对应的是 andReturn 方法:
IExpectationSetters<T> andReturn(T value);

我们仍然用 ResultSet 接口的 Mock 对象为例,如果希望方法 mockResult.getString(1) 的返回值为 "My return value",那么你可以使用以下的语句:
mockResultSet.getString(1);
            expectLastCall().andReturn("My return value");
            

以上的语句表示 mockResultSetgetString 方法被调用一次,这次调用的返回值是 "My return value"。有时,我们希望某个方法的调用总是返回一个相同的值,为了避免每次调用都为 Mock 对象的行为进行一次设定,我们可以用设置默认返回值的方法:
void andStubReturn(Object value);
            

假设我们创建了 StatementResultSet 接口的 Mock 对象 mockStatement 和 mockResultSet,在测试过程中,我们希望 mockStatement 对象的 executeQuery 方法总是返回 mockResultSet,我们可以使用如下的语句
mockStatement.executeQuery("SELECT * FROM sales_order_table");
            expectLastCall().andStubReturn(mockResultSet);
            

EasyMock 在对参数值进行匹配时,默认采用 Object.equals() 方法。因此,如果我们以 "select * from sales_order_table" 作为参数,预期方法将不会被调用。如果您希望上例中的 SQL 语句能不区分大小写,可以用特殊的参数匹配器来解决这个问题,我们将在 "在 EasyMock 中使用参数匹配器" 一章对此进行说明。

设定预期异常抛出

对象行为的预期输出除了可能是返回值外,还有可能是抛出异常。IExpectationSetters 提供了设定预期抛出异常的方法:
IExpectationSetters<T> andThrow(Throwable throwable);
            

和设定默认返回值类似,IExpectationSetters 接口也提供了设定抛出默认异常的函数:
void andStubThrow(Throwable throwable);
            

设定预期方法调用次数

通过以上的函数,您可以对 Mock 对象特定行为的预期输出进行设定。除了对预期输出进行设定,IExpectationSetters 接口还允许用户对方法的调用次数作出限制。在 IExpectationSetters 所提供的这一类方法中,常用的一种是 times 方法:
IExpectationSetters<T>times(int count);
            

该方法可以 Mock 对象方法的调用次数进行确切的设定。假设我们希望 mockResultSet 的 getString 方法在测试过程中被调用3次,期间的返回值都是 "My return value",我们可以用如下语句:
mockResultSet.getString(1);
            expectLastCall().andReturn("My return value").times(3);
            


注意到 andReturnandThrow 方法的返回值依然是一个 IExpectationSetters 实例,因此我们可以在此基础上继续调用 times 方法。

除了设定确定的调用次数,IExpectationSetters 还提供了另外几种设定非准确调用次数的方法:
times(int minTimes, int maxTimes):该方法最少被调用 minTimes 次,最多被调用 maxTimes 次。
atLeastOnce():该方法至少被调用一次。
anyTimes():该方法可以被调用任意次。

某些方法的返回值类型是 void,对于这一类方法,我们无需设定返回值,只要设置调用次数就可以了。以 ResultSet 接口的 close 方法为例,假设在测试过程中,该方法被调用3至5次:
mockResultSet.close();
            expectLastCall().times(3, 5);
            

为了简化书写,EasyMock 还提供了另一种设定 Mock 对象行为的语句模式。对于上例,您还可以将它写成:
expect(mockResult.close()).times(3, 5);
            


这个语句和上例中的语句功能是完全相同的。

将 Mock 对象切换到 Replay 状态

在生成 Mock 对象和设定 Mock 对象行为两个阶段,Mock 对象的状态都是 Record 。在这个阶段,Mock 对象会记录用户对预期行为和输出的设定。

在使用 Mock 对象进行实际的测试前,我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态,Mock 对象能够根据设定对特定的方法调用作出预期的响应。将 Mock 对象切换成 Replay 状态有两种方式,您需要根据 Mock 对象的生成方式进行选择。如果 Mock 对象是通过 org.easymock.EasyMock 类提供的静态方法 createMock 生成的(第1节中介绍的第一种 Mock 对象生成方法),那么 EasyMock 类提供了相应的 replay 方法用于将 Mock 对象切换为 Replay 状态:
replay(mockResultSet);
            

如果 Mock 对象是通过 IMocksControl 接口提供的 createMock 方法生成的(第1节中介绍的第二种Mock对象生成方法),那么您依旧可以通过 IMocksControl 接口对它所创建的所有 Mock 对象进行切换:
control.replay();
            

以上的语句能将在第1节中生成的 mockConnection、mockStatement 和 mockResultSet 等3个 Mock 对象都切换成 Replay 状态。

调用 Mock 对象方法进行单元测试

为了更好的说明 EasyMock 的功能,我们引入 src.zip 中的示例来解释 Mock 对象在实际测试阶段的作用。其中所有的示例代码都可以在 src.zip 中找到。如果您使用的 IDE 是 Eclipse,在导入 src.zip 之后您可以看到 Workspace 中增加的 project(如下图所示)。


图2:导入 src.zip 后的 Workspace
导入src.zip后的Workspace

下面是示例代码中的一个接口 SalesOrder,它的实现类 SalesOrderImpl 的主要功能是从数据库中读取一个 Sales Order 的 Region 和 Total Price,并根据读取的数据计算该 Sales Order 的 Price Level(完整的实现代码都可以在 src.zip 中找到):


清单2:SalesOrder 接口
            public interface SalesOrder
            {
            ……
            public void loadDataFromDB(ResultSet resultSet) throws SQLException;
            public String getPriceLevel();
            }
            

其实现类 SalesOrderImpl 中对 loadDataFromDB 的实现如下:


清单3:SalesOrderImpl 实现
            public class SalesOrderImpl implements SalesOrder
            {
            ......
            public void loadDataFromDB(ResultSet resultSet) throws SQLException
            {
            orderNumber = resultSet.getString(1);
            region = resultSet.getString(2);
            totalPrice = resultSet.getDouble(3);
            }
            ......
            }
            

方法 loadDataFromDB 读取了 ResultSet 对象包含的数据。当我们将之前定义的 Mock 对象调整为 Replay 状态,并将该对象作为参数传入,那么 Mock 对象的方法将会返回预先定义的预期返回值。完整的 TestCase 如下:


清单4:完整的TestCase
            public class SalesOrderTestCase extends TestCase {
            public void testSalesOrder() {
            IMocksControl control = EasyMock.createControl();
            ......
            ResultSet mockResultSet = control.createMock(ResultSet.class);
            try {
            ......
            mockResultSet.next();
            expectLastCall().andReturn(true).times(3);
            expectLastCall().andReturn(false).times(1);
            mockResultSet.getString(1);
            expectLastCall().andReturn("DEMO_ORDER_001").times(1);
            expectLastCall().andReturn("DEMO_ORDER_002").times(1);
            expectLastCall().andReturn("DEMO_ORDER_003").times(1);
            mockResultSet.getString(2);
            expectLastCall().andReturn("Asia Pacific").times(1);
            expectLastCall().andReturn("Europe").times(1);
            expectLastCall().andReturn("America").times(1);
            mockResultSet.getDouble(3);
            expectLastCall().andReturn(350.0).times(1);
            expectLastCall().andReturn(1350.0).times(1);
            expectLastCall().andReturn(5350.0).times(1);
            control.replay();
            ......
            int i = 0;
            String[] priceLevels = { "Level_A", "Level_C", "Level_E" };
            while (mockResultSet.next()) {
            SalesOrder order = new SalesOrderImpl();
            order.loadDataFromDB(mockResultSet);
            assertEquals(order.getPriceLevel(), priceLevels[i]);
            i++;
            }
            control.verify();
            } catch (Exception e) {
            e.printStackTrace();
            }
            }
            }
            

在这个示例中,我们首先创建了 ResultSet 的 Mock 对象 moResultSet,并记录该 Mock 对象的预期行为。之后我们调用了 control.replay(),将 Mock 对象的状态置为 Replay 状态。在实际的测试阶段,Sales Order 对象的 loadDataFromDB 方法调用了 mockResultSet 对象的 getStringgetDouble 方法读取 mockResultSet 中的数据。Sales Order 对象根据读取的数据计算出 Price Level,并和预期输出进行比较。

对 Mock 对象的行为进行验证

在利用 Mock 对象进行实际的测试过程之后,我们还有一件事情没有做:对 Mock 对象的方法调用的次数进行验证。

为了验证指定的方法调用真的完成了,我们需要调用 verify 方法进行验证。和 replay 方法类似,您需要根据 Mock 对象的生成方式来选用不同的验证方式。如果 Mock 对象是由 org.easymock.EasyMock 类提供的 createMock 静态方法生成的,那么我们同样采用 EasyMock 类的静态方法 verify 进行验证:
verify(mockResultSet);
            

如果Mock对象是有 IMocksControl 接口所提供的 createMock 方法生成的,那么采用该接口提供的 verify 方法,例如第1节中的 IMocksControl 实例 control:
control.verify();
            

将对 control 实例所生成的 Mock 对象 mockConnection、mockStatement 和 mockResultSet 等进行验证。如果将上例中 expectLastCall().andReturn(false).times(1) 的预期次数修改为2,在 Eclipse 中将可以看到:


图3:Mock对象验证失败
Mock对象验证失败

Mock 对象的重用

为了避免生成过多的 Mock 对象,EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化,我们可以采用 reset 方法。和 replay 和 verify 方法类似,EasyMock 提供了两种 reset 方式:(1)如果 Mock 对象是由 org.easymock.EasyMock 类中的静态方法 createMock 生成的,那么该 Mock 对象的可以用 EasyMock 类的静态方法 reset 重新初始化;(2)如果 Mock 方法是由 IMocksControl 实例的 createMock 方法生成的,那么该 IMocksControl 实例方法 reset 的调用将会把所有该实例创建的 Mock 对象重新初始化。

在重新初始化之后,Mock 对象的状态将被置为 Record 状态。




3.在 EasyMock 中使用参数匹配器

EasyMock 预定义的参数匹配器

在使用 Mock 对象进行实际的测试过程中,EasyMock 会根据方法名和参数来匹配一个预期方法的调用。EasyMock 对参数的匹配默认使用 equals() 方法进行比较。这可能会引起一些问题。例如在上一章节中创建的mockStatement对象:
mockStatement.executeQuery("SELECT * FROM sales_order_table");
            expectLastCall().andStubReturn(mockResultSet);
            

在实际的调用中,我们可能会遇到 SQL 语句中某些关键字大小写的问题,例如将 SELECT 写成 Select,这时在实际的测试中,EasyMock 所采用的默认匹配器将认为这两个参数不匹配,从而造成 Mock 对象的预期方法不被调用。EasyMock 提供了灵活的参数匹配方式来解决这个问题。如果您对 mockStatement 具体执行的语句并不关注,并希望所有输入的字符串都能匹配这一方法调用,您可以用 org.easymock.EasyMock 类所提供的 anyObject 方法来代替参数中的 SQL 语句:
mockStatement.executeQuery( anyObject() );
            expectLastCall().andStubReturn(mockResultSet);
            

anyObject 方法表示任意输入值都与预期值相匹配。除了 anyObject 以外,EasyMock还提供了多个预先定义的参数匹配器,其中比较常用的一些有:

  • aryEq(X value):通过Arrays.equals()进行匹配,适用于数组对象;
  • isNull():当输入值为Null时匹配;
  • notNull():当输入值不为Null时匹配;
  • same(X value):当输入值和预期值是同一个对象时匹配;
  • lt(X value), leq(X value), geq(X value), gt(X value):当输入值小于、小等于、大等于、大于预期值时匹配,适用于数值类型;
  • startsWith(String prefix), contains(String substring), endsWith(String suffix):当输入值以预期值开头、包含预期值、以预期值结尾时匹配,适用于String类型;
  • matches(String regex):当输入值与正则表达式匹配时匹配,适用于String类型。

自定义参数匹配器

预定义的参数匹配器可能无法满足一些复杂的情况,这时你需要定义自己的参数匹配器。在上一节中,我们希望能有一个匹配器对 SQL 中关键字的大小写不敏感,使用 anyObject 其实并不是一个好的选择。对此,我们可以定义自己的参数匹配器 SQLEquals。

要定义新的参数匹配器,需要实现 org.easymock.IArgumentMatcher 接口。其中,matches(Object actual) 方法应当实现输入值和预期值的匹配逻辑,而在 appendTo(StringBuffer buffer) 方法中,你可以添加当匹配失败时需要显示的信息。以下是 SQLEquals 实现的部分代码(完整的代码可以在 src.zip 中找到):


清单5:自定义参数匹配器SQLEquals
            public class SQLEquals implements IArgumentMatcher {
            private String expectedSQL = null;
            public SQLEquals(String expectedSQL) {
            this.expectedSQL = expectedSQL;
            }
            ......
            public boolean matches(Object actualSQL) {
            if (actualSQL == null && expectedSQL == null)
            return true;
            else if (actualSQL instanceof String)
            return expectedSQL.equalsIgnoreCase((String) actualSQL);
            else
            return false;
            }
            }
            

在实现了 IArgumentMatcher 接口之后,我们需要写一个静态方法将它包装一下。这个静态方法的实现需要将 SQLEquals 的一个对象通过 reportMatcher 方法报告给EasyMock:


清单6:自定义参数匹配器 SQLEquals 静态方法
            public static String sqlEquals(String in) {
            reportMatcher(new SQLEquals(in));
            return in;
            }
            

这样,我们自定义的 sqlEquals 匹配器就可以使用了。我们可以将上例中的 executeQuery 方法设定修改如下:
mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"));
            expectLastCall().andStubReturn(mockResultSet);
            


在使用 executeQuery("select * from sales_order_table") 进行方法调用时,该预期行为将被匹配。





4.特殊的 Mock 对象类型

到目前为止,我们所创建的 Mock 对象都属于 EasyMock 默认的 Mock 对象类型,它对预期方法的调用次序不敏感,对非预期的方法调用抛出 AssertionError。除了这种默认的 Mock 类型以外,EasyMock 还提供了一些特殊的 Mock 类型用于支持不同的需求。

Strick Mock 对象

如果 Mock 对象是通过 EasyMock.createMock() 或是 IMocksControl.createMock() 所创建的,那么在进行 verify 验证时,方法的调用顺序是不进行检查的。如果要创建方法调用的先后次序敏感的 Mock 对象(Strick Mock),应该使用 EasyMock.createStrickMock() 来创建,例如:
ResultSet strickMockResultSet = createStrickMock(ResultSet.class);
            

类似于 createMock,我们同样可以用 IMocksControl 实例来创建一个 Strick Mock 对象:
IMocksControl control = EasyMock.createStrictControl();
            ResultSet strickMockResultSet = control.createMock(ResultSet.class);
            

Nice Mock 对象

使用 createMock() 创建的 Mock 对象对非预期的方法调用默认的行为是抛出 AssertionError,如果需要一个默认返回0,null 或 false 等"无效值"的 "Nice Mock" 对象,可以通过 EasyMock 类提供的 createNiceMock() 方法创建。类似的,你也可以用 IMocksControl 实例来创建一个 Nice Mock 对象。





5.EasyMock 的工作原理

EasyMock 是如何为一个特定的接口动态创建 Mock 对象,并记录 Mock 对象预期行为的呢?其实,EasyMock 后台处理的主要原理是利用 java.lang.reflect.Proxy 为指定的接口创建一个动态代理,这个动态代理,就是我们在编码中用到的 Mock 对象。EasyMock 还为这个动态代理提供了一个 InvocationHandler 接口的实现,这个实现类的主要功能就是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。下图是 EasyMock 中主要的功能类:


图4:EasyMock主要功能类
Mock对象验证失败

和开发人员联系最紧密的是 EasyMock 类,这个类提供了 createMock、replay、verify 等方法以及所有预定义的参数匹配器。

我们知道 Mock 对象有两种创建方式:一种是通过 EasyMock 类提供的 createMock 方法创建,另一种是通过 EasyMock 类的 createControl 方法得到一个 IMocksControl 实例,再由这个 IMocksControl 实例创建 Mock 对象。其实,无论通过哪种方法获得 Mock 对象,EasyMock 都会生成一个 IMocksControl 的实例,只不过第一种方式中的 IMocksControl 的实例对开发人员不可见而已。这个 IMocksControl 的实例,其实就是 MocksControl 类的一个对象。MocksControl 类提供了 andReturn、andThrow、times、createMock 等方法。

MocksControl 类中包含了两个重要的成员变量,分别是接口 IMocksBehaviorIMocksControlState 的实例。其中,IMocksBehavior 的实现类 MocksBehavior 是 EasyMock 的核心类,它保存着一个 ExpectedInvocationAndResult 对象的一个列表,而 ExpectedInvocationAndResult 对象中包含着 Mock 对象方法调用和预期结果的映射。MocksBehavior 类提供了 addExpectedaddActual 方法用于添加预期行为和实际调用。

MocksControl 类中包含的另一个成员变量是 IMocksControlState 实例。IMocksControlState 拥有两个不同的实现类:RecordStateReplayState。顾名思义,RecordState 是 Mock 对象在 Record 状态时的支持类,它提供了 invoke 方法在 Record 状态下的实现。此外,它还提供了 andReturn、andThrow、times 等方法的实现。ReplayState 是 Mock 对象在 Replay 状态下的支持类,它提供了 invoke 方法在 Replay 状态下的实现。在 ReplayState 中,andReturn、andThrow、times 等方法的实现都是抛出IllegalStateException,因为在 Replay 阶段,开发人员不应该再调用这些方法。

当我们调用 MocksControlcreateMock 方法时,该方法首先会生成一个 JavaProxyFactory 类的对象。JavaProxyFactory 是接口 IProxyFactory 的实现类,它的主要功能就是通过 java.lang.reflect.Proxy 对指定的接口创建动态代理实例,也就是开发人员在外部看到的 Mock 对象。

在创建动态代理的同时,应当提供 InvocationHandler 的实现类。MockInvocationHandler 实现了这个接口,它的 invoke 方法主要的功能是根据 Mock 对象状态的不同而分别调用 RecordStateinvoke 实现或是 ReplayStateinvoke 实现。

创建 Mock 对象

下图是创建 Mock 对象的时序图:


图5:创建 Mock 对象时序图
创建 Mock 对象时序图

EasyMock 类的 createMock 方法被调用时,它首先创建一个 MocksControl 对象,并调用该对象的 createMock 方法创建一个 JavaProxyFactory 对象和一个 MockInvocationHandler 对象。JavaProxyFactory 对象将 MockInvocationHandler 对象作为参数,通过 java.lang.reflect.Proxy 类的 newProxyInstance 静态方法创建一个动态代理。

记录 Mock 对象预期行为

记录 Mock 的预期行为可以分为两个阶段:预期方法的调用和预期输出的设定。在外部程序中获得的 Mock 对象,其实就是由 JavaProxyFactory 创建的指定接口的动态代理,所有外部程序对接口方法的调用,都会指向 InvocationHandler 实现类的 invoke 方法。在 EasyMock 中,这个实现类是 MockInvocationHandler。下图是调用预期方法的时序图:


图6:调用预期方法时序图
调用预期方法时序图

MockInvocationHandlerinvoke 方法被调用时,它首先通过 reportLastControl 静态方法将 Mock 对象对应的 MocksControl 对象报告给 LastControl 类,LastControl 类将该对象保存在一个 ThreadLocal 变量中。接着,MockInvocationHandler 将创建一个 Invocation 对象,这个对象将保存预期调用的 Mock 对象、方法和预期参数。

在记录 Mock 对象预期行为时,Mock 对象的状态是 Record 状态,因此 RecordState 对象的 invoke 方法将被调用。这个方法首先调用 LastControlpullMatchers 方法获取参数匹配器。如果您还记得自定义参数匹配器的过程,应该能想起参数匹配器被调用时会将实现类的实例报告给 EasyMock,而这个实例最终保存在 LastControl 中。如果没有指定参数匹配器,默认的匹配器将会返回给 RecordState

根据 Invocation 对象和参数匹配器,RecordState 将创建一个 ExpectedInvocation 对象并保存下来。

在对预期方法进行调用之后,我们可以对该方法的预期输出进行设定。我们以
expectLastCall().andReturn(X value).times(int times)
            


为例说明。如果 times 方法未被显式的调用,EasyMock 会默认作为 times(1) 处理。下图是设定预期输出的时序图:


图7:设定预期输出时序图
设定预期输出时序图

在预期方法被调用时,Mock 对象对应的 MocksControl 对象引用已经记录在 LastControl 中,expectLastCall 方法通过调用 LastControllastControl 方法可以获得这个引用。MocksControl 对象的 andReturn 方法在 Mock 对象 Record 状态下会调用 RecordStateandReturn 方法,将设定的预期输出以 Result 对象的形式记录下来,保存在 RecordState 的 lastResult 变量中。

MocksControltimes 方法被调用时,它会检查 RecordState 的 lastResult 变量是否为空。如果不为空,则将 lastResult 和预期方法被调用时创建的 ExpectedInvocation 对象一起,作为参数传递给 MocksBehavioraddExpected 方法。MocksBehavioraddExpected 方法将这些信息保存在数据列表中。

在 Replay 状态下调用 Mock 对象方法

EasyMock 类的 replay 方法可以将 Mock 对象切换到 Replay 状态。在 Replay 状态下,Mock 对象将根据之前的设定返回预期输出。下图是 Replay 状态下 Mock 对象方法调用的时序图:


图8:调用 Mock 对象方法时序图
调用 Mock 对象方法时序图

在 Replay 状态下,MockInvocationHandler 会调用 ReplayStateinvoke 方法。该方法会把 Mock 对象通过 MocksBehavioraddActual 方法添加到实际调用列表中,该列表在 verify 方法被调用时将被用到。同时,addActual 方法会根据实际方法调用与预期方法调用进行匹配,返回对应的 Result 对象。调用 Result 对象的 answer 方法就可以获取该方法调用的输出。





6.使用 EasyMock 进行单元测试小结

如果您需要在单元测试中构建 Mock 对象来模拟协同模块或一些复杂对象,EasyMock 是一个可以选用的优秀框架。EasyMock 提供了简便的方法创建 Mock 对象:通过定义 Mock 对象的预期行为和输出,你可以设定该 Mock 对象在实际测试中被调用方法的返回值、异常抛出和被调用次数。通过创建一个可以替代现有对象的 Mock 对象,EasyMock 使得开发人员在测试时无需编写自定义的 Mock 对象,从而避免了额外的编码工作和因此引入错误的机会。

posted @ 2009-11-21 17:34 shiwf 阅读(280) | 评论 (0)编辑 收藏
 
Copyright © shiwf Powered by: 博客园 模板提供:沪江博客