Skip to content
Advertisement

Java Proxy.newProxyInstance IllegalArgumentException

I define an interface follow

interface SdkInterface {
   void onPause();

   void onResume();

   void onStop();
}

then I define a abstract class implement the interface

abstract class BaseCore implements SdkInterface {
    public static final byte[] lock = new byte[0];
    private static final String TAG = "BaseCore";
    public static Core instance;

    @Override
    public void onPause() {
        Log.e(TAG, "onPause: ");
    }

    @Override
    public void onResume() {
        Log.e(TAG, "onResume: ");
    }

    @Override
    public void onStop() {
        Log.e(TAG, "onStop: ");
    }
}


then I has a class extends the abstract class

class Core extends BaseCore {
    private Core() {
    }

    public static Core getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new Core();
                }
            }
        }
        return instance;
    }

}

now I want to generate a proxy for the instance field of the BaseCore class,I do this

 public static void register() {
        try {
            Class<?> core = Class.forName("com.secoo.coobox.Core");
            Field instance = core.getSuperclass().getDeclaredField("instance");
            Method getInstance = core.getDeclaredMethod("getInstance");
            Object invoke = getInstance.invoke(null);
            Object o = Proxy.newProxyInstance(core.getClassLoader(), core.getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Log.e("BaseCore", "invoke: before " + method.getName());
                    Object invoke1 = method.invoke(invoke, args);
                    Log.e("BaseCore", "invoke: after " + method.getName());
                    return invoke1;
                }
            });
            instance.set(invoke, o);
        } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

but I receive the exception

Caused by: java.lang.IllegalArgumentException: field com.secoo.coobox.BaseCore.instance has type com.secoo.coobox.Core, got $Proxy2
        at java.lang.reflect.Field.set(Native Method)
        at com.secoo.coobox.TestProxy.register(TestProxy.java:31)
        at com.secoo.coobox.MainActivity.onCreate(MainActivity.kt:24)
        at android.app.Activity.performCreate(Activity.java:8006)
        at android.app.Activity.performCreate(Activity.java:7990)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3584)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3775) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2246) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:233) 
        at android.app.ActivityThread.main(ActivityThread.java:8010) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978) 

who can help me explain the excetion and how I can solve the problem? thanks

Advertisement

Answer

Your code has serious style issues, and it is broken. And we haven’t even gotten to the error you’re asking about yet. I suggest you skip the actual answer and instead read why you’re barking up the wrong tree and need to rethink this code, making the actual answer irrelevant for you.

The actual answer

Proxy will make a new instance of a proxy class (that’s where that $Proxy2 thing is coming from). Proxy cannot make an object that is an instance of that actual class: That’s just not how java works. Thus, you have an instance of an unknown, effectively irrelevant class. However, that $Proxy2 class does implement whatever interfaces you want it to. It can’t extend anything (other than, obviously, java.lang.Object). No final classes, no normal classes, not even abstract classes. That’s just how it works: If you want a proxy that extends something specific, you’re out of luck.

Thus: You can only proxy interfaces.

Had the field been:

public static SdkInterface instance;

instead, it would have worked fine; That $Proxy2 class is a class made on the fly by the Proxy.newProxyInstance call, and this class implements all the interface you passed in.

But, as I said, do not just start changing things, you need to rethink this code more fundamentally.

The more significant issues with your code

  • Your BaseCore class has a field of type Core, eliminating any point or purpose in the class hierarchy of it. Its type should possible be BaseCore, or the ‘singleton holder’ field should be in Core. Generally fields should have the least specific type that you want to use, e.g. we write List x = new ArrayList – because List is less specific. You’ve gone the other way, and made the field the most specific type you could find (Core, which is more specific than BaseCore, which is more specific than SdkInterface). If that static field’s type is SdkInterface, you can assign a proxy object to it. But if the type of that field is a class (other than java.lang.Object), then that is impossible – as proxies implement whatever interfaces you want, but extend j.l.Object and can’t be made to extend anything else.
  • Double checked locking does not work, so don’t do it. Your getInstance() method is broken in a really nasty way: It is broken, but, most tests do not catch it. It requires a specific combination of hardware, android version, and phase of the moon for it to fail. There are only 2 sane ways to ‘load’ singletons: Generally, just initialize the field (and make it final), because java lazy loads; the only benefit to having an actual method that loads it only when you invoke .getInstance() or whatnot, is when it is plausible you will ‘touch’ the class (load a class that has a field of type Core, for example), but never actually need the instance. This happens, but it is rare. If it doesn’t apply to you, there is no point to any of this, and you can just write public static final Core INSTANCE = new Core(); and be done with it all. If you really do need the getSingleton method, use the java classloader which is extremely good at efficient locking – at least as good as anything you can write in java and possibly better:
public static Core get() {
  return CoreSingletonHolder.INSTANCE;
}

private static class CoreSingletonHolder {
  static final Core INSTANCE = new Core();
}

This works because java doesn’t load classes until it is needed, and nothing ‘touches’ CoreSingletonHolder (causing it to be loaded), except invoking the get() method. Hence, new Core() is executed exactly once, and only once, as per JVM guarantees, using ClassLoader’s own locking mechanism, which is very efficient. It’s efficient because java has to do this with every single last class your app ever loads. If it wasn’t efficient, java/android would be dog slow, and we know it isn’t, QED.

  • Your intended singleton instance is public and non-final; anything can overwrite it. The above strategies fix these issues; given that all access needs to go through get() in order to ensure its initialized, why make it public?
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement