使用javassist生成对象转换器Converter

在编程当中,作为Converter, spring体系自带的conversionService可以解决大部分基本对象的转换问题,但对于在业务系统中写的domain,vo,po等对象.spring是不能完成相应对象之间的转换的, 当然也可以使用类似BeanUtils.copyProperties来完成属性之间的数据复制功能. 除此之外,还可以使用第三方组件,比如dozer,都可以进行信息复制处理.

不过上面的方法的问题在于, 除扩展之外,相应的转换过程均是使用反射来完成的.比如通过 PropertyDescriptor 来获取property的readMethod, 然后再通过writeMethod来写入目标对象的数据值,或者直接通过Field.set来完成字段级的数据写入.从编码手法上来看,当前我们更希望通过一些非反射的手法来完成这个操作, 一种实现方法即是通过字节码生成来构建一个特定场景的Converter, 直接通过方法调用来完成相应的映射过程.

一个标准的转换器接口定义如下:

public interface Converter<S, T> {
    T convert(S s);
}

实现者除了要完成具体的convert过程之外, 还需要对外暴露出具体的泛型信息,以方便框架进行读取和解析. 比如spring conversionService即会通过读取converter实现类泛型来完成内部 from -> to的映射过程(而不需要调用者手动进行类型传参).

本文描述了一种通过javassist,字节码操作工具, 动态地读取from,to类的描述信息和注解信息, 完成convert body的字符串生成. 同时, 写入相应的泛型信息, 以实现泛型编程.

继续阅读“使用javassist生成对象转换器Converter”

使用javassist编写一个简单的agentClassTransformer

前段时间找到一个很好的工具Greys-anatomy, 相应的参考地址为参考地址:https://github.com/oldmanpushcart/greys-anatomy. 为此,专门研究了一下基工作原理.并简单研究了一下基于Instrumentation进行运行时代码调整的一些实现手法. 本文, 简单介绍一下如何使用javassist来简单对一个代码作编织, 实现简单的一些监控指标手段.

附: 另外,前几年淘宝也简单作了一个通过启动时agent达到运行方法监控的目的, 称之为TProfiler,对研究一些实现手法很有用.

一个主要的ClassFileTransformer定义如下:

    byte[]
    transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

传递到当前实现的主要有用的信息即为className, classfileBuffer两个数据. className即为当前正准备加载的类, 而classfileBuffer即传递给当前处理的字节码, 需要做的即是将这个字节码进行处理,然后返回一个已经处理过的字节码,即完成instrument操作.

继续阅读“使用javassist编写一个简单的agentClassTransformer”

结合javassist进行通用性代码扩展修改的思路

最近产品中有一种想法,即在保证源项目不动的基础之上,提供一种通用的扩展原产品逻辑的方法.对于这种情况,有很多方法可以做,比如直接上aspectj,或者是spring aop,也可以是单个javassist修改.但对于实施方扩展源码,还是从相应的几个问题入手.

1. 修改的实现手段是否有难度,比如aspectj的切面语法
2. 相应的应用技术是否对项目运行时有性能影响,比如aspectj中*号匹配模式
3. 过多的修改类是否容易进行管理和处理,比如过多的切面是否好处理

考虑到以上的思路,想做一种简单的修改方式,即将相应的场景固定下来.比如,修改参数信息的,修改结果,以及替换实现等几个固定场景.并且,这些场景都是针对单一的修改场景,并不是一种范围匹配方式的.因为根据实际经验,进行实施时,往往是修改特定的场景中的特定的数据信息. 至于常规的类似日志记录,信息拦截等,还是通过切面来统一实现的好.

以下以一种修改参数信息的场景进行举例:
相应的实际效果,如下:

//源类
public class T8 {
    public static void abc(int a, int b) {
        System.out.println("->" + a + "," + b);
    }
}

//修改类
public class T81 {
    @Param(clazz = "t2.T8", method = "abc", params = {"int", "int"}, order = 2)
    public Map<Integer, Object> xchg1(int a, int b) {
        System.out.println("xchg1->" + a + "," + b);
        return ImmutableMap.of(1, a + 2, 2, b + 3);
    }
}

以上的代码,基于以下的工作方式.

1 获取相应的原始调用参数信息
2 将相应的参数传递给相应的修改处理器
3 如果有多个处理器,即链式调用
4 处理完之后,将参数信息重新传递回原来的方法调用

整个工作方式,基于javassist.因为它提供一种代码式的插入方式,相比asm,在应用层面更加方便.相应的文档参考地址如: http://jboss-javassist.github.io/javassist/tutorial/tutorial.html
因为是,处理参数信息,即在整个方法调用前插入相应的修改代码,使用的即是相应的ctmethod.insertBefore(code) 处理.

继续阅读“结合javassist进行通用性代码扩展修改的思路”

使用代码生成技术避免在struts2 action中大量的set/get方法

在使用struts2进行功能开发时,我们经常碰到的问题就是如果这个类有很多个参数,这里就需要有很多个get/set方法.如果你发现你的一个action中很上百行的set/get方法,那无疑让人抓狂,因此会让真正的业务方法隐藏其中,不管是开发还是维护都难以查找.
从业务开发的角度,action中的方法只能够表现于与业务的一一对应,如果没有相应的业务,则不相关的方法都不应该出现在当前这个类中.只不过,我们使用了struts2,这就表示必须有比业务方法数大得多的set/get充斥其中.

本文即通过一种代码生成(或称之为代码改写)使得我们在开发的时候不需要编写相应的set/get,而在运行时自动产生这些方法,相应的问题就可以解决了.具体结果可参考如下:

public class UserAction {
/** 表示要操作的用户信息 */
@SetAndGet(set=true,get=true)
private String user;
@SetAndGet(get=false)
private long idx;
}

在工程进行启动时,就即会相应的处理器自动处理相应的action,然后根据这些注解信息自动生成相应的set/get,这样即可以满足struts2的规范要求,又可以避免代码海洋,同时进行维护时也可以直接查找到相应的业务,直接查看字段信息,即可以了解这些属性有什么用(如用于数据返回(只需要get,或参数传递(需要set/get)).

继续阅读“使用代码生成技术避免在struts2 action中大量的set/get方法”

使用javassist支持mybatis进行结果集自动匹配和指定匹配

在使用mybatis时,经常的写法即使用下面的*写法,这种方法最简单,不用复杂的resultMap.如下所示:

select * from table where condition

在这种写法下,要求一定很严格,即在相应的domain中的属性字段必须和数据库中的字段相一致,否则即不能进行匹配。如果在数据库中,有一个字段为a_id,那么在domain中的属性也必须这样写,写成a_id,这种代码编写方式肯定不符合代码规范。

针对这种情况,在mybatis中就提出一个匹配变量mapUnderscoreToCamelCase,即在碰到有下划线的时候,自动转化为驼峰式的方式。但是这个变量有一个问题,即它会强制进行转换。当我们的domain即有a_id,又有aId的编写方式时,这个变量就一点作用也没有,并且如果进行配置了,还会造成程序出错。

还有一种需求就是,如果我们的domain先于数据库产生,当最终的数据库产生时,需要一种类似在hibernate中的column mapping的需求产生,即数据库中指定字段匹配domain中的指定属性。

经过程序中进行查找,我们发现最终的结果匹配过程是由类FastResultSetHandler来完成的,而在这个实现中,针对在上面的处理过程,是由方法applyAutomaticMappings来完成的。它的方法签名是protected,那么我们是否可以通过继承这个类,然后重写这个方法呢。答案是否定的,因为这个类的初始化已经固化在类Configuration中了,即我们有了实现类,但是mybatis始终不会实现化我们所要求的类。并且,在过程中,mybatis的插件模式即plugin也没有办法,因为类fastResultSetHandler的接口ResultSetHandler只有2个方法,但是整个实现类中有N个方法,我们不可能将所有的方法都重新写一次。

接下来最终的方法就只有一个,即在之前我们所使用的,代码替换。我们需要一个类直接将其实现替换即可。

继续阅读“使用javassist支持mybatis进行结果集自动匹配和指定匹配”

从源码上分析hibernate的load和get之间的区别

一说到hibernate的get和load之间的区别,大多数网上的都会说出如下的区别:get不走缓存,load走缓存;或者get不会使用二级缓存之类,然而这些都是错误的。其实两者没有大多的区别,真正的区别在于二者获取对象的方式,以及如何使用对象上。本文从源码分析上分析两者的具体区别。本方使用的hibernate 版本为3.6.3。

获取对象的API,二者都使用统一的方式调用,如下所示:

来源于sessionImpl
		load的api:
                LoadEvent event = new LoadEvent(id, entityName, false, this);
		fireLoad( event, LoadEventListener.LOAD );
                
               get的api:
		LoadEvent event = new LoadEvent(id, entityName, false, this);
		fireLoad(event, LoadEventListener.GET);

可以看出,二者的api都一样,主要的不一样在于,触发事件的方式不一样。load时使用LoadEventListener.LOAD而get时使用LoadEventListener.GET。我们看看两者的具体不一样:

public static final LoadType GET = new LoadType("GET")
			.setAllowNulls(true)
			.setAllowProxyCreation(false)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);
	
	public static final LoadType LOAD = new LoadType("LOAD")
			.setAllowNulls(false)
			.setAllowProxyCreation(true)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);

主要的区别在于:在allowNulls上get允许而load不允许,allowProxyCreation上get不允许而load允许。具体这两者的区别在哪儿,我们进一步地从源码上进行分析。

继续阅读“从源码上分析hibernate的load和get之间的区别”