使用ByteBuddy进行链/变换方法调用

时间:2017-07-05 20:42:56

标签: java reflection byte-buddy

使用ByteBuddy,我可以通过调用另一个实例并转换结果来实现一个实例方法吗?

例如(玩具示例):

public abstract class Foo {
  public String bar() {
    return "bar";
  }

  public abstract int baz();
}

鉴于上述情况,我可以实现baz,使其调用bar()并返回返回字符串的长度吗?即,好像它是:

public int baz() {
  return bar().length();
}

天真地,我尝试了以下内容:

Method bar = Foo.class.getDeclaredMethod("bar");
Method baz = Foo.class.getDeclaredMethod("baz");

Method length = String.class.getDeclaredMethod("length");

Foo foo = new ByteBuddy()
  .subclass(Foo.class)
  .method(ElementMatchers.is(baz))
  .intercept(
    MethodCall.invoke(bar)                 // call bar()...
      .andThen(MethodCall.invoke(length))  // ... .length()?
  ).make()
  .load(Foo.class.getClassLoader())
  .getLoaded()
  .newInstance();

System.out.println(foo.baz());

但是,我认为在第一次调用的返回值上调用andThen()时我错了;它看起来像是在生成的实例上调用的。

Exception in thread "main" java.lang.IllegalStateException: 
  Cannot invoke public int java.lang.String.length() on class Foo$ByteBuddy$sVgjXXp9
    at net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation
    .invoke(MethodCall.java:1667)

我也试过拦截器:

class BazInterceptor {
  public static int barLength(@This Foo foo) {
    String bar = foo.bar();
    return bar.length();
  }
}

使用:

Foo foo = new ByteBuddy()
  .subclass(Foo.class)
  .method(ElementMatchers.is(baz))
  .intercept(MethodDelegation.to(new BazInterceptor()))
  // ...etc.

这已经运行了,但产生了无意义的结果870698190,并且在barLength()中设置断点和/或添加打印语句表示它永远不会被调用;很明显,我也不能正确理解拦截器或@This

如何让ByteBuddy调用一个方法,然后在其返回值上调用另一个方法?

k5_'s answerBazInterceptor如果 则有效:

  • 我们如上所述委托new BazInterceptor(),但将barLength()作为实例方法,或者:
  • 我们保留barLength()类方法,但委托给BazInterceptor.class而不是实例。

我怀疑870698190已委托给hashCode()BazInterceptor个实例,但我实际上没有检查过。

2 个答案:

答案 0 :(得分:1)

您使用实例作为拦截器,这意味着首选实例方法(可能根本不接受静态方法)。有一种与int baz()方法的签名匹配的实例方法,它是int hashCode()。您获得的数字是new BazInterceptor()实例的哈希码。

我知道的选项:

  • static移除barLength,以便它实际用于拦截。
  • 将类添加为拦截器.intercept(MethodDelegation.to(BazInterceptor.class))

我更喜欢第二个选项,因为您没有使用BazInterceptor实例的任何字段/状态。

答案 1 :(得分:1)

目前在Byte Buddy中没有一个好方法,但这是一个很容易添加的功能。你可以track the progress on GitHub。我会在找到一些时间后添加它。

如果要在今天实现此类链式调用,可以使用Java代码实现它们,并使用var request = require("request") var Hapi = require('hapi'); var Slack = require('slack-node'); var h = 0; var s = 0; var e = 0; function onlineBooking(){ request({ url: "http://example.com", json: true }, function (error, response, body) { if (!error && response.statusCode === 200) { // 'if/else' checks that it receives an up respsose four times in a row // the variable e is increased by .25 for every 200(ok response) until e reaches 1 // it will then send a message that the server is up if(e < 1 && response.statusCode === 200){ setTimeout(function () { console.log(response.statusCode) // Print the response code e =e+0.25; }, 6000); // 6 seconds delay between each response } else { while(h == 0){ console.log(response.statusCode) // Print the response code console.log("********************") slackReviewBot("Website :robot_face: ", response.statusCode + " - OK", "http://example.com", ""); h++; s = 0; } }// end of else }// end of if else { console.log(response.statusCode) // Print the response code e = 0; setTimeout(function () { while(s == 0){ console.log(response.statusCode) // Print the response code console.log("********************") slackReviewBot("Website :robot_face: ", response.statusCode, "http://example.com", ""); s++; h=0; }}, 3000); } // end of else }) } // sets the loop for checking every 7 seconds setInterval(function(){ onlineBooking(); }, 7000); // this function sends server name, a message and url to slack function slackReviewBot(servername, body, urls, bod) { var time = require('time'); // Create a new Date instance var now = new time.Date(); now.setTimezone("Europe/London"); var bo = body; var bod = bod; var urls = urls; var sname = servername; // you'll need to replace the slack webhook below // you'll find info on webhooks here https://api.slack.com/incoming-webhooks var webhook_url = 'https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxx'; slack = new Slack(); slack.setWebhook(webhook_url); slack.webhook({ channel: "#server-uptime", username: "Server:", icon_emoji: "http://4.bp.blogspot.com/-mYCTaPOu-60/VK98X5CJEyI/AAAAAAAAApM/0oplbclvnUY/s1600/unnamed.png", text: " " + "\n" + "*" + sname + " * " + "\n" + "Status: " + bo + "\n" + now + "\n" + "Check the status here: " + urls + "\n" }, function(err, response) { console.log(response); }); } // below is so you can send a test json object to the server // http POST localhost:1337/jsonpost test=Test // you'll get a slack message letting you know the server is running var server = new Hapi.Server(); server.connection({ port: 1337 }); exports.add = function(i, j) { return i + j; }; // Slack function for sending the test reply function slackReviewBot2(testserver) { testserver = testserver; // you'll need to replace the slack webhook below // you'll find info on webhooks here https://api.slack.com/incoming-webhooks var webhook_url = 'https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxx'; slack2 = new Slack(); slack2.setWebhook(webhook_url); slack.webhook({ channel: "#server-uptime", username: "Server-Test-Reply:", icon_emoji: "http://www.wonderfulwebsites.ie/logo.png", text: ":star: :star: :star: :star: :star:" + "\n" + "\n" + "Sever is up and running!!!" }, function(err, response) { console.log(response); }); } // take the json object for testing server.route({ method: 'POST' , path: '/jsonpost', handler: function(req, reply) { var review = { userName: req.payload.userName } //passes the review to the slackbot function slackReviewBot2(review.userName); reply("Received"); } }); // prints a server running message server.start(function(){ console.log('server running at: ', server.info.url); }); 组件内联此代码。或者,您可以通过基于Advice实例创建自己的ByteCodeAppender来更明确地编写字节代码,但必须手动加载参数。