记录实例的任何方法调用

Log any method call of an instance

是否可以记录 class 实例的任何方法调用?我有以下实例:

InputStream serverInputStream = this.serverProcess.getInputStream();

我想在运行时的给定时间查看在 serverInputStream 中调用了哪些方法。我相信解决方案是反射,特别是代理。我已经尝试让 this example 工作,但无法做到 运行。

我想到了类似这样的代码:

MyInterceptor myInterceptor = new MyInterceptor(this.serverProcess.getInputStream());
InputStream serverInputStream = myInterceptor.getInterceptedInstance();

serverInputStream.methodOne();
serverInputStream.methodTwo();
serverInputStream.methodThree();

myInterceptor.printIntercepts();

得到类似这样的结果:

1. InputStream.InputStream();
2. InputStream.methodOne();
3. InputStream.methodTwo();
4. InputStream.methodThree();

这可能吗?

这里有两个选择:

  1. 使用 JDK 中包含的动态代理功能,或
  2. 使用字节码操作库,例如 ASM,

java.lang.reflect.Proxy只能生成一组接口的实现,而字节码操作库也可以修补现有的类。

你有什么理由不能只写一个吗?然后你可以用其中之一包裹你想看的 InputStream

class LoggingInputStream extends InputStream {
    private final InputStream original;

    public LoggingInputStream(InputStream original) {
        super();
        this.original = original;
    }

    @Override
    public int read(byte[] b) throws IOException {
        // Logging here - and below.
        return super.read(b);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        return super.read(b, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        return super.skip(n);
    }

    @Override
    public int available() throws IOException {
        return super.available();
    }

    @Override
    public void close() throws IOException {
        super.close();
    }

    @Override
    public synchronized void mark(int readlimit) {
        super.mark(readlimit);
    }

    @Override
    public synchronized void reset() throws IOException {
        super.reset();
    }

    @Override
    public boolean markSupported() {
        return super.markSupported();
    }

    @Override
    public int read() throws IOException {
        return original.read();
    }
}

这是由 IntelliJ 自动生成的。

我会尝试Java方面。通过定义一些切入点,您可以监视或劫持其他 class 方法的行为,拦截参数,计算方法被调用的次数...

举个例子:

package org.bpa.fd;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class ListAspect {

    @Pointcut("call(public * java.io.InputStream.*(..))")
    public void inputStreamMethod() {
    }

    @Before("inputStreamMethod()")
    public void anyMethod(JoinPoint jp) {
        System.out.println(jp.toLongString());
    }
}

使用此切入点,您将看到对 class InputStream 的 public 方法的任何调用。 JointPoint 还存储方法参数以获得更完整的概述。

您可以使用 gradle 导入此库:

buildscript {
    repositories {
        maven {
            url "https://maven.eveoh.nl/content/repositories/releases"
        }
    }

    dependencies {
        classpath "nl.eveoh:gradle-aspectj:2.0"
    }
}

project.ext {
    aspectjVersion = '1.8.12'
}

apply plugin: 'aspectj'

取自:https://github.com/eveoh/gradle-aspectj

如果您想执行类似 Java 动态代理的操作,

proxy 可能是一种替代解决方案。它非常轻便且易于使用。

Maven 配置:

<dependency>
    <groupId>com.ericsson.commonlibrary</groupId>
    <artifactId>proxy</artifactId>
    <version>1.2.0</version>
</dependency>

满足您要求的代码示例:

public class ProxyExample {

    public static void main(String[] args) {
        SomeImpl proxy = Proxy.intercept(new SomeImpl(), new MyInterceptor());
        proxy.log("hello world");
        //Output:
        //SomeImpl.log
        //hello world
    }

    public static class SomeImpl {

        public void log(String log) {
            System.out.println(log);
        }
    }

    public static class MyInterceptor implements Interceptor {

        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println(invocation.getThis().getClass().getSuperclass().getSimpleName() + "." + invocation.getMethodName());
            Object returnObject = invocation.invoke(); //original method invocation.
            return returnObject;
        }
    }
}

要扩展 Ning 关于使用代理的建议, 你可以简单地做这个 oneliner:

InputStream serverInputStream = Proxy.addTimerToMethods(this.serverProcess.getInputStream());
serverInputStream.anymethodreally();
// any usage will now be printed to console with parameters+ performance data

使用新的 Java 8+ API:

更清晰地重写了 Ning 的代理建议
     InputStream serverInputStream = with(this.serverProcess.getInputStream())
                .interceptAll(i -> {
                    Object result = i.invoke();
                    System.out.println("after method: " + i.getMethodName() + " param: " + i.getParameter0());
                    return result;
                }).get();
     serverInputStream.anymethodreally();
    // any usage will now be printed to console(or whatever you decide to do!)

使用 Proxy 而不是 AspectJ 的绝对主要好处是不需要学习一些自定义编译或语言。 Proxy 基本上是一种更轻量级且易于使用的解决方案(当然也有限制)

使用代理与普通 decorator/interceptor/proxy class 相比,您当然不必创建一个新的 class 来实现每个方法。而且您也不必为每个要以这种方式拦截的额外 class 实施新的 decorator/interceptor/proxy class。

Proxy 基本上允许您创建一个可以应用于任何 class.

的通用方法拦截器