配置例子
<!-- 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); }