websocket-cometd源碼閱讀-基於註解初始化(三)

配置例子

 <!-- CometD Servlet -->
  <servlet>
    <servlet-name>cometd</servlet-name>
    <servlet-class>org.cometd.annotation.server.AnnotationCometDServlet</servlet-class>
    <!--liqiang todo 600000-->
    <init-param>
      <param-name>maxProcessing</param-name>
      <param-value>600000</param-value>
    </init-param>
    <init-param>
      <param-name>timeout</param-name>
      <param-value>20000</param-value>
    </init-param>
    <init-param>
      <param-name>interval</param-name>
      <param-value>0</param-value>
    </init-param>
    <init-param>
      <param-name>maxInterval</param-name>
      <param-value>10000</param-value>
    </init-param>
    <init-param>
      <param-name>handshakeReconnect</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>maxLazyTimeout</param-name>
      <param-value>5000</param-value>
    </init-param>
    <init-param>
      <param-name>long-polling.multiSessionInterval</param-name>
      <param-value>2000</param-value>
    </init-param>
    <init-param>
      <param-name>services</param-name>
      <param-value>org.cometd.examples.ChatService</param-value>
    </init-param>
    <init-param>
      <param-name>ws.cometdURLMapping</param-name>
      <param-value>/cometd/*</param-value>
    </init-param>
    <!--容器啓動時調用init方法初始化 而不是第一次調用時-->
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
  </servlet>

基於註解的使用例子

@Service("chat") //comted註解
public class ChatService {
    //保存用戶信息 不同房間
    private final ConcurrentMap<String, Map<String, String>> _members = new ConcurrentHashMap<>();
    @Inject //comted註解注入
    private BayeuxServer _bayeux;
    @Session //comted註解注入
    private ServerSession _session;

    //初始化 "/chat/**", "/members/**" 渠道
    @Configure({"/chat/**", "/members/**"})
    protected void configureChatStarStar(ConfigurableServerChannel channel) {
        DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(), new BadWordFilter());
        channel.addListener(noMarkup);
        channel.addAuthorizer(GrantAuthorizer.GRANT_ALL);
    }
    //初始化 "/service/members" 渠道
    @Configure("/service/members")
    protected void configureMembers(ConfigurableServerChannel channel) {
        channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
        channel.setPersistent(true);
    }

    /**
     * 渠道監聽器
     * @param client
     * @param message
     */
    @Listener("/service/members")
    public void handleMembership(ServerSession client, ServerMessage message) {
        //獲得消息
        Map<String, Object> data = message.getDataAsMap();
        //獲得房間信息
        String room = ((String)data.get("room")).substring("/chat/".length());
        //獲得房間的用戶信息
        Map<String, String> roomMembers = _members.get(room);
        if (roomMembers == null) {
            Map<String, String> new_room = new ConcurrentHashMap<>();
            //加入房間
            roomMembers = _members.putIfAbsent(room, new_room);
            if (roomMembers == null) {
                roomMembers = new_room;
            }
        }
        Map<String, String> members = roomMembers;
        //獲得房間的用戶信息
        String userName = (String)data.get("user");
        //用戶信息與clientId映射
        members.put(userName, client.getId());
        //添加監聽器 此客戶端斷開連接後 告訴當前房間所有人連接詞用戶下線 以及清除連接信息
        client.addListener((ServerSession.RemovedListener)(s, m, t) -> {
            //清除此房間連接信息
            members.values().remove(s.getId());
            //通知所有用戶
            broadcastMembers(room, members.keySet());
        });
        //建立連接後推送訂閱者最新的成員名單
        broadcastMembers(room, members.keySet());
    }

    private void broadcastMembers(String room, Set<String> members) {
        // Broadcast the new members list 推送訂閱此房間的所有用戶告知用戶下線
        ClientSessionChannel channel = _session.getLocalSession().getChannel("/members/" + room);
        channel.publish(members);
    }

    @Configure("/service/privatechat")
    protected void configurePrivateChat(ConfigurableServerChannel channel) {
        DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(), new BadWordFilter());
        channel.setPersistent(true);
        channel.addListener(noMarkup);
        channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
    }

    @Listener("/service/privatechat")
    public void privateChat(ServerSession client, ServerMessage message) {
        Map<String, Object> data = message.getDataAsMap();
        String room = ((String)data.get("room")).substring("/chat/".length());
        Map<String, String> membersMap = _members.get(room);
        if (membersMap == null) {
            Map<String, String> new_room = new ConcurrentHashMap<>();
            membersMap = _members.putIfAbsent(room, new_room);
            if (membersMap == null) {
                membersMap = new_room;
            }
        }
        String[] peerNames = ((String)data.get("peer")).split(",");
        ArrayList<ServerSession> peers = new ArrayList<>(peerNames.length);

        for (String peerName : peerNames) {
            String peerId = membersMap.get(peerName);
            if (peerId != null) {
                ServerSession peer = _bayeux.getSession(peerId);
                if (peer != null) {
                    peers.add(peer);
                }
            }
        }

        if (peers.size() > 0) {
            Map<String, Object> chat = new HashMap<>();
            String text = (String)data.get("chat");
            chat.put("chat", text);
            chat.put("user", data.get("user"));
            chat.put("scope", "private");
            ServerMessage.Mutable forward = _bayeux.newMessage();
            forward.setChannel("/chat/" + room);
            forward.setId(message.getId());
            forward.setData(chat);

            // test for lazy messages
            if (text.lastIndexOf("lazy") > 0) {
                forward.setLazy(true);
            }

            for (ServerSession peer : peers) {
                if (peer != client) {
                    peer.deliver(_session, forward, Promise.noop());
                }
            }
            client.deliver(_session, forward, Promise.noop());
        }
    }

    class BadWordFilter extends JSONDataFilter {
        @Override
        protected Object filterString(ServerSession session, ServerChannel channel, String string) {
            if (string.contains("dang")) {
                throw new DataFilter.AbortException();
            }
            return string;
        }
    }
}

源碼

<1>

org.cometd.annotation.server.AnnotationCometDServlet#init

   @Override
    public void init() throws ServletException {
        //<2>繼承了comet 這裏是先對comte進行初始化
        super.init();

        //基於註解方式初始化處理類對象的Processor  new ServerAnnotationProcessor(bayeuxServer);
        processor = newServerAnnotationProcessor(getBayeux());

        //獲得servlet initParameter的Service配置
        String servicesParam = getInitParameter("services");
        if (servicesParam != null && servicesParam.length() > 0) {

            for (String serviceClass : servicesParam.split(",")) {
                //<3>創建處理類對象 也就是xml配置的chatServerce
                Object service = processService(processor, serviceClass.trim());
                services.add(service);
                //設置當前Service到requestAttribute
                registerService(service);
            }
        }
    }

    protected ServerAnnotationProcessor newServerAnnotationProcessor(BayeuxServer bayeuxServer) {
        return new ServerAnnotationProcessor(bayeuxServer);
    }

<3>

org.cometd.annotation.server.AnnotationCometDServlet#processService

    protected Object processService(ServerAnnotationProcessor processor, String serviceClassName) throws ServletException {
        try {
            //反射根據類的全名稱創建類的對象
            Object service = newService(serviceClassName);
            //<4>初始化
            processor.process(service);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Processed annotated service {}", service);
            }
            return service;
        } catch (Exception x) {
            LOGGER.warn("Failed to create annotated service " + serviceClassName, x);
            throw new ServletException(x);
        }
    }

    protected Object newService(String serviceClassName) throws Exception {
        return Loader.loadClass(getClass(), serviceClassName).newInstance();
    }

<4>

org.cometd.annotation.server.ServerAnnotationProcessor#process

  public boolean process(Object bean) {
        //<5>基於註解依賴注入主要處理 @Inject 和@Session註解
        boolean result = processDependencies(bean);
        //<6>根據@Configure註解的方法初始化channel
        result |= processConfigurations(bean);
        //<7>處理Subscription 訂閱渠道
        result |= processCallbacks(bean);
        //<8>調用@PostConstruct 進行初始化操作
        result |= processPostConstruct(bean);
        return result;
    }

<5>

org.cometd.annotation.server.ServerAnnotationProcessor#processDependencies

 public boolean processDependencies(Object bean) {
        if (bean == null) {
            return false;
        }

        Class<?> klass = bean.getClass();
        //首選需要打了comted的@Service註解
        Service serviceAnnotation = klass.getAnnotation(Service.class);
        if (serviceAnnotation == null) {
            return false;
        }

        //容器中bean對象
        List<Object> injectables = new ArrayList<>(Arrays.asList(this.injectables));
        //默認增加是bayeuxServer 所以支持@Inject進行 bayeuxServer注入
        injectables.add(0, bayeuxServer);
        //<9>針對 bean的@Inject註解進行 注入
        boolean result = processInjectables(bean, injectables);
        //<10>針對Service初始化loaclSession 同時初始化一個serverSession與bayeuxServer模擬建立連接
        LocalSession session = findOrCreateLocalSession(bean, serviceAnnotation.value());
        //<11>session注入到bean 後續我們可以通過操作session發送消息
        result |= processSession(bean, session);
        return result;
    }

<9>

org.cometd.annotation.AnnotationProcessor#processInjectables

 protected boolean processInjectables(Object bean, List<Object> injectables) {
        boolean result = false;
        //遍歷容器
        for (Object injectable : injectables) {
            result |= processInjectable(bean, injectable);
        }
        return result;
    }

    protected boolean processInjectable(Object bean, Object injectable) {
        boolean result = false;
        for (Class<?> c = bean.getClass(); c != Object.class; c = c.getSuperclass()) {
            //反射獲得fields 獲得某個類的所有聲明的字段,即包括public、private和proteced,但是不包括父類的申明字段。
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                //如果打了@Inject註解
                if (field.getAnnotation(Inject.class) != null) {
                    /**
                     * 如果是A.isAssignableFrom(B) 確定一個類(B)是不是繼承來自於另一個父類(A),一個接口(A)是不是實現了另外一個接口(B),
                     * 或者兩個類相同。主要,這裏比較的維度不是實例對象,而是類本身,因爲這個方法本身就是Class類的方法,判斷的肯定是和類信息相關的。
                     */
                    if (field.getType().isAssignableFrom(injectable.getClass())) {
                        Object value = getField(bean, field);
                        if (value != null) {
                            if (LOGGER.isDebugEnabled()) {
                                LOGGER.debug("Avoid injection of field {} on bean {}, it's already injected with {}", field, bean, value);
                            }
                            continue;
                        }
                        //完成注入
                        setField(bean, field, injectable);
                        result = true;
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("Injected {} to field {} on bean {}", injectable, field, bean);
                        }
                    }
                }
            }
        }

        //針對打在set方法上 的注入
        List<Method> methods = findAnnotatedMethods(bean, Inject.class);
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                if (parameterTypes[0].isAssignableFrom(injectable.getClass())) {
                    invokePrivate(bean, method, injectable);
                    result = true;
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Injected {} to method {} on bean {}", injectable, method, bean);
                    }
                }
            }
        }
        return result;
    }

<10>

org.cometd.annotation.server.ServerAnnotationProcessor#findOrCreateLocalSession

private LocalSession findOrCreateLocalSession(Object bean, String name) {
        LocalSession session = sessions.get(bean);
        if (session == null) {
            //<12>創建一個Local session
            session = bayeuxServer.newLocalSession(name);
            //bean的名字作爲key維護放入sessions
            LocalSession existing = sessions.putIfAbsent(bean, session);
            if (existing != null) {
                session = existing;
            } else {
                //<13>默認握手加入bayeuxServer
                session.handshake();
            }
        }
        return session;
    }

<12>

org.cometd.server.BayeuxServerImpl#newLocalSession

@Override
    public LocalSession newLocalSession(String idHint) {
        return new LocalSessionImpl(this, idHint);
    }

<13>

org.cometd.server.LocalSessionImpl#handshake

 @Override
    public void handshake(Map<String, Object> template, ClientSession.MessageListener callback) {
        if (_session != null) {
            throw new IllegalStateException("Method handshake() invoke multiple times for local session " + this);
        }

        ServerSessionImpl session = new ServerSessionImpl(_bayeux, this, _idHint);

        ServerMessage.Mutable hsMessage = newMessage();
        if (template != null) {
            hsMessage.putAll(template);
        }
        String messageId = newMessageId();
        hsMessage.setId(messageId);
        hsMessage.setChannel(Channel.META_HANDSHAKE);
        registerCallback(messageId, callback);

        doSend(session, hsMessage, Promise.from(hsReply -> {
            if (hsReply != null && hsReply.isSuccessful()) {
                _session = session;
                ServerMessage.Mutable cnMessage = newMessage();
                cnMessage.setId(newMessageId());
                cnMessage.setChannel(Channel.META_CONNECT);
                cnMessage.getAdvice(true).put(Message.INTERVAL_FIELD, -1L);
                cnMessage.setClientId(session.getId());
                doSend(session, cnMessage, Promise.from(cnReply -> {
                    // Nothing more to do.
                }, failure -> messageFailure(cnMessage, failure)));
            }
        }, failure -> messageFailure(hsMessage, failure)));
    }

<11>

org.cometd.annotation.server.ServerAnnotationProcessor#processSession

 

private boolean processSession(Object bean, LocalSession localSession) {
        ServerSession serverSession = localSession.getServerSession();

        boolean result = false;
        for (Class<?> c = bean.getClass(); c != Object.class; c = c.getSuperclass()) {
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                if (field.getAnnotation(Session.class) != null) {
                    Object value = null;
                    if (field.getType().isAssignableFrom(localSession.getClass())) {
                        value = localSession;
                    } else if (field.getType().isAssignableFrom(serverSession.getClass())) {
                        value = serverSession;
                    }

                    if (value != null) {
                        setField(bean, field, value);
                        result = true;
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("Injected {} to field {} on bean {}", value, field, bean);
                        }
                    }
                }
            }
        }

        List<Method> methods = findAnnotatedMethods(bean, Session.class);
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Object value = null;
                if (parameterTypes[0].isAssignableFrom(localSession.getClass())) {
                    value = localSession;
                } else if (parameterTypes[0].isAssignableFrom(serverSession.getClass())) {
                    value = serverSession;
                }

                if (value != null) {
                    invokePrivate(bean, method, value);
                    result = true;
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Injected {} to method {} on bean {}", value, method, bean);
                    }
                }
            }
        }
        return result;
    }

<6>

org.cometd.annotation.server.ServerAnnotationProcessor#processConfigurations

public boolean processConfigurations(Object bean) {
        if (bean == null) {
            return false;
        }

        Class<?> klass = bean.getClass();
        //首選需要打了@Service註解
        Service serviceAnnotation = klass.getAnnotation(Service.class);
        if (serviceAnnotation == null) {
            return false;
        }

        //首先需要打了打了@Configure的方法
        List<Method> methods = findAnnotatedMethods(bean, Configure.class);
        if (methods.isEmpty()) {
            return false;
        }

        for (Method method : methods) {
            Configure configure = method.getAnnotation(Configure.class);
            //@Configure配置的value爲channelName
            String[] channels = configure.value();
            for (String channelName : channels) {
                //定義一個Initializer接口的匿名方法 內部會調用調用@Configure註解方法
                Initializer init = channel -> {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Configure channel {} with method {} on bean {}", channel, method, bean);
                    }
                    invokePrivate(bean, method, channel);
                };

                //<14>針對@Configure創建Channel
                MarkedReference<ServerChannel> initializedChannel = bayeuxServer.createChannelIfAbsent(channelName, init);

                //內部初始化成功會將marked設置爲true
                if (!initializedChannel.isMarked()) {
                    //是否配置了configureIfExists 爲true 默認false 如果爲true則會調用打了@Configure的方法
                    if (configure.configureIfExists()) {
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("Configure again channel {} with method {} on bean {}", channelName, method, bean);
                        }
                        init.configureChannel(initializedChannel.getReference());
                    } else if (configure.errorIfExists()) {
                        throw new IllegalStateException("Channel already configured: " + channelName);
                    } else {
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("Channel {} already initialized. Not called method {} on bean {}", channelName, method, bean);
                        }
                    }
                }
            }
        }
        return true;
    }

<14>

org.cometd.server.BayeuxServerImpl#createChannelIfAbsent

  @Override
    public MarkedReference<ServerChannel> createChannelIfAbsent(String channelName, Initializer... initializers) {
        ChannelId channelId;
        boolean initialized = false;
        //嘗試根據channelName獲取 判斷是否存在
        ServerChannelImpl channel = _channels.get(channelName);
        if (channel == null) {
            // Creating the ChannelId will also normalize the channelName.
            //嘗試通過處理過的channelId獲取
            channelId = new ChannelId(channelName);
            String id = channelId.getId();
            if (!id.equals(channelName)) {
                channelName = id;
                channel = _channels.get(channelName);
            }
        } else {
            channelId = channel.getChannelId();
        }

        //表示沒有被初始化
        if (channel == null) {
            //新建一個channel
            ServerChannelImpl candidate = new ServerChannelImpl(this, channelId);
            //放入_channels
            channel = _channels.putIfAbsent(channelName, candidate);
            if (channel == null) {
                // My candidate channel was added to the map, so I'd better initialize it

                channel = candidate;
                if (_logger.isDebugEnabled()) {
                    _logger.debug("Added channel {}", channel);
                }

                try {
                    //通知 Initializer實現 可以對ServerChannelImpl做自定義配置
                    for (Initializer initializer : initializers) {
                        notifyConfigureChannel(initializer, channel);
                    }

                    //調用listeners中ChannelListener的configureChannel方法可以對channel進行自定義配置
                    for (BayeuxServer.BayeuxServerListener listener : _listeners) {
                        if (listener instanceof ServerChannel.Initializer) {
                            notifyConfigureChannel((Initializer)listener, channel);
                        }
                    }
                } finally {
                    channel.initialized();
                }
                //調用listeners中ChannelListener的channelAdded表示已經被初始化
                for (BayeuxServer.BayeuxServerListener listener : _listeners) {
                    if (listener instanceof BayeuxServer.ChannelListener) {
                        notifyChannelAdded((ChannelListener)listener, channel);
                    }
                }

                initialized = true;
            }
        } else {
            channel.resetSweeperPasses();
            // Double check if the sweeper removed this channel between the check at the top and here.
            // This is not 100% fool proof (e.g. this thread is preempted long enough for the sweeper
            // to remove the channel, but the alternative is to have a global lock)
            _channels.putIfAbsent(channelName, channel);
        }
        // Another thread may add this channel concurrently, so wait until it is initialized
        channel.waitForInitialized();
        return new MarkedReference<>(channel, initialized);
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章