一个简单的Agent

 

Java agent是用一个简单的jar文件来表示的。跟普通的Java程序很相似,Java agent定义了一些类作为入口点。 这些作为入口点的类需要包含一个静态方法,这些方法会在你原本的Java程序的main方法调用之前被调用:

1

2

3

4

5

class MyAgent {

  public static void premain(String args, Instrumentation inst) {

    // implement agent here ...

  }

}

关于处理Java agent时最有趣的部分,是premain方法中的第二个参数。这个参数是以一个Instrumentation接口的实现类实例的形式存在的。这个接 口提供了一种机制,能够通过定义一个ClassFileTransformer,来干预对Java类的加载过程。有了这种转设施,我们就能够在Java类 被使用之前,去实现对类逻辑的强化。

这个API的使用一开始看上去不那么直观,很可能是一种新的(编程模式)挑战。Class文件的转换是通过修改编 译过后的Java类字节码来完成的。 实际上,JVM并不知道什么是Java语言, 它只知道什么是字节码。也正是因为字节码的抽象特性,才让JVM能够具有运行多种语言的能力,例如Groovy, Scala等等。这样一来,一个注册了的类文件转换器就只需要负责把一个字节码序列转换成另外一个字节码序列就可以了。

尽 管已经有了像ASMBCEL这样的类库,提供了一些简易的API,能够对编译过的Java类进行操作,但是使用这些库的门槛较高,需要开发者对原始的字 节码的工作原理有充分的了解。更可怕的是,想直接操作字节码并做到不出问题,这基本上就是一个冗长拉锯的过程,甚至非常细微的错误,JVM也会直接抛出又 臭又硬的VerifierError。不过还好,我们还有更好,更简单的选择,来对字节码进行操作。

Byte Buddy这是一个我编写,并负责维护的工具库。这个库提供了简洁的API,用来对编译后的Java字节码进行操作,也可以用来创建Java agent. 从某些方面来看,Byte Buddy也是一个代码生成的工具库,这和cglib以及Javassit的功能很类似。然而,跟他们不同的是,Byte Buddy还能够提供统一的API,实现子类化,以及重定义现有类的功能。在本文中,我们只会研究如何用Java agent来重定义一个类。如果读者有更多的兴趣,可以参照Byte Buddy’s webpage which offers a detailed tutorial ,那个里面有很详细的描述。

使用Byte Buddy创建simple agent

Byte Buddy提供的一种定义手段采用了依赖注入的方法。其原理是这样的:使用一个拦截器类——这个类是一个POJO——来获得标注参数所需要的信息。例如: 将Byte Buddy的@Origin标注使用在一个Method类型的参数上,Byte Buddy即可推演出拦截器目前要拦截的就是method变量。这样,我们就可以定义一个泛型的拦截器,只要method一出现,就会被拦截器拦截。

1

2

3

4

5

class LogInterceptor {

  static void log(@Origin Method method) {

    Logger.log(method + " was called");

  }

}

当然,Byte Buddy可以作用于多个标注上。

但是,这些拦截器如何能够表示我们提出的日志框架所需要的代码逻辑呢?到目前为止,我们仅仅是定义了一个拦截器,用来拦截我们的method调用。还缺少对 于method所在的原始代码序列的调用。幸运的是,Byte Buddy提供的手段是可组合(compose)的。首先我们定义一个MethodDelegation类,并将其组合到LogInterceptor 中,这个拦截器类会在每一次method被调用的时候去默认调用拦截器的静态方法。以此为起点,我们可以通过一种序列调用的方式,将代理类和原先调用 method的代码组合起来,就跟Super MethodCall表示的一样:

1

2

3

4

class LogAgent {

MethodDelegation.to(LogInterceptor.class)

  .andThen(SuperMethodCall.INSTANCE)

}

最后,我们还需要通知Byte Buddy,将被拦截的方法与特定的逻辑绑定。就像我们在前面阐述的一样,我们想把一段逻辑(就是记录日志的功能——译者注)施加到每一个加了@Log标 注的地方。在Byte Buddy中,通过使用ElementMatcher方法,(被标注的)方法就会被识别出来,这和Java 8的断言机制很类似。在静态工具类ElementMatcher中,我们可以用相应的matcher来识别我们(用@Log)标注后的方 法:ElementMatchers.isAnnotatedWith(Log.class)。

通 过上述的方式,我们就实现一个agent的定义,可以完成我们提出logging framework的要求。就跟我们在前文叙述的原理一样,Byte Buddy提供了一套工具API来构建Java agent,这些工具API则是基于可对(编译后的)class进行修改的(JavaEE原生)API。就跟下面这段API一样,其设计上与面向领域语言 相似,从代码的字面上就可以轻松弄懂其含义。显然,定义一个agent就仅仅需要几行代码而已:

1

2

3

4

5

6

7

8

9

10

11

class LogAgent {

  public static void premain(String args, Instrumentation inst) {

    new AgentBuilder.Default()

      .rebase(ElementMatchers.any())

      .transform( builder -> return builder

                              .method(ElementMatchers.isAnnotatedWith(Log.class))

                              .intercept(MethodDelegation.to(LogInterceptor.class)

                                  .andThen(SuperMethodCall.INSTANCE)) )

      .installOn(inst);

  }

}

注意,上面这段最简Java agent代码不会对原有的代码产生干扰。对于已有的代码来说,附加的逻辑代码就仿佛是直接把硬编码插入到带有标注的方法处一样(类似于C++内联的效果——译者注)

转载于:https://my.oschina.net/chendongj/blog/761612

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值