bytebuddy与Spring Boot中的AOP无法正常工作。

huangapple go评论97阅读模式
英文:

bytebuddy with aop in springboot not work

问题

我尝试使用 ByteBuddy 在 Spring Boot 中实现 AOP以下是代码示例

```java
package klordy.learning.annotation;
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface CostTime {
}
package klordy.learning.agent;
public class Agent {
private static Agent instance = new Agent();
    private Logger logger = LoggerFactory.getLogger(Agent.class);
    private Agent() {}
    public static Agent getInstance(){ return instance; }
    public void install() {
        ByteBuddyAgent.install();
        AgentBuilder.Listener listener = new AgentBuilder.Listener() {
            // 什么也不做
            ...
        };

        new AgentBuilder.Default()
                .type(ElementMatchers.nameStartsWith("klordy.learning"))
                .transform((builder, typeDescription, classLoader, module) ->
                        builder.visit(Advice.to(TimeAdvice.class).on(ElementMatchers.isAnnotatedWith(named("klordy.learning.annotation.CostTime")))))
                .with(listener)
                // *** 根据预期添加,但似乎仍然不起作用。
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .installOnByteBuddyAgent();
         logger.info("byte buddy 修改完成。");
     }
}
public class TimeAdvice {
    @Advice.OnMethodEnter
    static long enter(@Advice.AllArguments Object args[], @Advice.Origin Method method){
        return System.currentTimeMillis();
    }

    @Advice.OnMethodExit
    static void exit(@Advice.Enter long startTime,
                     @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
                     @Advice.Origin Method method,
                     @Advice.Thrown Throwable throwable){
        if(throwable != null){
            System.out.println("错误函数 " + System.currentTimeMillis());
        }else {
            System.out.println("函数耗时 " + (System.currentTimeMillis() - startTime));
        }
    }
}

Spring Boot 的监听器如下所示:

public class AppEnvListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    private Logger logger = LoggerFactory.getLogger(AppEnvListener.class);
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
        Agent.getInstance().install();
        logger.info("byte buddy 安装完成。");
    }
}

最后,在 Spring Boot 启动时注册监听器:

@SpringBootApplication
@ComponentScan(basePackages = "klordy.learning")
public class SpringBootDemoApplication {

    public static void main(String[] args) {
       SpringApplication application = new SpringApplication(SpringBootDemoApplication.class);
       // 注册监听器
       application.addListeners(new AppEnvListener());
       application.run(args);
    }
}

当我启动应用程序时,调试日志显示正常。然而,在处理请求时,AOP 并未生效。我做错了什么?我感到困惑...


<details>
<summary>英文:</summary>

I tried to use bytebuddy to implement aop in springboot. Code as below:

package klordy.learning.annotation;
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface CostTime {
}

package klordy.learning.agent;
public class Agent {
private static Agent instance = new Agent();
private Logger logger = LoggerFactory.getLogger(Agent.class);
private Agent() {}
public static Agent getInstance(){ return instance; }
public void install() {
ByteBuddyAgent.install();
AgentBuilder.Listener listener = new AgentBuilder.Listener() {
// do nothing
...
};

    new AgentBuilder.Default()
            .type(ElementMatchers.nameStartsWith(&quot;klordy.learning&quot;))
            .transform((builder, typeDescription, classLoader, module) -&gt;
                    builder.visit(Advice.to(TimeAdvice.class).on(ElementMatchers.isAnnotatedWith(named(&quot;klordy.learning.annotation.CostTime&quot;)))))
            .with(listener)
            // *** added as supposed, but still seems not work.
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .installOnByteBuddyAgent();
     logger.info(&quot;byte buddy modification done.&quot;);
 }

}

public class TimeAdvice {
@Advice.OnMethodEnter
static long enter(@Advice.AllArguments Object args[], @Advice.Origin Method method){
return System.currentTimeMillis();
}

@Advice.OnMethodExit
static void exit(@Advice.Enter long startTime,
                 @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
                 @Advice.Origin Method method,
                 @Advice.Thrown Throwable throwable){
    if(throwable != null){
        System.out.println(&quot;error func &quot; + System.currentTimeMillis());
    }else {
        System.out.println(&quot;func takes &quot; + (System.currentTimeMillis() - startTime));
    }
}

}

Listener of springboot as below:

public class AppEnvListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private Logger logger = LoggerFactory.getLogger(AppEnvListener.class);
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
Agent.getInstance().install();
logger.info("finished byte buddy installation.");
}
}

At last, register listener in springboot startup:

@SpringBootApplication
@ComponentScan(basePackages = "klordy.learning")
public class SpringBootDemoApplication {

public static void main(String[] args) {
   SpringApplication application = new SpringApplication(SpringBootDemoApplication.class);
   // register listener
   application.addListeners(new AppEnvListener());
   application.run(args);
}

}

As I start up the application, debug logger showed fine. However, the aop does not work when request is processed. What am I doing wrong? I am confused...

</details>


# 答案1
**得分**: 2

好的,我认为通过使用Spring Boot重新创建您的情况,我找到了问题所在。尽管没有使用Spring也会出现这个问题。基本上,您遇到了[这个问题](https://github.com/raphw/byte-buddy/issues/123#issuecomment-216822493)。

所以,如果您将advice修改为 `@Advice.OnMethodExit(onThrowable = Throwable.class)`,应该就没问题了。另外,您还应该在代理中添加 `.disableClassFormatChanges()`。这有助于避免重新转换先前加载的类时出现问题。JVM要求这些类在结构上不被更改。

我是怎么找出问题所在的呢?我在ByteBuddy中启用了控制台日志记录。之前我使用了您的监听器,使其记录每个操作,所以我可以看到实际上触发了ByteBuddy。但是ByteBuddy的日志记录确实显示了由于您错误使用了 `@Advice.Thrown`(在advice注释中没有使用 `onThrowable = Throwable.class`)而导致的异常。

```java
public void install() {
  ByteBuddyAgent.install();
  new AgentBuilder.Default()
    .disableClassFormatChanges()
    .with(RETRANSFORMATION)
    .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
    .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
    .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
    .type(ElementMatchers.nameStartsWith("klordy.learning"))
    .transform((builder, typeDescription, classLoader, module) ->
      builder.visit(
        Advice
          .to(TimeAdvice.class)
          .on(isAnnotatedWith(named("klordy.learning.annotation.CostTime")))
      )
    )
    .installOnByteBuddyAgent();
  logger.info("byte buddy modification done.");
}
英文:

Okay, I think I found your problem by recreating your situation with Spring Boot. It would happen without Spring too, though. Basically you hit this problem.

So if you modify your advice to @Advice.OnMethodExit(onThrowable = Throwable.class), you should be fine. You also should add .disableClassFormatChanges() to your agent, BTW. It helps avoid problems retransforming previously loaded classes. The JVM requires them not to be changed structurally.

How did I find out what was happening? I activated console logging in ByteBuddy. Previously I had used your listener, making it log every action, so I could see that BB in fact was getting triggered. But BB logging really showed the exceptions due to your wrong usage of @Advice.Thrown (without onThrowable = Throwable.class in the advice annotation).

<!-- language: java -->

public void install() {
  ByteBuddyAgent.install();
  new AgentBuilder.Default()
    .disableClassFormatChanges()
    .with(RETRANSFORMATION)
    .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
    .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
    .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
    .type(ElementMatchers.nameStartsWith(&quot;klordy.learning&quot;))
    .transform((builder, typeDescription, classLoader, module) -&gt;
      builder.visit(
        Advice
          .to(TimeAdvice.class)
          .on(isAnnotatedWith(named(&quot;klordy.learning.annotation.CostTime&quot;)))
      )
    )
    .installOnByteBuddyAgent();
  logger.info(&quot;byte buddy modification done.&quot;);
}

答案2

得分: 1

你所涉及的类可能已经被加载。

在你的代理配置中设置.with(RetransformationStrategy.RETRANSFORM),或者在main方法中加载Spring之前安装你的代理。

英文:

Your class in question is likely already loaded.

Set .with(RetransformationStrategy.RETRANSFORM) in your agent configuration or install your agent before loading Spring in your main method.

huangapple
  • 本文由 发表于 2020年8月30日 19:26:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/63656920.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定