如何使用 ByteBuddy 在没有 public 构造函数的情况下创建 class 的动态代理

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

我想创建一个 class 示例的动态代理,它有两个没有 public 构造函数,它不工作并给出错误。但是,如果我将构造函数设置为 Public 那么它就可以正常工作。字节好友有可能实现吗?

是否也可以将 addToList(..) 方法设为非public?

示例代码:

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

        }

    }
}

主要class测试:

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)

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

请注意,只有在同一运行时包中定义的包私有构造函数才对 class 可见。默认情况下,字节好友在加载 class 且未指定 ClassLoadingStrategy 时创建一个新的 class 加载程序。如果一个包的名称相同但不是由相同的 class 加载程序加载,则运行时包将不同。

我假设通过指定:

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

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