使用javassist编写一个简单的agentClassTransformer

2017/11/09 15:56:32 No Comments

前段时间找到一个很好的工具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操作.

(more…)

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

2017/04/10 22:29:05 No Comments

最近产品中有一种想法,即在保证源项目不动的基础之上,提供一种通用的扩展原产品逻辑的方法.对于这种情况,有很多方法可以做,比如直接上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) 处理.

(more…)

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

2013/10/14 11:47:34 2 Comments

在使用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)).

(more…)

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

2013/03/21 15:18:24 No Comments

在使用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个方法,我们不可能将所有的方法都重新写一次。

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

(more…)

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

2011/12/05 14:36:39 No Comments

一说到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允许。具体这两者的区别在哪儿,我们进一步地从源码上进行分析。

(more…)

使用javassist进行java类的实现方法修改

2011/03/25 12:01:20 No Comments

     前段时间在做一个关心软件授权方面的研究,即给一个软件进行授权管理,其中就涉及到如何创建license,并利用license来保护我们的软件。
    这其中最主要的问题,即是如何保护真正的授权部分代码了。即将真正进行授权访问的代码给保护起来,并不让软件使用人通过某种方法来进行软件的破解或者盗用。一般来说,破解软件最简单的方法即是修改授权部分代码的实现,让授权部分验证始终返回true。如果想办法不让使用者查看最终的授权部分代码,那么则只能通过其它方法来进行破解了。
    那么,我们可以通过一种手段来在程序内部来修改授权部分的实现,使真实的授权部分隐藏在其它代码部分,而可视的授权代码并不参与实际的授权授权,这样的话,对于破解者来说,修改表向的代码实现并不能真正修改代码实现,因为真实的实现已经通过其它代码将原始实现替换掉了。
    以下是一种修改的示意图:
       
    即在调用授权代码之前将授权原代码进行修改,然后调用授权代码时即调用已经修改后的授权代码,而真实的授权代码是查看不了的(通过某种方式注入),这样即达到一种授权方式的隐藏。

(more…)