一个简单的Agent

 

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

1github

2web

3apache

4编程

5框架

class MyAgent {工具

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

    // implement agent here ...编码

  }spa

}

关于处理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++内联的效果——译者注)

相关文章
相关标签/搜索