Creating a proxy with ByteBuddy that should call a protected method, I get: java.lang.VerifyError: Bad access to protected data in invokevirtual

huangapple go评论69阅读模式
英文:

Creating a proxy with ByteBuddy that should call a protected method, I get: java.lang.VerifyError: Bad access to protected data in invokevirtual

问题

I'm sorry, but it seems like you've requested not to translate code. If you have any specific questions or need assistance with a particular aspect of the code, please feel free to ask, and I'll do my best to help.

英文:

I'm trying to create a proxy with ByteBuddy that can delegate calls of a protected method getRawId of a MyEntityA class to the same method of an object of the same class referenced in a target field.

package it.mict.lab.bytebuddy.entity;

public class MyEntityA {

	private int id;
	
	public MyEntityA() {
	}
	
	public MyEntityA(int id) {
		this.id = id;
	}
	
	protected int getRawId() {
		return id;
	}
}

The proxy should do something similar to this MyEntityB class:

package it.mict.lab.bytebuddy.entity;

public class MyEntityB extends MyEntityA {

	private MyEntityA _target;
	
	public MyEntityB(MyEntityA _target) {
		this._target = _target;
	}
	
	public void hello() {
		System.out.println(_target.getRawId());
	}
}

And this is an example of what I would achieve:

package it.mict.lab.bytebuddy;


import java.lang.reflect.Constructor;

import it.mict.lab.bytebuddy.entity.MyEntityA;
import it.mict.lab.bytebuddy.entity.MyEntityB;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType.NamingStrategy;
import net.bytebuddy.matcher.ElementMatchers;

public class App 
{
	private static Class<? extends MyEntityA> creteProxyClass() {

		Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
				.with(new NamingStrategy.SuffixingRandom("MyProxy"))
				.subclass(MyEntityA.class)
				.defineField("_target", MyEntityA.class, Visibility.PRIVATE)
				.defineConstructor(Visibility.PUBLIC)
				.withParameter(MyEntityA.class)
				.intercept(
					MethodCall.invoke(getDefaultConstructor(MyEntityA.class))
						.andThen(FieldAccessor.ofField("_target").setsArgumentAt(0))
				)
				.method(ElementMatchers.nameStartsWith("getRaw")
					.or(ElementMatchers.nameStartsWith("setRaw")))
					.intercept(MethodDelegation.toField("_target"))
				.make();
		
		Class<? extends MyEntityA> proxyClass = unloadedClass
			.load(MyEntityA.class.getClassLoader())
			.getLoaded();
		
		return proxyClass;
	}

	private static Constructor<?> getDefaultConstructor(Class<MyEntityA> entityClass) {
		
		for (Constructor<?> constructor : entityClass.getDeclaredConstructors()) {
			if (constructor.getParameterCount() == 0) {
				return constructor;
			}
		}
		
		throw new IllegalStateException();
	}
	
    public static void main( String[] args ) throws Exception
    {
    	MyEntityB entityB = new MyEntityB(new MyEntityA(123));
    	entityB.hello();
    	
    	Class<? extends MyEntityA> proxyClass = creteProxyClass();
    	System.out.println("MyEntityA package : " + MyEntityA.class.getPackage().getName());
    	System.out.println("Proxy package     : " + proxyClass.getPackage().getName());

    	Constructor<? extends MyEntityA> proxyConstructor = proxyClass.getConstructor(new Class<?>[] { MyEntityA.class });
    	MyEntityA proxy = proxyConstructor.newInstance(new MyEntityA());
    }
}

I'm using:

OpenJDK Runtime Environment (Temurin)(build 1.8.0_332-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.332-b09, mixed mode)

and ByteBuddy version 1.14.4

When I execute this App class, I expect no errors, while I get:

123
MyEntityA package : it.mict.lab.bytebuddy.entity
Proxy package     : it.mict.lab.bytebuddy.entity
Exception in thread "main" java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq.getRawId()I @4: invokevirtual
  Reason:
    Type 'it/mict/lab/bytebuddy/entity/MyEntityA' (current frame, stack[0]) is not assignable to 'it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq'
  Current Frame:
    bci: @4
    flags: { }
    locals: { 'it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq' }
    stack: { 'it/mict/lab/bytebuddy/entity/MyEntityA' }
  Bytecode:
    0x0000000: 2ab4 000a b600 0cac                    

	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
	at java.lang.Class.getConstructor0(Class.java:3075)
	at java.lang.Class.getConstructor(Class.java:1825)
	at it.mict.lab.bytebuddy.App.main(App.java:63)

123 is what is printed using the MyEntityB class, then the next two lines check that that MyEntityA class and the one created by ByteBuddy are in the same package, and then there is the exception encountered while creating the instance of the proxy.

If I change MyEntityA.getRawId() visibility from protected to public, everything works fine (but of course, I need it to be protected).

答案1

得分: 1

根据(旧)问题,这是一个bytebuddy的错误,那里解释的解决方法是为子类使用相同的包,所以:

Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
    .with(new NamingStrategy.SuffixingRandom("MyProxy"))

变成

Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
    .with(new NamingStrategy.SuffixingRandom("it.mict.lab.bytebuddy.entity.MyProxy"))

还请确保使用与MyEntityA相同的类加载器加载类。

英文:

According to this (old) issue it's a bytebuddy bug, the workaround explained there is to use the same package for the Subclass, So:

Unloaded&lt;MyEntityA&gt; unloadedClass = new ByteBuddy()
    .with(new NamingStrategy.SuffixingRandom(&quot;MyProxy&quot;))

becomes

Unloaded&lt;MyEntityA&gt; unloadedClass = new ByteBuddy()
    .with(new NamingStrategy.SuffixingRandom(&quot;it.mict.lab.bytebuddy.entity.MyProxy&quot;))

Also make sure to load the class with the same Classloader as MyEntityA.

答案2

得分: 0

以下是您提供的代码的翻译部分:

"Being sure the classloader is the same for both the original class and the proxy is not easy at is seems. In fact I found a way to force the same classloader in Java 8, but it did not work in Java 17.
Then I found a wonderful 2018 article from the author of ByteBuddy JDK 11 and proxies in a world past sun.misc.Unsafe where he shows the correct way to specify class loading strategy and now my example works for both Java 8 and Java 17.
I copy here the new version of the App class example that shows that solution:"

请注意,这只是您提供的代码的翻译部分,不包括代码本身。如果您需要有关代码的任何解释或其他问题的帮助,请随时提问。

英文:

Being sure the classloader is the same for both the original class and the proxy is not easy at is seems. In fact I found a way to force the same classloader in Java 8, but it did not work in Java 17.
Then I found a wonderful 2018 article from the author of ByteBuddy JDK 11 and proxies in a world past sun.misc.Unsafe where he shows the correct way to specify class loading strategy and now my example works for both Java 8 and Java 17.
I copy here the new version of the App class example that shows that solution:

package it.mict.lab.bytebuddy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import it.mict.lab.bytebuddy.entity.MyEntityA;
import it.mict.lab.bytebuddy.entity.MyEntityB;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType.NamingStrategy;
import net.bytebuddy.matcher.ElementMatchers;

public class App 
{
	private static Class&lt;? extends MyEntityA&gt; creteProxyClass() throws Exception {

		Unloaded&lt;MyEntityA&gt; unloadedClass = new ByteBuddy()
				.with(new NamingStrategy.SuffixingRandom(&quot;MyProxy&quot;))
				.subclass(MyEntityA.class)
				.defineField(&quot;_target&quot;, MyEntityA.class, Visibility.PRIVATE)
				.defineConstructor(Visibility.PUBLIC)
				.withParameter(MyEntityA.class)
				.intercept(
					MethodCall.invoke(getDefaultConstructor(MyEntityA.class))
						.andThen(FieldAccessor.ofField(&quot;_target&quot;).setsArgumentAt(0))
				)
				.method(ElementMatchers.nameStartsWith(&quot;getRaw&quot;)
					.or(ElementMatchers.nameStartsWith(&quot;setRaw&quot;)))
					.intercept(MethodDelegation.toField(&quot;_target&quot;))
				.make();
		
		// This is the strategy part shown in the Rafael blog

		ClassLoadingStrategy&lt;ClassLoader&gt; strategy;
		if (ClassInjector.UsingLookup.isAvailable()) {
			Class&lt;?&gt; methodHandles = Class
					.forName(&quot;java.lang.invoke.MethodHandles&quot;);
			Object lookup = methodHandles.getMethod(&quot;lookup&quot;).invoke(null);
			Method privateLookupIn = methodHandles.getMethod(&quot;privateLookupIn&quot;,
					Class.class,
					Class.forName(&quot;java.lang.invoke.MethodHandles$Lookup&quot;));
			Object privateLookup = privateLookupIn.invoke(null, MyEntityA.class,
					lookup);
			strategy = ClassLoadingStrategy.UsingLookup.of(privateLookup);
		} else if (ClassInjector.UsingReflection.isAvailable()) {
			strategy = ClassLoadingStrategy.Default.INJECTION;
		} else {
			throw new IllegalStateException(
					&quot;No code generation strategy available&quot;);
		}
		
		Class&lt;? extends MyEntityA&gt; proxyClass = unloadedClass
			.load(MyEntityA.class.getClassLoader(), strategy)
			.getLoaded();
		
		return proxyClass;
	}

	private static Constructor&lt;?&gt; getDefaultConstructor(Class&lt;MyEntityA&gt; entityClass) {
		
		for (Constructor&lt;?&gt; constructor : entityClass.getDeclaredConstructors()) {
			if (constructor.getParameterCount() == 0) {
				return constructor;
			}
		}
		
		throw new IllegalStateException();
	}
	
    public static void main( String[] args ) throws Exception
    {
    	System.out.println(&quot;Java version: &quot; + System.getProperty(&quot;java.version&quot;));
    	
    	MyEntityB entityB = new MyEntityB(new MyEntityA(123));
    	entityB.hello();
    	
    	Class&lt;? extends MyEntityA&gt; proxyClass = creteProxyClass();
    	System.out.println(&quot;MyEntityA package : &quot; + MyEntityA.class.getPackage().getName());
    	System.out.println(&quot;Proxy package     : &quot; + proxyClass.getPackage().getName());

    	System.out.println(&quot;MyEntityA ClassLoader : &quot; + MyEntityA.class.getClassLoader());
    	System.out.println(&quot;Proxy ClassLoader     : &quot; + proxyClass.getClassLoader());

    	Constructor&lt;? extends MyEntityA&gt; proxyConstructor = proxyClass.getConstructor(new Class&lt;?&gt;[] { MyEntityA.class });
    	MyEntityA proxy = proxyConstructor.newInstance(new MyEntityA());
    	System.out.println(&quot;Proxy: &quot; + proxy);
    }
}

huangapple
  • 本文由 发表于 2023年5月10日 21:37:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76219120.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定