【问题标题】:How to create dynamic proxy of class with no public constructor using ByteBuddy如何使用 ByteBuddy 创建没有公共构造函数的类的动态代理
【发布时间】:2018-11-26 02:41:33
【问题描述】:

我想创建一个类 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)

【问题讨论】:

  • 保持构造函数包可见对您来说至关重要吗?使用受保护的构造函数很容易做到这一点。
  • 受保护也可以正常工作,唯一的要求是它不应该公开

标签: java reflection cglib byte-buddy


【解决方案1】:

请注意,包私有构造函数只有在同一个运行时包中定义时才对类可见。默认情况下,Byte Buddy 在加载类而不指定ClassLoadingStrategy 时会创建一个新的类加载器。如果一个包的名称相同但不是由同一个类加载器加载,则运行时包将不同。

我假设通过指定:

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

尽管其他答案中提到的反射访问中断,您的代理仍会按预期工作。但请注意,此策略使用了不安全的 API,它可能在 JVM 的未来版本中不再有效。

【讨论】:

    【解决方案2】:

    如果 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 可以访问它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-20
      • 2015-09-26
      • 1970-01-01
      • 1970-01-01
      • 2021-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多