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 typeCore
, 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 writeList x = new ArrayList
– becauseList
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 thanBaseCore
, which is more specific thanSdkInterface
). If that static field’s type isSdkInterface
, 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 extendj.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 typeCore
, 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 writepublic 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 throughget()
in order to ensure its initialized, why make it public?