Как создать динамический прокси класса без публичного конструктора, используя ByteBuddy - PullRequest
0 голосов
/ 14 ноября 2018

Я хочу создать динамический прокси класса Sample, у которого нет двух открытых конструкторов, он не работает и выдает ошибку. Но если я сделаю конструктор как Public, он будет работать нормально. Возможно ли в байтовом партнере добиться этого?

Также возможно ли сделать метод addToList (..) закрытым?

Пример кода:

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

        }

    }
}

Основной класс для тестирования:

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

Ошибка трассировки:

=== Сценарий 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 ]

0 голосов
/ 25 ноября 2018

Обратите внимание, что частный конструктор пакета виден только классу, если он определен в том же пакете времени выполнения.По умолчанию Byte Buddy создает новый загрузчик классов при загрузке класса и без указания ClassLoadingStrategy.Если пакет назван одинаково, но не загружен одним и тем же загрузчиком классов, пакеты времени выполнения будут другими.

Я предполагаю, что при указании:

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

ваш прокси будет работать, как и ожидалось, несмотря насломанный рефлексивный доступ, о котором говорилось в другом ответе.Однако обратите внимание, что в этой стратегии используется небезопасный API, который может больше не работать в будущей версии JVM.

0 голосов
/ 17 ноября 2018

Если конструкторы protected в порядке, тогда:

  1. Измените Sample() на protected Sample(), чтобы сделать сценарий 1 работающим: это делает конструктор без аргументов доступным из подкласса, созданного ByteBuddy.
  2. Измените Sample(String id) на protected Sample(String id), а затем в getProxyObject(String id) измените

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

    на

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

    Это делает сценарий 2 работающим.ConstructorStrategy.Default.IMITATE_SUPER_CLASS создает тот же защищенный конструктор в подклассе, поэтому вам нужно использовать getDeclaredConstructor(), чтобы получить его, а затем сделать его доступным.

Вы также можете сделать addToList(..) защищенным,это будет нормально работать, так как GreetingInterceptor будет иметь к нему доступ.

...