Introduction

As we mentioned in the previous article, jboss marshalling is a very good way to serialize java objects, it is compatible with the serialization that comes with JDK, and it also provides performance and usage optimizations.

So can such an excellent serialization tool be used in netty as a way of message passing?

The answer is of course yes, everything is possible in netty.

marshalling provider in netty

Looking back at the common usage of jboss marshalling, we need to create a Marshaller from MarshallerFactory, because there are different implementations of mashaller, so we need to specify a specific implementation to create a MarshallerFactory, as shown below:

 MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("river");

This MarshallerFactory is actually a MarshallerProvider.

Such an interface is defined in netty:

 public interface MarshallerProvider {

    Marshaller getMarshaller(ChannelHandlerContext ctx) throws Exception;
}

MarshallerProvider actually does the same job as MarshallerFactory.

Since MarshallerProvider is an interface, what are its implementations?

It has two implementation classes in netty, DefaultMarshallerProvider and ThreadLocalMarshallerProvider.

What is the difference between the two?

Let's take a look at DefaultMarshallerProvider first:

 public class DefaultMarshallerProvider implements MarshallerProvider {

    private final MarshallerFactory factory;
    private final MarshallingConfiguration config;

    public DefaultMarshallerProvider(MarshallerFactory factory, MarshallingConfiguration config) {
        this.factory = factory;
        this.config = config;
    }

    public Marshaller getMarshaller(ChannelHandlerContext ctx) throws Exception {
        return factory.createMarshaller(config);
    }

}

As the name suggests, DefaultMarshallerProvider is the default implementation of marshallerProvider. From the specific implementation code, we can see that DefaultMarshallerProvider actually needs to pass in MarshallerFactory and MarshallingConfiguration as parameters, and then use the incoming MarshallerFactory to create a specific marshaller Provider, and we create it manually The way marshaller is consistent.

But in the above implementation every time getMarshaller needs to create a new one from the factory, there may be performance problems. So netty implements a new ThreadLocalMarshallerProvider:

 public class ThreadLocalMarshallerProvider implements MarshallerProvider {
    private final FastThreadLocal<Marshaller> marshallers = new FastThreadLocal<Marshaller>();

    private final MarshallerFactory factory;
    private final MarshallingConfiguration config;

    public ThreadLocalMarshallerProvider(MarshallerFactory factory, MarshallingConfiguration config) {
        this.factory = factory;
        this.config = config;
    }

    @Override
    public Marshaller getMarshaller(ChannelHandlerContext ctx) throws Exception {
        Marshaller marshaller = marshallers.get();
        if (marshaller == null) {
            marshaller = factory.createMarshaller(config);
            marshallers.set(marshaller);
        }
        return marshaller;
    }
}

The difference between ThreadLocalMarshallerProvider and DefaultMarshallerProvider is that ThreadLocalMarshallerProvider saves a FastThreadLocal object. FastThreadLocal is an optimized version of ThreadLocal in JDK, which is faster than ThreadLocal.

In the getMarshaller method, first get the Marshaller object from FastThreadLocal, if the Marshaller object does not exist, create a Marshaller object from the factory, and finally put the Marshaller object in ThreadLocal.

If there is a MarshallerProvider, there is an UnMarshallerProvider corresponding to him:

 public interface UnmarshallerProvider {

    Unmarshaller getUnmarshaller(ChannelHandlerContext ctx) throws Exception;
}

The UnmarshallerProvider in netty has three implementation classes, namely DefaultUnmarshallerProvider, ThreadLocalUnmarshallerProvider and ContextBoundUnmarshallerProvider.

The previous two DefaultUnmarshallerProvider and ThreadLocalUnmarshallerProvider are implemented in the same way as marshaller, so they will not be repeated here.

We mainly look at the implementation of ContextBoundUnmarshallerProvider.

As we can see from the name, this unmarshaller is related to ChannelHandlerContext.

ChannelHandlerContext represents the context of the channel, which has a method called attr, which can save attributes related to the channel:

 <T> Attribute<T> attr(AttributeKey<T> key);

The method of ContextBoundUnmarshallerProvider is to store the Unmarshaller in the context, and get it from the context each time it is used, and then get it from the factroy if it is not available.

Let's take a look at the implementation of ContextBoundUnmarshallerProvider:

 public class ContextBoundUnmarshallerProvider extends DefaultUnmarshallerProvider {

    private static final AttributeKey<Unmarshaller> UNMARSHALLER = AttributeKey.valueOf(
            ContextBoundUnmarshallerProvider.class, "UNMARSHALLER");

    public ContextBoundUnmarshallerProvider(MarshallerFactory factory, MarshallingConfiguration config) {
        super(factory, config);
    }

    @Override
    public Unmarshaller getUnmarshaller(ChannelHandlerContext ctx) throws Exception {
        Attribute<Unmarshaller> attr = ctx.channel().attr(UNMARSHALLER);
        Unmarshaller unmarshaller = attr.get();
        if (unmarshaller == null) {
            unmarshaller = super.getUnmarshaller(ctx);
            attr.set(unmarshaller);
        }
        return unmarshaller;
    }
}

ContextBoundUnmarshallerProvider inherits from DefaultUnmarshallerProvider. In the getUnmarshaller method, first take out the unmarshaller from ctx, if not, call the getUnmarshaller method in DefaultUnmarshallerProvider to take out the unmarshaller.

Marshalling encoder

We got the marshaller in the above chapter, let's take a look at how to use the marshaller for encoding and decoding operations.

Let's first look at the encoder MarshallingEncoder. MarshallingEncoder inherits from MessageToByteEncoder, and the received generic is Object:

 public class MarshallingEncoder extends MessageToByteEncoder<Object>

Is to encode the Object object into a ByteBuf. Recall that the encoding of the usual objects we mentioned before needs to use an object length field to divide the data of the object. The same MarshallingEncoder also provides a 4-byte LENGTH_PLACEHOLDER to store the length of the object.

Specifically look at its encode method:

 protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        Marshaller marshaller = provider.getMarshaller(ctx);
        int lengthPos = out.writerIndex();
        out.writeBytes(LENGTH_PLACEHOLDER);
        ChannelBufferByteOutput output = new ChannelBufferByteOutput(out);
        marshaller.start(output);
        marshaller.writeObject(msg);
        marshaller.finish();
        marshaller.close();

        out.setInt(lengthPos, out.writerIndex() - lengthPos - 4);
    }

The logic of encode is very simple, first get the marshaller object from the provider, then write 4 bytes of LENGTH_PLACEHOLDER to out, and then use the marshaller to
Write the encoded object in out, and finally fill out according to the length of the written object to get the final output.

Because the encoded data is stored with length data, a frame decoder called LengthFieldBasedFrameDecoder needs to be used when decoding.

There are usually two ways to use LengthFieldBasedFrameDecoder. One is to add LengthFieldBasedFrameDecoder to the pipline handler. The decoder only needs to process the object processed by the frame decoder.

Another way is that the decoder itself is a LengthFieldBasedFrameDecoder.

Here netty chooses the second method, let's look at the definition of MarshallingDecoder:

 public class MarshallingDecoder extends LengthFieldBasedFrameDecoder

First, you need to specify the field length of LengthFieldBasedFrameDecoder in the constructor. Here, the super method is called to achieve:

 public MarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize) {
        super(maxObjectSize, 0, 4, 0, 4);
        this.provider = provider;
    }

And override the extractFrame method:

 protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.slice(index, length);
    }

Finally, look at the decode method:

 protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        Unmarshaller unmarshaller = provider.getUnmarshaller(ctx);
        ByteInput input = new ChannelBufferByteInput(frame);
        try {
            unmarshaller.start(input);
            Object obj = unmarshaller.readObject();
            unmarshaller.finish();
            return obj;
        } finally {
            unmarshaller.close();
        }
    }

The logic of decode is also very simple, first call the super method to decode the frame ByteBuf. Then call the unmarshaller to read the object, and finally return the modified object.

Another implementation of Marshalling coding

We mentioned above that the encoding of the object uses LengthFieldBasedFrameDecoder, which determines the length of the field according to a length field before the actual data of the object, so as to read the real data.

So is it possible to accurately read the object without specifying the length of the object?

In fact, it is also possible, we can keep trying to read the data until we find the appropriate object data.

Friends who have read my previous articles may have thought, isn't ReplayingDecoder just doing this? In ReplayingDecoder, it will continue to retry until a message that meets the conditions is found.

So netty also has an implementation of marshalling encoding and decoding based on ReplayingDecoder, called CompatibleMarshallingEncoder and CompatibleMarshallingDecoder.

CompatibleMarshallingEncoder is very simple, because the actual length of the object is not required, so use the marshalling encoding directly.

 public class CompatibleMarshallingEncoder extends MessageToByteEncoder<Object> {

    private final MarshallerProvider provider;

    public CompatibleMarshallingEncoder(MarshallerProvider provider) {
        this.provider = provider;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        Marshaller marshaller = provider.getMarshaller(ctx);
        marshaller.start(new ChannelBufferByteOutput(out));
        marshaller.writeObject(msg);
        marshaller.finish();
        marshaller.close();
    }
}

CompatibleMarshallingDecoder inherits ReplayingDecoder:

 public class CompatibleMarshallingDecoder extends ReplayingDecoder<Void>

The core of its decode method is to call the unmarshaller method:

 Unmarshaller unmarshaller = provider.getUnmarshaller(ctx);
        ByteInput input = new ChannelBufferByteInput(buffer);
        if (maxObjectSize != Integer.MAX_VALUE) {
            input = new LimitingByteInput(input, maxObjectSize);
        }
        try {
            unmarshaller.start(input);
            Object obj = unmarshaller.readObject();
            unmarshaller.finish();
            out.add(obj);
        } catch (LimitingByteInput.TooBigObjectException ignored) {
            discardingTooLongFrame = true;
            throw new TooLongFrameException();
        } finally {
            unmarshaller.close();
        }

Note that there are two exceptions when decoding here. The first exception is the exception during unmarshaller.readObject, which will be caught by ReplayingDecoder and retry.

There is also an exception that the field is too long. This exception cannot be handled and can only be abandoned:

 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof TooLongFrameException) {
            ctx.close();
        } else {
            super.exceptionCaught(ctx, cause);
        }
    }

Summarize

The above is the implementation of encoding and decoding using marshalling in netty. The principle and object encoding and decoding are very similar, you can compare and analyze.

This article has been included in http://www.flydean.com/17-1-netty-marshalling/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望437 粉丝

欢迎访问我的个人网站:www.flydean.com