Netty中的AttributeMap属性(AttributeKey、AttributeMap、Attribute)


参考文章,点击这里
原文章,点击这里,过时的版本,请注意
参考实例2 ,点击这里
ChannelConfig以及Attribute结构层次的分析

0.AttributeKey、AttributeMap、Attribute

AttributeMap可以看成是一个key为AttributeKey类型,value为Attribute类型的Map,而Attribute内存储的是一个值引用,它可以原子的更新内容,是线程安全的,而AttributeMap的实现也是线程安全的

0.1 Attribute接口

该接口的定义如下:

public interface Attribute<T> {
    /**
     * 返回该Attribute对应的key.
     */
    AttributeKey<T> key();
    /**
     * 返回当前存储的值,可能是null,
     */
    T get();
    /**
     * Sets the value
     */
    void set(T value);
    /**
     *  原子的设置一个新值,并返回原来的旧值
     */
    T getAndSet(T value);
    /**
     *  当原来值不存在的时候才原子的设置一个值
     */
    T setIfAbsent(T value);
    /**
     * 原子的CAS设置一个新值
     */
    boolean compareAndSet(T oldValue, T newValue);
}

可以看出,Attribute相当于存储某种引用类型的值的一个容器,可以原子的设置值和获取值,还是比较简单的。

0.2 AttributeKey类

该类是Attribute存储在AttributeMap中时所对应的key,可以通过它来获取对应的Attribute,key不可能有相同的名字。其定义如下:

public final class AttributeKey<T> extends AbstractConstant<AttributeKey<T>> {
    //pool是一个常量池,存放了一系列AttributeKey类型的常量
    private static final ConstantPool<AttributeKey<Object>> pool = new ConstantPool<AttributeKey<Object>>() {
        @Override
        protected AttributeKey<Object> newConstant(int id, String name) {
            return new AttributeKey<Object>(id, name);
        }
    };
    /**
     * Returns the singleton instance of the {@link AttributeKey} which has the specified {@code name}.
     */
    public static <T> AttributeKey<T> valueOf(String name) {
        return (AttributeKey<T>) pool.valueOf(name);
    }
    /**
     * Returns {@code true} if a {@link AttributeKey} exists for the given {@code name}.
     */
    public static boolean exists(String name) {
        return pool.exists(name);
    }
    /**
     * Creates a new {@link AttributeKey} for the given {@code name} or fail with an
     * {@link IllegalArgumentException} if a {@link AttributeKey} for the given {@code name} exists.
     */
    public static <T> AttributeKey<T> newInstance(String name) {
        return (AttributeKey<T>) pool.newInstance(name);
    }

    public static <T> AttributeKey<T> valueOf(Class<?> firstNameComponent, String secondNameComponent) {
        return (AttributeKey<T>) pool.valueOf(firstNameComponent, secondNameComponent);
    }

    private AttributeKey(int id, String name) {
        super(id, name);
    }
}

从上述代码可以看出,AttributeKey的实现跟ChannelOption是很相似的,相当于是一个常量池,实现了Constant接口,可以通过name和id来进行区分,并没有什么特殊的实际意义,就像一个区分的标志。

0.3 AttributeMap

该接口持有着一系列的Attribute对象,定义还是非常简单的,如下:

public interface AttributeMap {
    /**
     * 根据key获取对应的值.
     */
    <T> Attribute<T> attr(AttributeKey<T> key);

    /**
     * 查看是否存在对应的Attribute
     */
    <T> boolean hasAttr(AttributeKey<T> key);
}

该接口的默认实现类是DefaultAttributeMap类,该类有一个内部类DefaultAttribute,实现了Attribute接口,其定义如下:

    private static final class DefaultAttribute<T> extends AtomicReference<T> implements Attribute<T> {
        private static final long serialVersionUID = -2661411462200283011L;
        private final DefaultAttribute<?> head;
        private final AttributeKey<T> key;
        private DefaultAttribute<?> prev;
        private DefaultAttribute<?> next;
        private volatile boolean removed;
        //构造函数,赋值head节点和Attribute对应的key
        DefaultAttribute(DefaultAttribute<?> head, AttributeKey<T> key) {
            this.head = head;
            this.key = key;
        }
        // Special constructor for the head of the linked-list.
        DefaultAttribute() {
            head = this;
            key = null;
        }
        //返回对应的key
        public AttributeKey<T> key() {
            return key;
        }
        //原子的设置新值,仅当原来不存在
        public T setIfAbsent(T value) {
            while (!compareAndSet(null, value)) {
                T old = get();
                if (old != null) {
                    return old;
                }
            }
            return null;
        }
        public T getAndRemove() {
            removed = true;
            T oldValue = getAndSet(null);
            remove0();
            return oldValue;
        }
        //remove的时候,删除该节点,设置为null
        public void remove() {
            removed = true;
            set(null);
            remove0();
        }
        //链表靠head节点访问,因此锁住head节点就可以
        private void remove0() {
            synchronized (head) {
                if (prev == null) {
                    // 如果不存在prev,说明已经移除了
                    return;
                }
                //删掉此节点
                prev.next = next;
                if (next != null) {
                    next.prev = prev;
                }
                //清空prev和next
                prev = null;
                next = null;
            }
        }
    }

该类继承了AtomicReference类,这是Netty内部实现的一个原子引用类,因此DefaultAttribute是线程安全的。它是一个双向链表的节点,也就是所有的Attribute连接成了一个双向链表的存储结构。而DefaultAttribute对象被存放在了一个AtomicReferenceArray类型的数组结构的属性attributes里面,这是AttributeMap内部的核心存储结构。

下面再来看AttributeMap的两个主要方法的实现思路:

hasAttr方法

   public <T> boolean hasAttr(AttributeKey<T> key) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
        //如果attributes是空的那就肯定不存在了,返回false
        if (attributes == null) {
            return false;
        }
        //获取key在attributes中对应的值
        int i = index(key);
        DefaultAttribute<?> head = attributes.get(i);
        if (head == null) {
            //不存在就返回false
            return false;
        }
        // We need to synchronize on the head.
        synchronized (head) {
            // Start with head.next as the head itself does not store an attribute.
            DefaultAttribute<?> curr = head.next;
            while (curr != null) {
                //如果存在且没被删除,就返回true
                if (curr.key == key && !curr.removed) {
                    return true;
                }
                curr = curr.next;
            }
            return false;
        }
    }
    //根据key获取其在attributes中的索引
    private static int index(AttributeKey<?> key) {
        return key.id() & MASK;
    }

2. attr方法:

    public <T> Attribute<T> attr(AttributeKey<T> key) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes; 
        //懒加载
        if (attributes == null) {
            attributes = new AtomicReferenceArray<DefaultAttribute<?>>(BUCKET_SIZE);
            if (!updater.compareAndSet(this, null, attributes)) {
                attributes = this.attributes;
            }
        }
        int i = index(key);
        DefaultAttribute<?> head = attributes.get(i);
        //没找到就放入新值
        if (head == null) {
            head = new DefaultAttribute();
            DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
            head.next = attr;
            attr.prev = head;
            if (attributes.compareAndSet(i, null, head)) {
                return attr;
            } else {
                head = attributes.get(i);
            }
        }
        //从链表中查找
        synchronized (head) {
            DefaultAttribute<?> curr = head;
            for (;;) {
                DefaultAttribute<?> next = curr.next;
                if (next == null) {
                    DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
                    curr.next = attr;
                    attr.prev = curr;
                    return attr;
                }

                if (next.key == key && !next.removed) {
                    return (Attribute<T>) next;
                }
                curr = next;
            }
        }
    }

1.前言

4.1之前的版本(注意4.1之前的版本已经废弃了)

本来没打算研究这个东西的,一开始觉得没啥用,甚至觉得这个东西有点鸡肋,不过慢慢接触之后,发现了这个AttributeMap的重要性

初学这个东西,我们还是先理解AttributeMap的用法吧

1)AttributeMap这是是绑定在Channel或者ChannelHandlerContext上的一个附件,相当于依附在这两个对象上的寄生虫一样,相当于附件一样,如图所示:
在这里插入图片描述

这个图还算比较形象地描述了AttributeMap的作用,我们知道每一个ChannelHandlerContext都是ChannelHandler和ChannelPipeline之间连接的桥梁,每一个ChannelHandlerContext都有属于自己的上下文,也就说每一个ChannelHandlerContext上如果有AttributeMap都是绑定上下文的,也就说如果A的ChannelHandlerContext中的AttributeMap,B的ChannelHandlerContext是无法读取到的

但是Channel上的AttributeMap就是大家共享的,每一个ChannelHandler都能获取到

我们再看看AttributeMap的结构:

在这里插入图片描述

可以看出这个是线程安全的,所以我们可以放心使用,再看看AttributeMap的结构,其实和Map的格式很像,key是AttributeKey,value是Attribute,我们可以根据AttributeKey找到对应的Attribute,并且我们可以指定Attribute的类型T:

4.1之后的版本

Q:ChannelHandlerContext和Channel都提供了attr方法,那么它们设置的属性作用域有什么不同了?
A:在Netty 4.1版本之前,它们两设置的属性作用域确实存在着不同,但从Netty 4.1版本开始,它们两设置的属性的作用域已经完全相同了。
在这里插入图片描述
从上面的描述上,我们可以知道从Netty 4.1 开始 “ChannelHandlerContext.attr(…) == Channel.attr(…)”。即放入它们的attribute的作用域是一样的了。每个Channel内部指保留一个AttributeMap。
而在Netty4.1之前,Channel内部保留有一个AttributeMap,而每个ChannelHandlerContext内部又保留有它们自己的AttributeMap,这样通过Channel.attr()放入的属性,是无法通过ChannelHandlerContext.attr()得到的,反之亦然。这种行为不仅令人困惑还会浪费内存。因此有了Netty 4.1将attr作用域统一的做法。

2. 使用

参考:NettyPro中的Attribute测试2:
下面的代码有错,还未及时修改。
1)首先定义一个AttributeKey:

public static final AttributeKey<NettyChannel> NETTY_CHANNEL_KEY = AttributeKey.valueOf("netty.channel");

我们AttributeMap中存储的是NettyChannel,这是我们自定义的一个类:


package com.lyncc.netty.attributeMap;
 
import java.util.Date;
 
public class NettyChannel {
    
    private String name;
    
    
    private Date createDate;
 
 
    public NettyChannel(String name,Date createDate) {
        this.name = name;
        this.createDate = createDate;
    }
 
    public String getName() {
        return name;
    }
 
 
    public void setName(String name) {
        this.name = name;
    }
 
 
    public Date getCreateDate() {
        return createDate;
    }
 
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    
 
}

那么我们可以这么使用ChannelHandler中这么使用:

@Override
    public void channelActive(ChannelHandlerContext ctx) {
        Attribute<NettyChannel> attr = ctx.attr(NETTY_CHANNEL_KEY);
        NettyChannel nChannel = attr.get();
        if (nChannel == null) {
            NettyChannel newNChannel = new NettyChannel("HelloWorld0Client", new Date());
            nChannel = attr.setIfAbsent(newNChannel);
        } else {
            System.out.println("attributeMap 中是有值的");
            System.out.println(nChannel.getName() + "=======" + nChannel.getCreateDate());
        }
        System.out.println("HelloWorldC0ientHandler Active");
        ctx.fireChannelActive();
    }

channelActive方法中的ChannelHandlerContext方法可以使用attr方法传入AttributeKey获取一个Attribute,如果我们之前没有赋值,那么此时的Attribute值应该是null,我们就创建一个NettyChannel,并使用setIfAbsent这个方法,这个方法是线程安全的,大体的使用方法就是如此

3.例子

现在我们举两个简单的例子

1)测试ChannelHandler上的AttributeMap是不是上下文绑定的------------首先我们在客户端写两个自定义的ChannelHandler方法,这两个方法的的ChannelActive都会在ChannelHandlerContext上的AttributeMap上写一些属性,然后在对应的ChannelRead方法上读取对应的值,看其是否能读取到:

我们先贴服务端的代码。很简单:

package com.lyncc.netty.attributeMap;
 
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
 
import java.net.InetSocketAddress;
 
public class HelloWorldServer {
 
    private int port;
    
    public HelloWorldServer(int port) {
        this.port = port;
    }
    
    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap sbs = new ServerBootstrap().group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder());
                            ch.pipeline().addLast(new HelloWorldServerHandler());
                        };
                        
                    }).option(ChannelOption.SO_BACKLOG, 128)   
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
             // 绑定端口,开始接收进来的连接
             ChannelFuture future = sbs.bind(port).sync();  
             
             System.out.println("Server start listen at " + port );
             future.channel().closeFuture().sync();
        } catch (Exception e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new HelloWorldServer(port).start();
    }
}

Channel:

package com.lyncc.netty.attributeMap;
 
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
 
public class HelloWorldServerHandler extends ChannelInboundHandlerAdapter{
    
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server channelRead..");
        System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString());
        ctx.write("server write"+msg);
        ctx.flush();
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
 
}

客户端的bootstrap代码:

package com.lyncc.netty.attributeMap;
 
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
 
public class HelloWorldClient {
    
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
 
    public static void main(String[] args) throws Exception {
        initChannel();
    }
    
    public static void initChannel() throws InterruptedException{
        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast("decoder", new StringDecoder());
                     p.addLast("encoder", new StringEncoder());
                     p.addLast(new HelloWorldClientHandler());
                     p.addLast(new HelloWorld2ClientHandler());
                 }
             });
 
            ChannelFuture future = b.connect(HOST, PORT).sync();
            future.channel().writeAndFlush("hello Netty,Test attributeMap");
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    
    }
 
}

一个常量类:

package com.lyncc.netty.attributeMap;
 
import io.netty.util.AttributeKey;
 
public class AttributeMapConstant {
    
    public static final AttributeKey<NettyChannel> NETTY_CHANNEL_KEY = AttributeKey.valueOf("netty.channel");
 
}

两个客户端的handler:

package com.lyncc.netty.attributeMap;
 
import static com.lyncc.netty.attributeMap.AttributeMapConstant.NETTY_CHANNEL_KEY;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.Attribute;
 
import java.util.Date;
public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {
 
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        Attribute<NettyChannel> attr = ctx.attr(NETTY_CHANNEL_KEY);
        NettyChannel nChannel = attr.get();
        if (nChannel == null) {
            NettyChannel newNChannel = new NettyChannel("HelloWorld0Client", new Date());
            nChannel = attr.setIfAbsent(newNChannel);
        } else {
            System.out.println("channelActive attributeMap 中是有值的");
            System.out.println(nChannel.getName() + "=======" + nChannel.getCreateDate());
        }
        System.out.println("HelloWorldC0ientHandler Active");
        ctx.fireChannelActive();
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Attribute<NettyChannel> attr = ctx.attr(NETTY_CHANNEL_KEY);
        NettyChannel nChannel = attr.get();
        if (nChannel == null) {
            NettyChannel newNChannel = new NettyChannel("HelloWorld0Client", new Date());
            nChannel = attr.setIfAbsent(newNChannel);
        } else {
            System.out.println("channelRead attributeMap 中是有值的");
            System.out.println(nChannel.getName() + "=======" + nChannel.getCreateDate());
        }
        System.out.println("HelloWorldClientHandler read Message:" + msg);
        
        ctx.fireChannelRead(msg);
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
 
}

handler2:

package com.lyncc.netty.attributeMap;
 
import static com.lyncc.netty.attributeMap.AttributeMapConstant.NETTY_CHANNEL_KEY;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.Attribute;
 
import java.util.Date;
 
public class HelloWorld2ClientHandler extends ChannelInboundHandlerAdapter {
 
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        Attribute<NettyChannel> attr = ctx.attr(NETTY_CHANNEL_KEY);
        NettyChannel nChannel = attr.get();
        if (nChannel == null) {
            NettyChannel newNChannel = new NettyChannel("HelloWorld2Client", new Date());
            nChannel = attr.setIfAbsent(newNChannel);
        } else {
            System.out.println("channelActive attributeMap 中是有值的");
            System.out.println(nChannel.getName() + "=======" + nChannel.getCreateDate());
        }
        System.out.println("HelloWorldC2ientHandler Active");
        ctx.fireChannelActive();
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Attribute<NettyChannel> attr = ctx.attr(NETTY_CHANNEL_KEY);
        NettyChannel nChannel = attr.get();
        if (nChannel == null) {
            NettyChannel newNChannel = new NettyChannel("HelloWorld0Client", new Date());
            nChannel = attr.setIfAbsent(newNChannel);
        } else {
            System.out.println("channelRead attributeMap 中是有值的");
            System.out.println(nChannel.getName() + "=======" + nChannel.getCreateDate());
        }
        System.out.println("HelloWorldClientHandler read Message:" + msg);
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
 
}

我们先运行服务器端:
在这里插入图片描述

客户端控制台:
在这里插入图片描述

这说明每个ChannelHandlerContext上的AttributeMap是相互不影响的

2.实例2

2)测试Channel上的AttributeMap:

我们只需要把2个channel获取Attribute的方法改下就可以了:

Attribute<NettyChannel> attr = ctx.channel().attr(NETTY_CHANNEL_KEY);

两个channel共有四处需要修改,改成获取channel后获取attribute:

再次运行:
在这里插入图片描述

好了,首先在ChannelHandler1中赋值了,然后在channelHandlerHandler2中的channelActivew打印了attributeMap中有值了,然后都能够在channelRead中读取到最新值

关于AttributeMap的一些内幕详解,参考一下:

http://blog.csdn.net/zxhoo/article/details/17719333

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
io.netty.util.AttributeKeyNetty网络编程框架的一个类,用于在Channel的属性Attribute存储和获取特定的键值对信息。通过 AttributeKey,我们可以给Channel动态地添加属性,并在需要的时候获取这些属性的值。 在Netty,每个Channel都有一个对应的AttributeMap,它是一个存储属性的容器。我们可以通过AttributeKey来定义属性的键,然后将键值对存储在AttributeMap。这样,在整个Channel的生命周期内,我们都可以通过AttributeKey来获取和修改这些属性的值。 AttributeKey的定义是通过泛型实现的,例如: ```java public class AttributeKey<T> extends UniqueName {} ``` 其,T代表属性值的类型。我们可以使用AttributeKey.valueOf(String name)方法来创建一个AttributeKey对象,例如: ```java AttributeKey<Integer> ageKey = AttributeKey.valueOf("age"); ``` 然后,我们可以通过Channel的attr(AttributeKey<T> key)方法来访问和操作Channel的属性,例如: ```java channel.attr(ageKey).set(25); int age = channel.attr(ageKey).get(); ``` 通过上述代码,我们给Channel添加了一个名为"age"的属性,并将其值设置为25。然后,我们又通过AttributeKey对象获取了该属性的值,并赋给了age变量。 总结来说,io.netty.util.AttributeKeyNetty用于定义和访问Channel属性的类,它为我们提供了一种便捷的方式来存储和获取与Channel相关的自定义信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值