`
ajoo
  • 浏览: 455176 次
社区版块
存档分类
最新评论
阅读更多
场景一:

个人喜欢state-based的单元测试。(定义见mock is not stub)。

可是有些时候,比如在测试一些使用java.sql.Connection, ibatis SqlMapClient等接口的类的时候,明显写stub很不好。(1,接口很大,有很多不相干的东西。2,版本一旦变化,这些接口可能跟着变化,如果写stub的话,就意味着stub要跟着这些第三方接口变化)

于是,只好mock。只好interaction based。只好每回内部实现一变就死盯着一坨坨的expectAndReturn找不再有效的expectation。

场景二:

一个遗留系统用自己的连接池,使用ConnectionManager.checkIn(Connection)来释放连接(而不是Connection.close)

为了重构,希望能够重载Connection.close(),让它调用checkIn()。

跟场景一的提到的原因一样,不希望直接写一个MyConnection implements Connection。于是只好自己写dynamic proxy,形成如下的代码:
  private Connection conn;
  public Object invoke(Object proxy, Method method, Object[] args){
    if((args==null || args.length==0) && method.getName().equals("close")){
      ConnectionManager.checkIn((Connection)conn);
    }
    else {
      try {
        return method.invoke(conn, args);
      }
      catch(InvocationTargetException e){
        throw e.getTargetException();
      }
    }
  }



后面又发现这个变态的一流系统居然用自己的startMyTransaction, endMyTransaction(), rollbackMyTransaction()来搞事务处理!
于是又要继续判断method.getName().equals("startTransaction"), method.getName().equals("commitTransaction")等等等等。

dynamic proxy的代码难看的要死。



最近终于受不了了。愤然写了一个叫做酒窝的东西(dimple)。

所谓dimple,就是dynamic implementor的意思。我要用纯java语法来实现我想实现的方法(不是expectAndReturn,也不是if(method.getName().equals("method1"))),但是同时我又不愿意写“implements TheInterface”,以避免被迫处理那些我不关心的method。

用法如下:

场景一:

这样实现我的stub:
MyParameter myparam = ...;
SqlMapClient client = (SqlMapClient) Implementor.proxy(SqlMapClient.class, new Object(){
  public Object insert(String id, Object param) {
    assertEquals("test id", id);
    assertSame(myparam, param);
    return null;
  }
});
assertNull(new SomeClassUsingSqlMapClient(client).runSomeInsert(myparam));


没有讨厌的expectAndReturn,不用担心parameter matcher。


场景二:
final Connection realConn = ...;
Connection conn = (Connection) Implementor.proxy(Connection.class, new Object(){
  public void close() {
    ConnectionManager.checkIn(realConn);
  }
  public void startTransaction(){
    ConnectionManager.startMyTransaction(realConn);
  }
  public void commitTransaction(){
    ConnectionManager.commitTransaction(realConn);
  }
}, realConn);

于是,对close(), startTransaction, commitTransaction(),我们都直接调用ConnectionManager, 而对其它的method则委托给realConn。




这个Implementor类是纯java代码,除了dynamic proxy没有用任何其它技术。(没有enhancement,aop之类的)



个人感觉还是挺有用的。大家点评一下?
分享到:
评论
31 楼 shaucle 2006-12-13  
D d=(D)Implementor.proxy(D.class, new Object());
如果D中实现了如hashCode之类的方法,那你的with(也就是那个new Object())会intercept D的hashCode么? (cls.getMethods())
不知道你的CglibUtils怎么实现的?
我这边测的会.
30 楼 taowen 2006-12-13  
拿砖头来砸死物,不用考虑砸出去快不快,砸得狠,砸得准就行。拿砖头来砸狗,你砸地得不快,狗不是跑了,就是冲上来咬你了。
29 楼 ajoo 2006-12-13  
taowen 写道
啊,我还一直以为dimple是拿来做测试的stub用的呢。还是do one thing one time吧。这样可以支持得更好一些,顾虑也可以更少。你说的都在理。还是先用用吧,空说无益。


嗯。不过stub和拦截器是一样的用法阿。是一件事。只不过你能把它用在两种不同的地方罢了。

就象砖头可以用来造房子,也可以用来砸人。
28 楼 taowen 2006-12-13  
啊,我还一直以为dimple是拿来做测试的stub用的呢。还是do one thing one time吧。这样可以支持得更好一些,顾虑也可以更少。你说的都在理。还是先用用吧,空说无益。
27 楼 ajoo 2006-12-13  
1。这里主要是说效率。这个检查感觉几乎能占用整个处理逻辑的40%的时间。当然,我没有做过profile。如果dynamic proxy的创建需要的时间是大头的话,这个效率损失可能就可以忽略了。

2。是个方法。我本来是想判断isAnonymousClass()的。可惜这个判断只有到了java 5才存在。判断Object总感觉有点头疼医头的感觉,不够系统化。而且怎么判断?看这个类是不是super class==Object?

3。非常同意。要是我能找到100%满意的解决方法,早就做了。

4。dimple用来做拦截器亚。比如拦截Connection.close()方法阿。场景二描述的就是production code亚。

5。实现倒不太复杂。就是从400行变成450行的事情。

6。对亚。如果能通过ide来解决问题,那么在框架内部鼓捣就不是太必要了亚。所以我才犹豫呀。javaeye有eclipse插件的牛人么?强烈要求加入dimple!

7。不是尽量pragmatic嘛。如果dimple实用中并没有人抱怨这个问题,那么做它的动力就不大了嘛。

这个brainstorm挺好。继续。
26 楼 taowen 2006-12-13  
引用

1。这个检查代价比较昂贵。基本上是现在要做的事情再多上一倍。


昂贵是指执行效率,占用空间,还是框架本身的代码行数?如果是框架作者的事情,那问题应该不大。做为一个feature排个序,有时间有必要就实现,没时间没必要就不实现。

引用
2。影响灵活性。比如你要是想从一个已有的类继承,然后用这个子类来动态实现接口,而基类里面已经有了额外多余的public method,怎么办?总不能跑到基类里面去加@Aux吧?或者现在基类没有多余方法,以后又突然增加了新的方法?我们一般作程序都有一个几乎是常识的假设:单单增加一个新的method,或者增加一个新的类,不会造成已有的代码失败。违反这个common sense有点怕怕。


那就区分对待好了。对于从Object继承下来的,明摆着就是一个同时实现一堆方法,但是又不想一次实现所有方法的语法糖。那就默认所有的方法都要改写要实现的接口的某个方法。对于从具体类集成下来的。那就不做检查。用@Override,让IDE给你检查好了。

引用
3。对stub的情况,假设方法名字写错了,一运行unit test马上就会发现错误了。在方法里设置个断点也是比较有效的方法。


是可以发现问题。只是加一点检查,可以fail fast。这和design by contract是一样的。没有那么多检查,代码该fail还是fail。只是多了一些检查,可以更快的失败,这样错误发生距离原因的距离就更近了一些。
引用

4。对在production code里面的动态implement,象场景二提到的那种,同样的,unit test必须要写的。而一旦有了unit test,错误也就会迅速体现出来了。


难道dimple不光是服务于测试的?在production里咋用?

引用
5。实现复杂度。需要实现一些额外的代码来做这件我还没拿定主意是否应当作或者怎么做最好的事。


实现复杂就先估计一下。列在TODO里。有时间再做。

引用
6。在dimple的外围做这个type checker也不难。就是判断一个类里面的所有public method签名在另一个接口里面都有定义。这个判断代码甚至不需要依赖dimple现有的代码。如果真需要,做个add-on好了。


最理想的是IDE的插件啦。Eclipse可以检查@Override,而且@Override的Retention Policy是Source级别的。如果有插件,也可以做到同样的便利程度。

引用
7。这个问题到底有多严重我还不是很清楚。如果用起来大家都觉得很担心把方法名字敲错,那么再想法子解决吧。ruby到处都是duck typing,也没看见大家很紧张的样子。


Ruby是Ruby嘛……到静态类型的语言中就该守静态类型的规矩,就应该利用静态类型带来的好处。在Ruby里还没法确定一个方法是不是存在于一个类中吧?只有调用了才知道(因为有method missing)。这就是Java的静态类型带来的束缚和好处。如果不利用,就浪费了。
25 楼 robbin 2006-12-13  
ajoo 写道
shaucle 写道
哦,原来是你的构造方法public Implementor(Class implClass)把俺搞混了,(很容易把构造方法里的implClass和proxy方法里的asType混在一起)
为什么不把它(们)设为私有? 或简单的实现另一个包装类来提供对外的接口.


你的duck typing还不错,不过俺还去想想另一种实现方法,对于addMethod中的多层HashMap俺认为用对象模型可能更好.

350行,可能是帖错了吧 ^_^


不是贴错了。看来可能是javaeye的syntax highlight有bug。呵呵。召唤robbin。


code highlight是用JS render出来的,太长的代码在不同的浏览器下面可能会不一样,我看着到挺正常的。

建议把代码打包作为帖子的附件上传。
24 楼 ajoo 2006-12-13  
shaucle 写道
哦,原来是你的构造方法public Implementor(Class implClass)把俺搞混了,(很容易把构造方法里的implClass和proxy方法里的asType混在一起)
为什么不把它(们)设为私有? 或简单的实现另一个包装类来提供对外的接口.


你的duck typing还不错,不过俺还去想想另一种实现方法,对于addMethod中的多层HashMap俺认为用对象模型可能更好.

350行,可能是帖错了吧 ^_^


不是贴错了。看来可能是javaeye的syntax highlight有bug。呵呵。召唤robbin。
23 楼 ajoo 2006-12-13  
taowen 写道
ajoo 写道
taowen 写道
nihongye 写道
就是重构起来麻烦点

这的确是一个问题。而且似乎没有什么好的解决办法做到重构安全。我能够看到的一个加强的地方是,验证new Object中每个方法是不是实现了要stub的接口或者具体类的方法。这样在改名了之后,可以fail fast,不会出莫名其妙的问题不知所以。
另外,不妨把酒窝当一个stub框架来做。

这样是可以,就是有点降低重用能力。我不能用一个Object给不同的interface做proxy么?

考虑可以在annotation上做文章。如果有一个@Override之类的annotation,那么就检查。

By default, every method is regarded as @Override, only the methods marked as @Aux will be escaped from the check. Otherwise, most of the methods have to be marked as @Override, which is not convinent.


几个原因让我对这个功能有点犹豫:
1。这个检查代价比较昂贵。基本上是现在要做的事情再多上一倍。
2。影响灵活性。比如你要是想从一个已有的类继承,然后用这个子类来动态实现接口,而基类里面已经有了额外多余的public method,怎么办?总不能跑到基类里面去加@Aux吧?或者现在基类没有多余方法,以后又突然增加了新的方法?我们一般作程序都有一个几乎是常识的假设:单单增加一个新的method,或者增加一个新的类,不会造成已有的代码失败。违反这个common sense有点怕怕。
3。对stub的情况,假设方法名字写错了,一运行unit test马上就会发现错误了。在方法里设置个断点也是比较有效的方法。
4。对在production code里面的动态implement,象场景二提到的那种,同样的,unit test必须要写的。而一旦有了unit test,错误也就会迅速体现出来了。
5。实现复杂度。需要实现一些额外的代码来做这件我还没拿定主意是否应当作或者怎么做最好的事。
6。在dimple的外围做这个type checker也不难。就是判断一个类里面的所有public method签名在另一个接口里面都有定义。这个判断代码甚至不需要依赖dimple现有的代码。如果真需要,做个add-on好了。
7。这个问题到底有多严重我还不是很清楚。如果用起来大家都觉得很担心把方法名字敲错,那么再想法子解决吧。ruby到处都是duck typing,也没看见大家很紧张的样子。
22 楼 shaucle 2006-12-13  
哦,原来是你的构造方法public Implementor(Class implClass)把俺搞混了,(很容易把构造方法里的implClass和proxy方法里的asType混在一起)
为什么不把它(们)设为私有? 或简单的实现另一个包装类来提供对外的接口.


你的duck typing还不错,不过俺还去想想另一种实现方法,对于addMethod中的多层HashMap俺认为用对象模型可能更好.

350行,可能是帖错了吧 ^_^
21 楼 ajoo 2006-12-12  
shaucle 写道
很不错的代码.
不过有几个问题.(字符串错误不说)

private void checkInstanceType(final Object instance) {
        if (!cls.isInstance(instance))
这个和
Implementor.proxy(SqlMapClient.class, new Object(){..}
是不是有点矛盾?

MyMethod的用意好像是使得一些重载的方法亦可被intercept,
但这是必要的么?
1 如果只想intercept重载的方法中某一个方法? (可否设置一个flag?)
2 如果愿用继承的话,直接override Method中的equals似乎更简单,而且不用设置多层次的hashMap(这个会使语义更难懂)


那个sort是否只是为了debug?


没很详细研究,说错的地方,还望多谅解和指点.

1. checkInstanceType()判断的和proxy里面的那个不是一个class object。不矛盾。
2。MyMethod的作用有三个:缓存parameter types,不用每次生成一个数组对象;缓存depth,不用每次重新计算;支持Serializable。
3。sort是为了保证有重载的时候,f(String)被优先于f(Object)使用。比如:
interface I {
  void f(String s);
  ...
}

class Impl {
  void f(Object s){...}
  void f(CharSequence s){...}
}
I impl = (I)proxy(I.class, new Impl());
impl.f("s");//calls Impl.f(CharSequence)

酱子就支持了parameter contravariance。加上返回值的covariance,基本比较duck typing了。


对了,哪里有字符串错误?
20 楼 shaucle 2006-12-12  
很不错的代码.
不过有几个问题.(字符串错误不说)

private void checkInstanceType(final Object instance) {
        if (!cls.isInstance(instance))
这个和
Implementor.proxy(SqlMapClient.class, new Object(){..}
是不是有点矛盾?

MyMethod的用意好像是使得一些重载的方法亦可被intercept,
但这是必要的么?
1 如果只想intercept重载的方法中某一个方法? (可否设置一个flag?)
2 如果愿用继承的话,直接override Method中的equals似乎更简单,而且不用设置多层次的hashMap(这个会使语义更难懂)


那个sort是否只是为了debug?


没很详细研究,说错的地方,还望多谅解和指点.
19 楼 taowen 2006-12-12  
ajoo 写道
taowen 写道
nihongye 写道
就是重构起来麻烦点

这的确是一个问题。而且似乎没有什么好的解决办法做到重构安全。我能够看到的一个加强的地方是,验证new Object中每个方法是不是实现了要stub的接口或者具体类的方法。这样在改名了之后,可以fail fast,不会出莫名其妙的问题不知所以。
另外,不妨把酒窝当一个stub框架来做。

这样是可以,就是有点降低重用能力。我不能用一个Object给不同的interface做proxy么?

考虑可以在annotation上做文章。如果有一个@Override之类的annotation,那么就检查。

By default, every method is regarded as @Override, only the methods marked as @Aux will be escaped from the check. Otherwise, most of the methods have to be marked as @Override, which is not convinent.
18 楼 ajoo 2006-12-12  
taowen 写道
nihongye 写道
就是重构起来麻烦点

这的确是一个问题。而且似乎没有什么好的解决办法做到重构安全。我能够看到的一个加强的地方是,验证new Object中每个方法是不是实现了要stub的接口或者具体类的方法。这样在改名了之后,可以fail fast,不会出莫名其妙的问题不知所以。
另外,不妨把酒窝当一个stub框架来做。

这样是可以,就是有点降低重用能力。我不能用一个Object给不同的interface做proxy么?

考虑可以在annotation上做文章。如果有一个@Override之类的annotation,那么就检查。
17 楼 ajoo 2006-12-12  
package org.codehaus.dimple;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This class is used to create implementation of interface(s) dynamically.
 * <p>
 * This class is ideal for creating stub or interceptor for interfaces where only a few methods are of interest while
 * most other methods are either ignored or delegated.
 * </p>
 * <p>
 * For example:
 * <pre> 
 * Connection realConn = ...;
 * Connection nonCloseableConnection = (Connection)Implementor.proxy(Connection.class, new Object(){
 *   public void close() {
 *     //we intercept close() call and do nothing.
 *   }
 * }, realConn);
 * nonCloseableConnection.close();// no-op. realConn is not closed.
 * </pre>
 * </p>
 * @author Ben Yu
 * Dec 9, 2006 11:27:44 PM
 */
public class Implementor implements Serializable {
  /**
   * Equivalent as new Implementor(with.getClass()).implementWithDefaultHandler(itf, with, defaultHandler)
   * <p>
   * This method is a convenience shortcut.
   * As the constructor of Implementor may be expensive, it is recommended to create an Implementor object once
   * and then use it repeatedly.
   */
  public static Object proxyWithDefaultHandler(Class asType, Object with, InvocationHandler defaultHandler) {
    return new Implementor(with.getClass()).implementWithDefaultHandler(asType, with, defaultHandler);
  }
  /**
   * Equivalent as new Implementor(with.getClass()).implement(asType, with, defaultDelegate)
   * <p>
   * This method is a convenience shortcut.
   * As the constructor of Implementor may be expensive, it is recommended to create an Implementor object once
   * and then use it repeatedly.
   */
  public static Object proxy(Class asType, Object with, Object defaultDelegate){
    if(!asType.isInstance(defaultDelegate)){
      throw new IllegalArgumentException("default delegate of type "+asType.getName() + " expected, "
          + ((defaultDelegate==null)?null:defaultDelegate.getClass().getName())+" encountered");
    }
    return new Implementor(with.getClass()).implement(asType, with, defaultDelegate);
  }
  
  /**
   * Equivalent as new Implementor(with.getClass()).implement(asType)
   * <p>
   * This method is a convenience shortcut.
   * As the constructor of Implementor may be expensive, it is recommended to create an Implementor object once
   * and then use it repeatedly. 
   */
  public static Object proxy(Class asType, Object with) {
    return new Implementor(with.getClass()).implement(asType, with);
  }

  /**
   * create a dynamic proxy that implements <i>asType</i> by calling <i>with</i>
   * if a method is implemented by the impl class.
   * Otherwise delegate call to defaultDelegate 
   * or throw UnsupportedOperationException if defaultDelegate is null.
   * @param asType the interface to implement or super class to override (cglib is required in this case). 
   * @param with the instance of the impl class.
   * @param defaultDelegate the default delegate.
   * @return the dynamic proxy that implements <i>asType</i>.
   */
  public Object implement(Class asType, Object with, Object defaultDelegate) {
    return newProxyInstance(with.getClass().getClassLoader(), asType, 
        createInvocationHandler(with, defaultDelegate));
  }
  /**
   * create a dynamic proxy that implements <i>asType</i> by calling <i>with</i>
   * if a method is implemented by the impl class.
   * Otherwise call the invoke() method of defaultHandler, 
   * or throw UnsupportedOperationException if defaultHandler is null.
   * @param asType the interface to implement or super class to override (cglib is required in this case). 
   * @param with the instance of the impl class.
   * @param defaultHandler the default InvocationHandler.
   * @return the dynamic proxy that implements <i>asType</i>.
   */
  public Object implementWithDefaultHandler(Class asType, Object with, InvocationHandler defaultHandler) {
    return newProxyInstance(with.getClass().getClassLoader(), asType, 
        createInvocationHandlerWithDefaultHandler(with, defaultHandler));
  }
  /**
   * create a dynamic proxy that implements <i>asType</i> by calling <i>with</i>
   * if a method is implemented by the impl class.
   * Otherwise call the invoke() method if <i>with</i> implements InvocationHandler,
   * or throw UnsupportedOperationException otherwise.
   * @param asType the interface to implement or super class to override (cglib is required in this case). 
   * @param with the instance of the impl class.
   * @return the dynamic proxy that implements <i>asType</i>.
   */
  public Object implement(Class asType, Object with){
    return newProxyInstance(with.getClass().getClassLoader(), asType, 
        createInvocationHandler(with));
  }
  /**
   * create an InvocationHandler object by calling <i>instance</i>
   * if a method is implemented by the impl class.
   * Otherwise call the invoke() method if <i>instance</i> implements InvocationHandler,
   * or throw UnsupportedOperationException otherwise. 
   * @param instance the instance of the impl class.
   * @return the InvocationHandler object.
   */
  public InvocationHandler createInvocationHandler(final Object instance){
    return createInvocationHandlerWithDefaultHandler(instance, 
        (instance instanceof InvocationHandler)?(InvocationHandler)instance:null);
  }
  /**
   * create an InvocationHandler object by calling <i>instance</i>
   * if a method is implemented by the impl class.
   * Otherwise call the provided default InvocationHandler object. 
   * @param instance the instance of the impl class.
   * @param defaultHandler the InvocationHandler object to provide default behavior.
   * If null, UnsupportedOperationException is thrown.
   * @return the InvocationHandler object.
   */
  public InvocationHandler createInvocationHandlerWithDefaultHandler(final Object instance, final InvocationHandler defaultHandler){
    return createInvocationHandlerWithDefaults(instance, null, defaultHandler);
  }
  /**
   * create an InvocationHandler object by calling <i>instance</i>
   * if a method is implemented by the impl class.
   * Otherwise forward the call to defaultDelegate. 
   * @param instance the instance of the impl class.
   * @param defaultDelegate the default delegate. If null, UnsupportedOperationException is thrown.
   * @return the InvocationHandler object.
   */
  public InvocationHandler createInvocationHandler(final Object instance, Object defaultDelegate){
    return createInvocationHandlerWithDefaults(instance, defaultDelegate, null);
  }
  InvocationHandler createInvocationHandlerWithDefaults(Object instance, Object defaultDelegate, InvocationHandler defaultHandler){
    checkInstanceType(instance);
    return new ImplInvocationHandler(instance, defaultDelegate, defaultHandler);
  }
  private void checkInstanceType(final Object instance) {
    if(!cls.isInstance(instance)){
      throw new IllegalArgumentException("instance of type "+cls.getName() + " expected, "
          + ((instance==null)?null:instance.getClass().getName())+" encountered");
    }
  }
  /**
   * To create an Implementor class.
   * @param implClass the class used to implement.
   */
  public Implementor(Class implClass){
    this.cls = implClass;
    addClass(implClass);
    sort();
  }
  public boolean equals(Object obj){
    if(obj instanceof Implementor){
      Implementor other = (Implementor)obj;
      return cls.equals(other.cls) && methods.equals(other.methods);
    }
    else return false;
  }
  public int hashCode(){
    return cls.hashCode(); 
  }
  public String toString(){
    return cls.toString();
  }
  /**
   * Get the impl class, which is the class whose public methods are used to implement target interface. 
   */
  public Class getImplClass(){
    return cls;
  }
  void addClass(Class cls) {
    final boolean force = !Modifier.isPublic(cls.getModifiers());
    final Method[] mtds = cls.getMethods();
    for (int i = 0; i < mtds.length; i++) {
      final Method mtd = mtds[i];
      if(force) setAccessible(mtd);
      addMethod(mtd);
    }
  }
  private static void setAccessible(Method mtd){
    try{
      mtd.setAccessible(true);
    }
    catch(SecurityException e){}
  }
  private final Class cls;
  private final Map methods = new HashMap();
  private final class ImplInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 7723292911817172565L;
    
    private final Object instance;
    private final Object defaultDelegate;
    private final InvocationHandler defaultHandler;
    ImplInvocationHandler(Object instance, Object defaultDelegate, InvocationHandler fwd) {
      this.instance = instance;
      this.defaultDelegate = defaultDelegate;
      this.defaultHandler = fwd;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final Method impl = lookupImplementingMethod(method);
      if(impl == null) {
        if(defaultDelegate != null) {
          return forwardCall(defaultDelegate, method, args);
        }
        if(defaultHandler != null) {
          return defaultHandler.invoke(proxy, method, args);
        }
        else{
          throw new UnsupportedOperationException();
        }
      }
      else {
        return forwardCall(instance, impl, args);
      }
    }
    private Object forwardCall(Object obj, final Method mtd, Object[] args) throws IllegalAccessException, Throwable {
      try {
        return mtd.invoke(obj, args);
      }
      catch(InvocationTargetException e){
        throw e.getTargetException();
      }
    }
  }

  private static final class MyMethod implements Serializable {
    private static final long serialVersionUID = 8758558523031285785L;
    private transient Method method;
    private transient Class[] parameterTypes;
    private transient Class returnType;
    private final long depth;
    public boolean equals(Object obj){
      if(obj instanceof MyMethod){
        final MyMethod other = (MyMethod)obj;
        return depth==other.depth && returnType.equals(other.returnType)
          && method.equals(other.method)
          && Arrays.equals(parameterTypes, other.parameterTypes);
      }
      else return false;
    }
    MyMethod(Method mtd) {
      this.method = mtd;
      this.parameterTypes = mtd.getParameterTypes();
      this.returnType = mtd.getReturnType();
      this.depth = getHierarchyDepthSum(parameterTypes);
    }
    public Method getMethod() {
      return method;
    }
    public Class[] getParameterTypes() {
      return parameterTypes;
    }
    public Class getReturnType() {
      return returnType;
    }
    public String toString(){
      return method.toString();
    }
    public long getDepth(){
      return depth;
    }

    private void writeObject(java.io.ObjectOutputStream out)
    throws java.io.IOException{
      out.defaultWriteObject();
      out.writeObject(method.getDeclaringClass());
      out.writeObject(method.getName());
      out.writeObject(parameterTypes);
    }
    private void readObject(java.io.ObjectInputStream in)
      throws java.io.IOException, ClassNotFoundException{
      in.defaultReadObject();
      try{
        final Class c = (Class)in.readObject();
        final String name = (String)in.readObject();
        this.parameterTypes = (Class[])in.readObject();
        this.method = c.getDeclaredMethod(name, parameterTypes);
        this.returnType = method.getReturnType();
      }
      catch(NoSuchMethodException e){
        throw new IllegalStateException(e.getMessage());
      }
    }

  }
  void addMethod(Method mtd){
    final String name = mtd.getName();
    List suite = (List) methods.get(name);
    if(suite==null){
      suite = new ArrayList();
      methods.put(name, suite);
    }
    suite.add(new MyMethod(mtd));
  }
  void sort(){
    for(Iterator it = methods.values().iterator(); it.hasNext();){
      final List suite = (List)it.next();
      sortMethods(suite);
    }
  }
  private static final Comparator SUB_PARAM_TYPES_FIRST = new Comparator(){
    public int compare(Object arg0, Object arg1) {
      return compareMyMethod((MyMethod)arg0, (MyMethod)arg1);
    }
  };
  private static int compareMyMethod(MyMethod m1, MyMethod m2){
    return compareParameterTypes(m1.getParameterTypes(), m1.getDepth(), 
        m2.getParameterTypes(), m2.getDepth());
  }
  static int compareParameterTypes(final Class[] params1, long depth1, 
      final Class[] params2, long depth2) {
    if(params1.length > params2.length) return -1;
    if(params1.length < params2.length) return 1;
    if(depth1 > depth2) return -1;
    if(depth1 < depth2) return 1;
    return 0;
  }
  private static void sortMethods(List suite){
    Collections.sort(suite, SUB_PARAM_TYPES_FIRST);
  }
  /**
   * To find a method in the impl class that can be used in place of the <i>implemented</i> method.
   * @param implemented the method to be implemented.
   * @return the method that can be used to implement, or null if not found.
   */
  public Method lookupImplementingMethod(Method implemented) {
    final String name = implemented.getName();
    final List suite = (List)methods.get(name);
    if(suite==null) return null;
    final int size = suite.size();
    final Class[] implementedParamTypes = implemented.getParameterTypes();
    final Class implementedReturnType = implemented.getReturnType();
    for(int i=0; i<size; i++){
      final MyMethod mm = (MyMethod)suite.get(i);
      final Class[] withParamTypes = mm.getParameterTypes();
      if(isParamsCompatible(withParamTypes, implementedParamTypes) && isReturnTypeCompatible(mm.getReturnType(), implementedReturnType)){
        return mm.getMethod();
      }
    }
    return null;
  }
  /**
   * To create a proxy instance for a given interface or superclass.
   * @param loader the class loader.
   * @param asType the interface or super class (cglib is required in this case).
   * @param handler the InvocationHandler to handle calls.
   * @return the proxy instance.
   */
  public static Object newProxyInstance(ClassLoader loader, Class asType, InvocationHandler handler){
    if(asType.isInterface()){
      return Proxy.newProxyInstance(loader, new Class[]{asType}, handler);
    }
    else {
      return CglibUtils.proxy(loader, asType, handler);
    }
  }
  static boolean isParamsCompatible(Class[] with, Class[] implemented){
    if(with.length!=implemented.length) return false;
    for (int i = 0; i < implemented.length; i++) {
      if(!with[i].isAssignableFrom(implemented[i])) return false;
    }
    return true;
  }
  static boolean isReturnTypeCompatible(Class with, Class implemented){
    if(void.class.equals(implemented)){
      return true;
    }
    return implemented.isAssignableFrom(with);
  }
  static long getHierarchyDepthSum(Class[] classes){
    long sum = 0;
    for (int i = 0; i < classes.length; i++) {
      sum += getHierarchyDepth(classes[i]);
    }
    return sum;
  }
  static int getHierarchyDepth(Class c){
    int depth = 0;
    if(c==null || Object.class.equals(c)){
      return depth;
    }
    int superDepth = 1+getHierarchyDepth(c.getSuperclass());
    if(superDepth>depth){
      depth = superDepth;
    }
    final Class[] itfs = c.getInterfaces();
    for(int i=0; i<itfs.length; i++){
      int itfDepth = 1+getHierarchyDepth(itfs[i]);
      if(itfDepth>depth){
        depth = itfDepth;
      }
    }
    return depth;
  }

  private static final long serialVersionUID = -5648266362433165290L;
}

codehaus总是这么慢。先把代码贴出来。大家给挑挑错。
cglib相关的类就不贴了。代码不能编译,大家包涵则个。
16 楼 ajoo 2006-12-11  
liuxg 写道
支持!

可否共享代码,大家学习一下,多谢

正在申请codehaus项目中。然后就可以下载了。
15 楼 ajoo 2006-12-11  
nihongye 写道
就是重构起来麻烦点

对亚。我也没有想到好的解决方法。

幸好,如果仅仅是用它来stub Connection, SqlMapSession这些第三方接口的话,就没有重构的问题了。

自己的接口当然还是直接"implements"的好。
14 楼 taowen 2006-12-11  
nihongye 写道
就是重构起来麻烦点

这的确是一个问题。而且似乎没有什么好的解决办法做到重构安全。我能够看到的一个加强的地方是,验证new Object中每个方法是不是实现了要stub的接口或者具体类的方法。这样在改名了之后,可以fail fast,不会出莫名其妙的问题不知所以。
另外,不妨把酒窝当一个stub框架来做。
13 楼 nihongye 2006-12-11  
就是重构起来麻烦点
12 楼 liuxg 2006-12-11  
支持!

可否共享代码,大家学习一下,多谢

相关推荐

    酒窝状换热板强化换热效果分析

    设计了一种酒窝状换热板,利用Fluent软件对酒窝板、平板及波纹板的换热及压降特性进行了模拟,对这3种板在不同流速下板间流道内壁面平均换热系数、换热量、平均努塞尔数以及摩擦阻力的变化特性进行了对比分析。...

    《小酒窝》教学设计.doc

    《小酒窝》教学设计正是为了打开这样一扇窗,让小学生在音乐的海洋中畅游。 《小酒窝》是一首旋律优美、节奏明快的歌曲,它不仅简单易学,而且充满了童趣,非常适合小学生的音乐课堂。而另一首乐曲《我们多么幸福》...

    三年级上册音乐教案 第一单元《小酒窝》人音版(五线谱)(2014秋).doc

    在小学三年级的音乐课堂上,有一首特别的歌曲正在等待着孩子们——《小酒窝》。这首歌以其独特的魅力和深沉的情感,为孩子们打开了一扇探索音乐世界的大门。借助人音版教材,通过五线谱的传授方式,孩子们将沉浸在这...

    人音三年级上册小酒窝PPT教案学习.pptx

    人音三年级上册小酒窝PPT教案学习.pptx

    小酒窝三年级上册人音小学音乐学唱PPT学习教案.pptx

    在今天的小学音乐课堂上,我们将会一起学习一首名为《小酒窝》的歌曲。这首歌曲以其轻松愉快的旋律和简单明快的节奏,成为了小学三年级学生音乐教学中一道亮丽的风景线。通过精心设计的PPT教案,我们将引导孩子们一...

    人音版三年级上册小酒窝PPT教案学习.pptx

    人音版三年级上册小酒窝PPT教案学习.pptx

    酒窝窝DIGG类web2.0全站 -ASP源码.zip

    【标题】"酒窝窝DIGG类web2.0全站 -ASP源码.zip" 提供的是一款基于ASP编程语言的Web2.0网站源码,适用于构建互动性强、用户参与度高的社交平台。Web2.0是互联网发展的一个重要阶段,强调用户的交互性、共创性和分享...

    酒窝143

    【酒窝143】项目概述 "酒窝143"这个名称可能是指一个项目的代号或主题,但它本身并没有直接提供关于IT技术的知识点。不过,从提供的压缩包文件名“dimple143-master”来看,我们可以推测这可能是一个开源软件项目...

    我也当上姐姐了作文.doc

    小男孩长得特别可爱,尤其是笑起来时露出的小酒窝,让人忍不住想多看几眼。我本就喜欢小孩子,所以心里暗自希望能和他多交流几句。 旅行的时间总是过得特别快,不知不觉间,火车已经行驶了大半路程。然而,就在这时...

    四川省仁寿县城北实验初级中学2020学年八年级生物下学期第一次月考试题(无答案) 新人教版.doc

    15. 酒窝性状遗传分析:儿子、儿媳均有酒窝但生出无酒窝孩子,说明他们都是杂合子(Aa),再生一个孩子有酒窝的概率为75%,无酒窝的概率为25%。 以上知识点涵盖了生物学的多个核心概念,如生物生殖、发育、遗传规律...

    rcdimple:htmlwidgets的rCharts +酒窝

    rcdimple | htmlwidget rcdimple是在凹坑片的版本 。 我们的目标是直接从R漂亮,可定制的d3.js图表,并且只需最少的代码并且不了解JavaScript。 首先,可以使用devtools::install_github进行安装。...

    四川省仁寿县城北实验2014 2015学年八年级生物下学期第一次月考试题(无答案) 新人教版.doc

    - 若有酒窝为显性,无酒窝为隐性,有酒窝的祖父母、儿子、儿媳可能基因型为Aa,孙子无酒窝则基因型为aa。 - 儿子、儿媳再次生育,有酒窝孩子的概率为50%,因为他们都是Aa,子代可能为AA或Aa,其中Aa表现为有酒窝。...

    生物历年大题归纳遗传题.doc

    - 小明的家庭中,如果有酒窝是显性(A),无酒窝是隐性(a),小明父亲无酒窝,因此其基因型为aa,母亲有酒窝,基因型为AA或Aa。反之,如果无酒窝是显性,有酒窝是隐性,则母亲的基因型为aa,父亲必须是Aa,因为...

    winbeep for python

    使用python给你beep个"祝你生日快乐" 和 "小酒窝" 简介不够代码来凑 import winsound def winbeep_di(): winsound.Beep(1000, 100) ....此处省略500宇宙代码 if __name__ == '__main__': winbeep_di() ...

    我自画像作文怎么写

    描述时,可以用比喻、拟人等修辞手法增加语言的表现力,如“笑起来非常好看,像是小酒窝里面装了酒一样”。外貌描述不仅要细致,还要力求生动、有趣,让人印象深刻。 其次,性格特征的呈现是自画像作文的重点。性格...

    贵州省六盘水市第十三中学八年级生物下册 第七单元 第二章 第三节 基因的显性和隐性导学案(无答案) 新人教版

    此外,一对无酒窝的夫妇无法生育出有酒窝的子女,因为有酒窝的基因D是显性的,而决定无酒窝的基因d是隐性的,所以无酒窝的夫妇基因型只能是dd,无法提供D基因给子女。 在能力提升环节,卷舌与非卷舌的例子进一步...

    小学六年级语文总复习之句型转换教学教案教(学)案.doc

    例如,"脸蛋印着酒窝"可以扩写为"可爱的脸蛋印着迷人的酒窝",缩写为"脸蛋印着酒窝"。 - **反问句与陈述句的互换**:反问句常用来强调陈述,转换时要去掉反问词,调整语气,如"我们是小学生,难道不要好好学习吗?...

    夸奖一个女孩子的话.doc

    3. 你笑起来的样子最为动人,两片薄薄的嘴唇在笑,长长的眼睛在笑,腮上两个陷得很举动的酒窝也在笑。 4. 青翠的柳丝,怎能比及你的秀发;碧绿涟漪,怎能比及你的眸子; 5. 你热情似火,你的微笑让我神魂颠倒 这些...

    六年级语文总复习之句型转换教学设计1电子版教(学)案.doc

    而缩句则相反,它通过去除修饰成分,保留句子的核心内容,如将"可爱的脸蛋印着迷人的酒窝"简化为"脸蛋印着酒窝"。掌握扩句与缩句的技巧,可以让学生的语言表达更为丰富和准确。 反问句与述句的互换也是句型转换中的...

    自我介绍的作文精选.doc

    例如:“我是一个阳光的女孩,虽然谈不上特别美丽,但也不丑陋,上翘的嘴角和薄薄的嘴唇,笑起来还有浅浅的酒窝。” 3. **性格特点**:性格特征是自我介绍中的重要组成部分,能反映你的内在世界。如:“我性格热情...

Global site tag (gtag.js) - Google Analytics