Search code examples
osgibyte-buddy

bytebuddy rebase versus subclass and wrong name/NoClassDefFoundError in OSGi


I'm trying to develop an advice that wraps the actual invocation of a method. This is how I declared my interceptor:

public class SecurityInterceptor() {

    @RuntimeType
    public Object intercept(
        @SuperCall Callable<Object> supercall, 
        @This Object target, 
        @Origin Method method, 
        @AllArguments Object[] args) {  

        // Check args and annotations ...       

        Object obj = supercall.call();

        // use Spring SPEL to post-process obj content ...
    }
}

The interceptor is registered as follows:

byte[] woven = new ByteBuddy().subclass(type)
    .method(ElementMatchers.isAnnotatedWith(Secured.class))
    .intercept(MethodDelegation.to(new SecurityInterceptor()))
    .make().getBytes();

where the woven byte array is managed through the WeavingHook/WovenClass OSGi mechanism.

As soon as the weaved class is loaded I get the following exception:

java.lang.NoClassDefFoundError: com/contoso/users/service/provider/UsersServiceImpl (wrong name: com/contoso/users/service/provider/UsersServiceImpl$ByteBuddy$q3pXZ5KY)
    at java.lang.ClassLoader.defineClass1(Native Method) ~[?:?]
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[?:?]
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.defineClass(BundleWiringImpl.java:2410) ~[?:?]
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.findClass(BundleWiringImpl.java:2194) ~[?:?]
    at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1607) ~[?:?]
    at org.apache.felix.framework.BundleWiringImpl.access$200(BundleWiringImpl.java:80) ~[?:?]
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:2053) ~[?:?]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:?]
    at org.apache.felix.framework.Felix.loadBundleClass(Felix.java:1927) ~[?:?]
    at org.apache.felix.framework.BundleImpl.loadClass(BundleImpl.java:978) ~[?:?]

If I use the rebase method instead of the subclass one and I remove the @SuperCall Callable<Object> supercall argument, the interceptor gets called.

This error only shows up when I apply the interceptor in OSGi: the same procedure works fine in vanilla java junit tests where I modify the classes as follows:

<T> T loadTestClass(Class<T> clazz) throws Exception {
    return new ByteBuddy()
              .subclass(clazz)
              .method(ElementMatchers.isAnnotatedWith(Secured.class))        
              .intercept(MethodDelegation.to(securityInterceptor))
              .make()
              .load(getClass().getClassLoader())
              .getLoaded()
              .newInstance();
}

Any idea on how to deal with this NoClassDefFoundError / wrong name error?


Solution

  • OSGi class loader implementations typically use a ClassLoader.defineClass method which takes the expected class name as an argument: e.g. https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/ClassLoader.html#defineClass(java.lang.String,byte%5B%5D,int,int). When supplying the expected class name, the class loader requires the class being defined to have its name match the expected class name. This is a nice sanity check.

    So if the class loader is defining the class Foo, you cannot supply the byte array for a class with a different name such as a subclass.