1

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)
2
  • Is it crucial for you to keep constructors package-visible? It is rather easy to make this working with protected constructors. Commented Nov 17, 2018 at 10:16
  • Protected will also works fine, only requirement is it should not be public Commented Nov 17, 2018 at 10:17

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.