activej / activej

ActiveJ is an alternative Java platform built from the ground up. ActiveJ redefines core, web and high-load programming in Java, providing simplicity, maximum performance and scalability
https://activej.io
Apache License 2.0
861 stars 70 forks source link

How to get an RPC example with generic response #258

Open l16h7n1n6s opened 1 year ago

l16h7n1n6s commented 1 year ago

i am trying to create an RPC server/client but i want the response to be generic but i failed this is an example from error that i get in server when trying to listen on port. Can you provide an example with solution here if possible?

thanks in advance

Exception in thread "Thread-2" java.lang.IllegalArgumentException: Type not found: sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl@12388301
    at io.activej.types.AnnotatedTypes.bind(AnnotatedTypes.java:94)
    at io.activej.types.AnnotatedTypes.bind(AnnotatedTypes.java:76)
    at io.activej.serializer.SerializerBuilder.tryAddGetter(SerializerBuilder.java:1012)
    at io.activej.serializer.SerializerBuilder.scanGetters(SerializerBuilder.java:896)
    at io.activej.serializer.SerializerBuilder.scanClass(SerializerBuilder.java:798)
    at io.activej.serializer.SerializerBuilder.doScan(SerializerBuilder.java:779)
    at io.activej.serializer.SerializerBuilder.scan(SerializerBuilder.java:758)
    at io.activej.serializer.SerializerBuilder.lambda$with$35(SerializerBuilder.java:230)
    at io.activej.types.scanner.TypeScannerRegistry.scan(TypeScannerRegistry.java:378)
    at io.activej.types.scanner.TypeScannerRegistry$Context.scan(TypeScannerRegistry.java:217)
    at io.activej.types.scanner.TypeScannerRegistry$Context.scan(TypeScannerRegistry.java:207)
    at io.activej.serializer.SerializerBuilder.lambda$with$35(SerializerBuilder.java:219)
    at io.activej.types.scanner.TypeScannerRegistry.scan(TypeScannerRegistry.java:378)
    at io.activej.types.scanner.TypeScannerRegistry$Context.scan(TypeScannerRegistry.java:217)
    at io.activej.serializer.SerializerBuilder.tryAddGetter(SerializerBuilder.java:1012)
    at io.activej.serializer.SerializerBuilder.scanGetters(SerializerBuilder.java:896)
    at io.activej.serializer.SerializerBuilder.scanClass(SerializerBuilder.java:798)
    at io.activej.serializer.SerializerBuilder.doScan(SerializerBuilder.java:779)
    at io.activej.serializer.SerializerBuilder.scan(SerializerBuilder.java:758)
    at io.activej.serializer.SerializerBuilder.lambda$with$35(SerializerBuilder.java:230)
    at io.activej.types.scanner.TypeScannerRegistry.scan(TypeScannerRegistry.java:378)
    at io.activej.types.scanner.TypeScannerRegistry.lambda$scanner$1(TypeScannerRegistry.java:359)
    at io.activej.serializer.SerializerBuilder.build(SerializerBuilder.java:479)
    at io.activej.serializer.SerializerBuilder.build(SerializerBuilder.java:459)
    at io.activej.rpc.server.RpcServer.onListen(RpcServer.java:213)
    at io.activej.net.AbstractServer.listen(AbstractServer.java:226)

Here is what i want to achieve this is my response class:

import io.activej.serializer.annotations.Deserialize;
import io.activej.serializer.annotations.Serialize;

import java.util.ArrayList;
import java.util.List;

public class Response<T> {

    private List<T> data;

    public Response(@Deserialize("data")List<T> data) {
        this.data = data;
    }

    @Serialize
    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }
}

here is what i do

T typeParameterClass=new MyClass();
Type fluentType=setModelAndGetCorrespondingList2(typeParameterClass.getClass());
rpcServer = RpcServer.create(eventloop)
                    .withMessageTypes(Request.class,fluentType.getClass())
                    .withSerializerBuilder(this.rpcserialize)
                    .withHandler(Request.class, helloServiceRequestHandler(new Service(typeParameterClass.getClass())))
                    .withListenAddress(inetSocketAddress);

 private <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
        return new TypeToken<Response<T>>() {
        }
                .where(new TypeParameter<T>() {
                }, type)
                .getType();
    }

and i think the problem is here in this line .withMessageTypes(Request.class,fluentType.getClass()) Any help how to fix this issue?

An alternative way that i thinking is to send bytes as response and then manually deserialize it when it will bereceived. will this approach add extra delay overhead or its ok to do it?

eduard-vasinskyi commented 1 year ago

Hi, @l16h7n1n6s

ActiveJ Serializer does not support generic T types as it requires concrete types to be resolved in order to build a serializer for a type. You would also need to know in advance which types could be serialized as a T.

The simplest solution to your issue would be to replace a generic T with an Object class. So, instead of List<T> you would have a List<Object>.

You would still need to pass all possible types that could be stored in a class. You would need to use @SerializeClass annotation for this (enumerate possible subclasses using a subclasses attribute).

Your response class could look like this (assuming that List may contain Integers and Strings):

public class Response {
    private List<Object> data;

    public Response(@Deserialize("data") List<Object> data) {
        this.data = data;
    }

    @Serialize
    public List<@SerializeClass(subclasses = {String.class, Integer.class}) Object> getData() {
        return data;
    }

    public void setData(List<Object> data) {
        this.data = data;
    }
}

However, now you are allowed to pass a list of mixed elements, for example List.of(“test”, 1, “example”);. Another disadvantage is that for every element of serialized list an additional byte is used to encode a type of the element.

Another, a bit more complicated solution, is to serialize not a List<T>, but some container interface, like ListHolder<T>. You would need to create some additional classes, but, overall, it would solve the above-mentioned problems.

Here is how your classes could look like:


@SuppressWarnings("unchecked")
public class Response<T> {
    private ListHolder<?> listHolder;

    public Response(@Deserialize("listHolder") ListHolder<?> listHolder) {
        this.listHolder = listHolder;
    }

    @Serialize
    public ListHolder<?> getListHolder() {
        return listHolder;
    }

    public List<T> getData() {
        return (List<T>) listHolder.getList();
    }

    public void setListHolder(ListHolder<T> listHolder) {
        this.listHolder = listHolder;
    }
}

@SerializeClass(subclasses = {IntegerListHolder.class, StringListHolder.class})
public interface ListHolder<T> {
    List<T> getList();
}

public class IntegerListHolder implements ListHolder<Integer> {
    private final List<Integer> list;

    public IntegerListHolder(@Deserialize("list") List<Integer> list) {
        this.list = list;
    }

    @Override
    @Serialize
    public List<Integer> getList() {
        return list;
    }
}

public class StringListHolder implements ListHolder<String> {
    private final List<String> list;

    public StringListHolder(@Deserialize("list") List<String> list) {
        this.list = list;
    }

    @Override
    @Serialize
    public List<String> getList() {
        return list;
    }
}
l16h7n1n6s commented 1 year ago

@eduard-vasinskyi thanks for you wonderful message your are my Hero!!!!!!