Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IllegalAccessError when loading a class generated with a NamingStrategy #1

Closed
jhalterman opened this issue May 30, 2014 · 5 comments
Closed
Assignees
Labels

Comments

@jhalterman
Copy link

I have a simple scenario where I receive an IllegalAccessError when loading a generated class that uses a NamingStrategy.

java.lang.IllegalAccessError: class FooBar.Foo cannot access its superclass org.modelmapper.FooTest$Foo
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
    at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:48)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    at net.bytebuddy.dynamic.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:59)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:1132)
    at org.modelmapper.FooTest.proxyClassFor(FooTest.java:35)
    at org.modelmapper.FooTest.test(FooTest.java:40)

Here's the complete test:

@Test
public class FooTest {
  static class Foo {
  }

  public void test() {
    new ByteBuddy().withNamingStrategy(new NamingStrategy() {
          public String getName(UnnamedType unnamedType) {
            return "FooBar." + unnamedType.getSuperClass().getSimpleName();
          }
        })
        .subclass(Foo.class)
        .method(MethodMatchers.any())
        .intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
          }
        }))
        .make()
        .load(FooTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();
  }
}
@raphw
Copy link
Owner

raphw commented May 30, 2014

Hi Jonathan,

you are defining a package-private class org.modelmapper.FooTest.Foo for which you define a default-package subclass FooBar.Foo. This latter class cannot access its super class. Therefore, you need to either define the subclass to be in the same package as its super class or you need to change the visibility of the super class to public. The naming strategy is supposed to create a fully qualified name.

Thank you for using Byte Buddy! You are somewhat an early adopter and I hope to improve this behavior in later versions. A future version of Byte Buddy will definitely come with an (optional) validator that will throw an appropriate exception. Today's version does not yet catch all validation errors with a detailed error message because this validation generates some run time overhead.

Also note that I am planing to release a version 0.2 of Byte Buddy in only a couple of weeks. It comes with several bug fixes, some new functionality to better support Java 8 and improved support for creating proxy classes.

Best regards, Rafael

@raphw raphw closed this as completed May 30, 2014
@jhalterman
Copy link
Author

Ah, I assumed that the package was always the same as the superclass and that the naming strategy was just for the class name. Good to know.

So using a fully qualified subclass name, things work well, but only if I use ClassLoadingStrategy.Default.INJECTION. Why is this?

@raphw
Copy link
Owner

raphw commented May 30, 2014

Java is a quite tricky language when it comes to package-private access. Package-private access is also rather an invention for being used by compilers rather then by users since they have strange semantics.

You might have heard before that two classes are only consider equal by the Java run time if they were loaded with the same class loader even if they otherwise represent an identical class. The same is true for Java packages. If a class is loaded with a different class loader, it cannot get private-package access to any classes (or class members) that belong to another class loader.

The injection strategy uses reflection to load a class into a given class loader. The wrapper class loader on the other hand, creates a new child class loader for loading a given class. The former strategy is more efficient since a class loader is a rather expensive object to Java (takes about 70 nanoseconds to create it in my benchmarks). However, it does not need to call internal methods. Also, the wrapper class loader allows the unloading of the generated class if none of its instances and its class and class loader are not longer reachable. This is handy when creating classes that are only needed for a limited amount of time such as in bootstrap procedures. One specific use case of this strategy would be mocking for example as for creating type safe DSLs. This allows to keep the perm gen requirements of an application down as the injector strategy often targets the system class loader what makes loaded classes invincible for the application's life time.

The injector strategy is equivalent to Javassist and cglib's approaches.

@jhalterman
Copy link
Author

Good info. I recalled that Guice uses a separate class loader for instrumenting/proxying classes/methods that are not private/package-private and the user classloader for everything else. A hybrid strategy like that is something I might need to look into.

@raphw
Copy link
Owner

raphw commented May 30, 2014

I thought about predefining such a strategy for Byte Buddy 0.2. However, this would require you to resolve any invokable method of a class in order to check if any of those methods is package-private relatively to the generated class in order to determine its necessity. Such reflective look-ups are unfortunately expensive. I might still just add the strategy and add an explicit warning of its costs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants