1

Is it possible to log any method call of a class instance? I have the following instance:

InputStream serverInputStream = this.serverProcess.getInputStream();

I would like to see at a given time during runtime what method has been called in serverInputStream. I believe the solution for that is reflection, specifically a proxy. I already tried to make this example work, but yet unable to make it running.

I thought of a code similar to this:

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

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

myInterceptor.printIntercepts();

Having a result similar to this:

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

Is that possible?

Socrates
  • 8,724
  • 25
  • 66
  • 113
  • Possible duplicate of [Attach proxy to an existing object?](https://stackoverflow.com/questions/21190458/attach-proxy-to-an-existing-object) – Lino Jun 18 '18 at 13:14
  • 2
    With AOP, it should be possible. See: https://stackoverflow.com/a/14670644/945214 – gargkshitiz Jun 18 '18 at 13:18
  • You could have a look at [AspectJ](https://www.eclipse.org/aspectj/) – QBrute Jun 18 '18 at 13:21
  • 1
    @gargkshitiz You would need to annotate the `InputStream`, which he presumably cannot do, and he only wants to log for a single instance, not all instances. – Michael Jun 18 '18 at 13:23
  • That's right, I just want to intercept the above instance to see what is called inside. With `@Loggable` I can only place it above a method. In my case that would mean to place one `@Loggable` over every method that `InputStream` implements. Furthermore, I cannot access the interna of `InputStream`, as it is not my class. – Socrates Jun 18 '18 at 13:29
  • Got it, not straight forward – gargkshitiz Jun 18 '18 at 14:05

5 Answers5

3

Is there a reason why you can't just write one? You can then just wrap the InputStream you want to watch with one of these.

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();
    }
}

This was generated by IntelliJ automatically.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • I'd implement this as a `DelegatingInputStream` which handles all of the boilerplate. I'd then extend that with a `LoggingInputStream` which calls `super` with the delegate and extends *only* the methods which need logging. It gives you a nicer separation: the `DelegatingInputStream` should require no maintenance, and you can more clearly see at a glance what `LoggingInputStream` is actually overriding – Michael Jun 18 '18 at 13:48
1

proxy could be one alternative solution if you want to do something like Java dynamic proxies. It is really light-weight and easy to use.

Maven configuration:

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

Code example to meet your request:

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 Zhang
  • 11
  • 3
1

To extend Ning's suggestion of using proxy, You could simply do this oneliner:

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

A cleaner rewrite of the Ning's proxy suggestion, using the new Java 8+ API:

     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!)

The absolutely main benefit to using Proxy instead of AspectJ is there no need for some custom compilation or language to learn. Proxy is basically a more lightweight and easy to uses solution(which of ofcourse comes with limitation also)

Using proxy compared with a plain decorator/interceptor/proxy class is ofcourse that you don't have to create a new class implementing every method. and you also don't have to implement a new decorator/interceptor/proxy class for every additional class you want to intercept this way.

Proxy basically allows you to create an universal method interceptor that could be applied to any class.

0

You have two options here:

  1. use the dynamic proxy feature included in the JDK, or
  2. use a bytecode manipulation library such as ASM,

The java.lang.reflect.Proxy can only generate an implementation of a set of interfaces, whereas bytecode manipulation libraries can also patch existing classes.

Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
  • Sounds like `java.lang.reflect.Proxy` isn't the right option if limited to interfaces. ASM though sounds promissing, though a bit difficult to understand for someone who has never worked with it. Any idea on what exactly to look for in the ASM library? – Socrates Jun 18 '18 at 17:45
  • @Socrates ASM requires some knowledge of the bytecode language, but there is another library called Javassist that lets you insert code at run time in a Java-like language. – Maurice Perry Jun 19 '18 at 05:03
0

I would give a try to Java Aspects. By defining some pointcuts you can spy or hijack the behaviour of other class method, intercept the parameters, count how many times a method has been called ...

Let's make an example:

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());
    }
}

With this pointcut you would see any call to public methods of the class InputStream. The JointPoint stores also the method parameters for a more complete overview.

You can import this library with 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'

Taken from: https://github.com/eveoh/gradle-aspectj

Uluaiv
  • 188
  • 2
  • 8