byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

How to enhance only specific objects?

Open lalaorya opened this issue 3 years ago • 1 comments

I want to monitor the state of the jedis connection pool, which uses org.apache.commons.pool2.impl.GenericObjectPool at the bottom, and I want to enhance this class directly to get some information, but it seems to enhance all the GenericObjectPool . Can I just enhance the GenericObjectPool in the redis.clients.jedis.util.Pool. If so, what should I do? I hope I can get an answer.

package redis.clients.jedis.util;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public abstract class Pool<T> implements Closeable {
  protected GenericObjectPool<T> internalPool;

  /**
   * Using this constructor means you have to set and initialize the internalPool yourself.
   */
  public Pool() {
  }

  public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
    initPool(poolConfig, factory);
  }

lalaorya avatar Sep 20 '22 01:09 lalaorya

You can only instrument classes. You can however emulate this by keeping an active set: you'd instrument classes but before any instrumented logic you would check if an instance is part of the active set and run the original code only, if not. This is how for example Mockito handles this.

raphw avatar Sep 20 '22 19:09 raphw

You can only instrument classes. You can however emulate this by keeping an active set: you'd instrument classes but before any instrumented logic you would check if an instance is part of the active set and run the original code only, if not. This is how for example Mockito handles this.

It's a good approach, and it does work

But I think for production applications, the less bytecode injection should be the better. For example, if I enhance the offer method of BlockingQueue, then all instances of BlockingQueue need to make a judgment call before calling the offer method to determine whether the current instance is in the active set, which I think will have some impact on performance.

lalaorya avatar Sep 29 '22 03:09 lalaorya

Therefore, I came up with the idea of using subclasses to solve this problem For example, if you want to enhance a ThreadPoolExecutor object, you can create a subclass of ThreadPooExecutor, make the enhancements you want in that subclass, and use that subclass object to replace the old ThreadPoolExecutor object

I wrote a example to do the above

package org.itstack.demo.agent.bytebuddy.subclass;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Receive an object and return the subclass object before and after the increment
 */
public class WrapperUtil {

    public static class Builder {

        public Builder() {
        }

        private Class[] parameterTypes;

        private Object[] params;

        private DynamicType.Builder<?> type;

        public Builder type(Object source) {
            this.type = new ByteBuddy().subclass(source.getClass());
            return this;
        }

        public Builder method(String methodName, Class interceptor) {
            this.type = type.method(ElementMatchers.named(methodName)).intercept(MethodDelegation.to(interceptor));
            return this;

        }

        public Builder construct(Class<?>... parameterTypes) {
            this.parameterTypes = parameterTypes;
            return this;
        }

        public Builder params(Object... params) {
            this.params = params;
            return this;
        }

        public Object build() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            Class<?> loaded = type.make().load(WrapperUtil.class.getClassLoader()).getLoaded();
            Constructor<?> constructor = loaded.getConstructor(this.parameterTypes);
            return constructor.newInstance(params);
        }


    }
}



public class WrapperUtilTest {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {


        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1,
                1,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        ThreadPoolExecutor targetExecutor = (ThreadPoolExecutor) new WrapperUtil
                .Builder()
                .type(executor)
                .method("reject", CounterInterceptor.class)
                .construct(int.class, int.class, long.class, TimeUnit.class, BlockingQueue.class, ThreadFactory.class, RejectedExecutionHandler.class)
                .params(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy())
                .build();


        System.out.println(targetExecutor);
        System.out.println(targetExecutor.getCorePoolSize());
        System.out.println(targetExecutor.getMaximumPoolSize());
        System.out.println(targetExecutor.getRejectedExecutionHandler());
    }
}

It worked!! I managed to get a subclass object that I wanted. But I ran into a another problems.

Problem Statement

the reject method is of the parent class, and the reject method cannot be enhanced by method(name("reject")).intercept(MethodDelegation.to()), because the method is not present in the subclass object at all.

My Idea For the above case, I can achieve enhancement by overriding the reject method, but this method can't help if it is displaying a call to a parent class method, such as super.reject(). So, is there any way to directly match to the parent class's reject method and enhance it? If bytebuddy can't do it, can other tools like ASM do it?

Thank you very much for reading this far and I hope to get a reply. Enjoy your life and thanks!

lalaorya avatar Sep 29 '22 08:09 lalaorya

Byte Buddy can do everything if it is can be expressed within the Java VM.

You can invoke a super class by injecting a proxy into the interceptor. Have a look at the javadoc. Likely you are looking for @SuperMethodCall?

raphw avatar Sep 30 '22 18:09 raphw