kryo-serializers icon indicating copy to clipboard operation
kryo-serializers copied to clipboard

CGLibProxySerializer

Open PaulWeb opened this issue 12 years ago • 14 comments

CGLibProxySerializer only works if proxy object has one callback look at http://grepcode.com/file/repository.springsource.com/net.sourceforge.cglib/com.springsource.net.sf.cglib/2.2.0/net/sf/cglib/proxy/Enhancer.java?av=f#354 without filter you cann't recreate proxy object

http://sourceforge.net/p/cglib/bugs/6/

PaulWeb avatar Dec 09 '13 00:12 PaulWeb

Can you provide an example that shows the issue?

magro avatar Dec 09 '13 13:12 magro

cglib-2.2.0

import java.lang.reflect.Method;
import net.sf.cglib.proxy.*;
import java.io.*;
import org.junit.*;
import static org.junit.Assert.*;


public class Issue18Test {

    @BeforeClass
    public static void setUpClass() throws Exception {
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }
    public Issue18Test(){}

    @Test
    public void test(){
        Bad joe=new Bad(50);
        Enhancer e=new Enhancer();
        e.setSuperclass(Bad.class);
        e.setCallbacks(new Callback[]{new SerializableNoOp()});
        Bad simpleProxy=null;
        System.out.println("--simple proxy with one callbacks--");
        try{
            simpleProxy=(Bad)e.create();
            assertTrue(true);
            assertNotSame(joe, simpleProxy);
             System.out.println("No errors");
        }catch(IllegalStateException ex){
            System.out.println(ex.getMessage());
            assertTrue(false);
        }
        e=new Enhancer();
        e.setSuperclass(Bad.class);
        e.setCallbacks(new Callback[]{new SerializableNoOp(), new StaticUnadvisedInterceptor(joe)});
        Bad joeProxy=null;
        System.out.println("--composite proxy without filter--");
        try{
            joeProxy=(Bad)e.create();
            assertTrue(false);            
        }catch(IllegalStateException ex){
            System.out.println("Error:"+ex.getMessage());
            assertTrue(true);
        }
        e.setCallbackFilter(new ProxyCallbackFilter());
         System.out.println("--composite proxy with filter--");
        try{
            joeProxy=(Bad)e.create();
            assertTrue(true);  
            assertEquals(joe, joeProxy);
            System.out.println("No errors");
        }catch(IllegalStateException ex){
            System.out.println(ex.getMessage());
            assertTrue(false);
        }
    }
    /**
     * pojo
     */
    public static class Bad{
        private int _score;
        public Bad(){
        }
        public Bad(int score){
            _score=score;
        }

        public int getScore() {
            return _score;
        }

        public void setScore(int _score) {
            this._score = _score;
        }

        @Override
        public boolean equals(Object obj) {
            if(!(obj instanceof Bad)) return false;
            return _score==((Bad)obj).getScore();
        }



    }

    /**
     * Callbacks and filter
     */
    public static class SerializableNoOp implements NoOp, Serializable {
    }

    private static Object massageReturnTypeIfNecessary(Object proxy, Object target, Method method, Object retVal) {

        if (retVal != null && retVal == target) {
            retVal = proxy;
        }

        return retVal;
    }

    private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {

        private final Object target;

        public StaticUnadvisedInterceptor(Object target) {
            this.target = target;
        }

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object retVal = methodProxy.invoke(this.target, args);
            return massageReturnTypeIfNecessary(proxy, this.target, method, retVal);
        }
    }

    private static class ProxyCallbackFilter implements CallbackFilter {

        @Override
        public int accept(Method method) {          
            return 1;
        }
    }
}

PaulWeb avatar Dec 10 '13 00:12 PaulWeb

Thanx. I ran the test in the kryo1 branch (in master there's a VerifyError), and there it's green. The console prints

--simple proxy with one callbacks--
No errors
--composite proxy without filter--
Error:Multiple callback types possible but no filter specified
--composite proxy with filter--
No errors
PASSED: test

I haven't looked in details what's happening, can you tell what should be changed to CGLibProxySerializer? Btw, are you working on master or kryo1 branch?

magro avatar Dec 10 '13 08:12 magro

Btw, are you working on master or kryo1 branch? I'm working with my project (tomcat redis session). I was looking solution for this problem but found only your serilization class but it has also problem. I haven't looked in details what's happening, can you tell what should be changed to CGLibProxySerializer? You have to add exception when many callbacks or fix cglb because developers hide filter in constructor and i didn't find solution how to pull filter from proxy.

PaulWeb avatar Dec 10 '13 08:12 PaulWeb

Tomcat redis session sounds interesting, which project exactly are you referring to? I'm working on memcached-session-manager btw, which should get pluggable backends soon, with an implementation for Redis provided. Maybe we can combine the forces?

You have to add exceptin when many callbacks or fix cglb because developers hide filter in constructor and i didn't find solution how to pull filter from proxy.

Do you want to submit a pull request? I can publish a release soon then.

magro avatar Dec 10 '13 08:12 magro

we took as a basis https://github.com/jcoleman/tomcat-redis-session-manager, now we are facing with problem: spring session scope.

with an implementation for Redis provided

Do you want to implement redis in your project? Did you test with spring mvc+tomcat?

PaulWeb avatar Dec 10 '13 08:12 PaulWeb

maybe the better solution for cglib will be the check: does have proxy class writeReplace function

PaulWeb avatar Dec 10 '13 09:12 PaulWeb

Do you want to implement redis in your project?

Yes. There's this pull request that also comes with a Redis implementation which I want to pick up soon.

Did you test with spring mvc+tomcat?

We used spring session scope as well, but IIRC I didn't test spring mvc separately (we were using wicket).

maybe the better solution for cglib will be the check: does have proxy class writeReplace function

Yes, this would be possible. But when using kryo, wouldn't it be better to rely on kryo concepts to serialize/deserialize the callbacks (the user can supply/register appropriate kryo serializers)?

magro avatar Dec 10 '13 10:12 magro

wouldn't it be better to rely on kryo concepts to serialize/deserialize the callbacks (the user can supply/register appropriate kryo serializers)?

Yes, it will be better, but serialization for cglib will be work only if somebody fix cglib and add filter to proxy as field For example spring has own writeReplae for bean, look at http://grepcode.com/file/repo1.maven.org/maven2/org.springframework/spring-beans/3.1.2.RELEASE/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#984

PaulWeb avatar Dec 10 '13 10:12 PaulWeb

Shouldn't it work to register the kryo JavaSerializer for DefaultListableBeanFactory?

Do you have a simple spring sample application that we could use to create a test case for this?

magro avatar Dec 10 '13 11:12 magro

Do you have a simple spring sample application that we could use to create a test case for this?

I'll prepare simple app with spring session scope, now i'm researching how to work DefaultListableBeanFactory, because function writeReplace works with standartsession(tomcat) writeObject, but via custom serialization doesnt' (something strange) spring session scope is a proxy cglib object (if you set in context aop:scoped-proxy proxy-target-class="true") and when serializing must be called writeReplace, because for example if we restart tomcat then our proxy will not load because it class already is not present in the classloader

PaulWeb avatar Dec 10 '13 11:12 PaulWeb

I just put together a simple test that shows how to serialize a bean that holds a DefaultListableBeanFactory, see commit 9baf56ca (or branch spring-DefaultListableBeanFactory-ser).

magro avatar Dec 10 '13 12:12 magro

I was hoping that beanfactory solves problem with aop proxy object but one of callbacks proxy has reference on factory. I did not correctly read stack of calls when an spring aop proxy object is serialized. Now is too late, tomorrow i'll describe problem with scope session in more detail with a fresh mind. thanks for test.

PaulWeb avatar Dec 10 '13 13:12 PaulWeb

https://github.com/PaulWeb/simplemvc run tomcat with you session manager deploy app set name stop tomcat run it and look at catalina log ''' SEVERE: Unable to deserialize into session java.lang.ClassNotFoundException: org.wp.issue18.User$$EnhancerByCGLIB$$c148b9f6 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at org.apache.catalina.util.CustomObjectInputStream.resolveClass(CustomObjectInputStream.java:76) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1593) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) at org.apache.catalina.session.StandardSession.readObject(StandardSession.java:1595) at org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1060) at com.radiadesign.catalina.session.JavaSpringSerializer.deserializeInto(JavaSpringSerializer.java:54) at com.radiadesign.catalina.session.RedisSessionManager.loadSessionFromRedis(RedisSessionManager.java:403) at com.radiadesign.catalina.session.RedisSessionManager.findSession(RedisSessionManager.java:325) at org.apache.catalina.connector.Request.isRequestedSessionIdValid(Request.java:2391) at org.apache.catalina.connector.CoyoteAdapter.parseSessionCookiesId(CoyoteAdapter.java:955) at org.apache.catalina.connector.CoyoteAdapter.postParseRequest(CoyoteAdapter.java:689) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:403) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1008) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:722) ''' problem was in using @SessionAttributes({"user"}) and session scope together, but cglib proxy object cannot be serialized and deserialized in different class loaders. Of course there is proxy serialization pattern but how to create proxy after deserializing

PaulWeb avatar Dec 10 '13 15:12 PaulWeb