云自无心水自闲

天平山上白云泉,云自无心水自闲。何必奔冲山下去,更添波浪向人间!
posts - 288, comments - 524, trackbacks - 0, articles - 6
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2月25日,IBatis3发布了Beta10,在主页上宣称,一个月来Beta9的公测都没有收到真正的Bug提交。
所有收到的问题都是新的功能要求。
所以,如果没有意外的话,Beta10将会用于投票表决是否作为GA(General Available)版本。

posted @ 2010-03-08 19:50 云自无心水自闲 阅读(1742) | 评论 (1)编辑 收藏

在教程的第四部分(http://www.blogjava.net/usherlight/archive/2009/06/20/283396.html)我们已经粗略地介绍了Tapestry的前缀,其中提及最常用的两种literal和prop。这里顺便再介绍一下其他的几个前缀.
1、context,这个经常在引用图片的时候用到。比如:<img src="${context:images/icon.png}"/> 这样就能够在页面上显示在web-inf/images/icon.png
2、message,这个是需要I18N的应用必用的前缀。${message:some-key}
3、var,用于定义生成一个临时变量。比如:<li t:type="loop" source="1..10" value="var:index">${var:index}</li>。如果没有加这个前缀var:的话,就需要在页面类中添加一个property: index以及相应的getter和setter。
我们这次介绍自定义一个cycle前缀,这个可以用于在表格中显示不同背景的行(也就是斑马条)。
比如:
<t:grid ....  rowClass="cycle:line1,line2">
...
</t:grid>
或者
<t:loop ...>
<div class="${cycle:line1,line2}">aaaa</div>
</tloop>

自定义prefix一般来说是3个步骤,
1、定义一个BindingFactory,这个需要实现BindingFactory接口
2、定义一个Binding继承AbstractBinding
3、注册这个Binding

看一下具体的prefix的类:

 1import java.util.ArrayList;
 2import java.util.List;
 3
 4import org.apache.tapestry5.Binding;
 5import org.apache.tapestry5.BindingConstants;
 6import org.apache.tapestry5.ComponentResources;
 7import org.apache.tapestry5.ioc.Location;
 8import org.apache.tapestry5.services.BindingFactory;
 9import org.apache.tapestry5.services.BindingSource;
10
11/**
12 * Implementation of the cycle: binding prefix -- we parse list of bindings
13 * and generate delegate bindings for each element<br>
14 * default binding is literal, other bindings can be used by specifying prefix.<br>
15 * example: "cycle:prop:name,prop:lastName,sth,sth else"
16 */

17public class CycleBindingFactory implements BindingFactory {
18    private final BindingSource _bindingSource;
19
20    public CycleBindingFactory(BindingSource source){
21        this._bindingSource = source;
22    }

23    
24    public Binding newBinding(String description, ComponentResources container, ComponentResources component,
25            String expression, Location location)
26    {
27        List<Binding> delegates = new ArrayList<Binding>();
28        String[] bindingNames = expression.split(",");
29
30        for (String bindingName : bindingNames){
31            String defaultBinding = BindingConstants.LITERAL;
32
33            Binding binding = _bindingSource.newBinding(description, container, component, defaultBinding, bindingName, location);
34            delegates.add(binding);
35        }

36        
37        CycleBinding cycleBinding = new CycleBinding(delegates);
38        container.addPageLifecycleListener(cycleBinding);
39            
40        return cycleBinding;
41    }

42}

 1import java.util.List;
 2
 3import org.apache.tapestry5.Binding;
 4import org.apache.tapestry5.internal.bindings.AbstractBinding;
 5import org.apache.tapestry5.runtime.PageLifecycleListener;
 6
 7
 8public class CycleBinding extends AbstractBinding implements PageLifecycleListener{
 9    private final List<Binding> delegates;
10    private int index = 0;
11
12    public CycleBinding(List<Binding> delegates) {
13        this.delegates = delegates;
14    }

15
16    public Object get() {
17        Object ret = delegates.get(index).get();
18        index ++
19        if(index>=delegates.size()) index = 0;
20        return ret;
21    }

22    
23    @Override
24    public boolean isInvariant() {
25        return false;
26    }

27    
28    @Override
29    public Class<Object> getBindingType() {
30        return Object.class;
31    }

32
33
34    public void containingPageDidDetach() {
35        index=0;
36    }

37
38    public void containingPageDidAttach() {/*not interested*/}
39    
40    public void containingPageDidLoad() {/*not interested*/}
41}

Binding和BindingFactory写好了,注册后就可以使用了,注册的过程是在AppModel中添加以下一段代码:
1    public static void contributeBindingSource(
2            MappedConfiguration<String, BindingFactory> configuration,
3            BindingSource bindingSource
4            )
5    {
6        configuration.add("cycle",new CycleBindingFactory(bindingSource));
7    }
 


posted @ 2010-03-06 15:54 云自无心水自闲 阅读(2232) | 评论 (0)编辑 收藏

1. 先开始看SpringSide吧。
主要看寒冬日志版3.2.1开始有的JMS演示和WebService演示

posted @ 2010-03-01 12:24 云自无心水自闲 阅读(392) | 评论 (0)编辑 收藏

一直觉得EJB好像已经日薄西山了,但是实际上生命力还挺顽强的。
另外,还有Web Service, SOA这个概念还是有人相信的。

所以,延伸项目有:JMS, MQ

posted @ 2010-03-01 12:21 云自无心水自闲 阅读(372) | 评论 (0)编辑 收藏

我记得好像是Appfuse的作者曾经这样评价过Tapestry:只要你真正掌握了Tapestry,你的开发效率将会得到极大的提高。为什么呢?我认为他这样说的一个重要原因就是Tapestry的组件机制。Tapestry提供了非常便利的组件定义机制,随着Tapestry的组件不断积累,Tapestry的开发将会变得越来越简单。
本文就用一个实例来看一下Tapestry中是如何添加一个自定义组件的。
Tapestry的内置组件只提供了checkbox,而且只能返回一个boolean,用于表明是否被选中。
比如,要进行一个群众喜爱的水果调查,选项有: 苹果,葡萄,桃子,香蕉...,就需要对应每个选项设置一个布尔型变量,显得比较繁琐。
这里我们将添加一个组件用于将一组checkbox集中起来返回一个逗号分隔的字符串值。
通过查看Tapestry中的checkbox的源码(已经附在文章的后面)可以知道,Tapestry可以很容易地通过Request来获取Form中的变量的值。
遇到的问题:
Tapestry的checkbox组件不允许设置相同的name,如果name相同,Tapestry会自动在name后面添加后缀来使之不同。
If a component renders multiple times, a suffix will be appended to the to id to ensure uniqueness(http://tapestry.apache.org/tapestry5.1/tapestry-core/ref/org/apache/tapestry5/corelib/components/Checkbox.html)。如果各checkbox的name不同,我们无法通过request来获得一组checkbox的值。
思路:
在页面模板中不使用tapestry的checkbox组件,而使用Html的checkbox,这样可以避免tapestry自动修改checkbox的name。
添加一个新的tapestry组件,来映射接受所有同名的checkbox的值,并把值返回给tapestry页面中对应的变量。这个组件需要有一个属性,这个属性的值就是所有同组checkbox的name,这样,这个组件就可以通过Request来获取所有相同name的checkbox的值。
代码:
 1 public class CheckBoxGroup extends AbstractField {
 2 
 3     @SuppressWarnings("unused")
 4     @Parameter(required = true, autoconnect = true)
 5     private String value;
 6 
 7     @Parameter(required = true, autoconnect = true)
 8     private String groupName;
 9 
10     @Inject
11     private Request request;
12 
13     @SuppressWarnings("unused")
14     @Mixin
15     private RenderDisabled renderDisabled;
16 
17     @Inject
18     private ComponentResources resources;
19 
20     @BeginRender
21     void begin(MarkupWriter writer)
22     {
23         writer.element("input""type""checkbox",
24                        "name", groupName,
25                        "id", getClientId(),
26                        "style""display:none");
27 
28         resources.renderInformalParameters(writer);
29 
30         decorateInsideField();
31     }
32 
33     @AfterRender
34     void after(MarkupWriter writer)
35     {
36         writer.end(); // input
37     }
38 
39     @Override
40     protected void processSubmission(String elementName)
41     {
42         String elementValue = "";
43         String[] valueArray = request.getParameters(groupName);
44         if ( valueArray != null && valueArray.length > 0 ) {
45             elementValue = valueArray[0];
46             for ( int i = 1; i < valueArray.length; i ++ ) {
47                 elementValue += "," + valueArray[i];
48             }
49         }
50         value = elementValue;
51     }
52 }

组件的使用:
-----tml------
<t:CheckBoxGroup t:groupName="literal:bookId" t:value="selectedBooks"/>
<t:loop source="bookList" value="book" encoder="encoder">
    <div><input type="checkbox" name="bookId" value="${book.id}"/> ${book.name}</div>
</t:loop>
注意checkBoxGroup的groupName和其他checkbox的name必须一致,checkBoxGroup的value的值就是页面中的变量名

-----java-----
    @SuppressWarnings("unused")
    @Property
    private final ValueEncoder<Book> encoder = new ValueEncoder<Book>() {
        public String toClient(Book value) {
            return String.valueOf(value.getId());
        }
        public Book toValue(String clientValue) {
            return bookDao.getBook(Integer.parseInt(clientValue));
        }
    };
   
    public List<Book> getBookList() {
        return bookDao.getBooks();
    }
   
    @SuppressWarnings("unused")
    @Property
    private Book book;
   
    @SuppressWarnings("unused")
    @Property
    private String selectedBooks;





posted @ 2010-02-12 07:26 云自无心水自闲 阅读(3290) | 评论 (3)编辑 收藏

在tapestry5中,在页面之间传递基本有3种方法
1、存放在Session中
2、使用@Persist进行持久化
3、使用页面context来传递参数。
其中1和2都需要将数据存放在Session中,相对来说系统的开销比较大。尤其是多用户高并发情况下,对于性能可能会有一定的影响。
使用页面Context来传递则需要在开发时写一些代码,增加了一些开发量,显得没有前两种方法方便。
第3种方法的实现是需要在页面中添加onActivate和onPassivate方法来完成页面参数的传递。
我们先来看一下其背后的故事。
举个例子,比如说我们有两个页面,第一个是查询条件输入页面input,另一个是查询结果输出页面output。input页面中有两个查询条件,起始时间dateFrom和终止时间dateTo
在Input.java中,我们可以很直观地这样写:
@InjectPage
private Output output;

@Property
private String dateFrom;

@Property
private String dateTo;

Object onFormSubmit() {
        output.setDateFrom(dateFrom);
        output.setDateTo(dateTo);
        return output;
}
首先使用注解注入output页面,然后在表单的提交事件中,返回output,这样就在程序中定义了返回页面,而不是使用配置文件的方式。
但是这样的实现却不能正确运行,原因是因为Tapestry5的使用了页面池技术,页面在每次渲染前都是从页面池中随机获取一个页面,而从页面池中取得的页面,所有的属性都是被清空了的。
也就是说在上例中,虽然我们注入了output页面,但是此页面马上就被放入了页面池,而且其中的属性值马上就被清空了。这就是引入onActivate和onPassivate这丙个方法的原因。tapestry5在清空属性前会首先查看是否包含onPassivate方法,如果有,就把其返回值保存起来,然后从页面池中取得页面后,再把刚才保存的值作为参数传递给onActivate方法。
这就是方法3的基本原理,但是无论是在官方的文档或是示例或者网上其他的应用中,可以发现大部分都是使用单个参数的,比如说id。这也很容易理解,因为onPassivate的方法的返回值只能有一个。
在Tapestry5的官方文档中,只有一句非常简要的话介绍了如果传递多个文档的方法: The activation context may consist of a series of values, in which case the return value of the method should be an array or a List. (参见:http://tapestry.apache.org/tapestry5.1/guide/pagenav.html)。
但是这并不是说只要在onPassivate中把参数的值加入到List中,返回一个List,而在onActivate中接受一个List参数,然后就可以得到其中的参数了,因为Tapestry5把参数传给onActivate的方法其实是通过将参数作为HttpRequest中的参数的。如果试图使用上述方法就是得到一个“无法将List转换成String的错误”
所以方法应该是这样的,在Output中:

private List<String> paramList;
public void setParamList(List<String> paramList) {
        this.paramList = paramList;
}
public List<String> getParamList() {
        return paramList;
}
List<String> onPassivate() {
        return paramList;
}
void onActivate(String dateFrom, String dateTo) {
        this.dateFrom = dateFrom;
        this.dateTo = dateTo;
}
private String dateFrom;
private String dateTo;

在Input页面中,需要把onFormSubmit改一下:
Object onFormSubmit() {
        List<String> list = new ArrayList<String>();
        output.setParamList(list);
        return output;
}

其中,需要注意的是output中的onActivate方法,基参数的顺序必须和List中放入的参数顺序一致。


 

posted @ 2010-02-06 12:36 云自无心水自闲 阅读(3549) | 评论 (4)编辑 收藏

在上一篇中我们研究了如何实现SpringSecurity中Jsp Tag的<security:authorize ifAllGranted="ROLE_SUPERVISOR">的功能。这一次我们一起研究一下如何实现在Tapestry5.1中添加一个Filter来对所有的操作进行权限的过滤控制。
在SpringSecurity中,我们一般是在application-context.xml中,添加一个SpringSecurity的Filter,然后在另外一个xml中详细配置如何根据Url的规则进行权限的控制。而Tapestry的哲学是尽量减少Xml中的配置(其IOC容器也基本上是借鉴Guice而不Spring的),所以我们也是在代码中实现权限规则的控制。
总体上来看,可以用两种方式来实现url规则,一种是Request级别的Filter,一种是页面组件级别的Filter,如果是Request级别的话,可以从Request对象中获取Url路径,这样就与SpringSecurity基本一样了。本文主要介绍页面组件级别的Filter,从中我们也可以体会到Tapestry5.1中的IOC容器的强大和便利。

这就是Filter的代码,这个Filter必须实现ComponentRequestFilter接口。值得注意的是其构造函数所需要用到的4个参数,这4个参数都是Tapestry5本身自有的服务,所以我们什么也不用做,Tapestry5自动会将服务的实例注入进来,这就是Tapestry-IOC的威力。
ComponentRequestFilter接口一共有4个方法需要实现,具体代码如下:

 1 public class RequiresLoginFilter implements ComponentRequestFilter {
 2 
 3     private final PageRenderLinkSource renderLinkSource;
 4 
 5     private final ComponentSource componentSource;
 6 
 7     private final Response response;
 8    
 9     private final ApplicationStateManager appStateManager;
10    
11     public RequiresLoginFilter(PageRenderLinkSource renderLinkSource,
12             ComponentSource componentSource, Response response,
13             ApplicationStateManager appStateManager
14             ) {
15         this.renderLinkSource = renderLinkSource;
16         this.componentSource = componentSource;
17         this.response = response;
18         this.appStateManager = appStateManager;
19     }
20 
21     public void handleComponentEvent(
22             ComponentEventRequestParameters parameters,
23             ComponentRequestHandler handler) throws IOException {
24 
25         if (dispatchedToLoginPage(parameters.getActivePageName())) {
26             return;
27         }
28 
29         handler.handleComponentEvent(parameters);
30 
31     }
32 
33     public void handlePageRender(PageRenderRequestParameters parameters,
34             ComponentRequestHandler handler) throws IOException {
35         if (dispatchedToLoginPage(parameters.getLogicalPageName())) {
36             return;
37         }
38         handler.handlePageRender(parameters);
39 
40     }
41 
42     private boolean dispatchedToLoginPage(String pageName) {
43         Component page = componentSource.getPage(pageName);
44        
45         if (page.getClass().isAnnotationPresent(RequiresLogin.class)) {
46             if ( ! appStateManager.exists(Authentication.class)) {
47                 redirect();
48                 return true;
49             }
50             Authentication auth = appStateManager.get(Authentication.class);
51             if ( auth == null ) {
52                 redirect();
53                 return true;
54             }
55            
56              if ( ! auth.isLoggedIn()) {
57                  redirect();
58                  return true;
59              }
60 
61             RequiresLogin requireLogin = page.getClass().getAnnotation(
62                     RequiresLogin.class);
63             String ifNotGranted = requireLogin.ifNotGranted();
64             String ifAllGranted = requireLogin.ifAllGranted();
65             String ifAnyGranted = requireLogin.ifAnyGranted();
66             boolean permitted = auth.checkPermission(ifNotGranted, ifAllGranted, ifAnyGranted);
67             if ( ! permitted ) {
68                 return true;
69             }
70         }
71 
72         return false;
73     }
74    
75     private void redirect() {
76          Link link = renderLinkSource.createPageRenderLink("Logout");
77        
78          try {
79              response.sendRedirect(link);
80          } catch (Exception e) {
81          }
82     }
83 
84 }

在ComponentRequestFilter中,我们无法使用@SessionState注解来直接注入Session中的变量,但是我们可以通过ApplicationStateManager来取得。

现在我们需要把刚定义的Filter注册到系统中,很简单,只要在AppModule中添加以下函数就行了:

1 public static void contributeComponentRequestHandler(
2                 OrderedConfiguration<ComponentRequestFilter> configuration) {
3         configuration.addInstance("RequiresLogin", RequiresLoginFilter.class);
4 }
5 
从本例子中我们可以看到Tapesty Ioc容器使用的便利性,也认识到了Ioc容器在Tapestry体系中的重要性

posted @ 2010-02-04 19:17 云自无心水自闲 阅读(3201) | 评论 (1)编辑 收藏

IBatis2中提供了3种DataSource的配置:JNDI, Apache DBCP, IBatis自带的SimpleDataSource。但在IBatis3中只提供了两种DataSource: UNPOOLED, POOLED。
如果要实现自定义的DataSource,就需要通过扩展DataSourceFactory。本文就演示一下这个过程。
准备工作:Connection Pool的选择,通过搜索发现目前比较流行的免费数据库连接池主要有3种:Apache DBCP, C3P0, Proxool。
看了一下,Proxool的最新版本是0.9.1(2008-08-23), C3P0的最新版本是0.9.1.2(2007-05-21), DBCP最新版本是1.2.2(2007-04-04)
好像这3个项目都已经挺长时间没有更新了。但是总体评价上C3P0无论从稳定上还是效率上都要好一点。
(具体这3个项目谁更优秀,并不是本文的重点,本文主要是介绍一下如何在IBatis3中自定义数据源)
大致步骤:
1、实现org.apache.ibatis.datasource.DataSourceFactory接口,主要是2个方法
a、public DataSource getDataSource() 如何具体地得到一个数据源
b、public void setProperties(Properties properties) 如何设置数据源的参数属性
2、实现javax.sql.DataSource,这个就是提供给DataSourceFactory的实例
3、在IBatis3中引用新加入的数据源

1. 从代码中可以看出,IBatis3与IBatis2不同,不再通过一个Configuration类来进行数据源属性的设置,而是使用反射机制直接调用数据源的方法来设置参数。
这就要求配置文件中的参数名称必须与数据源类中的方法名匹配.
 1 public class C3p0DataSourceFactory implements DataSourceFactory {
 2 
 3     private DataSource dataSource;
 4 
 5     public C3p0DataSourceFactory() {
 6         dataSource = new C3p0DataSource();
 7     }
 8 
 9     public DataSource getDataSource() {
10         return dataSource;
11     }
12 
13     public void setProperties(Properties properties) {
14         Properties driverProperties = new Properties();
15         MetaObject metaDataSource = MetaObject.forObject(dataSource);
16         for (Object key : properties.keySet()) {
17             String propertyName = (String) key;
18             if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
19                 String value = properties.getProperty(propertyName);
20                 driverProperties.setProperty(propertyName
21                         .substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
22             } else if (metaDataSource.hasSetter(propertyName)) {
23                 String value = (String) properties.get(propertyName);
24                 Object convertedValue = convertValue(metaDataSource,
25                         propertyName, value);
26                 metaDataSource.setValue(propertyName, convertedValue);
27             } else {
28                 throw new DataSourceException("Unkown DataSource property: "
29                         + propertyName);
30             }
31         }
32         if (driverProperties.size() > 0) {
33             metaDataSource.setValue("driverProperties", driverProperties);
34         }
35     }
36 
37     @SuppressWarnings("unchecked")
38     private Object convertValue(MetaObject metaDataSource, String propertyName,
39             String value) {
40         Object convertedValue = value;
41         Class targetType = metaDataSource.getSetterType(propertyName);
42         if (targetType == Integer.class || targetType == int.class) {
43             convertedValue = Integer.valueOf(value);
44         } else if (targetType == Long.class || targetType == long.class) {
45             convertedValue = Long.valueOf(value);
46         } else if (targetType == Boolean.class || targetType == boolean.class) {
47             convertedValue = Boolean.valueOf(value);
48         }
49         return convertedValue;
50     }
51 
52     private static final String DRIVER_PROPERTY_PREFIX = "driver.";
53     private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX
54             .length();
55 
56 }
57 

2. 数据源类,其中的一堆setter就是用于设置属性的。
 1 public class C3p0DataSource implements DataSource {
 2 
 3     private ComboPooledDataSource dataSource;
 4     public C3p0DataSource() {
 5         this.dataSource = new ComboPooledDataSource();
 6     }
 7     
 8     public Connection getConnection() throws SQLException {
 9         return dataSource.getConnection();
10     }
11 
12     public Connection getConnection(String username, String password)
13             throws SQLException {
14         return dataSource.getConnection(username, password);
15     }
16 
17     public PrintWriter getLogWriter() throws SQLException {
18         return dataSource.getLogWriter();
19     }
20 
21     public int getLoginTimeout() throws SQLException {
22         return dataSource.getLoginTimeout();
23     }
24 
25     public void setLogWriter(PrintWriter out) throws SQLException {
26         dataSource.setLogWriter(out);
27     }
28 
29     public void setLoginTimeout(int seconds) throws SQLException {
30         dataSource.setLoginTimeout(seconds);
31     }
32     
33     
34     public synchronized void setDriver(String driver) {
35         try {
36             dataSource.setDriverClass(driver);
37         } catch (Exception e) {
38         }
39     }
40     
41     public void setUrl(String url) {
42         dataSource.setJdbcUrl(url);
43     }
44     
45     public void setUsername(String username) {
46           dataSource.setUser(username);
47     }
48 
49     public void setPassword(String password) {
50         dataSource.setPassword(password);
51     }
52     
53     public void setInitialPoolSize(int initialPoolSize) {
54         dataSource.setInitialPoolSize(initialPoolSize);
55     }
56     
57     public void setMaxPoolSize(int maxPoolSize) {
58         dataSource.setMaxPoolSize(maxPoolSize);
59     }
60       
61     public void setMinPoolSize(int minPoolSize) {
62         dataSource.setMinPoolSize(minPoolSize);
63     }
64     
65     public void setPreferredTestQuery(String preferredTestQuery) {
66         dataSource.setPreferredTestQuery(preferredTestQuery);
67     }
68     
69     public void setPoolPingQuery(String poolPingQuery) {
70         dataSource.setPreferredTestQuery(poolPingQuery);
71     }
72 }

3. 在配置文件Configuration.xml中,可以先定义数据源的别称,然后就象POOLED和UNPOOLED一样使用别称来引用数据源。
<Configuration>
    ...
    <typeAlias>
        <typeAlias type="com.test.datasource.C3p0DataSourceFactory" alias="C3P0"/>
    </typeAlias>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="C3P0">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
                <property name="poolPingQuery" value="${pingquery}"/>           
            </dataSource>
        </environment>
    </environments>
    ...
<Configuration>



posted @ 2010-02-01 12:57 云自无心水自闲 阅读(3895) | 评论 (2)编辑 收藏

最近在使用tapestry5.1.0.5开发项目的时候,突然报错:
Exception in thread "main" java.lang.ClassFormatError: Invalid length 561 in LocalVariableTable in class file

在网上搜索后,发现有人也有同样的错误,解决方法有两种:
http://mail-archives.apache.org/mod_mbox/tapestry-users/200909.mbox/%3Cecd0e3310909040909id5275beld935fc60d54d490a@mail.gmail.com%3E
其中一个人的错误原因是在其类路径中有不同版本的javassists的jar文件。
另一个的解决方法是使用eclipse自带的jdk来编译java类。
而我自己仔细检查了类路径中的文件,并没有重复的javassists,不过我觉得问题应该就在javassists上,
因为这显然是javassists在操作class文件时报的错误,
我去网上搜索这方面的信息,发现有好几个人都和我一样在使用javassists3.11.0GA版本的时候,会出现这个错误。
后来,我改用Tapestry5中自带的javassists3.9.0GA后,问题消失了。
这次经验教训是并不是所有最新的东西都是最好的。合适的才是最好的。

posted @ 2010-01-21 07:28 云自无心水自闲 阅读(3646) | 评论 (0)编辑 收藏

IBatis3的Beta8版本已经发布了,在官方网站上声称目前的版本已经非常稳定,只有4个已知的问题,其中2个是非功能性的。作者宣称,这样的状况使它对于近期发布GA版本充满信心。
那么IBatis3与IBatis2相比,究竟变化在哪里呢?
最重要的变化是IBatis3中引入了接口绑定(Interface Binding)的概念。在IBatis2中,没有应用Java5的泛型,所以需要大量使用强制类型转换,比如:
Employee employee = (Employee)sqlMapper.queryForList("getEmployee", 5);
//...and...
List employees = sqlMapper.queryForList("listAllEmployees");
但是在IBatis3中,方法改变成:
MapperFactory factory = someConfiguration.buildMapperFactory();
EmployeeMapper employeeMapper = factory.getMapper (EmployeeMapper.class);
Employee emp = empMapper.getEmployee(5);
//...and...
List<Employee> employees = empMapper.listAllEmployees();
所以IBatis3至少需要使用Java5以上的版本。上面代码中,EmployeeMapper是一个自定义的接口(注意,开发人员只需要定义一个接口,不需要提供具体的实现)
public interface EmployeeMapper {
  Employee getEmployee (int employeeId);
  List<Employee> listAllEmployees();
}
这样就行了,IBatis会自动为你生成接口的具体实现。是不是感觉有点酷?

posted @ 2010-01-15 20:49 云自无心水自闲 阅读(2357) | 评论 (4)编辑 收藏

仅列出标题
共29页: First 上一页 7 8 9 10 11 12 13 14 15 下一页 Last