分享

Java下的框架编程(反射,泛型,元数据,CGLib,代码动态生成,AOP,动态语言嵌入)

 ShangShujie 2007-04-11
作者:江南白衣
                                                
   以Ruby为助力的Ruby on Rails 横空出世,又刷新了一次人们对Framework的认识:Closures、Maxin、Continuations、Metaprogramming、Reflection,又一堆名词砸得人悴不及防。
      Java 虽然没有动态语言般暴起,但仍然天连天,水接水的生出好多框架技术---反射(reflection),泛型(generics),元数据(annotation),proxies(proxy/cglib),代码动态生成(asm),AOP(aspectJ),动态语言嵌入(groovy/javascript/beanshell)。面对着这些,我们像一夜暴富的农企,有点手足无措的样子。

    第一感觉,应该出现新的Design Pattern书籍了。
    多年前基于纯C++语法写就的那本经典,很多模式都可以被更优雅的实现,还有更多的新模式,会随着这些技术的推广而出现。

    第二感觉,新的框架模式出现了。
    不断升级自己的Spring,综合运用了除泛型外的大部分技术,把编程界的想像力MFC/ EJB2.0 Container这样传统的template模式/钩子函数的思路,向完全无侵入的标准过渡。
    当你不顾性能狂用反射时,C++下的遗憾感开始越来越远。
    
    第三感觉,自己应该重新充电了。
    从C++下编写框架的经历到尽用“反射,泛型,元数据,proxy,代码动态生成,AOP,动态语言嵌入”是一条明显的分界线,自己应该调度足够的业余时间,沉实的学习一遍。

    这个系列开始一个个介绍上述的积木。

Java5泛型的用法,T.class的获取和为擦拭法站台

作者:江南白衣 

   Java 5的泛型语法已经有太多书讲了,这里不再打字贴书。GP一定有用,不然Java和C#不会约好了似的同时开始支持GP。但大家也清楚,GP和Ruby式的动态OO语言属于不同的意识形态,如果是一人一票,我想大部分的平民程序员更热衷动态OO语言的平白自然。但如果不准备跳槽到支持JSR223的动态语言,那还是看看GP吧。

   胡乱总结泛型的四点作用:
   第一是泛化,可以拿个T代表任意类型。 但GP是被C++严苛的静态性逼出来的,落到Java、C#这样的花语平原里----所有对象除几个原始类型外都派生于Object,再加上Java的反射功能,Java的Collection库没有范型一样过得好好的。

   第二是泛型 + 反射,原本因为Java的泛型拿不到T.class而觉得泛型没用,最近才刚刚学到通过反射的API来获取T的Class,后述。

   第三是收敛,就是增加了类型安全,减少了强制类型转换的代码。这点倒是Java Collection历来的弱项。

   第四是可以在编译期搞很多东西,比如MetaProgramming。但除非能完全封闭于框架内部,框架的使用者和扩展者都不用学习这些东西的用法,否则那就是自绝于人民的票房毒药。C++的MetaProgramming好厉害吧,但对比一下Python拿Meta Programming生造一个Class出来的简便语法,就明白什么才是真正的叫好又叫座。

   所以,作为一个架构设计师,应该使用上述的第2,3项用法,在框架类里配合使用反射和泛型,使得框架的能力更强; 同时采用收敛特性,本着对人民负责的精神,用泛型使框架更加类型安全,更少强制类型转换。
   
   擦拭法避免了Java的流血分裂 :
    大家经常骂Java GP的擦拭法实现,但我觉得多亏于它的中庸特性---如果你用就是范型,不用就是普通Object,避免了Java阵营又要经历一场to be or not to be的分裂。 
    最大的例子莫过Java 5的Collection 框架, 比如有些同学坚持认为自己不会白痴到类型出错,而且难以忍受每个定义的地方都要带一个泛型定义List〈Book〉,不用强制类型转换所省下的代码还不够N处定义花的(对了,java里面还没有tyepdef.....),因此对范型十分不感冒,这时就要齐齐感谢这个搽拭法让你依然可以对一个泛型框架保持非泛型的用法了...

   通过反射获得 T.class:
   
    不知为何书上不怎么讲这个,是差沙告诉我才知道的,最经典的应用见Hibernate wiki的Generic Data Access Objects, 代码如下: 
abstract public class BaseHibernateEntityDao<T> extends HibernateDaoSupport {
 
private Class<T> entityClass;
 
public BaseHibernateEntityDao() {
        entityClass 
=(Class<T>) ((ParameterizedType) getClass()
                                .getGenericSuperclass()).getActualTypeArguments()[0];
    }
 
public T get(Serializable id) {
        T o 
= (T) getHibernateTemplate().get(entityClass, id);
}
}

  精华就是这句了:
Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 

  泛型之后,所有BaseHibernateEntityDao的子类只要定义了泛型,就无需再重载getEnttityClass(),get()函数和find()函数,销益挺明显的,所以SpringSide的Dao基类毫不犹豫就泛型了。

  不过擦拭法的大棒仍在,所以子类的泛型语法可不能乱写,最正确的用法只有:
   
public class BookDao extends BaseHibernateEntityDao<Book>
 

Java下的框架编程(3)--关于反射的碎话

     反射,所有教科书都正儿八经的讲了,这里只能再随便讲两句。

     反射是一种让框架能够根据 "以字符串形式存在的信息" 来调用对象的属性和函数的技术,是Java对C++最大的进步之一---让框架编程真正走向平民化。MFC年代,无论侯捷如何深入浅出,还在念大学的我就是搞不懂那些注册"消息--函数映射"的魔法宏。

     不过Java的反射也就是对着C++比较自豪而以,因为C#,Ruby,Python甚至php都标配了反射的功能。而且,人家的反射语法都是内嵌在基础Object类的,拿最弱的php来看:
$func_name="helloworld";
$foo->$func_name;

     而Java,却搞出了Class,Methed, Field,Constructor这么一大堆类出来。本来这是Java设计师很严谨,很cool的体现,问题是它居然不提供一种集成的简便的写法......相同的情形还出现在Java的I/O 类库里。
    微软这方面就做得好些,懂得讨好开发人员。

    因为Java的无情,就搞得大家的项目里要自制BeanUtils了。幸亏Apache Jakarta Commons 已经做了一个比较好的,可以直接使用--以前写的介绍文章
    另外Spring也做了一个。

    闲得没事做的,还可以emule一本〈Relection in action〉回来看。

    而C++下面的"反射",见我偶像di文章。另还有一个比较BT的C++框架叫ACDK的,把自己整得和Java很像,有反射和垃圾收集,甚至和JSDK差不多的线程,Unicode,I/O,网络,XML API。可惜的是,即使到了C++0x, B大叔还是不准备在语言级支持反射。
 

Java下的框架编程(4)--Annotation vs XML vs Interface 最新一Round

作者:江南白衣 

1.因为两者的用途不同,所以Annotation与XML应该并存

 Anootation vs XML 到了现在,在Spring和EJB3各占一边的持续演示下,各自的用途已清晰。
   虽然两者之间完全可以相互替代,没有绝对不能做的事情,但却存在好不好的问题,两者的用途不同,应该并用:

   "size=10,db_user=ito"这样的参数,仍然适合放在XML,YAML(ruby),properties,ini文件里。
    而Annotation中所放,是真正程序的一部分,是旧编程体系中所缺的那一块东西。
 看看编程语言的发展:
    一开始只是函数的封装;
    到了OO语言,对象有了自己的属性和方法;
    到了框架满天飞的年代,这些属性和方法又有了自己的属性,来告诉框架如何为自己提供服务。比如Spring和EJB3,POJO就用配置信息来告诉框架无侵入的提供服务。整个进化的过程非常自然。

    因为这些信息本来就是程序的一部分,所以应该仍然放在Code里头,Spring把它割裂到XML明显影响了代码的阅读。

2.Anotation/XML PK Interface,Spring 无侵入性的基础
   切尔斯基的Blog讲了Annotation/XML 对Interface的PK。这次PK,也可以认为是Spring 对 EJB2.1在框架无侵入性上的一次PK。
   在EJB2.1时代,POJO必须通过接口来向框架声明一些东西,这就造成了框架的侵入性,强迫POJO实现一堆接口。而Spring,把这些元信息搬到了XML配置文件。
 在Spring里随便就可以举个例子。比如一个POJO,在Spring为它进行依赖注入A和B后,需要根据A和B来装配一个内部属性C,这样就需要再跑一个init()函数。Spring提供两种方式,一种侵入性的,实现InitializingBean接口的afterPropertiesSet()函数供Spring调用。   而另一种是无侵入性的,在Bean的xml节点里面自行定义init函数的名字。

3.Annotation目前的弱点
 一是Hibernate、Struts、三姑妈、四姨婆的annotation如果一股脑儿的全堆在可怜的POJO上很没人道,如果三姑六婆都抢Transation这个Anontation就更惨了。
    二是annoation的表达能力有限,不如XML的强。

4.JSR250 - Common Annotations for the Java
    Final Draft已经发布,八卦回来一看,也只是多定义了几个Common Annotation而已。
   1.Generated  自动生成的代码要用此声明,而且必须说明工具的名称,如@Generated(“com.sun.xml.rpc.AProcessor”)

   2.Resource/Resources 就是EJB3里面用的资源引用。

   3.PostConstruct / PreDestroy 声明那些如题的函数。
  
   还有一些安全方面的annotation,不关心
 

Java下的框架编程(5)--cglib的应用

   作者:江南白衣 

   反射、Proxy和元数据是Java最强的三个特征,再加上CGLib (Code Generation Library)和ASM,使得Java虽然没有Ruby,Python般后生可畏,一样能做出强悍的框架。
   Proxy可以看作是微型的AOP,明白提供了在继承和委托之外的第三个代码封装途径,只要有足够的想象力,可以做得非常好玩,Spring的源码里用Proxy就用得很随便,看得我非常眼红。可惜Proxy必须基于接口。因此Spring的做法,基于接口的用proxy,否则就用cglib。AOP么,一般小事非compoent一级的就不麻烦AspectJ出手了。

    cglib的Enhancer说起来神奇,用起来一页纸不到就讲完了。
    它的原理就是用Enhancer生成一个原有类的子类,并且设置好callback到proxy, 则原有类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数:
public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy)

 在intercept()函数里,你可以在执行Object result=proxy.invokeSuper(o,args);来执行原有函数,在执行前后加入自己的东西,改变它的参数值,也可以瞒天过海,完全干别的。说白了,就是AOP中的around advice。

    AOP没有出现以前,该领域经典的设计模式是Decorator,像Java IO Stream的设计就是如此.不过,如果为每个DAO, 每个方法的写Decorator函数会写死人的,所以用上cglib的好处是一次过拦截所有方法。 

     另外,cglib除了Enhancer之外,还有BulkBean和Transform,都是Hibernate持久化的基础,但文档贫乏,一时还没去看怎么用。

1.AOP里讲了一百遍阿一百遍的log aspect在cglib是这样做的:


   
public class LogDAOProxy implements MethodInterceptor
   {
       
private Logger log=Logger.getLogger(LogDAOProxy.class);
       
private Enhancer enhancer=new Enhancer();
        
//返回DAO的子类
       public Object getDAO(Class clz)
       {
           enhancer.setSuperclass(clz);
           enhancer.setCallback(
this);
           
return enhancer.create();
       }
       
//默认的拦截方法
      public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy) throws Throwable
      {
           log.info(
"调用日志方法"+method.getName());
           Object result
=proxy.invokeSuper(o,args);
           
return result;
      }
   }

    应用的代码:
    LogDAOProxy proxy = new LogDAOProxy();
    GoodsDAO  dao 
= (GoodsDAO)proxy.getDAO(GoodsDAO.class);
    dao.insert(goods);

2.而在Spring的管理下应该略加修改的高级Decorator
   上面的例子用return enhancer.create();创建子类实例,但在Spring管理下,一些Bean的实例必须由Spring来创建和管理,而不由enhancer来创建的。所以我对上述用法略加修改,使它真正当一个Proxy的角色,请对比黑体字的部分


  public class LogDAOProxy implements MethodInterceptor
  {
       
private Logger log=Logger.getLogger(LogDAOProxy.class);
       
private Object dao=null;
       
private Enhancer enhancer=new Enhancer();
        
//返回DAO的子类
       public Object getDAO(Class clz,Object dao)
       {
           
this.dao = dao;
           enhancer.setSuperclass(clz);
           enhancer.setCallback(
this);
           
return enhancer.create();
       }      
       
//默认的拦截方法
      public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy) throws Throwable
      {
           log.info(
"调用日志方法"+method.getName());
           Object result
=proxy.invoke(dao, args);
           
return result;
      }
  }

可见,原来模式里在getDao()时由enhancer创建dao,而 调用intercept时则将enhancer创建的dao以Object o参数传回。
而新模式里,dao在getDao()时从外面传入,enhancer.create()返回的是一个proxy. 而调用intercept时,实际会用之前传入的dao进行操作,而忽略Object o参数传入的proxy.

有点遗憾, intercept函数里MethodProxy的Signature是固定的 , 即客户如果调用foo(String),你不可以用proxy.invoke偷换成foo(String,String);

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多