Is it possible to serialize anonymous class without outer class?

前端 未结 6 979
广开言路
广开言路 2021-02-06 03:45

I made a small research on web and reviewed related topics on this site, but the answers were contradictory: some people said it is not possible, others said it is possible, but

相关标签:
6条回答
  • 2021-02-06 04:07

    I'd like to add to this topic. There is a way to achieve what you want, but will require reflection.

    Here is a good tutorial on implementing a custom serializable object using writeObject and readObject

    And here is a good tutorial (website font is kind of an eyesore, but the content is worth it) on on how Reflection is used to for serialization. The tutorial refers to final fields, but applies to any field.

    You'll have to use Reflections getDeclaredField

    0 讨论(0)
  • 2021-02-06 04:08

    You can't do exactly what you want, which is to serialize an anonymous inner class, without also making its enclosing instance serializable and serializing it too. The same applies to local classes. These unavoidably have hidden fields referencing their enclosing instances, so serializing an instance will also attempt to serialize their enclosing instances.

    There are a couple different approaches you can try.

    If you're using Java 8, you can use a lambda expression instead of an anonymous inner class. A serializable lambda expression does not (necessarily) have a reference to its enclosing instance. You just need to make sure that your lambda expression doesn't reference this explicitly or implicitly, such as by using fields or instance methods of the enclosing class. The code for this would look like this:

    public class Caller {
        void call() {
            getRpcService().call(() -> {
                System.out.println("It worked!");
                return null;
            });
    }
    

    (The return null is there because RPCService.Runnable.run() is declared to return Object.)

    Also note that any values captured by this lambda (e.g., local variables, or static fields of the enclosing class) must also be serializable.

    If you're not using Java 8, your next best alternative is to use a static, nested class.

    public class Caller {
        static class StaticNested implements RPCService.Runnable {
            @Override
            public Object run() {
                System.out.println("StaticNested worked!");
                return null;
            }
        }
    
        void call() {
            getRpcService().call(new StaticNested());
        }
    }
    

    The main difference here is that this lacks the ability to capture instance fields of Caller or local variables from the call() method. If necessary, these could be passed as constructor arguments. Of course, everything passed this way must be serializable.

    A variation on this, if you really want to use an anonymous class, is to instantiate it in a static context. (See JLS 15.9.2.) In this case the anonymous class won't have an enclosing instance. The code would look like this:

    public class Caller {
        static RPCService.Runnable staticAnonymous = new RPCService.Runnable() {
            @Override
            public Object run() {
                System.out.println("staticAnonymous worked!");
                return null;
            }
        };
    
        void call() {
            getRpcService().call(staticAnonymous);
        }
    }
    

    This hardly buys you anything vs. a static nested class, though. You still have to name the field it's stored in, and you still can't capture anything, and you can't even pass values to the constructor. But it does satisfy your the letter of your initial question, which is how to serialize an instance of an anonymous class without serializing an enclosing instance.

    0 讨论(0)
  • 2021-02-06 04:15

    The answer is no. You cannot do that since Inner class will need outer class to be serialized. Also you would run into troubles when you'd try to call the instance method of the outer class within the inner class. Why don't you just have another top level class which you could send?

    0 讨论(0)
  • 2021-02-06 04:22

    You could try making Caller.call() a static method.

    However, the anonymous class would still need to be available in the context in which you deserialize the serialized instance. That is unavoidable.

    (It is hard to imagine a situation where the anonymous class would be available but the enclosing class isn't.)


    So, if someone can show, how I can properly override writeObject and readObject methods in my anonymous class ...

    If you make Caller.call() static, then you would do this just like you would if it was a named class, I think. (I'm sure you can find examples of that for yourself.)


    Indeed, (modulo the anonymous class availability issue) it works. Here, the static main method substitutes for a static Classer.call() method. The program compiles and runs, showing that an anonymous class declared in a static method can be serialized and deserialized.

    import java.io.*;
    
    public class Bar {
    
        private interface Foo extends Runnable, Serializable {}
    
        public static void main (String[] args) 
                throws InterruptedException, IOException, ClassNotFoundException {
    
            Runnable foo = new Foo() {
                @Override
                public void run() {
                    System.out.println("Lala");
                }
            };
    
            Thread t = new Thread(foo);
            t.start();
            t.join();
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(foo);
            oos.close();
            Foo foofoo = (Foo) new ObjectInputStream(
                new ByteArrayInputStream(baos.toByteArray())).readObject();
    
            t = new Thread(foofoo);
            t.start();
            t.join();
        }
    }
    

    Another important thing to remember about: the Caller class is not present in the environment, that executes the method, so I'd like to exclude all information about it during serialization to avoid NoClassDefFoundError.

    There is no way to avoid that. The reason that deserialization in the remote JVM is complaining is that the class descriptor includes a reference to the outer class. The deserializing side needs to resolve that reference even if you managed to clobber the reference, and even if you never explicitly or implicitly used the synthetic variable in the deserialized object.

    The problem is that the remote JVM's classloader needs to know the type of the outer class when it loads the classfile for the inner class. It is needed for verification. It is needed for reflection. It is needed by the garbage collector.

    There is no workaround.

    (I'm not sure if this also applies to a static inner class ... but I suspect that it does.)


    Attempting to serialize anonymous Runnable instance without outer class refers not only to a serialization problem, but to a possibility of arbitrary code execution in another environment. It would be nice to see a JLS reference, describing this question.

    There is no JLS reference for this. Serialization and classloaders are not specified in the JLS. (Class initialization is ... but that is a different issue.)

    It is possible to run arbitrary code on a remote system via RMI. However you need to implement RMI dynamic class loading to achieve this. Here is a reference:

    • http://www.cis.upenn.edu/~bcpierce/courses/629/jdkdocs/guide/rmi/spec/rmi-arch.doc.html#280

    Note that adding dynamic class loading for remote classes to RMI introduces significant security issues. And you have to consider issues like classloader leaks.

    0 讨论(0)
  • 2021-02-06 04:26

    If you mad enough to do the trick you can use reflection to find field that contains reference to outer class and set it to null.

    0 讨论(0)
  • 2021-02-06 04:31

    Your example as stated above cannot work in Java because the anonymous inner class is declared within class Caller, and you explicitly stated that class Caller in not available on the RPC server (if I understood that correctly). Note that with Java RPC, only data is sent over the network, the classes must already be available on the client and the server. It that respect your example doesn't make sense because it looks like you want to send code instead of data. Typically you would have your serializable classes in a JAR that is available to the server and the client, and each serializable class should have a unique serialVersionUID.

    0 讨论(0)
提交回复
热议问题