Hemant Singh
Hemant Singh

Reputation: 1598

How to create dynamic proxy of class with no public constructor using ByteBuddy

I want to create a dynamic proxy of class Sample which has two no public constructor, it's not working and giving the error. But if I make the constructor as Public then it works fine. Is it possible in byte buddy to achieve that?

Also is it possible to make addToList(..) method as non-public?

Sample code:

public class Sample {
    private String name;
    private String college;
    private String id;
    private List<String> fieldList = new LinkedList<>();

     Sample() {
        //some code
        System.out.println("No arg constructor invoked");
    }

     Sample(String id) {
        this();
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        System.out.println("Setting name: "+ name);
        this.name = name;
    }


    public String getCollege() {
        return college;
    }

    public void setCollege(String college) {
        System.out.println("Setting college: "+college);
        this.college = college;
    }

    public void addToList(String fieldName){
        fieldList.add(fieldName);
    }

    public List<String> getFieldList() {
        return Collections.unmodifiableList(fieldList);
    }

    public static Sample getProxyObject(String id) {
        Sample proxyObj = null;
        try {
            Class<? extends Sample> dynamicType = new ByteBuddy()
                    .subclass(Sample.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS )
                    .method(ElementMatchers.nameStartsWith("set"))
                    .intercept(MethodDelegation.to(new GreetingInterceptor()))
                    .make()
                    .load(Sample.class.getClassLoader())
                    .getLoaded();
            proxyObj = dynamicType.getConstructor(String.class).newInstance(id);
        } catch (Exception ex){
            ex.printStackTrace();
        }
        return proxyObj;
    }

    public static Sample getProxyObject() {
        Sample proxyObj = null;
        try {
             proxyObj = new ByteBuddy()
                    .subclass(Sample.class)
                    .method(ElementMatchers.nameStartsWith("setName"))
                    .intercept(MethodDelegation.to(new GreetingInterceptor()))
                    .make()
                    .load(Sample.class.getClassLoader())
                    .getLoaded().newInstance();
        } catch (Exception ex){
            ex.printStackTrace();
        }
        return proxyObj;
    }

    public static class GreetingInterceptor {
        public void abc(@SuperCall Callable<Void> zuper, @Origin Method method, @Super Sample parentObj, @This Object myself, @AllArguments Object[] args) {
            try {

                parentObj.addToList(method.getName());
                zuper.call();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }
}

Main class to test:

public class SampleMain {
public static void main(String[] args) {
    System.out.println("===Scenario 1=====");
    Sample proxyObject = Sample.getProxyObject();
    proxyObject.setName("John Doe");

    System.out.println("===Scenario 2=====");
    proxyObject.getFieldList().stream().forEach(System.out::println);
    Sample proxyObject1 = Sample.getProxyObject("id123");
    proxyObject1.setName("John Doe");
    proxyObject1.setCollege("MIT");

    System.out.println("Id is: "+proxyObject1.getId());

    proxyObject1.getFieldList().stream().forEach(System.out::println);
} 
}

Error trace:

===Scenario 1=====

Exception in thread "main" java.lang.IllegalAccessError: tried to access method com.algorithm.Sample.<init>()V from class com.algorithm.Sample$ByteBuddy$J74XiIU8
    at com.algorithm.Sample$ByteBuddy$J74XiIU8.<init>(Unknown Source)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at com.algorithm.Sample.getProxyObject(Sample.java:88)
    at com.algorithm.SampleMain.main(SampleMain.java:6)

Upvotes: 1

Views: 1147

Answers (2)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

Note that a package-private constructor is only visible to a class if it is defined in the same runtime package. By default, Byte Buddy creates a new class loader when loading a class and not specifying a ClassLoadingStrategy. If a package is named equally but not loaded by the same class loader, the runtime packages will be different.

I assume that by specifying:

.load(Sample.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)

your proxy would work as expected despite the broken reflective access that was adressed in the other answer. Note however that this strategy uses unsafe API which might no longer work in a future version of the JVM.

Upvotes: 3

Alex
Alex

Reputation: 654

In case protected constructors are okay, then:

  1. Change Sample() to protected Sample() to make Scenario 1 working: this makes no-args constructor accessible from the subclass produced by ByteBuddy.
  2. Change Sample(String id) to protected Sample(String id) and then in getProxyObject(String id) change

    proxyObj = dynamicType.getConstructor(String.class).newInstance(id);
    

    to

    Constructor<? extends Sample> ctor = dynamicType.getDeclaredConstructor(String.class);
    ctor.setAccessible(true);
    proxyObj = ctor.newInstance(id);
    

    This makes Scenario 2 working. ConstructorStrategy.Default.IMITATE_SUPER_CLASS produces the same protected constructor in a subclass, so you need to use getDeclaredConstructor() to get it and then make it accessible.

You can also make addToList(..) protected, it'll work just fine, as GreetingInterceptor will have an access to it.

Upvotes: 2

Related Questions